Это будет применяться в основном для приложения 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. Вот пример: «выберите значение собака из Entities.DogSet как собака, где собака.ID = @ID». Синтаксис очень похож на синтаксис SQL. Вы также можете выполнять довольно сложные манипуляции с объектами, что хорошо объяснено [здесь] [1].
Хорошо, вот как это сделать, используя ObjectQuery <>
string query = "select value dog " +
"from Entities.DogSet as dog " +
"where dog.ID = @ID";
ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance));
oQuery.Parameters.Add(new ObjectParameter("ID", id));
oQuery.EnablePlanCaching = true;
return oQuery.FirstOrDefault();
При первом запуске этого запроса среда генерирует дерево выражений и сохраняет его в памяти. Поэтому в следующий раз, когда он будет выполнен, вы сэкономите на этом дорогостоящем шаге. В этом примере EnablePlanCaching = true, что не нужно, поскольку это опция по умолчанию.
Другим способом компиляции запроса для последующего использования является метод CompiledQuery.Compile. Это использует делегат:
static readonly Func<Entities, int, Dog> query_GetDog =
CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
ctx.DogSet.FirstOrDefault(it => it.ID == id));
или используя linq
static readonly Func<Entities, int, Dog> query_GetDog =
CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
(from dog in ctx.DogSet where dog.ID == id select dog).FirstOrDefault());
для вызова запроса:
query_GetDog.Invoke( YourContext, id );
Преимущество CompiledQuery заключается в том, что синтаксис вашего запроса проверяется во время компиляции, а EntitySQL - нет. Однако есть и другие соображения ...
Включает
Допустим, вы хотите, чтобы данные о владельце собаки были возвращены запросом, чтобы не делать 2 вызова в базу данных. Легко сделать, верно?
EntitySQL
string query = "select value dog " +
"from Entities.DogSet as dog " +
"where dog.ID = @ID";
ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance)).Include("Owner");
oQuery.Parameters.Add(new ObjectParameter("ID", id));
oQuery.EnablePlanCaching = true;
return oQuery.FirstOrDefault();
CompiledQuery
static readonly Func<Entities, int, Dog> query_GetDog =
CompiledQuery.Compile<Entities, int, Dog>((ctx, id) =>
(from dog in ctx.DogSet.Include("Owner") where dog.ID == id select dog).FirstOrDefault());
Теперь, что, если вы хотите настроить параметризацию «Включить»? Я имею в виду, что вы хотите иметь одну функцию Get (), которая вызывается с разных страниц, которые заботятся о разных отношениях собаки. Один заботится о владельце, другой - о его любимой еде, другой - о его любимой еде и так далее. По сути, вы хотите указать в запросе, какие ассоциации загружать.
Это легко сделать с EntitySQL
public Dog Get(int id, string include)
{
string query = "select value dog " +
"from Entities.DogSet as dog " +
"where dog.ID = @ID";
ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>(query, EntityContext.Instance))
.IncludeMany(include);
oQuery.Parameters.Add(new ObjectParameter("ID", id));
oQuery.EnablePlanCaching = true;
return oQuery.FirstOrDefault();
}
Включение просто использует переданную строку. Достаточно просто. Обратите внимание, что можно улучшить функцию Include (string) (которая принимает только один путь) с помощью IncludeMany (string), которая позволит вам передавать строку разделенных запятыми ассоциаций для загрузки. Посмотрите далее в разделе расширения для этой функции.
Однако если мы попытаемся сделать это с помощью CompiledQuery, мы столкнемся с многочисленными проблемами:
Очевидное
static readonly Func<Entities, int, string, Dog> query_GetDog =
CompiledQuery.Compile<Entities, int, string, Dog>((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: «Владелец» и «FavoriteFood» (который не следует путать с «Owner.FavoriteFood»). !).
Тогда, давайте использовать IncludeMany (), которая является функцией расширения
static readonly Func<Entities, int, string, Dog> query_GetDog =
CompiledQuery.Compile<Entities, int, string, Dog>((ctx, id, include) =>
(from dog in ctx.DogSet.IncludeMany(include) where dog.ID == id select dog).FirstOrDefault());
Опять неправильно, на этот раз это потому, что EF не может проанализировать IncludeMany, потому что он не является частью распознаваемых функций: это расширение.
Хорошо, значит, вы хотите передать произвольное количество путей к вашей функции, а Includes () принимает только один. Что делать? Вы можете решить, что вам никогда не понадобится больше, скажем, 20 включений, и передать каждую отдельную строку в структуре в 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<Entities, int, ObjectQuery<Dog>> query_GetDog =
CompiledQuery.Compile<Entities, int, string, ObjectQuery<Dog>>((ctx, id) =>
(ObjectQuery<Dog>)(from dog in ctx.DogSet where dog.ID == id select dog));
public Dog GetDog( int id, string include )
{
ObjectQuery<Dog> oQuery = query_GetDog(id);
oQuery = oQuery.IncludeMany(include);
return oQuery.FirstOrDefault;
}
Это должно сработать, за исключением того, что когда вы вызываете IncludeMany (или Include, Where, OrderBy ...), вы лишаете законной силы кэшированный скомпилированный запрос, потому что он теперь совершенно новый! Таким образом, дерево выражений необходимо повторно проанализировать, и вы снова получите удар по производительности.
Так в чем же решение? Вы просто не можете использовать CompiledQueries с параметризованными включениями. Вместо этого используйте 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<Entities, MyParams, IEnumerable<Dog>> query_GetDog =
CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((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<Dog> 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<Entities, int, string, IEnumerable<Dog>> query_GetDog =
CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) =>
from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog);
public IEnumerable<Dog> GetSomeDogs( int age, string name )
{
return query_GetDog(YourContext,age,name);
}
public void DataBindStuff()
{
IEnumerable<Dog> dogs = GetSomeDogs(4,"Bud");
// but I want the dogs ordered by BirthDate
gridView.DataSource = dogs.OrderBy( it => it.BirthDate );
}
Что здесь произойдет? Продолжая играть с исходным ObjectQuery (то есть с реальным типом возврата оператора Linq, который реализует IEnumerable), он аннулирует скомпилированный запрос и будет вынужден выполнить повторный анализ. Таким образом, эмпирическое правило должно возвращать список <> объектов вместо этого.
static readonly Func<Entities, int, string, IEnumerable<Dog>> query_GetDog =
CompiledQuery.Compile<Entities, int, string, IEnumerable<Dog>>((ctx, age, name) =>
from dog in ctx.DogSet where dog.Age == age && dog.Name == name select dog);
public List<Dog> GetSomeDogs( int age, string name )
{
return query_GetDog(YourContext,age,name).ToList(); //<== change here
}
public void DataBindStuff()
{
List<Dog> dogs = GetSomeDogs(4,"Bud");
// but I want the dogs ordered by BirthDate
gridView.DataSource = dogs.OrderBy( it => it.BirthDate );
}
Когда вы вызываете ToList (), запрос выполняется согласно скомпилированному запросу, а затем, позже, OrderBy выполняется для объектов в памяти. Это может быть немного медленнее, но я даже не уверен. Несомненно, вы не беспокоитесь о неправильной обработке ObjectQuery и аннулировании скомпилированного плана запроса.
Еще раз, это не общее утверждение. ToList () - это защитная хитрость программирования, но если у вас есть веская причина не использовать ToList (), продолжайте. Есть много случаев, когда вы хотели бы уточнить запрос перед его выполнением.
Производительность
Как влияет на производительность компиляция запроса? Это может быть довольно большим. Эмпирическое правило заключается в том, что компиляция и кэширование запроса для повторного использования занимает как минимум вдвое больше времени, чем простое выполнение без кэширования. Для сложных запросов (читай 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<Entities, MyParams, IEnumerable<Dog>> query_GetDog =
CompiledQuery.Compile<Entities, MyParams, IEnumerable<Dog>>((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<Dog> GetSomeDogs()
{
MyParams myParams = new MyParams();
myParams.name = "Bud";
myParams.checkName = true;
myParams.age = 0;
myParams.checkAge = false;
return query_GetDog(YourContext,myParams).ToList();
}
Преимущество здесь в том, что вы получаете все преимущества предварительно скомпилированного керта. Недостатки в том, что вы, скорее всего, в конечном итоге получите предложение where, которое довольно сложно поддерживать, что вы будете подвергаться большему штрафу за предварительную компиляцию запроса, и что каждый выполняемый запрос не так эффективен, как мог бы (особенно с брошенными соединениями).
Другим способом является построение запроса EntitySQL по частям, как мы все делали с SQL.
protected List<Dod> 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<Dog> oQuery = new ObjectQuery<Dog>( 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<Dod> GetSomeDogs( string name, int age, string city)
{
string query = "select value dog from Entities.DogSet where dog.Owner.Address.City == @City ";
ObjectQuery<Dog> oQuery = new ObjectQuery<Dog>( query, YourContext );
oQuery.Parameters.Add( new ObjectParameter( "City", city ) );
List<Dog> 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 / конкатенацию строк, когда структура может быть сложной и когда возможное количество различных результирующих запросов невелико (что означает меньшее количество обращений перед компиляцией).
- Используйте фильтрацию в памяти, когда вы работаете с небольшим подмножеством данных или когда вам все равно нужно было сначала извлечь все данные из данных (если производительность удовлетворительная для всех данных, тогда фильтрация в памяти не будет заставлять любое время быть проведенным в БД).
Singleton access
Лучший способ справиться с вашим контекстом и сущностями на всех ваших страницах - использовать шаблон синглтона:
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[instanceKey] == null)
{
On3GoEntities entity = new On3GoEntities();
context.Items[instanceKey] = entity;
}
return (YourEntities)context.Items[instanceKey];
}
}
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, стоит ли это?
При выполнении запроса вы можете указать платформе отслеживать объекты, которые он будет возвращать или нет. Что это значит? При включенном отслеживании (опция по умолчанию) платформа будет отслеживать, что происходит с объектом (был ли он изменен? Создан? Удален?), А также будет связывать объекты вместе, когда дальнейшие запросы будут сделаны из базы данных, что представляет интерес здесь.
Например, предположим, что у собаки с идентификатором == 2 есть владелец с идентификатором == 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<Dog> oDogQuery = (ObjectQuery<Dog>)
(from dog in YourContext.DogSet where dog.ID == 2 select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
Dog dog = oDogQuery.FirstOrDefault();
//dog.OwnerReference.IsLoaded == false;
ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>)
(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<Dog> oDogQuery = (ObjectQuery<Dog>)
(from dog in YourContext.DogSet where dog.ID == 2 select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
Dog dog2 = oDogQuery.FirstOrDefault();
dog1 и dog2 - это 2 разных объекта, один отслеживается, а другой нет. Использование отсоединенного объекта в обновлении / вставке вызовет Attach (), который скажет: «Подождите, у меня уже есть объект с тем же ключом базы данных. Сбой». И когда вы присоединяете () один объект, вся его иерархия также присоединяется, вызывая проблемы повсюду. Будьте особенно осторожны.
Насколько быстрее это с NoTracking
Это зависит от запросов. Некоторые из них гораздо более подвержены отслеживанию, чем другие. У меня нет простого правила, но это помогает.
Значит, тогда я должен везде использовать NoTracking?
Не совсем так. Есть несколько преимуществ для отслеживания объекта. Во-первых, объект кэшируется, поэтому последующий вызов этого объекта не попадет в базу данных. Этот кеш действителен только на время жизни объекта YourEntities, который, если вы используете приведенный выше одноэлементный код, совпадает с временем жизни страницы. Один запрос страницы == один объект YourEntity. Таким образом, для нескольких вызовов одного и того же объекта, он будет загружаться только один раз за запрос страницы. (Другой механизм кэширования может расширить это).
Что происходит, когда вы используете NoTracking и пытаетесь загрузить один и тот же объект несколько раз? База данных будет запрашиваться каждый раз, так что там есть влияние. Как часто вы должны / должны вызывать один и тот же объект во время запроса одной страницы? Как можно меньше, конечно, но это случается.
Также помните часть выше о том, чтобы автоматически связывать ассоциации для вас? У вас нет этого с NoTracking, поэтому, если вы загружаете свои данные несколькими партиями, между ними не будет ссылки:
ObjectQuery<Dog> oDogQuery = (ObjectQuery<Dog>)(from dog in YourContext.DogSet select dog);
oDogQuery.MergeOption = MergeOption.NoTracking;
List<Dog> dogs = oDogQuery.ToList();
ObjectQuery<Person> oPersonQuery = (ObjectQuery<Person>)(from o in YourContext.PersonSet select o);
oPersonQuery.MergeOption = MergeOption.NoTracking;
List<Person> owners = oPersonQuery.ToList();
В этом случае ни у одной собаки не будет установлено свойство .Owner.
Некоторые вещи, которые следует иметь в виду, когда вы пытаетесь оптимизировать производительность.
Не ленивая загрузка, что мне делать?
Это можно рассматривать как скрытое благословение. Конечно, раздражает загружать все вручную. Однако это уменьшает количество обращений к БД и заставляет задуматься о том, когда следует загружать данные. Чем больше вы можете загрузить в одну базу данных вызова, тем лучше. Это всегда было правдой, но теперь оно реализуется с помощью этой «функции» EF.
Конечно, вы можете позвонить
if (! ObjectReference.IsLoaded) ObjectReference.Load ();
если вы хотите, но лучшая практика - заставить фреймворк загружать объекты, которые, как вы знаете, вам понадобятся за один выстрел. Именно здесь начинается обсуждение параметризованных включений.
Допустим, у вас есть собачий объект
public class Dog
{
public Dog Get(int id)
{
return YourContext.DogSet.FirstOrDefault(it => it.ID == id );
}
}
Это тип функции, с которой вы работаете все время. Он вызывается повсюду, и как только у вас будет этот объект Dog, вы будете делать с ним совершенно разные вещи в разных функциях. Во-первых, это должно быть предварительно скомпилировано, потому что вы будете вызывать это очень часто. Во-вторых, каждая страница должна иметь доступ к разным подмножествам данных 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 " +