Практикуємось у ADO.NET Entity Framework. Model First.

Відразу зазначу, яке відношення має цикл статей для Entity Framework довеб-програмування. Надалі я маю намір використовувати отриману модель для створення веб-додатку на базі технології ASP.NET MVC 3, яка дуже добре поєднується зEntity Framework.
Перш за все – вам потрібно мати встановлену Visual Studio 2010 (здається 2008 теж підійде) та встановлений пакет ADO.NET Entity Framework 4.1 знайдіть його за посиланням або у пошуку на microsoft.com. Тепер нам стають доступними всі можливості ADO.NET Entity Framework. Зауважу, що четверта версія відрізняється від раніше, так що, якщо у вас встановлена більш рання версія, то не гарантую, що у вас буде працювати той код, що я привів у статті.
Отже, створюємо звичайний консольний додаток. Надаємо йому ім'я test1. Додаємо до проекту модель даних.
називається її MyEFModel.edmx.
Перед тим як створювати сутності, скажімо дизайнерові моделі, за якими правилами формуватимуться імена колекцій сутностей. Я ось про що. Одночасно з сутністю моделі створюється також контейнер цих сутностей, тобто по суті колекція. Аналогія з таблицею в БД пряма: сутність – це рядок таблиці, контейнер сутностей – сама таблиця. Дизайнер дає ім'я контейнеру виходячи з імені сутності (ім'я контейнера, втім, завжди можна змінити), і для сутності User контейнер може назвати UserSet або Users. На мене так назва Users набагато приємніша. Тому для самої моделі у властивостях ми виставимо Pluralize New Objects = True.
Отже, створюємо сутності (сутності створюються правим кліком на діаграмі моделі даних та вибором контекстного меню Add-> Entity…):
1) Entity Name = User Entity Set = Users Інші властивості залишимо беззмін. Зауважимо лише, що за умовчанням дизайнер моделі створює первинний ключ Id цілого типу, що для нас цілком прийнятно. Додаємо до цієї сутності властивості(Properties): Login (Type = String, Max Length = 255) Registered (Type = DateTime) Властивості додаються в контекстному меню самої сутності (Add -> Scalar Property).
2) Entity Name = Group Entity Set = Groups Властивості: Name (Type = String, Max Length = 255)
3) Entity Name = Right Entity Set = Rights Властивості: Description (Type = String, Max Length = 255)
Отже, тепер ми маємо три сутності, поки що не пов'язані один з одним. Займемося цим питанням. Створюємо зв'язки (Add-> Association):
1) Association Name = UserGroup Початок: Entity = User, Multiplicity = Many Кінець: Entity = Group, Multiplicity = One Це співвідношення "Один до багатьох", тобто кожен користувач може складатися тільки в одній групі, але в кожній групі може бути багато користувачів. (Якби у сутності Group ми вибрали Multiplicity = Zero or One, то ми отримали відношення “Один до багатьох”, але дали б при цьому можливість мати користувачів не належать жодній групі, тобто у користувача властивість Group було б Nullable).
2) Association Name = GroupRight Початок: Entity = Group, Multiplicity = Many Кінець: Entity = Right, Multiplicity = Many Це співвідношення “Багато багатьох”, тобто група містить у собі безліч прав, у своїй кожне право може належати кільком різним групам.
Ось яка схема даних у нас виникла:

Зверніть увагу, що після створення зв'язків у кожній із сутностей з'явилися додаткові навігаційні властивості. Наприклад, властивість Group сутності User дає намможливість дізнатися групу, в якій складається користувач, а властивість Rights сутності Group дає можливість отримати список всіх прав групи. Таким чином, звернувшись до concreteUser.Group.Rights, ми отримаємо список прав користувача. Як видно зі схеми даних, середовище розробки створило також зустрічні (зворотні) навігаційні властивості Right.Groups і Group.Users.
Ну що ж, модель даних готова. Але щоб почати писати програмний код, що оперує з даними, потрібно спочатку створити саму базу даних. Використання Visual Studio2010 зробити це дуже легко.
Праве клацання миші на діаграмі, потім у контекстному меню “Generate Database from Model…“. Тут ми можемо вибрати існуюче підключення до БД або створити нове. Ми створимо нове підключення (кнопка New Connection…), тут ви вибираєте сервер БД, спосіб і параметри аутентифікації, та ім'я бази даних (якщо хочете створити нову, то просто введіть ім'я нової БД у полі Select or enter a database name), натискаємо OK . (Я створив базу EFUSERS на локальному комп'ютері). Майстер запитає вас чи потрібно створити БД, ви погоджуйтесь. Після цього ми знову повернемось у майстер генерації БД, переконайтеся, що галочка “Save entity connection settings in App.config as” встановлена та натисніть Next. Потім у вкладціDDL майстер відобразить вам DDL-скрипт схеми даних. Тут ви натисніть Finish.
Після роботи майстра ми отримали наступне: 1)скрипт схеми даних у файлі MyEFModel.edmx.sql, 2)рядок підключення MyEFModelContainer у файлі App.config (відкрийте його і подивіться в розділ сonnectionStrings) 3) поки щопорожня база даних EFUSERS.
Тепер нам потрібно виконати DDL-крипт, щоб створити всі необхідні об'єкти БД. Ви можете виконати файл скрипту в Query Analizer, а можете зробитице прямо в Visual Studio, відкривши скрипт і натиснувши кнопку Execute SQL на панелі інструментів, або натиснувши комбінацію Ctrl+Shift+E. Студія запросить у вас параметри підключення до БД, а потім виконає скрипт. Відкривши базу в Enterprise Manager ми можемо побачити, що там створено 4 таблиці: Users, Rights, Groups, GroupRight. Перші три таблиці зберігають у собі сутності трьох видів, а ось четверту Junction-таблицю (GroupRight) Entity Framework створив для того, щоб зберігати відносини "Багато хто до багатьох" між сутностями Group і Right. Як бачимо, за допомогою підходу Model First ми повністю пішли від етапу проектування таблиць у БД, всю роботу Entity Framework зробив за нас. Звернемо також увагу, що в таблиці Users з'явилася колонка Group_Id, яка посилається на групу, в яку входить користувач, колонка ця NOT NULL, і це гарантує, що користувач обов'язково буде належати тій чи іншій групі. Нижче наведено схему БД, на якій відображені поля таблиць, первинні та зовнішні ключі.

