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

Вложенный DbContext из-за вызовов методов - Entity Framework

В следующем случае, когда два DbContexts вложены из-за вызовов методов:

public void Method_A() {
    using (var db = new SomeDbContext()) {
        //...do some work here
        Method_B();
        //...do some more work here
    }
}

public void Method_B() {
    using (var db = new SomeDbContext()) {
        //...do some work
    }
}

Вопрос:

  • Будет ли это гнездовать какие-либо проблемы? (и будет ли правильный DbContext удален в нужное время?)

  • Является ли это вложенность считающейся плохой практикой, если Method_A будет реорганизован на:

    public void Method_A() {
        using (var db = new SomeDbContext()) {
            //...do some work here
        }
    
        Method_B();
    
        using (var db = new SomeDbContext()) {
            //...do some more work here
        }
    }
    

Спасибо.

4b9b3361

Ответ 1

Ваш производный класс DbContext фактически управляет по крайней мере тремя вещами для вас здесь:

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

Entity Framework, как правило, кэширует метаданные (элемент 1), чтобы они были доступны всем экземплярам контекста (или, по крайней мере, всем экземплярам, ​​использующим одну и ту же строку соединения). Таким образом, здесь это не вызывает беспокойства.

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

Вы также получаете два клиентских кеша (пункт 3). Если вы загрузите объект из внешнего контекста, а затем снова из внутреннего контекста, у вас будет две копии его в памяти. Это определенно будет путать, и может привести к тонким ошибкам. Это означает, что если вы не хотите использовать общие объекты контекста, то ваш вариант 2, вероятно, будет лучше, чем вариант 1.

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

Итак, где это вас оставляет?

Если вы используете этот шаблон просто для того, чтобы не пропускать объекты DbContext в вашем коде, тогда вам, вероятно, будет лучше рефакторинг MethodB, чтобы получить контекст в качестве параметра. Вопрос о том, как долговечные объекты контекста должны появляться неоднократно. Как правило, создайте новый контекст для одной операции с базой данных или для ряда связанных операций с базой данных. (См., Например, это сообщение в блоге и этот вопрос.)

(В качестве альтернативы вы можете добавить конструктор к вашему производному классу DbContext, который получает существующее соединение. Тогда вы можете использовать одно и то же соединение между несколькими контекстами.)

Один полезный шаблон - написать свой собственный класс, который создает объект контекста и сохраняет его как личное поле или свойство. Затем вы создаете свой класс IDisposable и его метод Dispose() предоставляет объект контекста. Ваш код вызывающего кода обновляет экземпляр вашего класса и не должен беспокоиться о контекстах или соединениях вообще.

Когда вам может потребоваться одновременное использование нескольких контекстов?

Это может быть полезно, когда вам нужно написать многострочный код. Соединение с базой данных не является потокобезопасным, поэтому вы должны получать доступ к соединению (и, следовательно, контексту EF) только из одного потока за раз. Если это слишком ограничительно, вам нужно несколько соединений (и контекстов), по одному на поток. Вы можете найти этот интересный.

Ответ 3

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

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

Вставка простого примера.

public class SomeContext : SomeDbContext
{
    protected int UsingCount = 0;
    public static SomeContext GetContext(SomeContext context)
    {
        if (context != null)
        {
            context.UsingCount++;
        }
        else
        {
            context = new SomeContext();
        }
        return context;
    }

    private SomeContext()
    {
    }

    protected bool MyDisposing = true;
    protected override void Dispose(bool disposing)
    {
        if (UsingCount == 0)
        {
            base.Dispose(MyDisposing);
            MyDisposing = false;
        }
        else
        {
            UsingCount--;
        }
    }

    public override int SaveChanges()
    {
        if (UsingCount == 0)
        {
            return base.SaveChanges();
        }
        else
        {
            return 0;
        }
    }
}

Пример использования

public class ExmapleNesting
{
    public void MethodA()
    {
        using (var context = SomeContext.GetContext(null))
        {
            // manipulate, save it, just do not call Dispose on context in using
            MethodB(context);
        }

        MethodB();
    }

    public void MethodB(SomeContext someContext = null)
    {
        using (var context = SomeContext.GetContext(someContext))
        {
            // manipulate, save it, just do not call Dispose on context in using
            // Even more nested functions if you'd like
        }
    }
}

Простой и удобный.

Ответ 4

Если вы считаете, что количество подключений к базе данных и влияние времени открытия новых соединений не являются важной проблемой, и у вас нет ограничений для поддержки вашего приложения для работы с максимальной производительностью, все в порядке. Ваш код работает хорошо. Поскольку создание только контекста db оказывает незначительное влияние на производительность, метаданные будут кэшироваться после первой загрузки, а соединение с вашей базой данных происходит только тогда, когда код должен выполнить запрос. С учетом соображений производительности и дизайна кода я предлагаю вам создать контекст factory, чтобы иметь только экземпляр каждого контекста Db для каждого экземпляра вашего приложения.

Вы можете взглянуть на эту ссылку, чтобы узнать больше о соображениях производительности http://msdn.microsoft.com/en-us/data/hh949853