asp.net: Entity Framework, одно из отличий от LINQ to Sql

    Этой статьей хотел бы открыть цикл статей по Entity Framework. В дальнейшем планирую постить результаты своего опыта в процессе использования EF, а пока я приведу вольное изложение интересной статьи на тему «Чем отличается LINQ to SQL от Entity Framework».

    Сравним два куска кода, первый написан с использованием LINQ to SQL:
    L2SDataContext linqToSqlContext = new L2SDataContext();

    this.MyDataGrid1.DataSource = linqToSqlContext.Customers.First().Orders;

    this.MyDataGrid1.DataBind();
    * This source code was highlighted with Source Code Highlighter.

    В результате этого кода в DataGrid отобразиться три записи.
    Следующий код написан с использованием EntityFramework:
    EFEntities entityFrameworkContext = new EFEntities();

    this.MyDataGrid2.DataSource = entityFrameworkContext.Customers.First().Orders;

    this.MyDataGrid2.DataBind();
    * This source code was highlighted with Source Code Highlighter.

    Как можно убедиться код по структуре практически не отличается. Но вместе с тем, в DataGrid не отобразиться ни одной записи. В чем же тут дело?

    Lazy Loading


    Ленивая загрузка (Lazy Loading) – это как раз та техника имеющаяся в Linq to Sql, которая скрытно подгружает в первом примере для Customers связанные записи Orders. В Entity Framework ленивая загрузка отсутствует. Этому моменту есть объяснение от команды разработчиков EF: «При разработке большого проекта для разработчиков очень важно ясно представлять себе как они получают доступ к ресурсам, таким как база данных» (When architecting a larger project, it is highly important for developers to clearly understand when they are accessing certain resources, such as the database). Другими словами, EF предлагает разработчикам самим определить, что и когда нужно загружать из базы. И делается это вот так:
    this.MyDataGrid2.DataSource = entityFrameworkContext.Customers
      .Include(«Orders»).First().Orders;
    * This source code was highlighted with Source Code Highlighter.

    Другой способ состоит в том, чтобы явно загрузить данные:
    var customer = entityFrameworkContext.Customers.First();

    customer.Orders.Load();

    this.MyDataGrid2.DataSource = customer.Orders;
    * This source code was highlighted with Source Code Highlighter.


    Ленивая загрузка – безусловно, полезная вещь и экономит много времени, поэтому далее автор статьи приводит небольшой пример того, как эту технику можно «вернуть» в EF. Пример не самый востребованный, потому что основан на редактировании кода сгенерированного автоматически.

    Отсутствие ленивой загрузки — это, пожалуй, одно из основных отличий в EF. Это очень важный момент, который стоит иметь в виду при переходе с Linq to Sql.

    Оригинал статьи «Entity Framework and Lazy Loading» можно почитать тут:
    www.singingeels.com/Articles/Entity_Framework_and_Lazy_Loading.aspx
    Поделиться публикацией

    Похожие публикации

    Комментарии 16

      +2
      Спасибо за статью.

      Entity Framework, наряду с ASP.NET MVC Framework, одна из интересующих меня вещей. Несмотря на то, что механизм маппинга в ней (посредством трех XML-файлов) немного напрягает.
      Linq to SQL я попробовал в паре внутренних проектов, и он вызвал у меня двоякие чувства. С одной стороны, делать запросы через Linq безотносительно к состоянию объектов (выбор из DB или уже загруженной коллекции) удобно, но... с другой стороны, ограничения, накладываемые на структуру базы данных и модели предметной области, отсутствие поддержки many-to-many, несколько странная работа системы отслеживания изменений в DataContext (время от времени изменения в EntitySet'ах ну никак не хотели записываться).

      Я надеюсь, Entity Framework будет смотреться намного лучше, тем более, что какие-то его средства (было упомянуто вскользь на Heroes Happen Here) будут включены в SQL Server 2008. К сожалению, пока катастрофически не хватает времени и желания всерьез покопаться с ним.

      Еще волнует производительность, ибо база данных обычно нагибается в первую очередь.
        0
        Пожалуйста, тем более, что я только изложил чужой материал своими словами, да и то не весь.

        Про маппинг ваша информация уже устарела, маппинг в EF сейчас делается через один файл с тремя разделами.

        Можно подробней об "ограничения, накладываемые на структуру базы данных и модели предметной области", не совсем понял, что вы имели в виду.

        Many-to-many, конечно же поддерживаются (вас читают и те, кто не знают ничего про asp.net и могут не так понять), не поддерживается только прямой мэппинг минуя промежуточную таблицу, но на то оно и DB ORM, чтобы копировать структуру БД один в один. С другой стороны, конечно, хотелось бы и побольше возможностей в этом плане.

        Что такое "система отслеживания изменений в DataContext"? Тоже не совсем понял о чем вы говорите.
          0
          >> ограничения, накладываемые на структуру базы данных и модели предметной области
          Ну, например, в Linq to SQL поддерживается только модель «одна сущность — одна таблица». Разнести данные одной сущности на несколько таблиц уже не получится, что иногда мешает нормализации БД. Наследование — тоже только по модели Single Table Inheritance. В EF этих проблемы уже нет.

          >> Many-to-many, конечно же поддерживаются
          Да, но через задницудополнительную сущность. Тем более странно, что в известных ORM-решениях (в частности, NHibernate, Gentle.Net) это вполне работает и напрямую. Кстати, тут я вас уже не понял: «прямой мэппинг минуя промежуточную таблицу» — как это? Как раз и интересен мэппинг через таблицу вида { FirstEntityID, SecondEntityID }, хотя бы потому, что используется едва ли не в каждом проекте.

          >> Что такое "система отслеживания изменений в DataContext"
          Это внутренняя подсистема Linq to SQL, отслеживающая изменения в объектах, которые были запрошены. По сути, она хранит объект в исходном состоянии и ссылку на тот, который используется вами. При вызове метода SubmitChanges() для DataContext она сравнивает объекты и вносит в базу данных произошедшие изменения. Кроме того, это позволяет осуществлять проверку, не были ли изменены ваши объекты с момента последней их выборки (optimistic concurrency checking).
          Подробнее об этом можно посмотреть тут: http://weblogs.asp.net/scottgu/archive/2…
          Так вот, этот самый SubmitChanges() у меня вносил изменения в зависимости от фазы Луны. Может быть, я где-то был неправ, но код-то сгенерированный.
            0
            >>>Кстати, тут я вас уже не понял: «прямой мэппинг минуя промежуточную таблицу» — как это?

            Этим я их хотел сказать то, что вы уже сказали, при мэппинге участвует промежуточная таблица, с которой надо работать. Соответственно в EF такая таблица не мапится в сущность. Вы правы в Linq есть такое ограничение.

            Сам проблем с SubmitChanges() не испытывал, но стало интересно в чем заключались проблемы? Какое-то особое состояние БД? Сильная нагрузка?
              0
              Какая уж там сильная нагрузка на внутреннем проекте? :)
              Нет, дело совсем не в базе. Там была моделька вида «каталог - элементы», соответственно, один-ко-многим. В каждом каталоге — EntitySet, в который грузятся лежащие в нем элементы. Так вот, выбиралось все прекрасно, но вот когда делаешь Add() в EntitySet, а потом сохраняешь изменения, возникала проблема. Либо все писалось в базу, но без CatalogID (равным нулю, если точнее), и элемент не выбирался, либо вообще ничего не сохранялось. Проблему обошел, только тупо присваивая item.Catalog = catalog, и после этого вызывая context.Items.InsertOnSubmit(item).
              Вот, кстати, еще один, имхо, недостаток Linq to SQL: чтобы прописать Association, необходимо, чтобы в дочернем объекте существовало свойство, хранящее значение идентификатора родителя. С учетом типизации, то есть, EntityRef<> тут не подойдет. Как раз это я и назвал «ограничением Linq на модели предметной области».
                0
                Еще бы на код взглянуть :)
                "Прописать Association" - это задать связь с чем-то новым чтоли? На этапе маппинга или вы во время исполнения создаете? Как можно связать дочерний элемент с родителем без наличия идентификатора родителя? Что-то не вникаю. Жара чтоли?

                Или вы все про определение сущностей говорите? Тогда все эти недостатки вытекают из одного - Linq to Sql тупо повторяет структуру БД и даже меньше (к примеру не обрабатывает автоматически default constraint, хотя мог бы). Поэтому то и предлагают EF, как далеко продвинутый вариант ORM маппинга.
                  0
                  Я про определение сущностей. Иллюстрирую:

                    [Table(Name = "Catalogs")]
                    public class Catalog
                    {
                      private int _id;
                      private EntityRef<Directory> _parent;


                      [Column(Name = "ID", DbType = "int not null",
                        IsDbGenerated = true, IsPrimaryKey = true,
                        CanBeNull = false, Storage = "_id",
                        AutoSync = AutoSync.OnInsert)]
                      public int ID
                      {
                        get
                        {
                          return _id;
                        }
                      }

                      [Column(Name = "ParentID", Storage = "_parentID",
                        CanBeNull = true, UpdateCheck = UpdateCheck.Never)]
                      private int? _parentID;

                      [Association(ThisKey = "_parentID", Storage = "_parent")]
                      public Directory Parent
                      {
                        get
                        {
                          return _parent.Entity;
                        }
                        set
                        {
                          _parentID = value.ID;
                          _parent.Entity = value;
                        }
                      }
                      ...


                  * This source code was highlighted with Source Code Highlighter.


                  Чтобы создать ссылку на родителя, необходимо добавлять вот такое извращение, как private int? _parentID. Если в EF можно так не делать, это очень радует. Хотя, казалось бы, сделать код, который, если поле/свойство помечено атрибутами Column и Accociation одновременно, читал/писал бы значение заданного поля для связанной сущности, не так сложно, ан нет...
                    0
                    По моему ничего лишнего тут нет. Программист должен иметь возможность обратиться к значению _parentId, а вы его хотите скрывать.
                      0
                      Ну зачем ему _parentID, если у него есть Parent? :)
                        0
                        Иначе как отработать ситуацию, когда меняется ключ? По вашему я должен создать экземпляр предка и присвоить его потомку, вместо того, чтобы просто присвоить простой идентификатор объявленный как Int? Может быть именно тут порылась собака которая у вас обрушала SubmitChanges()?
                          0
                          Может быть, и здесь.
                          Все равно что-то мне не нравится в таком подходе. Может быть, он и проще, но по логике... Для чего, спрашивается, создаются все эти Entities? Что мешает работать напрямую, скажем, c DataSet? Мы создаем domain model, все-таки исходя из задач, специфичных для предметной области, а не из того, как у нас в БД это хранится. В это-то и соль ORM: в БД своя схема, domain model — другая схема, а ORM (или рукописный data access layer, неважно) сопоставляет одно с другим.
                          По-хорошему, программист должен вообще быть избавлен от деталей, как там у нас данные хранятся.

                          Допустим, мы пишем магазин. Есть связь вида «категория - продукт». Если мы получаем товары из свойства Products класса Category, логично предположить, что категорию продукта следует брать из свойства Category класса Product, так? А у нас там как Category, так и какой-нибудь CategoryName. Программист, увидя такое, наверняка задастся вопросом (а я бы задался и взялся за декомпилятор) «А как именно мне переместить продукт в другую категорию? Достаточно ли просто поменять CategoryName, или нужно менять свойство Category? А что будет, если я поменяю, скажем, product.Category.Name?»
                            0
                            >> По вашему я должен создать экземпляр предка и присвоить его потомку, вместо того, чтобы просто присвоить простой идентификатор объявленный как Int?

                            Ну, вообще-то, как раз такие вещи ORM должна отрабатывать.
                            0
                            У меня вроде бы такая задача как раз и реализовывалась стандартными средствами. Попробую ещё детальное как у вас.
                            Я столкнулся с той проблемой, что когда мы удаляем из коллекции Directory.Catalogs, то LINQ пытается просто удалить связь с родителем, а не удаляет элемент прямо из базы. С одной стороны логично, но как сделать чтобы удаляло напряемую через эту коллекцию — сразу не нашел. Пришлось делать только через db.Catalogs. Т.е. работать напрямую с дочерними элементами. В моем случае это был заказ и товары заказа. В общем-то сами по себе товары заказа не имею ценности и я бы вообще закрыл к ним доступ напрямую, но вот с такими проблемами столкнулся..
                              +1
                              Удаляет связь с родителем == устанавливает поле ParentID в БД равным NULL?
                              Тогда, насколько я понимаю, достаточно установить DeleteOnNull = true в атрибуте Association, маркирующем EntitySet.
                              Для этого, правда, придется менять либо DBML, либо сгенерированный код.
                              Подробнее об этом можно прочитать здесь: http://forums.microsoft.com/MSDN/ShowPos…

                              А вот что делать, когда он не добавляется — пока не нашел (собственно, пока и не особо искал :)
                                0
                                Да, пытается установить в NULL.
                                Спасибо! Сегодня ваше решение протестирую — посмотрю чем закончится. Вроде бы все свойства пересмотрел, ничего подходящего (ещё и с таким очевидным названием) не нашел.
            +1
            Ух ты. Видно автора интересует:). Тогда вот статья, возможно, автор уже в курсе:)
            Даёшь подробные детали Entity. Лучше сразу с маппинга, ну и с подробного описания, зачем нужны именно 3 файла для описания модели.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое