Как стать автором
Обновить

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

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

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. К сожалению, пока катастрофически не хватает времени и желания всерьез покопаться с ним.

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

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

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

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

Что такое "система отслеживания изменений в DataContext"? Тоже не совсем понял о чем вы говорите.
>> ограничения, накладываемые на структуру базы данных и модели предметной области
Ну, например, в 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() у меня вносил изменения в зависимости от фазы Луны. Может быть, я где-то был неправ, но код-то сгенерированный.
>>>Кстати, тут я вас уже не понял: «прямой мэппинг минуя промежуточную таблицу» — как это?

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

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

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

  [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 одновременно, читал/писал бы значение заданного поля для связанной сущности, не так сложно, ан нет...
По моему ничего лишнего тут нет. Программист должен иметь возможность обратиться к значению _parentId, а вы его хотите скрывать.
Ну зачем ему _parentID, если у него есть Parent? :)
Иначе как отработать ситуацию, когда меняется ключ? По вашему я должен создать экземпляр предка и присвоить его потомку, вместо того, чтобы просто присвоить простой идентификатор объявленный как Int? Может быть именно тут порылась собака которая у вас обрушала SubmitChanges()?
Может быть, и здесь.
Все равно что-то мне не нравится в таком подходе. Может быть, он и проще, но по логике... Для чего, спрашивается, создаются все эти Entities? Что мешает работать напрямую, скажем, c DataSet? Мы создаем domain model, все-таки исходя из задач, специфичных для предметной области, а не из того, как у нас в БД это хранится. В это-то и соль ORM: в БД своя схема, domain model — другая схема, а ORM (или рукописный data access layer, неважно) сопоставляет одно с другим.
По-хорошему, программист должен вообще быть избавлен от деталей, как там у нас данные хранятся.

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

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

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

Публикации

Истории