.NET
December 20 2008

Наследование в ADO.NET Entity Framework

О чём вы, Морфеус?


Приветствую всех!
Моя первая статья на хабре была оценена хабраюзерами достаточно высоко. Что же, спасибо всем кто оставил своё мнение о статье, мне было приятно вас почитать, я продолжаю.

В новой статье хотелось бы поговорить о наследовании. Признаться честно, до изучения ADO.NET Entity Framework я вообще даже не задумывался о том, чтобы вводить в свои проекты наследование сущностей в объектно-ориентированных обёртках для БД. Обычно базу строили так, чтобы максимально избегать наследования. Хотя, порой оно и маячило на горизонте, но обходилось. Сейчас я опишу, как я добавил в свой проект два очень простых класса, которые были отнаследованны от уже имеющихся таблиц.

Опять же, замечу, что я не буду углубляться в теорию, всё показываю на практике. Для теории я сделаю отдельный цикл статей.

И так,

Скажу сразу. Я над кодом просидел час, разбирая те ошибки, которые у меня возникли во время создания этой статьи. И моя цель сейчас — не только показать, как реализуется наследование в ANEF но и показать вам типовые ошибки, которых можно избежать.

Да, кстати, неявно договорились вот о чём: ANEF = ADO.NET Entity Framework

Вот моя база данных.

Со времён первой статьи структура БД немного изменилась, но всё же осталась очень простой. Изначально в БД присутствовали только две таблицы: Post и User, сейчас схема была немного усложнена

  1. Я создал таблицу BlogPost. Смысл этого в том, что в моей системе Post будет является не только постом блога, но и комментарием, сообщением, и вообще всем, что один пользователь может передать другому. Такая схема сделана исключительно для целей обучения. Потому что в большинстве систем комментарии — это очень критичная таблица, в которой постоянно много записей, и которая является самой нагруженной таблицей в системе. Соответственно, BlogPost должен унаследовать все данные из Post и нести в себе дополнительную информацию. Я хочу, чтобы в моей системе под каждым постом блога пользователи сами писали название ссылки на комментарии. Например: «Жгут здесь», «Об этом думают». Это внесёт некое разнообразие. Ну, что же, все мои желания хорошо видны на схеме БД. Тут я применю первый тип наследования — каждая Entity имеет свою таблицу.
  2. Так же, мне было бы удобно, чтобы в моей модели БД были и простые User'ы и Admin'ы. Посему, используя поле IsAdmin я буду делать второй тип наследования — одна таблица на несколько Entity. По значению в этом поле будет происходить отсеивание entity


Сразу оговорюсь. Здесь я сделал самую большую свою ошибку, которая стоила мне 20-ти минут работы с форумами. А в общем-то она была очень глупой, и была сделана по недосмотру.



Вот на этом скриншоте приведены правильные расстановки связей в БД. В первый раз, я по глупости развернул их в другую сторону, тобишь я сделал таблицу BlogPost Primary в связи. Ладно, бывает, поехали дальше.

Заходим в студию, в наш проект базы данных и обновляем нашу схему из БД. При этом должна появиться новая Entity BlogPost. Всё отлично, появилась. Даже со связью. Удаляйте эту связь первым делом. В наследованных Entity она нам не нужна. После этого — в контекстном меню Entity Post мы добавляем новое наследование. Выходит что-то типа этого:


И вот тут я в первый раз словил разочарование от визуальной среды разработки. Запускаем валидацию проекта (я рекомендую запускать эту валидацию чуть ли не после каждого чиха, если вы ещё не совсем освоились в работе с наследованием ANEF). И валидация с треском падает. Неприятность заключается в том, что нам надо ещё немного пошаманить и кое что понять, прежде чем нам удастся нормально организовать наследование.
У нас есть две таблицы: BlogPost и Post. В таблицах есть два ключа BlogPost.PostId и Post.Id, так считает SQL. ANEF достаточно верно считает, что на самом деле, у таблицы BlogPost нет ключа BlogPost.PostId, а есть ключ Post.Id. В принципе — это более чем логично, у нас есть связь 1:1, так зачем нам надо заморачиваться с ещё одним ключём? Тоже верно. Удаляем из Entity BlogPost параметр PostId. После этого валидация снова рушится. Тоже правильно. У нас в таблице есть значение PostId, но оно не имеет никаких мэппингов. Исправляем этот недочёт:



Мы выставляем мэппинг для поля таблицы PostId в переменную Id. Сразу возникает вопрос, а где это мы успели определить переменную Id для таблицы BlogPost. Как я говорил раньше — ANEF считает что у Entity Post и BlogPost есть только один ключ, поэтому неявно пририсовала его к таблице BlogPost. В итоге, наша конечная Entity выглядит так:


Кажется всё, теперь это можно использовать. Пытайтесь, компилируйте.
Это был первый тип наследования, который мы разбирали. Каждая Entity привязана к своей таблице в БД. Теперь перейдём ко второму типу наследования, где у нас есть только одна таблица, и несколько типов Entity.

Как я уже говорил — я хотел отделить Entity Admin от User. C помощью ANEF это достаточно просто сделать. Через контекстное меню добавляйте новую Entity, при этом укажите, что она отнаследована от Entity User. Сразу же можете удалять свойство IsAdmin из entity User и из Admin, у меня оно оставлено для наглядности, но с ним вы не пройдёте валидацию. Короче, вот так выглядит первое приближение нашего наследования:



Опять надо немного поколдовать. Для начала, надо настроить условия мэппингов. В частности, сейчас у нас все User и все Admin одинаковы, их ничего не разделяет. Переходим к мэппингам entity и выбираем условия, при которых должен проходить мэппинг:


Тут я уже удалил ненужное нам поле IsAdmin, более того, ANEF не даёт возможности вынести это поле в переменную, так как оно является условием мэппинга. Ну что же, у нас появились админы, а что будет в таблице пользователей? Вот это была ещё одна крутая ошибка. Я лазил по форумам ещё пять минут, пока до меня не дошёл достаточно простой факт: если у нас по какому-либо условию идёт отсеивание записей в таблицу Admin, то это не значит, что все остальные записи автоматом уйдут в таблицу пользователей. Поэтому, необходимо в мэппингах таблицы User тоже включить условие и отсеивать остальные записи в эту таблицу.

Сразу оговорюсь, у меня поле IsAdmin, несмотря на название, не Boolean, а Int16, так что отсеивание проводилось по принципу IsAdmin=1 и IsAdmin=0, в дальнейшем у нас была идея расширить возможности по администрированию, так что взято с заделом на будущее.

Ну вот, у нас образовались две очень простых, но всё же отнаследованных entity. Какие именно свойства и как наследовать — это не важно. Вы сами можете поэкспериментировать с этим. Моей целью было показать, как именно проводить это наследование. Cделано. Для начала.

Могу лишь заметить вот что. Пока я копался в наследовании ANEF я прикинул несколько схем, реализация которых действительно была бы облегчена с использованием наследования. Например, в одной из наших систем существовала система работы с портфолио, где человек мог вводить о себе различные данные, такие как: место работы, образования, научные публикации, итд. Для каждого из типов записи в портфолио была отдельная таблица, и могу вам сказать точно — SQL запрос, который выбирал эти данные был просто адским. Всё было очень неудобно и криво. Если бы я тогда использовал наследование, то я имел бы List<> базовых классов, например PortfolioEntry, с которыми мог бы быстро и удобно работать. Это — пример из жизни, я думаю, поковырявшись во внутренностях ANEF вы сами найдёте множество таких примеров в своём коде.

В следующей статье я постараюсь привести побольше программного кода, который покажет как правильно работать с наследованными entity.

P.S. Самому писать себе систему скучно. Вот мне и подумалось, почему бы не пригласить Коллективный Разум к себе, и не проконсультироваться с ним, по поводу всего этого. Предполагается, что в следующую субботу я смогу пригласить к себе двух-трёх заинтересованных человек, с которыми можно будет на равных условиях мучить ANEF, проводить тесты, писать код и описывать теорию. Просто я один за всем не услежу. В конце трёх-четырёх часового мероприятия участники получат халявное пиво. Если кто-то заинтересован в подобных групповых тестах — пишите в личку, будем продумывать.

P.P.S. Всё это было проделано на бесплатных версиях Visual Studio и Management Studio. Microsoft не самым зверским образом урезал свои продукты.
+14
7.4k 37
Comments 15
Top of the day