Показаны сообщения с ярлыком EFLazyLoading. Показать все сообщения
Показаны сообщения с ярлыком EFLazyLoading. Показать все сообщения

воскресенье, сентября 07, 2008

Перевод Part 2 - Implementation of EFLazyLoading

Part 2 - Implementation of EFLazyLoading
Прозрачная "ленивая загрузка" для Entity Framework - Часть 2 - Реализация EFLazyLoading

As I promised last time, I would like to present the result of a little experiment in implementing transparent lazy loading for Entity Framework. You can download the sample code here, the rest of this post tries to explain how it all works.

Как я обещал в прошлый раз, я хотел бы представить результат небольшого эксперимента в реализации прозрачной "ленивой загрузки" для Entity Framework. Вы можете загрузить код примера здесь, остальная часть этого поста пытается объяснить, как все это работает.
Requirements
Требования
I set myself some goals:

a) Objects should be code-generated in a way similar to the standard Entity Framework code generation and the resulting code’s public surface should be similar. There will be some differences in the way collections and references are handled.

b) Collections should be represented by classes that implement ICollection<T> and should always be ready to use without “IsLoaded/Load”.

c) EntityReference<T> and EntityCollection<T> should be completely hidden from the user

d) Each (N-to-0..1) reference should be represented solely by a property where the type is the target object type (no EntityReference<T> properties in the object layer).

e) We don’t want to materialize the object at the other end of the relationship just to see whether it is null or not.

f) We don’t want to materialize the object if we don’t care about its properties (for example changing “Customer” navigation property on “o” does not require the Customer object to be loaded at all - today we can use EntityKeys to achieve similar thing).

g) Each object must be able to live in two states: loaded and unloaded and the object must be able to load itself on first access to the property. Unloaded objects that haven’t been accessed are really just wrappers for the EntityKey, objects that have been touched have actual data.

h) Object in the unloaded state should be as cheap to create as possible.

Я поставил перед собой цели:
a) Объекты должны быть сгенерированы подобно стандартной кодо-генерации Entity Framework, и public реализация кода должна быть также подобной. Будут некоторые различия в способе, которым будут обрабатываться коллекции и ссылки.
b) Коллекции должны быть представлены классами, которые реализуют ICollection<T> и должны всегда быть готовы к использованию без “IsLoaded/Load()”.
c) EntityReference <T> и EntityCollection <T> должен быть полностью скрыт от пользователя.
d) Каждая (N-to-0.. 1) ссылка должна быть представлена исключительно свойством, тип которого - целевой тип объекта (никаких EntityReference<T> свойств в слое объекта).
e) Мы не хотим загружать объект только для того, чтобы видеть, является ли свойство, ссылающееся на него пустым или нет:

Order o;

if (o.Customer != null)
{
Console.WriteLine("We have a customer!");
}

f) Мы не хотим загружать объект, если мы не запрашиваем значения его свойств (например, для проверки свойства Customer объекта Order, нет необходимости загружать объект Customer - сегодня мы можем использовать EntityKeys, чтобы достигнуть подобного поведения):

Order o;
Order o2;
o.Customer = o2.Customer;

g) Каждый объект должен уметь жить в двух состояниях: "загружен" и "не загружен" и объект должен быть в состоянии загрузить себя при первом доступе к его свойствам. Незагруженные объекты, к которым не было обращения, являются лишь обертками для EntityKey, а объекты, к свойствам которых был доступ, должны содержать фактические данные:
Order o = ...;

if (o.Customer != null)
{
// loads o.Customer on-demand
Console.WriteLine(o.Customer.Address.City);
}

h) Затраты на создание незагруженного объекта должны быть минимальны.

Implementation
Реализация


Because each object has to be delay-loadable and cheap to create, we are representing a single entity as a pair of objects. One is the “shell” that has all the properties and navigation properties of an entity and the EntityKey and the other that holds actual data (minus the key). Property getters and setters on the shell class delegate read/write operations to the data class which is lazily created (to conserve memory when not needed).

This is a pseudo-code that demonstrates this (_data management is not shown here – actual _data reference and entity key is held in the base class)


Поскольку каждый объект должен быть загружаемым с задержкой и легко создаваемым, мы представим единственную сущность как пару объектов. Первый - "shell" (оболочка), у которой есть все свойства и свойства-связи сущности, а также EntityKey; второй - содержит фактические данные (за исключением ключа). Геттеры и сеттеры свойств класса оболочки делегируют операции чтения-записи к классу данных, который лениво создается (чтобы сохранить память, когда он не нужен).

Псевдокод, который демонстрирует это (управление _data не показано здесь – реально _data и _key приведены в базовом классе):
// shell class - has no fields to hold actual data, just
// a reference to lazy-initialized data object –this will not compile
public class Order
{
private EntityKey _key; // each shell has an identity
private OrderData _data; // reference to lazy-initialized data

public int OrderID
{
get { return _key.Something; }
set { _key.Something = value; }
}

public DateTime OrderDate
{
get { return _data.OrderDate; }
set { _data.OrderDate = value; }
}

public string ShipTo
{
get { return _data.ShipTo; }
set { _data.ShipTo = value; }
}

public string BillTo
{
get { return _data.BillTo; }
set { _data.BillTo = value; }
}

public Customer Customer { get; set; } // details not shown
public ICollection Lines { get; }
}

// data class - just a bunch of fields
internal class OrderData
{
internal DateTime OrderDate;
internal string ShipTo;
internal string BillTo;
}


For objects in “unloaded” stage there is just one object (Order), for loaded objects “OrderData” is initialized so property accesses actually work. The first time user accesses the property getter or setter and _data is null, the data is brought from the store.

Для объектов на "незагруженной" стадии есть только один объект (Order), для загруженных объектов, "OrderData" инициализирутся и акцессоры свойств фактически работают. Когда в первый раз происходит доступ к геттеру свойства или сеттеру, и _data является пустым, данные загружаются из хранилища.
When the user navigates a {one,many}-to-one relationship we create a shell object that has only primary key initialized, attach it to the context and return to user. The Data object is not created at all and “_data” pointer is null. When a property is accessed for the first time, the data gets initialized by calling objectcontext.Refresh(StoreWins) which brings all properties and relationships into memory.

Когда пользователь перемещается по связи {один, многие}-to-one мы создаем объект-оболочку, у которого есть только инициализированный первичный ключ, присоединяем его к контексту и возвращаем пользователю. Объект данных не создан полностью и “_data” является пустым. Когда к свойству объекта получают доступ впервые, данные инициализируются вызывая objectcontext. Обновление (StoreWins) которое загружает все свойства и отношения в память.
Collections are rather simple – all we have to do is return a wrapper over EntityCollection<T> that does Load() under the hood when the data is actually needed (for example in foreach()).

С коллекциями ещё проще – всё, что мы должны сделать,это вернуть обертку над EntityCollection<T>, которая лениво вызывает Load(), когда данные фактически необходимы (например в foreach ()).

Implementation details

Детали реализации

The implementation takes advantage of the fact that Entity Framework supports IPOCO. We introduce a base class called LazyEntityObject that all code-generated objects derive from, and that implements all interfaces required by Entity Framework (IEntityWithKey, IEntityWithChangeTracking, IEntityWithRelationships) and a new interface ILazyEntityObject. The implementation of these interfaces is done explicitly, which means that there is no single public API exposed on actual entity objects (not even EntityKey).

