Подтвердить что ты не робот

Каковы хорошие методы проектирования при работе с Entity Framework

Это будет применяться в основном для приложения 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, вы можете работать с ассоциациями undefined в других классах

// 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. Вы также можете выполнять довольно сложные манипуляции с объектом, что хорошо объяснено здесь [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());

Теперь, если вы хотите, чтобы параметр 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<Dog> oQuery = new ObjectQuery<Dog>(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<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: "Owner" и "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 включений, и передать каждую разделенную строку в 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<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 с параметризованным 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<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), он приведет к аннулированию скомпилированного запроса и приведет к повторному анализу. Итак, эмпирическое правило состоит в том, чтобы вместо этого возвращать List < > объектов.

    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(), продолжайте. Существует много случаев, когда вы хотите уточнить запрос перед его выполнением.

Производительность

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

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

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

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

{
    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();
}

Преимущество в том, что вы получаете все преимущества предварительно скомпилированного quert. Недостатки заключаются в том, что вы, скорее всего, закончите предложение 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/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[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 есть владелец, который 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<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 - это два разных объекта, один - гусеничный, а другой нет. Использование отделяемого объекта в обновлении/вставке заставит Attach(), который скажет "Подождите минуту, у меня уже есть объект здесь с тем же ключом базы данных. Fail". И когда вы присоединяете() один объект, вся его иерархия также привязана, что вызывает проблемы во всем мире. Будьте особенно осторожны.

Насколько быстрее он работает с 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.

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

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

Это можно рассматривать как замаскированное благословение. Конечно, это раздражает, чтобы загрузить все вручную. Тем не менее, это уменьшает количество вызовов в 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 " +
4b9b3361

Ответ 1

Пожалуйста, не используйте всю приведенную выше информацию, такую ​​как "Singleton access". Вы абсолютно 100% не должны хранить этот контекст для повторного использования, поскольку он не является потокобезопасным.

Ответ 2

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