Почему Entity Framework? Применение концептуальных моделей на практике


работы core

Это будет применяться в основном для приложения asp.net, где данные не доступны через soa. Это означает, что вы получаете доступ к объектам, загруженным из фреймворка, а не к объектам передачи, хотя некоторые рекомендации по-прежнему применяются.

Это сообщение сообщества, поэтому, пожалуйста, добавьте его, как вы считаете нужным.

Применяется : Entity Framework 1.0 поставляется с Visual Studio 2008 sp1.

Зачем выбирать EF в первую очередь?

Учитывая, что это молодая технология с множеством проблем (см. Ниже), это может быть трудная продажа, чтобы попасть на победителя EF для вашего проекта. Тем не менее, это технология Microsoft толкает (за счет Linq2Sql, который является подмножеством EF). Кроме того, вы не можете быть удовлетворены NHibernate или другими решениями там. Какими бы ни были причины, есть люди (включая меня), работающие с EF, и жизнь не плохая. Подумайте.

EF и наследование

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

(Ниже приведена таблица для каждой модели класса, так как у меня нет опыта работы с таблицей на иерархию, которая в любом случае ограничена). Реальная проблема возникает, когда вы пытаетесь запустить запросы, включающие один или несколько объектов, которые являются частью дерево наследования: сгенерированный sql невероятно ужасен, требуется много времени, чтобы получить синтаксический анализ EF и требуется много времени для выполнения. Это настоящий шоу-стоппер. Достаточно того, что EF, вероятно, не следует использовать с наследованием или как можно меньше.

Вот пример того, насколько это было плохо. Моя модель EF имела ~ 30 классов, ~ 10 из которых были частью дерева наследования. При запуске запроса для получения одного элемента из базового класса, такого простого, как Base.Get (id), сгенерированный SQL был более 50 000 символов. Затем, когда вы пытаетесь вернуть некоторые ассоциации, он еще больше вырождается, и он бросает исключения SQL из-за невозможности запросить более 256 таблиц сразу.

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

Итак, рекомендации? Избегайте наследования, если можете, производительность будет намного лучше. Используйте его экономно там, где вам нужно. На мой взгляд, это делает EF прославленным инструментом генерации sql-запросов для запросов, но все же есть преимущества для его использования. И способы реализовать механизм, похожий на наследование.

Обход наследования с интерфейсами

Первое, что нужно знать, пытаясь получить какое-то наследство, идущее с EF, - это то, что вы не можете назначить класс, не относящийся к EF, базовому классу. Даже не пытайтесь, он будет перезаписан модератором. Так что делать?

Вы можете использовать интерфейсы для обеспечения того, чтобы эти классы реализовали некоторые функции. Например, здесь есть интерфейс IEntity, который позволяет вам определять ассоциации между объектами EF, во время которых вы не знаете, каким будет тип объекта.

Public enum EntityTypes{ Unknown = -1, Dog = 0, Cat } public interface IEntity { int EntityID { get; } string Name { get; } Type EntityType { get; } } public partial class Dog: IEntity { // implement EntityID and Name which could actually be fields // from your EF model Type EntityType{ get{ return EntityTypes.Dog; } } }

Используя этот IEntity, вы можете работать с неопределенными ассоциациями в других классах

// lets take a class that you defined in your model. // that class has a mapping to the columns: PetID, PetType public partial class Person { public IEntity GetPet() { return IEntityController.Get(PetID,PetType); } }

который использует некоторые функции расширения:

Public class IEntityController { static public IEntity Get(int id, EntityTypes type) { switch (type) { case EntityTypes.Dog: return Dog.Get(id); case EntityTypes.Cat: return Cat.Get(id); default: throw new Exception("Invalid EntityType"); } } }

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

Он также не может моделировать отношения «один ко многим», «многие ко многим», но с творческим использованием «Союза» его можно заставить работать. Наконец, он создает побочный эффект загрузки данных в свойство / функцию объекта, о котором вы должны быть осторожны. Использование четкого соглашения об именах, такого как GetXYZ (), помогает в этом отношении.

Скомпилированные запросы

Производительность Entity Framework не так хороша, как прямой доступ к базе данных с ADO (очевидно) или Linq2SQL. Однако есть способы улучшить его, один из которых заключается в компиляции ваших запросов. Производительность скомпилированного запроса похожа на Linq2Sql.

Что такое скомпилированный запрос? Это просто запрос, для которого вы указываете инфраструктуре, чтобы сохранить синтаксическое дерево в памяти, поэтому его не нужно восстанавливать при следующем запуске. Итак, в следующем прогоне вы сэкономите время, затрачиваемое на разбор дерева. Не обесценивайте это, поскольку это очень дорогостоящая операция, которая становится еще хуже с более сложными запросами.

Существует два способа скомпилировать запрос: создание ObjectQuery с EntitySQL и использование функции CompiledQuery.Compile (). (Обратите внимание, что с помощью EntityDataSource на вашей странице вы фактически будете использовать ObjectQuery с EntitySQL, чтобы скомпилировать и кэшировать).

Отбросьте здесь, если вы не знаете, что такое EntitySQL. Это строковый способ написания запросов к EF. Вот пример: «выберите значение dog из Entities.DogSet как собака, где dog.ID = @ID». Синтаксис довольно похож на синтаксис SQL. Вы также можете сделать довольно сложную манипуляцию с объектом, что хорошо объяснено [здесь] .

Итак, вот как это сделать, используя ObjectQuery <>

oQuery = new ObjectQuery(query, EntityContext.Instance)); oQuery.Parameters.Add(new ObjectParameter("ID", id)); oQuery.EnablePlanCaching = true; return oQuery.FirstOrDefault();

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