Реализация использует в своих интересах факт, что Entity Framework поддерживает IPOCO. Мы вводим базовый класс по имени LazyEntityObject, из которого происходят все созданные кодо-генератором объекты, и которые реализуют все интерфейсы, требуемые Entity Framework (IEntityWithKey, IEntityWithChangeTracking, IEntityWithRelationships) и новый интерфейс ILazyEntityObject. Реализация этих интерфейсов сделана явно и это означает,что не существует единого публичного API воздействия на фактическое образование объектов (даже не EntityKey).
In the actual implementation (compared to the pseudo-code) the data class is an inner private class of each entity class and property getters and setters are implemented through statically declared Data Properties – a concept similar to WPF dependency properties. They are statically initialized with delegates that get/set actual data but perform all the needed operations under the hood (such as change tracking and lazy initialization). As a result everything is type-safe and there is no need to use reflection. Thanks to Colin for the idea!

В фактической реализации (по сравнению с псевдокодом) класс данных - внутренний приватный класс каждого класса сущности и акцессоры свойств реализованы через статически объявленные Data Properties – понятие, подобное свойствам зависимости WPF. Они статически инициализируются с делегатами, которые получают/устанавливают фактические данные, но выполняют все необходимые операции "за кадром" (такие как прослеживание изменений и ленивая инициализация). В результате все типо-безопасно и нет никакой потребности использовать рефлекцию. Спасибо Colin за идею!
With this in place the code generated for each property getter/setter is a simple one-liner, whether it is a simple property, a reference or a collection:

Ниже приведён код, произведенный для каждого геттера/сеттера свойства, и является просто одной строкой, не смотря на то, является ли это простым свойством, ссылкой или коллекцией:
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
public Single Discount
{
get { return Data.DiscountProperty.Get(this); }
set { Data.DiscountProperty.Set(this, value); }
}

[EdmRelationshipNavigationPropertyAttribute("NorthwindEFModel", "Order_Details_Order", "Order")]
public Order Order
{
get { return Data.OrderProperty.Get(this); }
set { Data.OrderProperty.Set(this, value); }
}

The Data class itself is also clean (just a bunch of fields + static data properties) and all the hard work is done in the implementation of Data Property classes.

Сам класс данных также чист (только связка полей + статические свойства данных), и вся тяжелая работа сделана в реализации классов Data Properties.
private class Data : ILazyEntityObjectData
{
private Decimal UnitPrice;
private Int16 Quantity;
private Single Discount;

// primary key
public static DataKeyProperty<OrderDetail,Int32> OrderIDProperty =
new DataKeyProperty<OrderDetail,Int32>(c => c.OrderID, (c, v) => c.OrderID = v, "OrderID");
public static DataKeyProperty<OrderDetail,Int32> ProductIDProperty =
new DataKeyProperty<OrderDetail,Int32>(c => c.ProductID, (c, v) => c.ProductID = v, "ProductID");
// non-key properties
public static DataProperty<OrderDetail,Data,Decimal> UnitPriceProperty =
new DataProperty<OrderDetail,Data,Decimal>(c => c.UnitPrice, (c, v) => c.UnitPrice = v, "UnitPrice");
public static DataProperty<OrderDetail,Data,Int16> QuantityProperty =
new DataProperty<OrderDetail,Data,Int16>(c => c.Quantity, (c, v) => c.Quantity = v, "Quantity");
public static DataProperty<OrderDetail,Data,Single> DiscountProperty =
new DataProperty<OrderDetail,Data,Single>(c => c.Discount, (c, v) => c.Discount = v, "Discount");
// references
public static DataRefProperty<OrderDetail,Data,Order> OrderProperty =
new DataRefProperty<OrderDetail,Data,Order>("NorthwindEFModel.Order_Details_Order","Order","Order");
public static DataRefProperty<OrderDetail,Data,Product> ProductProperty =
new DataRefProperty<OrderDetail,Data,Product>("NorthwindEFModel.Order_Details_Product","Product","Product");
}


Data Properties Explained

Data Properties изнутри

Each data property is statically initialized in the data class and has two methods: Get() and Set().

- Get() takes a single argument – the shell object and returns the property value
- Set() takes two arguments: shell object and new property value. It sets the property to the value provided.

Каждое свойство данных статически инициализируется в классе данных и имеет два метода: Get() и Set().

