Entity Framework. Часть 3 – Code First и соглашения

Вернемся к создаваемому демонстрационному проекту каталога книг. Давайте посмотрим, каким образом в нем используется рассматриваемая библиотека Entity Framework.

Давайте вспомним, что на данный момент веб-приложение уже вполне работоспособно. При помощи заготовок были созданы Контроллеры и Представления необходимые для работы с указанными классами Модели. Кроме того, был добавлен код, использующий Entity Framework для взаимодействия с базой данных. Разумеется возникает вопрос: а где она сама?

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

Необходимые составляющие

Что необходимо для работы Entity Framework Code First, разумеется без учета самого ядра библиотеки? Это контекст базы данных, строка соединения и исходный код классов Модели.

Классы Модели

Здесь стоит обратить внимание на тот факт, что в исходном коде классов Модели на данный момент нет никаких атрибутов. Равно как и на факт отсутствия необходимости наследования их от специальных базовых классов. Это обычные POCO (Plain Old CLR Object) классы.

Дело в том, что Сode First для генерация базы данных использует ряд соглашений по умолчанию. Они определяют правила создания таблиц и достаточны для достаточно большого числа ситуаций. Разумеется, при необходимости их можно уточнить, используя атрибуты или с помощью специального интерфейса Fluent API. Все это будет рассмотрено далее. А пока просто еще раз выделим тот факт, что для работы Code First достаточно простых классов, которые на данный момент и присутствуют в создаваемом веб-приложении.

Контекст базы данных

Следующая составляющая – контекст базы данных, который расположен в классе CatalogContext. Вот его исходный код:

namespace BookCatalog.Models.DbContext
{
    using System.Data.Entity;
    using BookCatalog.Models;

    public class CatalogContext : DbContext
    {
        // You can add custom code to this file. Changes will not be overwritten.
        // 
        // If you want Entity Framework to drop and regenerate your database
        // automatically whenever you change your model schema, add the following
        // code to the Application_Start method in your Global.asax file.
        // Note: this will destroy and re-create your database with every model change.
        // 
        // System.Data.Entity.Database.SetInitializer(new System.Data.Entity.DropCreateDatabaseIfModelChanges<BookCatalog.Models.CatalogContext>());

        public DbSet<Tag> Tags { get; set; }

        public DbSet<Publisher> Publishers { get; set; }

        public DbSet<Language> Languages { get; set; }

        public DbSet<BookDetails> Books { get; set; }
    }
}

Он содержит свойства с помощью которых можно обмениваться информацией с базой данных. Все они имеют тип DbSet<T> и могут быть использованы для получения экземпляров T. При необходимости можно расширить этот список и указать другие классы Модели, добавив свойства вида:

public DbSet<MyClass> MyTableName { get; set; }

Код этого класса можно модифицировать самостоятельно. В дальнейшем, при необходимости помощники ASP.NET MVC 3 будут добавлять в него новые свойства, не изменяя остальное содержимое.

Строка соединения

Строка соединения определяет с какой системой управления базами данных будет взаимодействовать ядро Code First. Обратите внимание, что её имя в web.config совпадает с именем класса контекста, а вид гораздо проще, чем у обычной строки Entity Framework:

  .........
  </appSettings>

  <connectionStrings>
    <add name="CatalogContext"
         connectionString="Data source=|DataDirectory|\BooksCatalog.sdf"
         providerName="System.Data.SqlServerCe.4.0" />
  </connectionStrings>

  <system.web>
  .........

Приведенный выше вариант демонстрирует настройку подключения к Microsoft SQL Server Compact. Файл базы данных будет создан в папке App_Data и получит имя BooksCatalog.sdf.

При желании можно воспользоваться Microsoft SQL Server Express или полной версией Microsoft SQL Server. Тогда строка соединения будет примерно вот такой:

  .........
  </appSettings>

  <connectionStrings>
    <add name="CatalogContext"
         connectionString="Data Source=(local); Initial Catalog=BookCatalog; Integrated Security=true;"
         providerName="System.Data.SqlClient" />
  </connectionStrings>

  <system.web>
  .........

Обратите внимание, что в данном случае тип используемой системы управления базами данных никак не влияет на исходный код. Все настройки делаются в файле конфигурации web.config (или в app.config). Разумеется, при необходимости их можно задать и программно.

Таким образом можно сказать, что классы Модели, контекст и строка соединения – это все что необходимо для организации работы с базой данных в рамках подхода Code First.

Созданные таблицы

Созданные таблицыДавайте теперь посмотрим на полученную в результате базу данных. Для этого потребуются SQL CE Tools for Visual Studio SP1. В Solution Navigator или Solution Explorer откроем папку App_Data. В ней сделаем двойной щелчок мышкой на файле BooksCatalog.sdf. Если его не видно, то необходимо в её панели включить отображение всех файлов.

После первого запуска веб-приложения Entity Framework создал следующие таблицы:

  • Основные, для хранения данных. В их основе лежат указанные в CatalogContext классы. При этом используются соглашения, которые будут рассмотрены ниже.
  • Вспомогательные, для организации связей (в данном случае это BookDetailsTags, обеспечивающая взаимосвязь между  BookDetails и Tags).
  • Служебную EdmMetadata. В ней содержится хэш-код, с помощью которого ядро Entity Framework определяет был ли изменен исходный код классов Модели. В этом случае потребуется пересоздать базу данных.

Давайте рассмотрим принципы соглашений, на базе которых и создаются таблицы.

Соглашения Entity Framework Code First

Навигационные свойства

Свойства, которые не представляют непосредственно конкретное поле таблицы, а используются для доступа к данным, связанным с выбранной записью, называются навигационными. Посмотрим на примере:

namespace BookCatalog.Models
{
    using System.Collections.Generic;

    public class Tag
    {
        public int Id { get; set; }

        public string Text { get; set; }

        public virtual ICollection<BookDetails> Books { get; set; }
    }
}

В данном случае таким свойством является Books. Оно представляет коллекцию книг, связанных с определенным ключевым словом. При обращении к нему данные будут взяты из таблицы BookDetails.

Имена таблиц

Для создания имен таблиц Entity Framework Code First по умолчанию использует имена соответствующих классов. Это поведение может быть переопределено атрибутами или с использованием Fluent API.

Первичный ключ таблицы

Свойства с именем Id или <имя класса>Id считается первичным ключом создаваемой таблицы. При этом регистр символов не учитывается.

Внешние ключи таблицы

Внешние ключи таблицы задаются с использованием пары свойств: обычного и навигационного. При этом первое должно иметь один из двух вариантов имени:

  • <имя навигационного свойства><имя первичного ключа из связанной таблицы>;
  • <имя класса, представляющего связанную таблицу><имя её первичного ключа>.

При этом обязательность такой связи напрямую зависит от того, может ли обычное свойство принимать значение null. Например:

namespace BookCatalog.Models
{
    using System;
    using System.Collections.Generic;

    public class BookDetails
    {
        .........

        public int LanguageId { get; set; }

        public int? PublisherId { get; set; }

        .........

        public virtual Language Language { get; set; }

        public virtual Publisher Publisher { get; set; }
    }
}

В результате Entity Framework Code First создаст два внешних ключа:

  1. LanguageId, который должен быть обязательно связан с полем Id таблицы Languages;
  2. PublisherId, который может быть связан с полем Id таблицы Publishers.

Обнаружение используемых типов

Entity Framework Code First определяет используемые типы и автоматически включает их в создаваемую Концептуальную модель. Например, в CatalogContext можно указать только свойство BookDetails. Но при этом все равно будут созданы остальные таблицы, т.к. связанные с ними типы используется в Модели. А вот обратиться к ним будет уже не возможно, поскольку нет соответствующих свойств. Разумеется, ничего не мешает добавить их позже.

Указание отношений

Code First позволяет определить различные типы отношений между строками двух таблиц.

  • One-to-one (одна к одной): данный тип отношений рассмотрен ранее, при описании внешних ключей.
  • One-to-many (одна к многим): в одном классе указывается внешний ключ, а в другом связанная с ним коллекция типа ICollection<T>:
public class BookDetails
{
    .........

    public int? PublisherId { get; set; }

    .........

    public virtual Publisher Publisher { get; set; }
}

public class Publisher
{
    public int Id { get; set; }

    .........

    public virtual ICollection<BookDetails> Books { get; set; }
}
  • Many-to-many (многие к многим): оба класса содержат коллекции ICollection<T> друг друга:
public class BookDetails
{
    .........

    public virtual ICollection<Tag> Tags { get; set; }

    .........
}

.........
    
public class Tag
{
    .........

    public virtual ICollection<BookDetails> Books { get; set; }
}

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

Комментарии (73) -

Артем 23.07.2011 10:08:01

Замечательная серия статей, но вот у меня возник один вопрос, а именно как подгружаются зависимости? К примеру, есть у нас некая сущность, скажем "Тема", у нее имеется коллекция сущностей "Пост", которые в свою очередь хранят коллекцию сущностей "Комментарий", у которых *продолжать сколь угодно долго*... При запросе сущности "Тема" притянутся ли мне до кучи несколько сотен сущностей "Пост" и к ним в придачу несколько тысяч сущностей "Комментарий"? Идеально, если бы использовалась отложенная инициализация и некие итераторы по коллекциям, иначе сервер просто захлебнется.

@ Артем: По умолчанию используется lazy для навигационных свойств.

Ринат 20.08.2011 23:18:58

Андрей, скажите, зачем для навигационных свойств используется модификатор virtual?

При использовании отложенной загрузки (lazy loading) вместо них будут "подставлены" экземпляры прокси-класса, которые её и обеспечат.

Ринат 21.08.2011 0:47:01

т.е. если не указать модификатор virtual, то отложенная загрузка использоваться не будет, так?

Такие навигационные поля (не virtual) будут равны null, если они явно не были указаны в исходном запросе. Что будет если к ним обратиться, думаю ясно.

Артем 21.08.2011 15:57:43

Хм, это очень существенные сведения. Мне кажется, что вам стоит добавить их в статью, это избавит от многих ошибок.=) Спасибо, что делитесь своими знаниями.

С одной стороны согласен, но тогда надо раскрывать тему отложенной загрузки. Подумаю как лучше.

Александр 28.09.2011 17:41:35

Скажите, пожалуйста, как быть в ситуации когда, программа (сайт) уже запущена, а разработчик еще дорабатывает ее, добавляет новый функционал и пр.?
Ведь получается, что при изменении модели EF попытается заново сгенерировать объект базы данных.
Мне приходится держать несколько баз данных: одна - для заново сгенерированной, другая - та, с которой работают пользователи. После генерации новых объектов аккуратно обновляю рабочую базу, и чтобы EF не ругался на то, что хэш текуй базы отличается от нужной, вручную копирую поле специальной таблицы EF.
Как вы поступаете в данной ситуации? Как аккуратно, не удалив ничего нужного, обновить рабочую базу данных?

@ Александр: Универсального рецепта нет. Все зависит от типа изменений (насколько глубоко они затрагивают структуру БД). Для небольших есть EF Migrations, для более серьезных - работа в ручную.

Евгений 18.11.2011 3:50:31

Андрей, хочу поблагодарить Вас за статью! Все очень доступно и понятно написано. Вы делаете большое дело!

Денис 18.11.2011 16:16:55

Даже очень большое) Спасибо!

@ Евгений:
@ Денис:
Пожалуйста. Приятно знать что написанное приносит пользу.

Спасибо вам , Андрей. Просто замечательно, что так хорошо объясняете новичкам.

Андрей, я не разобрался совсем. Создаю класс данных. Создаю контекстный класс ModelSimple. Добавляю в ХМЛ строку соединения.
А как файл базы появляется.
Попробовал
ModelSimple mb = new ModelSimple();
mb.Database.CreateIfNotExists();
Стал ругаться - "The provider did not return a ProviderManifestToken string"

Андрей, Ошибка, которую я указал в предыдущем посте, у меня выходила при использовании SQL Server. Поменял строку соединения на SQL CE 4, убрал "mb.Database.CreateIfNotExists();" - тогда все получилось, но со странностями. База .sdf у меня создалась, и как надо в папке App_Data. Но ее я увидел в проводнике файлов. А в Solution Explorer среды Visual Studio текущего проекта папка App_Data показывается пустой, даже после рестарта проекта. Что за дела? Что не так я делаю. Конечно потом путем добавки Add new existing item базу прикрепил к Solution Explorer, но это же криво-косо. Объясните пожалуйста мне, если можно, предыдущую ошибку и странности с пустой папкой App_Data.

@ Samir: Когда выдает "The provider did not return a ProviderManifestToken string" посмотрите что в InnerException. Там может быть причина.

Файлы БД в App_Data показываются только если в Visual Studio включена опция "Show All Files". Переключается она кнопкой в панели инструментов в самом Solution Navigator / Explorer.

Андрей C Новым Годом вас и всех "подписчиков".Присоединяюсь к благодарностям. Большое спасибо за статьи.

@ Alex: Спасибо за поздравления. Вас и всех читающих мой блог так же поздравляю с наступившим Новым Годом.

