JorisR.be

JorisR.be

Code-Run-Beer-Repeat
24-3-2020

Entity framework - Best Practices

Entity framework is een wonderlijke tool maar zorgt bij veel developers toch heel veel frustraties kwa performantie. En ja, de performantie van EF kan veel beter, maar de meeste oorzaken van snelheidsverlies liggen meestal toch in het niet correct gebruiken van EF.
Hier een paar zaken waar je best rekening mee houdt.

Eerst en vooral even over de tooling:
Is je code traag met bepaalde operaties naar EF? dan heb je verschillende mogelijkheden om te kijken welke SQL statements EF van je code maakt:

  • Je DB context eigenschappen, zodat deze sql statements in output zet
  • SQL Server profiler (via Tools in SQL server management studio)
  • Extendend events (op een SQL Server, Management, Session, New)

Lazy Loading

 

var result = new List<Client>();
var clients = _context.Clients.Where(x => x.Rating == 5).ToList();
foreach (var client in clients)
{
    if (client.Orders.Count(x => x.CreationDate.Year == 2019) <2)
    {
        result.Add(client);
    }
}


Als je bovenstaande code uitvoert en kijkt welke sql statements er gedaan worden zul je bemerken dat EF eerst de clients zal opvullen, en dan voor elke client nog een extra keer naar de database gaat om de property 'Orders' op te vullen. Dit is het principe van 'Lazy loading': alleen het noodzakelijke laden, als er later iets ontbreekt zal EF het dan wel gaan halen.
Calls naar de database zijn redelijk kostbaar qua performantie, daarom zou het beter zijn de property 'Orders' ineens mee op te vullen.

var result = new List<Client>();
var clients = _context.Clients.Include(x=> x.Orders).Where(x => x.Rating == 5);
foreach (var client in clients)
{
	if (client.Orders.Count(x => x.CreationDate.Year == 2019) < 2)
	{
		result.Add(client);
	}
}

Merk hier de 'Include' op. Op deze manier zal EF maar 1 call uitvoeren naar de DB om zowel Clients als Orders op te vullen.In EF6 kan je Lazy loading uitschakelen door op de DBContext deze property te zetten:

this.Configuration.LazyLoadingEnabled = false;

Dit heeft wel als nadeel dat wanneer je geen include gebeurd de onderliggende properties niet opgevuld zullen zijn, en NULL zijn.

Vanaf EFCore wordt het steeds moeilijker om 'Lazy loading' te gaan gebruiken, en is dit standaard disabled (goed nieus voor de developers!).

 

Haal enkel de data op die je nodig hebt

var client = _context.Clients.First(x=> x.Id == id);

Op deze manier zal heel het Client object opgevuld geraken. Maar als dit object redelijk veel properties bevat, en je enkel de naam nodig hebt is het niet nodig de rest op te halen.
Daarom kan je beter enkel deze velden ophalen:

var client = _context.Clients.First(x=> x.Id == id);

Dit geldt niet alleen in de breedte (kolomen), maar ook in de lengte: rijen: Als je bijvoorbeeld een tabel moet opvullen met veel data, maar je toont enkel maar 20 elementen kan je ook kiezen om de paging op EF level te doen, en enkel deze elementen terug te geven die je nodig hebt.

var clients = _context.Clients.Skip(20 * (page - 1)).Take(20).ToList();

 

Bulk insert/Delete

for(int i = 0; i<10; i++)
{
	var orderLine = rnd.GetRandomOrderLine();
	orderLine.OrderId = order.Id;
	orderLine.Order = order;
	_context.OrderLines.Add(orderLine);
}
_context.SaveChanges();

Op deze mannier zal de context.Add vele keren worden aangeroepen.  Bij elke Add() zal EF alles willen valideren en andere dingen doen, daarom is het beter al deze inserts in 1 keer te doen:

var orderLines = new List<OrderLine>();
for (int i = 0; i < 10; i++)
{
	var orderLine = rnd.GetRandomOrderLine();
	orderLine.OrderId = order.Id;
	orderLine.Order = order;
	orderLines.Add(orderLine);
}
_context.OrderLines.AddRange(orderLines);
_context.SaveChanges();

Op deze mannier heeft EF maar 1 keer werk. En zal het veel sneller gedaan zijn.
Hetzelfde geld ook voor remove, gebruik hiervoor RemoveRange().

 

As no tracking

Elke keer je een object invult vanuit EF, zal deze achterliggend allerlei gegevens bijhouden.  Dit kost natuurlijk ook tijd.  Om dit te disablen kan je dit afzetten:

var client = _context.Clients.Include(x => x.Orders.Select(y => y.OrderLines)).Where(x => x.Id == clientId).AsNoTracking().ToList();

Het nadeel is wel dat je de link met de DB en het object dan wel kwijt bent.  De veranderingen aan het object zullen na een SaveChanges niet naar de datase doorvloeien.

 

EF Core

Als je EF Core (1-2) gaat gebruiken, is het goed om op te letten bij bepaalde queries:

var user = _context.Users.FirstOrDefault(x=> x.DisplayName.Equals("jef", StringComparison.InvariantCultureIgnoreCase));

Bovenstaande query zal in EF6 problemen geven met het vertalen naar SQL. EF Core doet hier niet moeilijk over en geeft de correcte data. Alleen zal hij wanneer hij het niet kan omzetten naar SQL, de hele tabel binnentrekken, en de conditie dan pas afchecken. Niet echt wat je wil als je tabel veel data bevat.
Daarom is het goed regelmatig de SQL statements te checken die EF hiervan maakt.

 

Snelste query: query die niet naar DB gaat

Soms kan het veel handiger zijn dingen te cachen dan altijd opnieuw zelfde data terug op te halen.

Leave A Comment