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

Entity Framework, DBContext и использование() + async?

Есть вещь, которая долгое время искала меня в Entity Framework.

В прошлом году я написал большое приложение для клиента, использующего EF. И во время разработки все отлично работало.

Мы отправили систему в августе. Но через несколько недель я начал видеть странные утечки памяти на производственном сервере. Мой процесс ASP.NET MVC 4 занимал все ресурсы машины после нескольких дней работы (8 ГБ). Это было плохо. Я просматриваю в сети и вижу, что вы должны окружать все ваши EF-запросы и операции в блоке using(), чтобы контекст мог быть удален.

Через день я реорганизовал весь свой код для использования using(), и это решило мои проблемы, так как тогда процесс сидит на устойчивом использовании памяти.

Причина, по которой я не окружала мои запросы, в первую очередь заключается в том, что я начал свои первые контроллеры и репозитории из собственных лесов Microsoft, включенных в Visual Studio, они не окружали его запросы с использованием, вместо этого у него был DbContext как переменная экземпляра самого контроллера.

Прежде всего: если действительно важно избавиться от контекста (что-то, что не было бы странным, dbconnection нужно закрыть и так далее), Microsoft, возможно, должна иметь это во всех их примерах!

Теперь я начал работать над новым крупным проектом со всеми моими знаниями в затылке, и я изучал новые возможности .NET 4.5 и EF 6 async и await. EF 6.0 имеет все эти асинхронные методы (например, SaveChangesAsync, ToListAsync и т.д.).

public Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        return langRepo.Add(RequestOrganizationTypeEnum, language);
    }
}

В классе TblLanguageRepo:

public async Task<tblLanguage> Add(OrganizationTypeEnum requestOrganizationTypeEnum, tblLanguage language)
{
    ...
    await Context.SaveChangesAsync();
    return langaugeDb;
}

Однако, когда я теперь окружаю свои заявления в блоке using(), я получаю исключение, DbContext was disposed, прежде чем запрос сможет вернуться. Это ожидаемое поведение. Запрос выполняется async, а блок using завершен перед запросом. Но как я должен правильно распоряжаться своим контекстом при использовании async и ждать функций ef 6??

Просьба указать мне в правильном направлении.

Требуется ли using() в EF 6? Почему в примерах Microsoft никогда не упоминается? Как вы используете функции асинхронизации и правильно распоряжаетесь своим контекстом?

4b9b3361

Ответ 1

Ваш код:

public Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        return langRepo.Add(RequestOrganizationTypeEnum, language);
    }
}

удаляет хранилище перед возвратом Task. Если вы сделаете код async:

public async Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        return await langRepo.Add(RequestOrganizationTypeEnum, language);
    }
}

то он будет располагать репозиторий непосредственно перед завершением Task. Фактически, когда вы нажимаете await, метод возвращает неполный Task (обратите внимание, что блок using по-прежнему "активен" в этой точке). Затем, когда задача langRepo.Add завершается, метод Post возобновляет выполнение и предоставляет langRepo. Когда метод Post завершен, возвращается Task.

Дополнительную информацию см. в async вступлении.

Ответ 2

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

См. например: Один DbContext для запроса в ASP.NET MVC (без контейнера IOC)

Некоторые другие преимущества:

  • некоторые объекты могут быть реализованы в DbContext из предыдущих запросов, сохраняя некоторые дополнительные запросы.
  • У вас нет всех этих дополнительных операторов using, загромождающих ваш код.

Ответ 3

I согласен с @Dirk Boer, что лучший способ управлять временем жизни DbContext - с контейнером IoC, который предоставляет контекст при завершении HTTP-запроса. Однако, если это не вариант, вы также можете сделать что-то вроде этого:

var dbContext = new MyDbContext();
var results = await dbContext.Set<MyEntity>.ToArrayAsync();
dbContext.Dispose();

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

Подумайте об этом, вы не должны получать исключений объектов, если вы используете ключевое слово await в блоке использования:

public async Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        var returnValue = langRepo.Add(RequestOrganizationTypeEnum, language);
        await langRepo.SaveChangesAsync();
        return returnValue;
    }
}

Ответ 4

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

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

Другой - создать внутреннюю перегрузку каждого метода и принять там контекст.

Но, да, вы должны обернуть их при использовании.

В теории, сбор мусора ДОЛЖЕН очистить их, не обертывая их, но я не полностью доверяю GC.

Ответ 5

ИМХО, это опять-таки проблема, вызванная использованием ленивой загрузки. После того, как вы разместили свой контекст, вы больше не можете lazy-load свойство, потому что удаление контекста закрывает базовое соединение с сервером базы данных.

Если активизирована ленивая загрузка, а исключение происходит после области using, см. fooobar.com/questions/228219/...

Ответ 6

Если вы хотите сохранить свой метод синхронным, но хотите асинхронно сохранить его в БД, не используйте оператор using. Как сказал @danludwig, это просто синтаксический сахар. Вы можете вызвать метод SaveChangesAsync() и затем утилизировать контекст после завершения задачи. Один из способов сделать это заключается в следующем:

//Save asynchronously then dispose the context after
context.SaveChangesAsync().ContinueWith(c => context.Dispose());

Обратите внимание, что лямбда, передаваемая в ContinueWith(), также будет выполняться асинхронно.