* Get(), c единственным параметром – объектом-оболочкой и возвращает значение свойства
* Set() c двумя параметрами: объект-оболочка и новое значение свойства. Метод присваивает свойству переданное значение.

There are 4 types of data properties:

Simple properties (DataProperty class) that are responsible for getting and setting non-key, non-navigation properties
Key properties (DataKeyProperty) that are responsible for gettings and settings properties that are part of the primary key (the values are stored in the shell class itself)
Collection properties (DataCollectionProperty) that manage object collections
Reference properties (DataRefProperty) that are responsible for getting and setting reference properties

Есть 4 типа свойств данных:
1. Простые свойства (класс DataProperty), которые ответственны за получение и установку неключевых, свойств без ссылок на другие сущности.
2. Ключевые свойства (DataKeyProperty), которые ответственны за получение и установку свойств, которые являются частью первичного ключа (значения хранятся в классе-оболочке непосредственно).
3. Свойства-коллекции (DataCollectionProperty), которые управляют коллекциями объектов.
4. Сссылочные свойства (DataRefProperty), которые ответственны за получение и установку свойств, ссылающихся на другие сущности.
Simple property (implemented in DataProperty.cs) makes sure that the data object has been initialized on-demand and delegates to ObjectContext.Refresh() to fetch object values and relationships. When setting property values, it calls ReportPropertyChanging and ReportPropertyChanged so that object state is properly tracked.

Простые свойства (реализованы в DataProperty.cs) удостоверяются, что объект данных инициализирован по требованию и делегируют к ObjectContext.Refresh(), чтобы загрузить значения и отношения объекта. При установке значений свойств они вызывают ReportPropertyChanging и ReportPropertyChanged так, чтобы состояние объекта было должным образом прослежено.
Key properties do nothing more than calling ReportPropertyChanging/ReportPropertyChanged in addition to getting and setting actual key values in the shell object.

Collection properties take care of initializing relationships in the RelationshipManager and wrapping the results with LazyEntityCollection for load-on-demand functionality.

Ключевые свойства делают не что иное как вызов ReportPropertyChanging/ReportPropertyChanged в дополнение к получению и установке фактических значений ключа в объекте-оболочке.
Свойства-коллекции заботятся об инициализации отношений в RelationshipManager и обертывании результатов с LazyEntityCollection<T> для функционирования загрузки по требованию.
Reference properties are probably the most interesting ones, because they deal with stub objects. Whenever the user navigates a relationship that has not yet been initialized, a new stub object (that is just a shell without data) is created and attached to the object context. There is a little additional complication with handling polymorphic objects, because we need to know the concrete subtype to create based just on the EntityKey, but that is a story for a separate article.

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

Usage
Использование

Code generation application (EFLazyClassGen project in the sample solution) emits code that is meant to be a drop-in replacement for designer-generated code (namespaces and class names are the same). Just invoke that with two parameters:

EFLazyClassGen input.[csdl,edmx] output.cs

Кодо-генератор (проект EFLazyClassGen в прилагаемом солюшене) генерирует код, который предназначен для замены дизайнера-генерируемого кода (namespaces и названия классов - те же самые).

Просто запустите его с двумя параметрами:
EFLazyClassGen input.[csdl,edmx] output.cs
Only simple code generation is supported (for example multiple schemas are not) at this point and I’ve only tested this against NorthwindEF and AdventureWorksXLT schemas.

Поддерживается только простая кодо-генерация (например, множественные схемы не поддерживаются) и я проверил это только на NorthwindEF и AdventureWorksXLT схемах.
Generated classes have public interface similar to one generated by EdmGen - some notable differences are:

EntityKey and EntityState members are not publicly exposed (you can still get to them by casting to IEntityWithKey)
Serialization is not supported (no serialization-specific are generated). If you want to serialize lazy objects, you have to do this using DTO (Data Transfer Objects)
There is no *Reference property on many-to-one relationships. It means there is no way to control the "loaded" state of related end, but that should not be a problem since everything appears to be loaded.


Сгенерированные классы имеют public интерфейс, подобно сгенерированному EdmGen - но с некоторыми различиями:
1. EntityKey и члены EntityState публично не выставлены (Вы можете все еще добраться до них, преобразовывая к IEntityWithKey).

2. Сериализация не поддерживается. Если Вы хотите сериализовать ленивые объекты, Вы должны сделать это используя DTO (Data Transfer Objects)
3. Нет *Reference свойств на отношениях "многие к одному". Это означает, что нет никакого способа контроля за состоянием "загружен" связанных данных, но это не должно быть проблемой, так как все, как представляется, будет загружено.
LazyObjectContext derives from ObjectContext and adds two new events, which can be used to trace the internal workings of EFLazyLoading:
LazyObjectContext.StubCreated - occurs whenever new stub object is created
LazyObjectContext.ObjectLoaded - occurs whenever delayed occurs occurs

LazyObjectContext происходит из ObjectContext и добавляет два новых события, которые могут использоваться, чтобы проследить внутреннюю работу EFLazyLoading:
1. LazyObjectContext.StubCreated - происходит всякий раз, когда новый объект-заглушка создан.
2. LazyObjectContext.ObjectLoaded - происходит всякий раз, когда происходит загрузка по требованию.
See the samples for more information. There are also new LazyObjectContext methods:

Reset(ILazyEntityObject) - which detaches and releases data object from a shell object - while keeping the object attached to the context.
ResetAllUnchangedObjects() - does the same thing for all unchanged objects in the context - objects will be demand-loaded next time any of the properties is accessed.

См. примеры, чтобы получить дополнительную информацию. Есть также новые методы LazyObjectContext:
1. Reset(ILazyEntityObject) - который отделяет и освобождает объект данных от объекта-оболочки - пока сохраняется объект, присоединённый к контексту.
2. ResetAllUnchangedObjects () - делает ту же самую вещь для всех неизменных объектов в контексте - объекты будут загружены по требованию в следующий раз, когда к любому из свойств будет обращение.
In the ZIP file there is a help file (CHM) which has auto-generated API documentation (using Sandcastle). I hope this will be useful.

В ZIP-файле есть справочный файл (chm), который был сгенерирован с использованием Sandcastle. Я надеюсь, что это будет полезно.

Lessons learned
Чему мы научились :)


The first and foremost lesson learned is that it is quite possible to have transparent lazy loading working with Entity Framework. Being able to write your own entity classes (provided that they adhere to IPOCO specification) that add functionality under the hood opens up a whole new world of possibilities.

Possible applications of this technique may include cross-ObjectContext object sharing & caching (that may be actually very simple, because you can easily share “Data” objects if you can only make them read-only and copy on write).

In the next post I will explain the object type cache (for managing EntityKey to concrete type mapping) and introduce additional extension methods that make it possible to write LINQ and Entity SQL queries that return stubs of objects.

Прежде всего - то, что вполне возможно иметь прозрачную "ленивую загрузку", работающую с Entity Framework. Вы способны написать собственные классы сущностей (при условии, что они придерживаются спецификации IPOCO), которые добавляют функциональные возможности "за кадром" и это открывают целый новый мир возможностей.

Возможное использование этих техник может включать кросс-ObjectContext обмен и кэширование объектов (которое может быть фактически очень простым, потому что Вы можете легко поделиться объектами-"Данными", если Вы можете только сделать их только для чтения и копировать при записи).

В следующей заметке я объясню типизацию объектного кэша (для управления EntityKey при маппинге к конкретному типу) и представлю дополнительные расширения методов, которые позволяют написать LINQ и Entity-SQL запросы, которые возвращают заглушки объектов.

суббота, сентября 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.
Стратегия, которая будет использоваться, основана на том, что объект не должен полностью материализоваться, если вы не получаете доступ к его неключевым свойствам …