Я не наводитиму тут DDL-скрипт, згенерований студією, але він доступний для ознайомлення за посиланням MyEFModel.edmx
Ну ось, вся попередня робота зроблена, модель даних намальована, схема даних створена, порожні таблиці в БД є. Залишилося тепер спробувати попрацювати з цим добром.
Щоб звернутися доЗа даними БД, потрібно створити екземпляр контейнера моделі. Клас контейнера називається MyEFModelContainer, інстанцуємо його:
Як аргумент конструктора ми задаємо ім'я рядка підключення до БД. Цей рядок підключення є чимось більшим, ніж звичайна ConnectionString для підключення до бази даних, тому що в нашому випадку використовується провайдер System.Data.EntityClient, якому для ініціалізації потрібно набагато більше параметрів. Рядок підключення був створений майстром раніше і зберігається у файлі App.config, рядок підключення виглядає так:
Подивіться уважно, рядок підключення має три обов'язкові параметри: metadata, provider і provider connection string. Перший параметр визначає посилання на файли CSDL, SSDL і MSL моделей. Всі ці три файли зберігаються в папці edmxResourcesToEmbed(знайдіть її в дереві каталогів проекту) і є XML-файлами, що описують відповідно: CSDL - концептуальну(об'єктну) модель в термінах бізнес-рівня, SSDL - схема зберігання (реляційна схема), MSL - схема зіставлення (тобто зв'язки між елементами CSDL та SSDL). Всі ці XML-файли об'єднані в один з розширенням EDMX.
Параметри provider і provider connection string задають саме звичний рядок підключення до БД.
Конструктор класу MyEFModelContainer можна викликати також зовсім без параметрів, тоді за замовчуванням рядок підключення буде взятий з App.config і дорівнюватиме тому рядку підключення, для якого власне генерувався спочатку DDL-скрипт.
Як аргумент конструктора класу MyEFModelContainer можна подати також екземпляр класу EntityConnection, який є по суті об'єктна обгортка навколо рядка підключення, ми не ускладнюватимемо код, тому що конструктор класуMyEFModelContainer все одно зробить цю роботу за нас.
Отже, тепер у нас є контейнер даних, який окрім величезної кількості інших корисностей, містить у собі три колекції: Users, Groups, Rights – це ті самі колекції, заради яких, власне, все і затівалося. Операції, які ми виконуємо над цими колекціями, автоматично (ну або майже автоматично) відображаються в базі даних, додаючи, видаляючи або змінюючи рядки у відповідних таблицях. Спроба читання з колекції призведе до виконання запиту SELECT до відповідної таблиці.
Поки наші таблиці порожні спробуємо додати деякі дані. Нехай ми хочемо, щоб існували два користувача: chitatel і pushkin. Перший за статусом належить лише читання статей, другому – і читання і запис. Зрозуміло, що жодних статей у нас поки що немає, важливо лише саме розмежування прав на операції. При цьому chitatel належить групі "Читачі", а pushkin - групі "Письменники". Таким чином, нам потрібно створити два права: “Читати статті” та “Редагувати статті”; дві групи: “Читачі” та “Письменники”; двох користувачів: chitatel і pushkin. А також створити між ними коректні зв'язки. Наведу відразу шматок коду, який треба виконати один раз для того, щоб створити всі необхідні записи в таблицях.
Після виконання наведеного коду ми отримаємо такі дані в таблицях:

Розглянемо тепер алгоритм вибірки даних із контейнера. Я для цих цілей використовуватиму мову вбудованих запитів LINQ. Припустимо, що якийсь користувач увійшов до системи, і нам потрібно визначити, чи має він право редагувати статті. Тобто ми маємо на ім'я користувача отримати його групу та подивитися, чи має ця група відповідне право.
Для цього ми виконаємо наступний код:
Дослівно, LINQ-запит вибирає з БД всіх користувачів, які мають заданий логін (тобто єдиного користувача), і права групи в полі Description мають вміст “Редагувати статті”. Якщо такий користувач існує, то check.Count() == 1, а значить, користувач має необхідне право. Текст SQL-запиту, виконаного в БД, виглядає так:
Тут ми бачимо потворний SELECT FROM SELECT обтяжений EXISTS-ом та INNER JOIN-ом. На мою думку, запит далеко не оптимальний. Небагато спростити запит, прибравши з нього INNER JOIN можна, якщо шукати права не за назвою (“Редагувати статті”), що в будь-якому випадку є поганим тоном, тому що залежить від способу написання назви права, а шукати за Id права, адже погодьтеся зіставлення ID та DESCRIPTION у нашій базі завжди зберігатиметься. Таким чином, спрощуємо LINQ-запит:
І отримуємо злегка укорочений SQL-запит:
Я думаю, що на початковому етапі створення проекту, коли таблиці будуть ще не надто великими, підійде і цей запит, надалі ми просто виноситимемо всі часто використовувані не надто оптимальні запити в окремі оптимізовані функції і процедури, що зберігаються. На щастя в Entity Framework 4 закладена можливість виклику збережених процедур та функцій, і не просто їх виклику, а навіть використання їх у LINQ-запитах, що погодитеся дуже добре.
Отже, ми навчилися створювати модель даних, генерувати з неї схему даних, створювати сутності і робити з них вибірку.
У наступній статті я планую торкнутися зміни (UPDATE) сутностей та зміни схеми даних (ALTER), створення та використання функцій і процедур, що зберігаються.