Наследование в 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 не самым зверским образом урезал свои продукты.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 15

      +1
      Спасибо за хороший материал.
        +1
        Очень познавательно, спасибо
          0
          В свое время изрядно по гемороился с с этими дизайнерами в вижуал студии. Считаю, что лучший и наиболее прозрачный способ описания маппингов — руками в хмл.
            0
            Спасибо.
            А не в курсе, что из этого можно провернуть в Linq to SQL? Т.е. можно ли так же пронаследовать два класса из разных таблиц, из одной.
              0
              Да, в пределах одной таблицы можно делать иерархию в Linq to SQL
              0
              «Заходим в студию, в наш проект базы данных и обновляем нашу схему из БД. При этом должна появиться новая Entity BlogPost. Всё отлично, появилась. Даже со связью. Удаляйте эту связь первым делом. В наследованных Entity она нам не нужна.»

              Удалять эту связь мне придется каждый раз при обновлении схемы из БД?
                0
                Хороший вопрос. Только что проверил, второй раз связь не вылезала, и повторно удалять ничего не придётся.
                  0
                  Спасибо.
                  Это хорошо, наверное.
                  Но в этом случае нужно обязательно уметь понимать, проапдейчена уже схема или нет. Естественно не анализируя саму схему.
                  Иначе получается жутко непрозрачная штука, с которой страшно работать :)
                    0
                    Там просто можно выбирать те таблицы, которые надо апдейтить. Сейчас я проапдейтил BlogPost но связь не вылезла.
                      0
                      Это не решение.

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

                      Возможно структура метаданных схемы хоть как-то читабельна и её можно править ручками… Во всяком случае это было бы хорошо. Получился бы эдакий Microsoft NHibernate. :)
                        0
                        Ну, скажем так, в таком проекте я работал с Team Foundation Suite. В нём есть отличный комперер данных и система отката 8-) Так что всё становится проще, когда инструменты работают лучше. Думаю, МСовский редактор не единственный, да и как показала моя практика — 50% забугорных программеров вообще работают со схемой ХМЛ.

                        Ну, а в общем — я соглашусь, идеальных инструментов не бывает 8-)
                0
                Очень здорово:) Однако, не совсем понятно, какая именно система делается и что она будет делать:)
                  0
                  Эм, скажем так, я ещё не решился. То ли это будет спец система блогопостинга, то ли просто система для мучения и обучения 8-)
                    0
                    Есть возможность реализовать систему учета расхода собственных средств и планирования расходов на какой-то период:) Это моя система мучения, но, имхо, в отличие от вашей, у меня есть один клент — моя знакомая и я сам:)

                    Интерфейсы для сервисов данных я уже прописал. Если в кратце, то мы можем скооперироваться — я останусь со своим клиентом, а вы можете плескаться в данных как вам угодно. Первоначальная схема данных очень простая. Но ее потом можно усложнить.

                    Можно почитать тут:
                    acerv.livejournal.com/278203.html
                    Там по ссылкам «тут» можно провалится в самое начало.
                  0
                  Приведите, пожалуйста, запросы к унаследованным entity

                  Only users with full accounts can post comments. Log in, please.