it-swarm.com.ru

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

Пожалуйста, укажите мне в правильном направлении.

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

23
Objective Coder

Ваш код:

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 введение .

24
Stephen Cleary

Я бы выбрал способ «один DbContext на запрос» и повторно использовал бы DbContext в запросе. Так как все задачи должны быть выполнены в конце запроса, вы можете безопасно утилизировать его снова.

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

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

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

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

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

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

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

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;
    }
}
1
danludwig

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

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

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

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

Теоретически, сборка мусора ДОЛЖНА очищать их без упаковки, но я не совсем доверяю GC. 

1
Scottie

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

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

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

0
Ross Brigoli

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

Если у вас активирована отложенная загрузка, и исключение возникает после области действия using, см. https://stackoverflow.com/a/21406579/870604

0
ken2k