Андрей, подскажите, стоит ли использовать "code first", если БД уже есть и все изменения будут проводится вручную. Какие преимущества в этом? Так же вопрос немного не в тему, есть wcf, в нем доступ к БД идет через ЕF, возвращать entity wcf отказывается, но возвращает , так званые DTO-шки (тот же enity но без методов и пр.), это правильный подход ? Так же вопрос по "code first" , wcf не возвращает даже их(классы написаные вручную), если есть связь 1-*,получается что даже в этом случае нужно использовать DTO?  

@ Alex01: Если изначально проектируется БД, а код создается под неё, то самое место для Database First варианта. Или же речь про новый проект.

Про WCF не понял вопроса. Ради любопытства, создал WCF сервис и вернул данные из БД через EF CF. Работает. В чем у вас именно сложность?

Вот небольшой пример:
    [Table("Advert")]
    [DataContract]
    public class Advert
    {
        [DataMember]
        public int Id { get; set; }

        [DataMember]
        public string Title { get; set; }
        ....
   } // тоесть entity без навигационных свойств.
сам метод:
public IList<Advert.Data.Entities.Advert> GetAdverts()
        {
            IList<Advert.Data.Entities.Advert> list = context.Adverts.Where(x => x.RubricId == 57).Take(10).ToList();

            return list;
        }.
Вот так вот все работает, но как только добавим навиг.свойства:
[Table("Advert")]
    [DataContract]
    public class Advert
    {
        [DataMember]
        public int Id { get; set; }

        [DataMember]
        public string Title { get; set; }
        ....
        public virtual Rubric Rubric { get; set; }
   }.
Выскакивает ошибка. Тоесть заполеннение list в данном случае идет верно (вместе с rubiс-ами), а вот вернуть уже этот list wcf не может.
И еще раз переспрошу, если база всегда проектируется отдельно от приложения, то o code first , не может быть и речи?

Подозреваю что проблема в том, что для Rubric используется Lazy Loading. А в момент, когда идет попытка прочитать значение, контекст уже удален. При этом внутри GetAdverts() в отладчике все красиво, т.к. контекст еще жив. Можно явно указать включаемые в запрос навигационный поля (LINQ метод Include()).

Насчет Code First - можно использовать и его. Вот только смысл? Он хорош именно при разработке от кода. А для указанного случае как раз Database First придуман.

Andrey, мне очень понравился подход Code First.  Но как я понял его можно использовать только с Entity Framework 4.1 ?  У меня MS Studio 2010 (Framework 4)  /   Можно ли как то доустановить и что?

И можно ещё вопросик - про подход "Модель вначале" - (только начал смотреть EF)
1) Как изменить имена генерируемых таблиц бд? У меня русская VS и он добавляет к концу имени  слово "набор".
2) Можно ли при данном подходе сопоставить поля с уже существующими классами? .
Если мне необходимо расширить класс - добавить методы и т.п., то можно ли его продолжить в другом модуле (partial class)? Или что необходимо сделать.
3) Я меняю модель, генерирую код создания БД. Можно ли обойтись без удаления старых таблиц? Как это настроить?

@ Юрий: По поводу Code First - ответ в самом вопросе. Нужно установить Entity Framework 4.1 и выше (сейчас 4.2). Сделать это можно с помощью NuGet - или найти EnitytFramework в диалоге добавления пакетов или ввести в его командной строке: Install-Package EntityFramework

Далее по Model First. Отвечу кратко, но суть поймете:

1) У каждой Entity в редакторе есть свойство EntitySetName, которое связано с именем таблицы (это и имя свойства для доступа к данным в контексте).

2) Если я правильно понял вопрос, то нет. В Model First в роли DTO (data transfer objects) выступают собственные объекты. Хотите использовать обычные классы для этого – есть Code First. Добавлять в них свой код также не надо. Используйте их для обмена с БД.

3) Если вы ищите галочку "сохранить данные", то её нет. Вам придется позаботиться о переходе между моделями самостоятельно. В этом случае правда можно изменять саму БД и затем обновлять модель.

При добавлении в web.config connectionstring для MS SQL Server выскакивает ошибка: "При выполнении определения команды произошла ошибка." InnerException: "Invalid object name 'dbo.BookDetails'." А при подключении Sql Server CE все нормально работает. В чем может быть проблема?

P.S.: Entity Framework обновил до 4.3

Проверил пример с SQL Express + EF 4.3 -проблем не нашел. А в каком месте получаете исключение?

Ошибка вываливается во Views\Catalog\Index.cshtml

Вот здесь, в цикле:
@model IEnumerable<BookCatalog.Models.BookDetails>

....

@foreach (var item in Model) {
    <tr>
        <td>@item.Title</td>
        <td>@item.Author</td>
        <td>@String.Format("{0Laughing}", item.PublishedAt)</td>
        <td>@item.Url</td>
        <td>@item.Description</td>
        <td>@string.Join(", ", item.Tags.Select(tag => tag.Text).ToArray())</td>
        <td>@item.Rating</td>
        <td>@item.IsFree</td>
        <td>@(item.Language == null ? CommonRes.None : item.Language.Name)</td>
        <td>@(item.Publisher == null ? CommonRes.None : item.Publisher.Title)</td>
    </tr>
}

Попробал взять твой пример от 9 урока первой главы и просто заменить в нем connectionstring на
<add name="CatalogContext"
     connectionString="Data Source=(local);Initial Catalog=mvcTest;Integrated Security=True;Pooling=False"
     providerName="System.Data.SqlClient"/>

и все равно та же самая ошибка.

У меня все получилось. Заработало только тогда, когда я удалил БД на сервере. Т.е. прикол был в том, что БД заранее создавать не надо, MVC сам ее создаст.

@ Veligord: Т.е. вы создавали пустую БД без таблиц? Видимо из-за отсутствия таблицы с метаданными модели в EF4.3 не вылетает исключение о несоответствии схемы БД и Модели.

zimmer213 10.05.2012 1:39:06

Помогите регить следующи проблему. Бьюсь уже сутки над code-first. Есть 3 Класса:

public class A
    {
        public int id { get; set; }
        public string Name { get; set; }
    }

public class B
    {
        public int id { get; set; }
        public long Volume { get; set; }
        public int? CellId { get; set; }
        public virtual Cell Cell { get; set; }
        public int ResourceTypeId { get; set; }
        public virtual A a { get; set; }
    }

public class C
    {
        public int id { get; set; }
        public virtual ICollection<B> b { get; set; }
    }
и соотвествующие сущности БД: As, Bs, Cs.

Выполняется следующий код:

A a = new A();
a.Name = "test";
B b = new B();
b.Value = 10;
b.a = a;
C c = new C();
C.b.Add(b);

НА последней строке возникает ошибка: Object reference not set to an instance of an object. Так понимаю что неверно организованы отношения между сущностями. Помогите решить проблему.

@ zimmer213: Дел много, поэтому отвечаю не сразу. Ситуация простая - у вас c.b == null. Какого поведения вы хотите в этом случае. Вызывая по сути "null".Add(b). Надо инициализировать свойство b или добавлять сразу в таблицу B.

Александр 07.08.2012 16:21:26

Андрей, у Вас не было такого, что конфигурации БД как бы кешировалась? Ну например, Вы добавили новые поля, переименовали некоторые, поменяли типы и тд и тп. Запускаете проект с пересозданием базы. База удаляется и создаётся заново (это видно по логам). Смотрю таблицы БД, а они не поменяны.
Что делал:
1. Чистил пул приложения или iisreset
2. Удалял все папки obj и bin перед сборкой проекта
3. Ручками удалял базу с бэкапами
4. Чистил пап��и C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files и C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files
5. Перезапускал студию

Помогает только перезагрузка компа, но это не выход.
Что можете еще посоветовать?

Спасибо

@ Александр: Нет, не сталкивался. А какая VS и какие версии EF и MSSQL.

Александр 08.08.2012 10:13:35

VS 2010, EF 4.3.1, MS SQL 2008 R2

Вчера уже и перезагрузка компьютера не всегда помогала.

На сервере тестирования такой проблемы не наблюдается. Точнее иногда бывает что по старой конфигурации создается, но операции, описанные выше, помогают устранить этот баг. На разработческой машине такое стало появляться постепенно, но с каждым разом всё хуже и хуже. При этом никаких новых ПО не устанавливалось. Может какое обновление прилетело.

@ Александр: Кстати, а в обычном Temp (C:\Users\[Login]\AppData\Local\Temp) может чего остается? А вообще странный баг.

    public class BookDetails
    {
        .........
        // Зачем нужны эти поля? Пробовал, и без них отлично работает.
        public int LanguageId { get; set; }

        public int? PublisherId { get; set; }

        .........

        public virtual Language Language { get; set; }

        public virtual Publisher Publisher { get; set; }
    }

@ Denis: Вы про c Id на конце? В EF есть соглашение - LanguageId + Language создают внешний ключ к таблице Languages. В LanguageId  хранится Id связанного объекта типа Language.

@ Andrey: Спасибо, разобрался Smile

@ Andrey:
Скажите, а как делать рекурсивные отношения?

public class Human
{
  [Key]
  public int HumanId {get;set;} //Это ключ данного столбца в таблицу, но по идее такое же поле должно быть для указания, например, начальника этого человека
  public string Name {get;set;}
  
  public int HumanId {get;set;} //ошибка же
  public virtual Human {get;set;}
}

В классе выше вряд ли получится создать ркурсивную ссылку из-за соглашений. Так?

@ Andrey:
Совершенно забыл про атрибуты. Для рекурсивной связи по идее можно сделать так:
public class Human
{
  [Key]
  public int HumanId {get;set;} //Это ключ данного столбца в таблицу, но по идее такое же поле должно быть для указания, например, начальника этого человека
  public string Name {get;set;}
  
  [ForeignKey("Boss")]
  public int BossId {get;set;}

  [InverseProperty("Slaves")]
  public virtual Human Boss {get;set;}

  public virual ICollection<Human> Slaves {get;set;}
}
не знаю насколько рабочий код

* к public virtual ICollection<Human> Slabes {get;set;} надо добавить атрибут  [InverseProperty("Boss")]

Антон 17.12.2012 1:50:37

Здравствуйте.
Очень прошу помочь.
Осваиваюсь в сабже.
В основном по хорошей книге ASP NET MVC 3 и немного по Вашему сайту
Все прекрасно понятно но на этапе code first привязке к базе данных проблема.

Итак модель
  public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }
}

интерфейс хранилища


    public interface IProductRepository
    {
        IQueryable<Product> Products { get; }
    }
}

контекст

public class EfDbContext : DbContext
    {
        public DbSet<Product> Products { get; set;  }
    }

реализация интерфейса хранилища

public class EfDbProductRepository : IProductRepository
    {
        private EfDbContext context = new EfDbContext();
        public IQueryable<Product> Products
        {
            get { return context.Products; }
        }

    }

контроллер

  public class ProductController : Controller
    {
        private IProductRepository repository;

        public ProductController(IProductRepository repo)
        {
            repository = repo;
        }
        public ViewResult List()
        {
            return View(repository.Products);
        }

    }    

представление (строго-типизированное)
@model IEnumerable<Store.Domain.Items.Product>

@{
    ViewBag.Title = "Products";
}

@foreach (var p in Model)
{
    <div class="item">
    <h3>@p.Name</h3>
    @p.Description
    <h4>@p.Price.ToString("c")</h4>
</div>
}

в web config
<connectionStrings>    
    <add name="CatalogContext"  
         connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=ItemCatalog Integrated Security=true;"  
         providerName="System.Data.SqlClient" />    
</connectionStrings>

Таблицу базы данных заполнил вручную.
Проблема в том, что вид отображает пустую страницу.
Я так понимаю он не получает данные из таблицы, видимо не видит ее.
Никаких сообщений о ошибке.
Пробовал сделать имитацию интерфейса IProdectRepository посредством moq имитирущем метод products возвращаяющий List<Product> и кидающий структуру посредством контроллера в вид оно все отображается.

В связи с чем, что посоветуете?
Я сначала создал базу с названием сервера .\SQLEXPRESS
именем ItemCatalog
именем таблицы Products
Затем указал в веб конфинге.

Вопрос можно ли как-то еще подключить бд к проекту и фреймворку?
И где хранятся создаваемые базы данных.
Т.е. хотел попробовать добавить БД в решении через add item но не смог найти где она расположена.
Я так понимаю БД нужно сначала добавить в решении, потом соеденить БД и EF?








@ Роман:

Поскольку связь указывается через навигационные поля (или атрибуты), то можно так:

public class Human
{
  [Key]
  public int HumanId { get; set; }
  public string Name { get; set; }
  
  [ForeignKey("OtherHuman")]
  public int OtherHumanId { get; set; }
  public virtual Human OtherHuman { get; set; }
}

В случае c Boss-Slaves я бы просто сделал выборку по BossId.

@ Антон:
1) EF CF сам создает БД, поэтому самому её создавать не надо. Для существующей БД есть подход Database First или Code First Reverse Engineering.

2) Имя контекста в web.config должно совпадать с именем класса. При не совпадении (как у вас CatalogContext != EfDbContext) EF создаст БД с сгенерированным именем. Вот оттуда (а не из вашей БД) он будет брать данные.

