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

asp.net entity-framework visual-studio-2008

Вопрос

Это будет применяться в основном для приложения 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, потому что он не является частью распознаваемых функций: это расширение.

Итак, вы хотите передать произвольное количество путей к вашей функции, а Include () принимает только один. Что делать? Вы можете решить, что вам никогда не понадобится больше, скажем, 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 / конкатенацию строк, когда структура может быть сложной и когда возможное количество различных результирующих запросов невелико (что означает меньшее количество обращений перед компиляцией). - Используйте фильтрацию в памяти, когда вы работаете с небольшим подмножеством данных или когда вам в любом случае нужно было сначала извлечь все данные из данных (если производительность удовлетворительная для всех данных, тогда фильтрация в памяти не будет вызывать любое время, которое нужно провести в БД).

Синглтон доступ

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

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 - это два разных объекта, один отслеживается, а другой нет. Использование отсоединенного объекта в обновлении / вставке вызовет 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.

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

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

Это можно рассматривать как скрытое благословение. Конечно, раздражает загружать все вручную. Однако это уменьшает количество обращений к БД и заставляет задуматься о том, когда следует загружать данные. Чем больше вы можете загрузить в одну базу данных вызова, тем лучше. Это всегда было правдой, но теперь оно реализуется с помощью этой «функции» 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 " +

Популярные ответы

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



Related

Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему
Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему