Miroslav Holec
Premium

Optimalizační testová otázka k Entity Framework

Miroslav Holec   26. října 2015  update 29. března 2016

Tento článek je již zastaralý. Článek nemusí popisovat aktuální stav technologie, ideální řešení a můj současný pohled na dané téma.

Zaujala mě jedna certifikační otázka (MCSD / 70-487), která docela dobře pracuje s různými aspekty použití Entity Frameworku. Otázka s rozepsaným řešením bez cenzury.

👨‍🎓 Nové školení EF Core pro rok 2020

Školení nejnovější verze Entity Framework Core přímo u Vás ve firmě. Naučíte se používat EF Core v celém životním cyklu aplikace od vytvoření modelu, změn v podobě migrací až po dotazování a profilování databázových dotazů.

Více o školení Entity Framework Core

Otázka

You are developing an ASP.NET MVC application. The application is an order processing system that uses the ADO.NET Entity Framework against a SQL Server database. It has a controller that loads a page that displays all orders along with customer information. Lazy loading has been disabled. The Order class is shown below.

public partial class Order
{
	public string CustomerId { get; set; }
	...
	public virtual Customer Customer { get; set; } 
}

You need to return the orders and customer information in a single round trip to the database. Which code segment should you use?

A

public ActionResult Index()
{
	IQueryable<Order> orders = db.Orders;
	orders = orders.Include("Customer");
	return View (orders.ToList());
}

B

public ActionResult Index()
{
	IQueryable<Order> orders = db.Orders.Include("Order.Customer")
	return View (orders.ToList());
}

C

public ActionResult Index()
{
	IQueryable<Order> orders = db.Orders;
	orders.Select(o => o.Customer).Load();
	return View (orders.ToList());
}

D

public ActionResult Index()
{
	IQueryable<Order> orders = db.Orders;
	return View (orders.ToList());
}

Na této otázce se mi líbí právě to, že každá možnost je na první pohled správná. Kód jde ve všech případech zkompilovat, nicméně výsledek se liší tím, jaká data se přenášejí z databáze.

Řešení

Důležité je dobře si přečíst zadání otázky. Jsou v něm požadavky a předpoklady, ze kterých správná odpověď jasně plyne. Důležité je hlavně následující:

  • Lazy loading has been disabled.
  • You need to return the orders and customer information in a single round trip to the database.

Protože je vypnutý Lazy Loading, můžeme rovnou vyloučit možnost D. Po odeslání dotazu se načtou pouze samotné objednávky bez zákazníka. Kdybychom chtěli později donačíst zákazník(a/y), neobešli bychom se bez dodatečných dotazů proti databázi.

Varianta C se jeví jako dobré řešení, protože třída Order má definovaného Customer jako virtual a tudíž můžeme skutečně použít explicitní loading. V hromadě ukázek na internetu je tato odpověď označená jako správná. Problém je, že v zadání je jasně řečeno: return data in a single round trip to the database. V případé této konstrukce se ale do databáze odesílá více dotazů v závilosti na použití Lazy Loadingu.

Zbývají možnosti A / B, které obě využívají Include() pro dotažení zákazníka. Obě varianty jsou syntakticky v pořádku ale varianta B odkazuje na "Order.Customer", což je chybné. Protože už nad objektem Order pracujeme, chceme použít jen Include("Customer").

Tím se dostáváme ke správné odpovědi A. Tady může na první pohled zmást konstrukce

IQueryable<Order> orders = db.Orders;

protože Db.Orders je typu DbSet<Order>. Přiřazení je ale v pořádku, viz.:

public class DbSet<TEntity> : DbQuery<TEntity>, IDbSet<TEntity>, 
    IQueryable<TEntity>, IEnumerable<TEntity>, IQueryable, IEnumerable
where TEntity : class
...

Odpálením dotazu dojde k načtení objednávek včetně zákazníků, čímž jsou splněny požadavky v otázce.

Při práci s EF lze jedné a té samé věci docílit mnoha různými způsoby. Sestavovat dotazy občas vyžaduje více zamyšlení nad tím, jaká data potřebuji, kdy je potřebuji a co s nimi chci dělat.