3) В строке соединения после Initial Catalog=ItemCatalog пропущена ';'

4) Ну и как совет - не таскайте IQueryable по приложению. Причина: запрос к БД выполняется только при обращении к данным, а в этот момент контекст может быть уничтожен. Получите exception. Более того, в представлении IQueryable не нужен, т.к. это прямой путь к проникновению туда бизнес-логики. Используйте IEnumerable<T>.

Антон 17.12.2012 18:56:19

Это я по невнимательности запилил каталог контекст.
До этого было имя контекста.
Но да сегодня посмотрю еще.
Я человек невнимательнвй
А где создается новая база данных?
То есть она должна быть в проекте как я понимаю.
Но как ее просмотреть?
Т.е. я включаю в солюшен эксплотер "показать все файлы" но все равно не вижу бд.
Могу ли я ее как-то просмотреть и вручную заполнить?
И кстати что же тогда за бд создается по несуществующему контексту?
Пустая?
Заранее спасибо большое

Антон 17.12.2012 19:07:51

Хм.
web config выглядит как
<connectionStrings>    
    <add name="EfDbContext"  
         connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=ItemCatalog; Integrated Security=true;"  
         providerName="System.Data.SqlClient" />    
  </connectionStrings>
Но данные из соответствующей таблицы не отображаются.

Собственно я возможно заблуждаюсь но подобная строка в веб конфиге создает новуюпустую БД, для хранения определенных объектов?
Потому и получаю пустую страницу.
Я ожидал, что будет соединение с существующей.

Антон 17.12.2012 23:32:36

Андрей я правильно понимаю?

Если я напишу в web config

<connectionStrings>
    <add name="EfDbContext"
       connectionString="Data source=|DataDirectory|\ItemCatalog.sdf"
       providerName="System.Data.SqlServerCe.4.0" />
  </connectionStrings>

то будет создана новая незаполненная бд.
физически файл бд будет находится в app data

Поясните эта папка будет в директории решения?
У меня включена опция show all files но подобной папки нет.
Можете подсказать, что может быть не так?

http://postimage.org/image/ury4h0ls5/full/
скрин вебконфинга и дериктории

@ Антон:
> А где создается новая база данных?
Для MS SQL/MS SQLExpress - папка для баз задавалась при установке.
Для LocalDB - по умолчанию в C:\Users\[UserName]
Для SQLCE - в папке App_Data в папке проекта (или где укажите)

> Т.е. я включаю в солюшен эксплотер

В VS есть SQL Server Object Explorer. Конектитесь к серверу, указанной в web.config и смотрите БД.

> Я ожидал, что будет соединение с существующей.

Пути коннекта к существующей (подходы) я указал выше. Даже если правильно укажите БД, EF CE потребует удалить её.

В тексте и на скрине разные строки коннекта.

Поскольку разговор уходит от темы статьи - пишите вопросы в личку (через форму).

Антон 18.12.2012 0:23:22

Я просто пробовал разные коннект стринги.
Спасибо

Владимир 21.02.2013 0:55:26

Добрый день! Возник вопрос в связи с вашим постом. Для чего нужен внешний ключ если имеется навигационное свойство. Чтобы было понятнее приведу пример:

public class Author
{
public int AuthorId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public DateTime DOB { get; set; }

public virtual ICollection<book> Books { get; set; }
}

public class Book
{
public int BookId { get; set; }
public string Name { get; set; }
public DateTime PublishDate { get; set; }

// Зачем тут нужно это если есть навигационное свойство ниже???
public int AuthorId { get; set; }

public virtual Author Author{get;set;}

}

Ахмед 23.02.2013 5:11:16

Здравствуйте! Использую в начале код. Для связи многие ко многим, между сущностями News и Tags, нужно ли создавать промежуточный класс или его Entity Framework создает автоматически? Когда пытаюсь связать классы посредством связи многие ко многим без объединяющего класса, надеясь, что Entity Framework создаст его автоматически, выбрасывает исключение InnerException. Интересно, что же я там не так делаю:

public class Tag

    {
        public int id { get; set; }

        public string Name
        {
            get { return Name; }
            set { Name = value; }
        }

        public string Content {

            get { return Content; }
            set { Content = value; }
        }

        public virtual ICollection<News> New { get; set; }

    }