Другим способом компиляции запроса для последующего использования является метод CompiledQuery.Compile. Это использует делегат:

Static readonly Func ((ctx, id) => ctx.DogSet.FirstOrDefault(it => it.ID == id));

или используя linq

Static readonly Func query_GetDog = CompiledQuery.Compile((ctx, id) => (from dog in ctx.DogSet where dog.ID == id select dog).FirstOrDefault());

для вызова запроса:

Query_GetDog.Invoke(YourContext, id);

Преимущество CompiledQuery заключается в том, что синтаксис вашего запроса проверяется во время компиляции, где не существует EntitySQL. Однако есть и другие соображения...

Включает

Допустим, вы хотите, чтобы данные для владельца собаки возвращались по запросу, чтобы избежать внесения 2 вызовов в базу данных. Легко сделать, правильно?

String query = "select value dog " + "from Entities.DogSet as dog " + "where dog.ID = @ID"; ObjectQuery oQuery = new ObjectQuery(query, EntityContext.Instance)).Include("Owner"); oQuery.Parameters.Add(new ObjectParameter("ID", id)); oQuery.EnablePlanCaching = true; return oQuery.FirstOrDefault();

Static readonly Func query_GetDog = CompiledQuery.Compile((ctx, id) => (from dog in ctx.DogSet.Include("Owner") where dog.ID == id select dog).FirstOrDefault());

Итак, что, если вы хотите, чтобы параметр Include был параметризован? Я имею в виду, что вы хотите иметь единственную функцию Get (), которая вызывается с разных страниц, которые заботятся о разных отношениях для собаки. Один заботится о Владельце, другой о его Любимом Фуду, другом о его FavotireToy и так далее. В основном вы хотите указать запрос, какие ассоциации загружать.

Это легко сделать с EntitySQL

Public Dog Get(int id, string include) { string query = "select value dog " + "from Entities.DogSet as dog " + "where dog.ID = @ID"; ObjectQuery oQuery = new ObjectQuery(query, EntityContext.Instance)) .IncludeMany(include); oQuery.Parameters.Add(new ObjectParameter("ID", id)); oQuery.EnablePlanCaching = true; return oQuery.FirstOrDefault(); }

В include просто используется переданная строка. Достаточно легко. Обратите внимание, что можно улучшить функцию Include (string) (которая принимает только один путь) с помощью IncludeMany (string), которая позволит вам передать цепочку разделенных запятыми ассоциаций для загрузки. Посмотрите далее в разделе расширения этой функции.

Однако, если мы попытаемся сделать это с помощью CompiledQuery, мы столкнемся с многочисленными проблемами:

Очевидное

Static readonly Func query_GetDog = CompiledQuery.Compile((ctx, id, include) => (from dog in ctx.DogSet.Include(include) where dog.ID == id select dog).FirstOrDefault());

будет подавляться при вызове:

Query_GetDog.Invoke(YourContext, id, "Owner,FavoriteFood");

Поскольку, как упоминалось выше, Include () хочет видеть только один путь в строке, и здесь мы даем ему 2: «Owner» и «FavoriteFood» (что не следует путать с «Owner.FavoriteFood»!).

Затем давайте использовать IncludeMany (), которая является функцией расширения

Static readonly Func query_GetDog = CompiledQuery.Compile((ctx, id, include) => (from dog in ctx.DogSet.IncludeMany(include) where dog.ID == id select dog).FirstOrDefault());

Неправильно снова, на этот раз это потому, что EF не может разобрать IncludeMany, потому что он не является частью функций, которые распознаются: это расширение.

Итак, вы хотите передать произвольное количество путей к вашей функции, а Includes () - только один. Что делать? Вы можете решить, что вам никогда не понадобится больше, чем, скажем, 20 включений, и передать каждую разделенную строку в struct в CompiledQuery. Но теперь запрос выглядит так:

From dog in ctx.DogSet.Include(include1).Include(include2).Include(include3) .Include(include4).Include(include5).Include(include6) .[...].Include(include19).Include(include20) where dog.ID == id select dog

что тоже ужасно. Хорошо, тогда подождите, подождите. Не можем ли мы вернуть ObjectQuery <> с помощью CompiledQuery? Затем установите для этого включения? Ну, что бы я подумал так:

Static readonly Func> query_GetDog = CompiledQuery.Compile>((ctx, id) => (ObjectQuery)(from dog in ctx.DogSet where dog.ID == id select dog)); public Dog GetDog(int id, string include) { ObjectQuery oQuery = query_GetDog(id); oQuery = oQuery.IncludeMany(include); return oQuery.FirstOrDefault; }

Это должно сработать, за исключением того, что когда вы вызываете IncludeMany (или Include, Where, OrderBy ...), вы аннулируете кешированный скомпилированный запрос, потому что теперь он совершенно новый! Итак, дерево выражений должно быть переписано, и вы снова получите эту производительность.

