суббота, сентября 06, 2008

Transparent Lazy Loading for Entity Framework

Jaroslaw Kowalski опубликовал в своём блоге цикл статей Transparent Lazy Loading for Entity Framework

Далее следует мой перевод первой части...


This post is a part of the series that describes EFLazyLoading library.
Part 1 - Strategies for implementing lazy loading

Этот пост - часть цикла, который описывает библиотеку EFLazyLoading.
Часть 1 - Стратегии реализации lazy loading


The first release of Entity Framework supports explicit loading. This means that if you are navigating a relationship, you have to make sure it is loaded by explicitly calling Load()on EntityReference<T> or EntityCollection<T> objects, or pre-loading your relationships by using Include() on your query.
If you try to navigate a many-to-one or one-to-one relationship that is not loaded, you will get a NullReferenceException. In case of EntityCollection<T> that has not been loaded you will silently get an empty collection which may lead to subtle bugs.
One of the benefits of explicit loading is that you can easily locate all places in your code that cause database round-trips. Unfortunately general-purpose code that can be used in multiple units of work (such as validation, permission checks, etc.) does not typically know which relationships have been loaded. Because of that it always has to check whether the relationship being navigated has been loaded, and call Load() if not.
As you can imagine this can easily lead to code that is cluttered with IsLoaded/Load():

Первый релиз Entity Framework поддерживает явную загрузку. Это означает, что обращаясь к связанным объектам, вы должны убедиться, что те были загружены прямым вызовом Load() у EntityReference<T> или EntityCollection<T> объектов, или предварительно загружены, путем использования Include() в вашем запросе.
Если вы попытаетесь перейти по связи Многие-К-Одному или Один-К-Одному к объектам, которые не загружены, вы получите NullReferenceException. В случае EntityCollection <T>, вы получить пустую коллекцию, без каких либо сообщений об ошибках, что может привести к трудно отлавливаемым багам.
Одним из преимуществ явной загрузки в том, что вы можете легко найти все места в коде, в которых происходит загрузка из базы данных. К сожалению универсальный код, который может использоваться во многих частях приложения (таких как проверка валидности, проверки прав доступа, и т.д.) обычно не знает, какие отношения были загружены. Из-за этого это всегда нужно проверять, были ли управляемые отношения загружены, и вызывать Load() если нет.
Как вы можете предположить, это может легко привести к коду, который загроможден
IsLoaded/Load():

var prod = entities.Products.First();
prod.SupplierReference.Load();
var supplier = prod.Supplier;
prod.OrderDetails.Load();
foreach (OrderDetail det in prod.OrderDetails)
{
if (!det.OrderReference.IsLoaded)
det.OrderReference.Load();
Console.WriteLine("{0} {1}", det.Product.ProductName, det.Order.OrderDate);
}


Transparent lazy loading is a way to make your business logic code more readable by handling loads under the hood. As a result you get an illusion of a fully populated object graph, so the above example can be re-written as:

Прозрачная ленивая загрузка - способ сделать ваш код бизнес логики более удобочитаемым, управляя загрузкой "за кадром". В результате вы получаете иллюзию полностью загруженного графа объектов, таким образом вышеупомянутый пример может быть переписан как:

var prod = entities.Products.First();
var supplier = prod.Supplier;
foreach (OrderDetail det in prod.OrderDetails)
{
Console.WriteLine("{0} {1}", det.Product.ProductName, det.Order.OrderDate);
}


This simplicity comes at a cost:
- Database queries are more difficult to locate (potentially any relationship navigation can lead to a query)
- Object graph is fully populated so you cannot easily serialize parts of it without using DTO (Data Transfer Objects). Carelessly returning an object from a web service could potentially bring in the entire database with it.

Но эта простота имеет свои минусы:
-- Запросы к базе данных гораздо труднее найти (потенциально использование какой-либо связи может привести к запросу)
-- Граф объектов полностью загружен, поэтому вы не сможете легко сериализовать его частей без использования DTO(объектов для передачи данных). Небрежно возвращеный объект из веб-сервиса может потенциально притащить за собой чуть-ли не всю базу данных.

As we said, Entity Framework v1 supports explicit loading only, but the object layer code is something the developer can control, either by writing it by hand or creating a tool to do so. We just need to inject Load() method call in a few places. Sounds simple?

Как мы уже говорили, Entity Framework v1 поддерживает только явную загрузку, но код объектного слоя разработчик может контролировать, либо вручную, либо создав инструмент для этого. Нам просто нужно разместить вызов метода Load() в нескольких местах. Звучит просто?

Strategies for implementing transparent lazy loading

Стратегии реализации прозрачной ленивой загрузки

There are two main strategies when implementing transparent lazy loading. One approach is to fully materialize related objects whenever you access them – let’s call this approach Lazy Initialization.
Lazy Initialization is easy do in Entity Framework – all you have to do is to add extra code to do Load() in property getters that are used to navigate relationships (see Danny’s post about codegen events).

Есть две главных стратегии реализации прозрачной "ленивой загрузки". Первый подход - полностью загрузить связанные объекты всякий раз, когда Вы получаете доступ к ним – позволяет нам называть этот подход "ленивой инициализацией".
"Ленивая инициализация" легко реализуема в Entity Framework – все, что Вы должны сделать, это добавить дополнительный код, чтобы вызвать Load() в геттерах свойств, которые используются для перемещениям по связям. (см. пост Danny’s об codegen событиях).

The following code checks whether the relationship has been loaded and forces Load() if it has not – this frees the business logic to focus on business rules rather than plumbing (note that this source code change only works with attached objects – detached objects require special handling – not shown here):

Следующий код проверяет, были ли отношения загружены и вызывает Load(), если это не сделано – это освобождает код бизнес логики, позволяя сосредоточиться на бизнес правилах (обратите внимание на те эти изменения исходного текста, только работает с присоединенными объектами – отсоединенные объекты требуют специальной обработки – не показанной здесь):

[EdmRelationshipNavigationProperty("NorthwindEFModel", "Products_Supplier", "Supplier")]
[XmlIgnore]
[SoapIgnore]
[DataMember]
public Supplier Supplier
{
get
{
// added code
if (!SupplierReference.IsLoaded && this.EntityState != EntityState.Added) // для новых сущностей (подсказал BOleg)
SupplierReference.Load();
return ((IEntityWithRelationships)(this)).RelationshipManager.
GetRelatedReference("NorthwindEFModel.Products_Supplier", "Supplier").Value;
}
set
{
((IEntityWithRelationships)(this)).RelationshipManager.
GetRelatedReference("NorthwindEFModel.Products_Supplier", "Supplier").Value = value;
}
}

The result is that product.Supplier is always accessible, which is what we wanted. Unfortunately fully materializing related objects is not always desirable for performance reasons. There are cases where you do not care about related object attributes, but the object itself is interesting to you. Consider an example function ShareManager that returns true when two employees share the same manager and false otherwise:

В результате - product.Supplier всегда доступен, что является тем, чего мы добивались. К сожалению полная загрузка связанных объектов не всегда желательна по причинам производительности. Есть случаи, где вам не нужны связи объекта, но сам объект интересен вам. Считайте в качестве примера функцию ShareManager, которая возвращает истину, если два служащих подчиняются одному менеджеру и ложь, если иначе:

bool ShareManager(Employee emp1, Employee emp2)
{
if (emp1.Manager == emp2.Manager)
return true;
else
return false;
}


By merely touching emp1.Manager and emp2.Manager, we have potentially caused two Manager entities to materialize (and that means two database queries), while we were just interested in checking whether they are the same object.

Просто обращаясь к emp1.Manager и emp2.Manager, мы потенциально заставили материализоваться две сущности Manager (и это означает два запроса к базе данных), в то время как мы только интересовались, являются ли они тем же самым объектом.

In Entity Framework you can reason about identities of related objects without materializing them by examining EntityKey property on EntityReference<T> So our example can be re-written for performance as:

В Entity Framework вы можете сравнить тождество связанных объектов, не материализуя их, путем сравнения свойства EntityKey у EntityReference<T>. Таким образом наш пример может быть переписан:
bool ShareManager(Employee emp1, Employee emp2)
{
if (emp1.ManagerReference.EntityKey == emp2.ManagerReference.EntityKey)
return true;
else
return false;
}


But that is not nearly as nice as the first code snippet because you have to deal with EntityKeys now.
Fortunately it turns out that with some clever code generation it is possible to have the first syntax and not pay the price for object materialization except when it is absolutely needed. Intrigued? Stay tuned for Part 2 where I will introduce a lazy loading framework (code generator and supporting library) for EF.
The strategy that will be used is based on an observation that you do not need to materialize an object if you do not access its non-key properties…

Но это не почти столь же хорошо как первый пример кода, поскольку теперь Вы должны иметь дело с EntityKeys.
К счастью оказывается, что с продвинутым кодо-генератором возможно иметь первый синтаксис и не платить производительностью за материализацию объекта, кроме тех случаев, когда это действительно необходимо.
Заинтригованы?
Подождите написания Части 2, где я представлю "lazy loading framework" (генератор кода и библиотека поддержки) для EF.
Стратегия, которая будет использоваться, основана на том, что объект не должен полностью материализоваться, если вы не получаете доступ к его неключевым свойствам …

Комментариев нет:

Отправить комментарий