и
public class News

    {
        public int id { get; set; }

        public string Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime DatePublish { get; set; }

        [DataType(DataType.MultilineText)]
        public string Content { get; set; }

        
        public virtual ICollection<Tag> Tags { get; set; }
        
    }

@ Владимир: Потому что само навигационное свойство не обязательно. Кроме того, для его загрузки надо или это явно указать или использовать Lazy Loading (что далеко не всегда приемлемо). Ну и, наконец, в программе часто нужен не сам объект, а только его Id (например, надо удалить все книги данного автора из базы). Подгружать весь объект, только ради id IMHO очень не разумно.

@ Ахмед: А что в Exception, прочитайте (InnerException также содержит текст ошибки или еще одно InnerException) Smile И внимательно поглядите на get и set методы в Tag (там бесконечная рекурсия).

Ну и если будет желание контролировать создание вспомогательной таблицы, то можно так:

modelBuilder.Entity<News>()
    .HasMany(x => x.Tags)
    .WithMany(x => x.News)
    .Map(x => {
        x.ToTable("NewsToTags");
        x.MapLeftKey("NewsId");
        x.MapRightKey("TagsId");
    });

Ахмед 26.02.2013 2:26:30

Спасибо! Заработало! С свойствами,наверное, надо было так сделать:

public class Tag

    {
        private string name;
        private string content;

        public int id { get; set; }

        public string Name
        {
            get{return name;}
            set { name = value; }
        }


        public string Content
        {
            get { return content; }
            set { content = value; }
        }

        public virtual ICollection<News> New { get; set; }

    }

@ Ахмед: Если какой-либо логики внутри get и set не планируется, то лучше использовать auto-implemented properties, например:

public string Name { get; set; }

@ Andrey:
Спасибо!!! Вы единственный кто дал нормальный грамотный ответ. Спрашивал у своих сокурсников, отвечали мол создавай свойство навигации и не парься. Спрашивал на cyberforum, там начали строить из себя унмых. Спасибо!

Ахмед 08.03.2013 2:17:39

Здравствуйте! А как же из формы добавлять значение в коллекции  tags и books?

@ Ахмед:
Коллекция Books будет заполняться "сама", по мере добавления новых книг и указания издателя для них.
Tags - тут хоть строкой с разделителями, хоть своим контролом. Главное из ввода пользователя сформировать ICollection<Tag> с указанием как новых, так и существующих тегов.

Ахмед 09.03.2013 13:43:56

Спасибо! Очень нравится мне ваш блог.

Антон 14.02.2014 20:18:20

Андрей, спасибо за статью. Наконец-то въехал в эту тему.

Сергей 06.04.2014 1:47:51

Подскажите, а как посмотреть sql код, генерируемый EF?

@ Сергей:
* MSSQL профайлер.
* Сторонние профайлеры.
* Visual Studio IntelliTrace
* MiniProfiler
andrey.moveax.ru/.../...-asp-net-miniprofiler.aspx

Сергей 09.04.2014 1:54:45

@ Andrey:
Спасибо!

Здравствуйте! подскажите, если использовать Code First, можно ли при изменении модели подправить БД под измененную модель ,не используя миграции? и как вообще в дальнейшем будет правильно сопровождать подобный проект?

@ Cap: А чем миграции не угодили?
В принципе, ничего не мешает исправить саму схему базы вручную. Проблемы будут если забыть поменять потом модель или наоборот БД.

Константин 30.07.2016 11:40:31

Здравствуйте.
Читаю Ваш блог - очень интересно.
У меня тут возник вопрос, а если я захочу отобразить по каждому Publisher только книги, например, на русском языке? Т.е. как мне ограничить набор данных в коллекции Books у Publisher?

Я так понимаю, что что-то типа такого

List<Publisher> publishers = db.Publishers.Where(p => p.Books.Any( b=> b.LanguageId == 1)).ToList()

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

Константин Для решения подобной задачи я бы пошел от обратного - сначала собрал все книги на заданном языке, загрузив у них свойство Publisher. А уже потом в памяти, при необходимости, сгруппировал бы по издательствам.

Добрый день, подскажите пожалуйста как будет выглядит запрос со связью многие ко многим, допустим автор может написать множество книг а книгу может написать несколько авторов, мне нужна информация которая хранится в 2(автор, книги) этих таблицах

Юрий Так же как и с загрузкой любого навигационного свойства - используйте метод Include чтобы указать необходимые для загрузки данные. Что-то вроде
var results = context.Books.Include(x => x.Authors).Where(...).ToList();

Добавить комментарий