Так в чем же решение? Вы просто не можете использовать CompiledQueries с параметризованным Includes. Вместо этого используйте EntitySQL. Это не означает, что для CompiledQueries не используются. Это отлично подходит для локализованных запросов, которые всегда будут вызываться в одном контексте. В идеале CompiledQuery всегда следует использовать, потому что синтаксис проверяется во время компиляции, но из-за ограничений это невозможно.

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

Передача более 3 параметров в CompiledQuery

Func ограничивается 5 параметрами, последним из которых является тип возврата, а первый - ваш объект Entities из модели. Таким образом, вы получите 3 параметра. Сила, но ее можно улучшить очень легко.

Public struct MyParams { public string param1; public int param2; public DateTime param3; } static readonly Func> query_GetDog = CompiledQuery.Compile>((ctx, myParams) => from dog in ctx.DogSet where dog.Age == myParams.param2 && dog.Name == myParams.param1 and dog.BirthDate > myParams.param3 select dog); public List GetSomeDogs(int age, string Name, DateTime birthDate) { MyParams myParams = new MyParams(); myParams.param1 = name; myParams.param2 = age; myParams.param3 = birthDate; return query_GetDog(YourContext,myParams).ToList(); }

Типы возврата (это не относится к запросам EntitySQL, поскольку они не скомпилированы в то же время во время выполнения в качестве метода CompiledQuery)

Работая с Linq, вы обычно не принудительно выполняете выполнение запроса до самого последнего момента, если некоторые другие функции в нисходящем направлении хотят каким-то образом изменить запрос:

Static readonly Func> query_GetDog = CompiledQuery.Compile>((ctx, age, name) => from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog); public IEnumerable GetSomeDogs(int age, string name) { return query_GetDog(YourContext,age,name); } public void DataBindStuff() { IEnumerable dogs = GetSomeDogs(4,"Bud"); // but I want the dogs ordered by BirthDate gridView.DataSource = dogs.OrderBy(it => it.BirthDate); }

Что здесь будет? По-прежнему играя с исходным ObjectQuery (это фактический тип возвращаемого значения оператора Linq, который реализует IEnumerable), он приведет к аннулированию скомпилированного запроса и приведет к повторному анализу. Итак, эмпирическое правило состоит в том, чтобы вместо этого возвращать List <> объектов.

Static readonly Func> query_GetDog = CompiledQuery.Compile>((ctx, age, name) => from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog); public List GetSomeDogs(int age, string name) { return query_GetDog(YourContext,age,name).ToList(); //<== change here } public void DataBindStuff() { List dogs = GetSomeDogs(4,"Bud"); // but I want the dogs ordered by BirthDate gridView.DataSource = dogs.OrderBy(it => it.BirthDate); }

Когда вы вызываете ToList (), запрос выполняется в соответствии с скомпилированным запросом, а затем, позже, OrderBy выполняется против объектов в памяти. Это может быть немного медленнее, но я даже не уверен. Убедительная вещь в том, что вы не беспокоитесь о неправильной обработке ObjectQuery и недействительности скомпилированного плана запросов.

Опять же, это не просто заявление. ToList () - защитный программный трюк, но если у вас есть веская причина не использовать ToList (), продолжайте. Существует много случаев, когда вы хотите уточнить запрос перед его выполнением.

Представление

Каково влияние компиляции запроса на производительность? Это может быть довольно большой. Эмпирическое правило заключается в том, что компиляция и кэширование запроса для повторного использования занимает как минимум удвоенное время простое выполнение без кеширования. Для сложных запросов (read inherirante) я видел вверх до 10 секунд.

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

Когда вы загружаете страницу с предварительно скомпилированными запросами, вы в первый раз получаете хит. Он загрузится, возможно, через 5-15 секунд (очевидно, будет вызвано больше, чем один предварительно скомпилированный запрос), а последующие нагрузки занимают менее 300 мс. Драматическая разница, и вам решать, нормально ли для вашего первого пользователя сделать хит или вы хотите, чтобы скрипт вызывал ваши страницы, чтобы заставить компиляцию запросов.

Можно ли кэшировать этот запрос?

{ Dog dog = from dog in YourContext.DogSet where dog.ID == id select dog; }

Нет, специальные запросы Linq не кэшируются, и вы понесете затраты на создание дерева каждый раз, когда вы его вызываете.

Параметризированные запросы

Большинство возможностей поиска связаны с сильно параметризованными запросами. Существуют даже доступные библиотеки, которые позволят вам построить параметризованный запрос из выражений lamba. Проблема в том, что вы не можете использовать предварительно скомпилированные запросы с ними. Один из способов заключается в том, чтобы отобразить все возможные критерии запроса и флага, которые вы хотите использовать:

Public struct MyParams { public string name; public bool checkName; public int age; public bool checkAge; } static readonly Func> query_GetDog = CompiledQuery.Compile>((ctx, myParams) => from dog in ctx.DogSet where (myParams.checkAge == true && dog.Age == myParams.age) && (myParams.checkName == true && dog.Name == myParams.name) select dog); protected List GetSomeDogs() { MyParams myParams = new MyParams(); myParams.name = "Bud"; myParams.checkName = true; myParams.age = 0; myParams.checkAge = false; return query_GetDog(YourContext,myParams).ToList(); }

Преимущество здесь в том, что вы получаете все преимущества предварительно скомпилированного quert. Недостатки заключаются в том, что вы, скорее всего, закончите предложение where, которое довольно сложно поддерживать, что вы понесете больший штраф за предварительную компиляцию запроса и что каждый выполненный вами запрос не так эффективен, как может быть (в частности, с вставками).

Другой способ - построить запрос EntitySQL по частям, как мы все это сделали с SQL.

Protected List GetSomeDogs(string name, int age) { string query = "select value dog from Entities.DogSet where 1 = 1 "; if(!String.IsNullOrEmpty(name)) query = query + " and dog.Name == @Name "; if(age > 0) query = query + " and dog.Age == @Age "; ObjectQuery oQuery = new ObjectQuery(query, YourContext); if(!String.IsNullOrEmpty(name)) oQuery.Parameters.Add(new ObjectParameter("Name", name)); if(age > 0) oQuery.Parameters.Add(new ObjectParameter("Age", age)); return oQuery.ToList(); }

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

Другой вариант - запросить большое подмножество данных, а затем сузить его в памяти. Это особенно полезно, если вы работаете с определенным подмножеством данных, как и все собаки в городе. Вы знаете, что есть много, но вы также знаете, что их не так много... поэтому ваша страница поиска CityDog может загружать все собаки для города в память, что является одним предварительно скомпилированным запросом, а затем уточняет результаты

Protected List GetSomeDogs(string name, int age, string city) { string query = "select value dog from Entities.DogSet where dog.Owner.Address.City == @City "; ObjectQuery oQuery = new ObjectQuery(query, YourContext); oQuery.Parameters.Add(new ObjectParameter("City", city)); List dogs = oQuery.ToList(); if(!String.IsNullOrEmpty(name)) dogs = dogs.Where(it => it.Name == name); if(age > 0) dogs = dogs.Where(it => it.Age == age); return dogs; }

Это особенно полезно, когда вы начинаете отображать все данные, а затем разрешаете фильтрацию.

Проблемы: - Может привести к серьезной передаче данных, если вы не будете осторожны относительно своего подмножества. - Вы можете фильтровать только данные, которые вы вернули. Это означает, что если вы не вернете ассоциацию Dog.Owner, вы не сможете фильтровать на Dog.Owner.Name. Какое же самое лучшее решение? Нет. Вам нужно выбрать решение, которое наилучшим образом подходит для вас и вашей проблемы: - Используйте построение запросов на основе лямбда, когда вам не нужны предварительная компиляция ваших запросов. - Используйте полностью определенный предварительно скомпилированный запрос Linq, когда ваша структура объекта не слишком сложна. - Используйте конкатенацию EntitySQL / string, когда структура может быть сложной, и когда возможное количество различных результирующих запросов невелико (что означает меньшее количество попыток предварительной компиляции). - Используйте фильтрацию в памяти, когда вы работаете с небольшим подмножеством данных или когда вам приходилось сначала получать все данные по данным в любом случае (если производительность в порядке со всеми данными, тогда фильтрация в памяти не будет вызывают любое время, потраченное на db).

Доступ к Singleton

Лучший способ справиться с вашим контекстом и сущностями на всех ваших страницах - использовать шаблон singleton:

Public sealed class YourContext { private const string instanceKey = "On3GoModelKey"; YourContext(){} public static YourEntities Instance { get { HttpContext context = HttpContext.Current; if(context == null) return Nested.instance; if (context.Items == null) { On3GoEntities entity = new On3GoEntities(); context.Items = entity; } return (YourEntities)context.Items; } } class Nested { // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Nested() { } internal static readonly YourEntities instance = new YourEntities(); } }

NoTracking, это того стоит?

При выполнении запроса вы можете указать системе отслеживать возвращаемые объекты или нет. Что это значит? С включенным отслеживанием (опция по умолчанию) инфраструктура будет отслеживать, что происходит с объектом (была ли она изменена? Создана? Удалена?), А также свяжет объекты вместе, когда из базы данных будут сделаны дополнительные запросы, что и есть представляет интерес здесь.

Например, допустим, что у собаки с ID == 2 есть владелец, который ID == 10.

Dog dog = (from dog in YourContext.DogSet where dog.ID == 2 select dog).FirstOrDefault(); //dog.OwnerReference.IsLoaded == false; Person owner = (from o in YourContext.PersonSet where o.ID == 10 select dog).FirstOrDefault(); //dog.OwnerReference.IsLoaded == true;

Если мы будем делать то же самое без отслеживания, результат будет другим.

ObjectQuery oDogQuery = (ObjectQuery) (from dog in YourContext.DogSet where dog.ID == 2 select dog); oDogQuery.MergeOption = MergeOption.NoTracking; Dog dog = oDogQuery.FirstOrDefault(); //dog.OwnerReference.IsLoaded == false; ObjectQueryOPersonQuery = (ObjectQuery) (from o in YourContext.PersonSet where o.ID == 10 select o); oPersonQuery.MergeOption = MergeOption.NoTracking; Owner owner = oPersonQuery.FirstOrDefault(); //dog.OwnerReference.IsLoaded == false;

Отслеживание очень полезно и в идеальном мире без проблем с производительностью, он всегда будет включен. Но в этом мире есть цена за это, с точки зрения производительности. Итак, следует ли использовать NoTracking для ускорения работы? Это зависит от того, для чего вы собираетесь использовать данные.

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

На странице, где абсолютно нет обновлений базы данных, вы можете использовать NoTracking.

Прослеживание смешивания и NoTracking возможно, но для этого требуется, чтобы вы были осторожны с обновлениями / вставками / удалениями. Проблема заключается в том, что если вы смешиваете, вы рискуете иметь фреймворк, пытающийся подключить () объект NoTracking к контексту, где существует другая копия того же объекта с отслеживанием. В основном, я говорю, что

Dog dog1 = (from dog in YourContext.DogSet where dog.ID == 2).FirstOrDefault(); ObjectQuery oDogQuery = (ObjectQuery) (from dog in YourContext.DogSet where dog.ID == 2 select dog); oDogQuery.MergeOption = MergeOption.NoTracking; Dog dog2 = oDogQuery.FirstOrDefault();

dog1 и dog2 - это два разных объекта, один - гусеничный, а другой нет. Использование отделяемого объекта в обновлении / вставке заставит Attach (), который скажет «Подождите минуту, у меня уже есть объект здесь с тем же ключом базы данных. Fail». И когда вы присоединяете () один объект, вся его иерархия также привязана, что вызывает проблемы во всем мире. Будьте осторожны.

Насколько быстрее это происходит с NoTracking

Это зависит от запросов. Некоторые из них гораздо более восприимчивы к отслеживанию, чем другие. У меня нет быстрого легкого правила для этого, но это помогает.

Поэтому я должен везде использовать NoTracking?

Не совсем. Есть некоторые преимущества для отслеживания объекта. Первый заключается в том, что объект кэшируется, поэтому последующий вызов этого объекта не попадет в базу данных. Этот кеш действителен только для времени жизни объекта YourEntities, который, если вы используете вышеописанный одноэлементный код, совпадает с временем жизни страницы. Один запрос страницы == один объект YourEntity. Поэтому для нескольких вызовов для одного и того же объекта он будет загружаться только один раз на страницу запроса. (Другой механизм кеширования может расширить это).

Что происходит, когда вы используете NoTracking и пытаетесь загрузить один и тот же объект несколько раз? База данных будет запрашиваться каждый раз, поэтому есть влияние. Как часто вы / вызываете один и тот же объект во время запроса одной страницы? Как минимум, конечно, но это происходит.

Также помните, что выше было указано, что ассоциации связаны автоматически для вашего? У вас нет этого с NoTracking, поэтому, если вы загружаете свои данные несколькими партиями, у вас не будет ссылки на них:

ObjectQuery oDogQuery = (ObjectQuery)(from dog in YourContext.DogSet select dog); oDogQuery.MergeOption = MergeOption.NoTracking; List dogs = oDogQuery.ToList(); ObjectQueryOPersonQuery = (ObjectQuery)(from o in YourContext.PersonSet select o); oPersonQuery.MergeOption = MergeOption.NoTracking; ListOwners = oPersonQuery.ToList();

В этом случае ни одна собака не будет иметь свой собственный набор свойств.

Некоторые вещи следует иметь в виду, когда вы пытаетесь оптимизировать производительность.

Нет ленивой загрузки, что мне делать?

Это можно рассматривать как замаскированное благословение. Конечно, это раздражает, чтобы загрузить все вручную. Тем не менее, это уменьшает количество вызовов в db и заставляет вас думать, когда вы должны загружать данные. Чем больше вы можете загружать в одном вызове базы данных, тем лучше. Это всегда было правдой, но теперь она применяется с этой «особенностью» EF.

Конечно, вы можете вызвать if (! ObjectReference.IsLoaded) ObjectReference.Load (); если вы хотите, но лучше всего заставить платформу загружать объекты, которые, как вы знаете, вам понадобятся одним выстрелом. Именно здесь начинается дискуссия о параметризованных включениях.

Допустим, у вас есть объект Dog

Public class Dog { public Dog Get(int id) { return YourContext.DogSet.FirstOrDefault(it => it.ID == id); } }

Это тип функции, с которой вы работаете все время. Он вызывается со всех сторон, и как только у вас есть объект Dog, вы будете делать с ним разные вещи в разных функциях. Во-первых, он должен быть предварительно скомпилирован, потому что вы будете называть это очень часто. Во-вторых, каждая страница будет иметь доступ к другому подмножеству данных Собаки. Некоторым захочется Владелец, некоторые из них - FavoriteToy и т. Д.

Конечно, вы можете вызвать Load () для каждой ссылки, в которой вы нуждаетесь, когда захотите. Но это вызовет вызов в базу данных каждый раз. Плохая идея. Поэтому вместо этого каждая страница будет запрашивать данные, которые она хочет увидеть, когда она сначала запрашивает объект Dog:

Static public Dog Get(int id) { return GetDog(entity,"");} static public Dog Get(int id, string includePath) { string query = "select value o " + " from YourEntities.DogSet as o " +

Для создания объектной модели для базы данных, классы должны быть приведены в соответствие с сущностями, хранящимися в базе данных. Можно выделить три способа реализации такого приведения – можно задавать атрибуты для существующих объектов, можно использовать специальное средство, позволяющее автоматически сгенерировать объекты и использовать утилиту командной строки SQLMetal.

  • Объектно-реляционный конструктор . Этот конструктор предоставляет многофункциональный пользовательский интерфейс для создания объектной модели из существующей базы данных. Данное средство, являющееся частью интегрированной среды разработки Visual Studio, лучше всего подходит для баз данных небольшого или среднего размера.
  • Средство создания кода SQLMetal . По своему набору параметров эта консольная программа несколько отличается от Объектно-реляционного конструктора. Данное средство лучше всего подходит для моделирования больших баз данных.
  • Редактор кода . Можно написать собственный код с помощью редактора кода Visual Studio или другого редактора кода. Этот подход может привести к большому числу ошибок, поэтому при наличии существующей базы данных, которую можно использовать для создания модели с помощью Объектно-реляционного конструктора или программы SQLMetal, использовать его не рекомендуется. Однако редактор кода становится ценным инструментом, когда требуется уточнить или изменить код, уже созданный с помощью других средств.

Используем последний способ, задав атрибуты для существующих объектов :

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.Linq; using System.Data.Linq.Mapping; using System.Data.SqlClient; namespace LinqtoSQL { public class Customer { public string CustomerID { get; set; } public string City { get; set; } public override string ToString() { return CustomerID + "\t" + City; } } class Program { static void Main(string args) { DataContext db = new DataContext (@"Data Source=.\SQLEXPRESS; AttachDbFilename=|DataDirectory|\NORTHWND.MDF; Integrated Security=True; User Instance=True"); var results = from c in db.GetTable() where c.City == "Москва" select c; foreach (var c in results) Console.WriteLine("{0}\t{1}", c.CustomerID, c.City); Console.ReadKey(); } } }

В этом приложении есть класс Customer , который отображает таблицу Customers , и имеет поля CustomerID и City , отображающие поля этой таблицы. Объект класса DataContext задает входную точку в базу данных и имеет метод GetTable , который возвращает коллекцию определенного типа, в данном случае типа Customer .

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

Хранимая процедура извлекает из таблицы Products 10 самых дорогих продуктов и их цены:

USE GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO ALTER procedure . AS SET ROWCOUNT 10 SELECT Products.ProductName AS TenMostExpensiveProducts, Products.UnitPrice FROM Products ORDER BY Products.UnitPrice DESC

Для того чтобы вызвать эту процедуру из программы на языке C# и вывести результаты, достаточно написать всего 3 строки кода:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.Linq; using System.Data.Linq.Mapping; namespace LinqtoSQL { class Program { static void Main(string args) { var db = new northwindDataContext(); foreach (var r in db.Ten_Most_Expensive_Products()) Console.WriteLine(r.TenMostExpensiveProducts + "\t" + r.UnitPrice); Console.ReadKey(); } } }

Обратите внимание, что входная точка в базу данных теперь создается конструктором класса northwindDataContext (в общем случае класс будет называться так: {имя файла отображения} DataContext ), которому больше не нужно передавать параметром строку соединения в явном виде.

10.1.3. ADO.NET Entity Framework

ADO.NET Entity Framework (EF) – объектно-ориентированная технология доступа к данным, является object-relational mapping (ORM) решением для.NET Framework от Microsoft . Предоставляет возможность взаимодействия с объектами как посредством LINQ в виде LINQ to Entities, так и с использованием Entity SQL. Для облегчения построения Веб-решений используется как ADO.NET Data Services, так и связка из Windows Communication Foundation и Windows Presentation Foundation, позволяющая строить многоуровневые приложения, реализуя один из шаблонов проектирования MVC, MVP или MVVM.

Платформа ADO.NET Entity Framework позволяет разработчикам создавать приложения для доступа к данным, работающие с концептуальной моделью приложения, а не напрямую с реляционной схемой хранения. Ее целью является уменьшение объема кода и усилий по обслуживанию приложений, ориентированных на обработку данных. Приложения Entity Framework дают следующие преимущества .

  • приложения могут работать концептуальной моделью в терминах предметной области – в том числе с наследуемыми типами, сложными элементами и связями;
  • приложения освобождаются от жестких зависимостей от конкретного ядра СУБД или схемы хранения;
  • сопоставления между концептуальной моделью и схемой, специфичной для конкретного хранилища, могут меняться без изменения кода приложения;
  • разработчики имеют возможность работать с согласованной моделью объектов приложения, которая может быть сопоставлена с различными схемами хранения, которые, возможно, реализованы в различных системах управления данными;
  • несколько концептуальных моделей могут быть сопоставлены с единой схемой хранения;
  • поддержка интегрированных в язык запросов (LINQ ) обеспечивает во время компиляции проверку синтаксиса запроса относительно концептуальной модели.
10.1.3.1. Компоненты Entity Framework

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

В Entity Framework разработчики получают возможность работать с данными, представленными в форме относящихся к конкретным доменам объектов и свойств, таких как клиенты и их адреса, не будучи вынужденными обращаться к базовым таблицам и столбцам базы данных, где хранятся эти данные. Такая возможность появляется благодаря переходу на более высокий уровень абстракции, на котором разработчики могут работать с данными, применяя меньший объем кода для создания и сопровождения приложений, ориентированных на работу с данными.

Платформа Entity Framework является компонентом.NET Framework, поэтому приложения Entity Framework можно запускать на любом компьютере, на котором установлен.NET Framework 3.5 с пакетом обновления 1 (SP1).

10.1.3.1.1. Применение концептуальных моделей на практике

Давно известный и широко применяемый принцип проектирования в моделировании данных состоит в разделении модели данных на следующие три части: концептуальная модель, логическая модель и физическая модель. Концептуальная модель определяет сущности и связи в моделируемой системе. Логическая модель для реляционной базы данных обеспечивает нормализацию сущностей и связей в целях создания таблиц с ограничениями внешнего ключа . В физической модели учитываются возможности конкретной системы обработки данных путем определения зависящих от ядра базы данных подробных сведений о хранении данных, которые касаются секционирования и индексирования.

Физическая модель совершенствуется администраторами базы данных в целях повышения производительности, но программисты, которые разрабатывают код приложения, в основном вынуждены ограничиваться работой с логической моделью, подготавливая SQL-запросы и вызывая хранимые процедуры. Концептуальные модели в основном используются как инструмент для представления и обмена мнениями по поводу требований к приложению, поэтому чаще всего служат в качестве почти неизменных схем, которые рассматриваются и обсуждаются на ранних стадиях проекта, после чего перестают быть предметом внимания.

Платформа Entity Framework придает значимость концептуальным моделям, позволяя разработчикам выполнять запросы к сущностям и связям в концептуальной модели; при этом для перевода этих операций в команды, зависящие от источника данных, применяется сама платформа Entity Framework. Это позволяет отказаться от применения в приложениях жестко заданных зависимостей от конкретного источника данных. Концептуальная модель, модель хранения и их сопоставление выражаются во внешней спецификации, известной также как модель Entity

Самым известным, функциональным и широко используемым ORM в мире .NET является Entity Framework . Для работы с .NET Core была создана версия EF Core . Принцип работы EF Core остался тем же что у его предшественников, но это уже другая технология, так что сейчас ожидать полного набора функционала из Entity Framework 6 не приходится, но разработка проекта продолжается весьма активно. Из данной статьи вы узнаете как быстро приступить к использованию Entity Framework Core в ASP.NET Core проектах.

Для начала работы с EF Core необходимо установить необходимые библиотеки. Добавим пакеты Microsoft.EntityFrameworkCore.SqlServer и Microsoft.EntityFrameworkCore.Tools .

Для начала необходимо определиться с данными, которые будут храниться в базе данных. Я добавлю 2 класса User и Log , которые будут отвечать за данные пользователя и какие-то данные лога.

Public class User { public Guid Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } } public class Log { public long Id { get; set; } public DateTime UtcTime { get; set; } public string Data { get; set; } }

После этого можно создать контекст для работы с базой данных:

Class TestDbContext: DbContext { public TestDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .HasIndex(b => b.Email); } public DbSet Users { get; set; } public DbSet Logs { get; set; } }

DbContext — данный класс определяет контекст данных, используемый для работы с базой данных.

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

Сейчас необходимо настроить подключение к базе данных. Для этого откроем файл Startup.cs и в метод ConfigureServices добавим строку:

Services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

Строку подключения зададим в файле конфигурации, по умолчанию это appsettings.json

"ConnectionStrings": { "DefaultConnection": "СТРОКА_ПОДКЛЮЧЕНИЯ_ЗДЕСЬ" }

Создание базы данных

Откроем Package Manager Console

И выполним команду Add-Migration InitialCreate

Это создаст файлы необходимые для создания структуры базы данных. Созданные файлы можно увидеть в созданной директории Migrations .

После этого выполним команду Update-Database и база будет создана.

Добавление логирования запросов

Сейчас давайте добавим логирование для всех API запросов. Для этого добавим класс LoggingMiddleware со следующим содержанием:

Public class LoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public LoggingMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } public async Task Invoke(HttpContext context, TestDbContext dbcontext) { if (context != null && context.Request.Path.ToString().ToLower().StartsWith("/api")) { using (var loggableResponseStream = new MemoryStream()) { var originalResponseStream = context.Response.Body; context.Response.Body = loggableResponseStream; try { var request = await FormatRequest(context.Request); await _next(context); var response = await FormatResponse(loggableResponseStream); loggableResponseStream.Seek(0, SeekOrigin.Begin); var newLog = new Log { Path = HttpUtility.UrlDecode(context.Request.Path + context.Request.QueryString), UtcTime = DateTime.UtcNow, Data = request, Response = response, StatusCode = context.Response.StatusCode, }; await loggableResponseStream.CopyToAsync(originalResponseStream); await dbcontext.Logs.AddAsync(newLog); dbcontext.SaveChanges(); } catch (Exception ex) { //Здесь можно добавить логирование ошибок throw; } finally { context.Response.Body = originalResponseStream; } } } } private static async Task FormatRequest(HttpRequest request) { request.EnableRewind(); string responseBody = new StreamReader(request.Body).ReadToEnd(); request.Body.Position = 0; return responseBody; } private static async Task FormatResponse(Stream loggableResponseStream) { loggableResponseStream.Position = 0; var buffer = new byte; await loggableResponseStream.ReadAsync(buffer, 0, buffer.Length); return JsonConvert.SerializeObject(Encoding.UTF8.GetString(buffer)); } }

А в файле Startup.cs в методе Configure добавим:

App.UseMiddleware();

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

Еще добавим контроллер для работы с пользователями, добавим там метод для получения информации обо всех пользователях.

")] public class UsersController: Controller { private readonly TestDbContext _context; public UsersController(TestDbContext context) { _context = context; } public async Task GetAll() { var users = await _context.Users.ToListAsync(); return Ok(users); } }

Это базовая информация, которая позволит вам быстро начать работать с Entity Framework Core .

Приятного программирования.

4 ответов

В Entity Framework Core 2.1 мы можем использовать типы запросов, как предполагал Юрий Н.

Более подробную статью о том, как их использовать, можно найти

Самый простой подход в соответствии с примерами статей будет:

1. Мы имеем, например, следующие сущности Модели для управления публикациями

Public class Magazine { public int MagazineId { get; set; } public string Name { get; set; } public string Publisher { get; set; } public List

Articles { get; set; } } public class Article { public int ArticleId { get; set; } public string Title { get; set; } public int MagazineId { get; set; } public DateTime PublishDate { get; set; } public Author Author { get; set; } public int AuthorId { get; set; } } public class Author { public int AuthorId { get; set; } public string Name { get; set; } public List
Articles { get; set; } }

2. У нас есть вид под названием AuthorArticleCounts, определенный для возврата имени и количества статей, написанных автором

SELECT a.AuthorName, Count(r.ArticleId) as ArticleCount from Authors a JOIN Articles r on r.AuthorId = a.AuthorId GROUP BY a.AuthorName

3. Мы идем и создаем модель, которая будет использоваться для представления

Public class AuthorArticleCount { public string AuthorName { get; private set; } public int ArticleCount { get; private set; } }

4. После этого создадим свойство DbQuery в моем DbContext, чтобы использовать результаты просмотра внутри модели

Public DbQuery AuthorArticleCounts{get;set;}

5. Наконец, мы можем легко получить результаты представления как это.

Var results=_context.AuthorArticleCounts.ToList();

В настоящее время представления не поддерживаются Entity Framework Core. См. https://github.com/aspnet/EntityFramework/issues/827 .

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

Context.Images.FromSql("SELECT * FROM dbo.ImageView")

EF Core не создает DBset для представлений SQL автоматически в контексте calss, мы можем добавить их вручную, как показано ниже.

Public partial class LocalDBContext: DbContext { public LocalDBContext(DbContextOptions options) : base(options) { } public virtual DbSet YourView { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { entity.HasKey(e => e.ID); entity.ToTable("YourView"); entity.Property(e => e.Name).HasMaxLength(50); }); } }

Образец образца определяется ниже, с несколькими свойствами

Последнее обновление: 31.10.2015

Entity Framework представляет специальную объектно-ориентированную технологию на базе фреймворка.NET для работы с данными. Если традиционные средства ADO.NET позволяют создавать подключения, команды и прочие объекты для взаимодействия с базами данных, то Entity Framework представляет собой более высокий уровень абстракции, который позволяет абстрагироваться от самой базы данных и работать с данными независимо от типа хранилища. Если на физическом уровне мы оперируем таблицами, индексами, первичными и внешними ключами, но на концептуальном уровне, который нам предлагает Entity Framework, мы уже работает с объектами.

Первая версия Entity Framework - 1.0 вышла еще в 2008 году и представляла очень ограниченную функциональность, базовую поддержку ORM (object-relational mapping - отображения данных на реальные объекты) и один единственный подход к взаимодействию с бд - Database First. С выходом версии 4.0 в 2010 году многое изменилось - с этого времени Entity Framework стал рекомендуемой технологией для доступа к данным, а в сам фреймворк были введены новые возможности взаимодействия с бд - подходы Model First и Code First.

Дополнительные улучшения функционала последовали с выходом версии 5.0 в 2012 году. И наконец, в 2013 году был выпущен Entity Framework 6.0, обладающий возможностью асинхронного доступа к данным.

Центральной концепцией Entity Framework является понятие сущности или entity. Сущность представляет набор данных, ассоциированных с определенным объектом. Поэтому данная технология предполагает работу не с таблицами, а с объектами и их наборами.

Любая сущность, как и любой объект из реального мира, обладает рядом свойств. Например, если сущность описывает человека, то мы можем выделить такие свойства, как имя, фамилия, рост, возраст, вес. Свойства необязательно представляют простые данные типа int, но и могут представлять более комплексные структуры данных. И у каждой сущности может быть одно или несколько свойств, которые будут отличать эту сущность от других и будут уникально определять эту сущность. Подобные свойства называют ключами .

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

Отличительной чертой Entity Framework является использование запросов LINQ для выборки данных из БД. С помощью LINQ мы можем не только извлекать определенные строки, хранящие объекты, из бд, но и получать объекты, связанные различными ассоциативными связями.

Другим ключевым понятием является Entity Data Model . Эта модель сопоставляет классы сущностей с реальными таблицами в БД.

Entity Data Model состоит из трех уровней: концептуального, уровень хранилища и уровень сопоставления (маппинга).

На концептуальном уровне происходит определение классов сущностей, используемых в приложении.

Уровень хранилища определяет таблицы, столбцы, отношения между таблицами и типы данных, с которыми сопоставляется используемая база данных.

Уровень сопоставления (маппинга) служит посредником между предыдущими двумя, определяя сопоставление между свойствами класса сущности и столбцами таблиц.

Таким образом, мы можем через классы, определенные в приложении, взаимодействовать с таблицами из базы данных.

Способы взаимодействия с БД

Entity Framework предполагает три возможных способа взаимодействия с базой данных:

    Database first : Entity Framework создает набор классов, которые отражают модель конкретной базы данных

    Model first : сначала разработчик создает модель базы данных, по которой затем Entity Framework создает реальную базу данных на сервере.

    Code first : разработчик создает класс модели данных, которые будут храниться в бд, а затем Entity Framework по этой модели генерирует базу данных и ее таблицы



Понравилась статья? Поделиться с друзьями: