📜 ⬆️ ⬇️

Lazy Loading in Entity Framework

I want to talk about Lazy Loading in the Entity Framework and why it should be used with caution. At once I will say that I assume that the reader uses the Entity Framework, or at least read about it.

What is Lazy Loading?


Lazy loading is the ability of EF to automatically load related entities from the database when it is first accessed. For example, consider the class Trade :

public class Trade { public int Id { get; set; } public virtual Counterparty Buyer { get; set; } public virtual Counterparty Seller { get; set; } public decimal Qty { get; set; } } 

When you first access the Buyer or Seller properties, they will be automatically loaded from the database. Technically, this is implemented through the creation of proxy instances of the classes inherited from the Trade class. At the proxy class, the access to the properties is redefined and contains logic for loading data from the database. Accordingly, in order for the mechanism to work, the properties must be virtual.
')

What does this give?


In theory, lazy loading makes it possible to load only the data that is actually used and needed in the work. Facilitates the acquisition of related entities and potentially should speed up the work.

And what's wrong with that?


As usual, you have to pay for everything, here are some drawbacks that you should definitely consider when using lazy loading.

Violates data consistency.


Consider the following example:

 public class Order { public int Id {get;set;} public virtual IList<OrderLine> OrderLines {get;set;} } public string GetOrderInfo(int orderId) { using(var context = new Context()) { var order = context.Orders.First(x=>x.orderId == 1); // some logic var info = string.Join(",", order.OrderLines.Select(x=>x.Name)); return $"{order.Id} {info}"; } } 

Imagine that in the interval between loading an order from the database and accessing the OrderLines property, the contents of the order were changed. As a result, we will receive the contents of the order at the current moment, and not at the time of loading the order itself from the database.

May degrade performance.


How so, you ask, he is intended to accelerate ?? In theory, yes, but in practice I often had to solve problems arising from the illiterate use of this feature. A simple example:

 //      public string GetTradeDescription(int tradeId) { using (var context = new Context()) { var trade = context.Trades.First(x => x.Id == tradeId); var result = $"{trade.Id}:{trade.Buyer.Name}-{trade.Seller.Name}:{trade.Qty}"; return result; } } 

The question is, how many requests will be sent to the database? That's right, 3. In this case, perhaps this is not very scary.

But let's imagine that now we want to receive information immediately on an arbitrary number of transactions. Modifying our method:

 public List<string> GetTradeDescription(params int[] tradeIds) { using (var context = new Context()) { var trades = context.Trades.Where(x => tradeIds.Contains(x.Id)); var result = trades .AsEnumerable() .Select(trade => $"{trade.Id}:{trade.Buyer.Name}-{trade.Seller.Name}:{trade.Qty}") .ToList(); return result; } } 

As a result, the number of requests will increase in proportion to the number of requested transactions. Imagine that when requesting information on hundreds of transactions in the database will fly> 100 requests, although one would be enough. At the same time, such errors are not so easy to catch during the development / review phase, and such a code can fire already in the combat environment.

A similar problem arises when attempting to serialize objects with lazy loading support, the serializer will pull an object for each property, thus generating additional calls to the database.

Fluid abstraction


When using lazy loading, our POCO objects cease to be POCO, because now we work with proxies that store a link to the context and go to the database themselves. What will happen if you use these objects outside the context that loaded them?

 public void DoSomeLogic(int tradeId) { var trade = LoadTrade(tradeId); Console.WriteLine(trade.Buyer.Name);// Oops,     The ObjectContext instance has been disposed and can no longer be used for operations that require a connection } private Trade LoadTrade(int tradeId) { using (var context = new Context()) { var trade = context.Trades.First(x => tradeId == x.Id); return trade; } } 

Conclusion


Personally, in my projects I decided not to use lazy loading at all, maybe it’s too categorical, but based on my experience I’ll say that I got a lot more problems from it.

There is an alternative option to leave lazy loading turned on but strictly ensure that all navigation properties are not virtual (with the rare exception where hands itch lazy loading is very necessary).

Source: https://habr.com/ru/post/336812/


All Articles