#c# #async-await #linq-to-sql #entity-framework-core #ef-core-2.2
#c# #асинхронное ожидание #linq-to-sql #entity-framework-core #ef-core-2.2
Вопрос:
У меня есть следующий запрос и ядро EF 2.2.6
public async Task<List<SamplePointContamination>> GetSamplePointsContaminationAsync(int siteId, int buildingId)
{
var siteZones = await administrationApiClient.GetAsync<List<BuildingZone>>($"sites/{siteId}/buildingZones",
new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("IncludeBuildings", "True") });
var buildingZoneIds = siteZones.Where(x => x.Building.Id == buildingId).Select(x => x.Id);
if (!buildingZoneIds.Any())
throw new SecurityException("Building does not belong to the site.");
var timeFrom = DateTimeOffset.UtcNow.AddDays(-9);
using (var dbContext = CreateDbContext())
{
//TODO optimize query. Generated sql does not promise
return await dbContext.Analyses
.Where(x => buildingZoneIds.Contains(x.Sample.SamplePoint.BuildingZoneId) amp;amp; x.CreatedAt >= timeFrom
amp;amp; x.Sample.SamplePoint.Top.HasValue amp;amp; x.Sample.SamplePoint.Left.HasValue)
.Include(x => x.Sample.SamplePoint)
.Include(x => x.AnalysisRisks)
.ThenInclude(x => x.Risk)
.GroupBy(x => new
{
x.Sample.SamplePoint.Id,
x.Sample.SamplePoint.Name,
x.Sample.SamplePoint.Top,
x.Sample.SamplePoint.Left
})
.Select(x => new SamplePointContamination()
{
Id = x.Key.Id,
Name = x.Key.Name,
Top = x.Key.Top.Value,
Left = x.Key.Left.Value,
WaitingResults = x.Count(y => !y.ResultPositive.HasValue),
RiskTypes = x.SelectMany(y => y.AnalysisRisks)
.GroupBy(y => new { Id = (int)y.Risk.RiskType, Name = y.Risk.RiskType.ToString() })
.Select(y => new RiskTypeContamination()
{
Id = y.Key.Id,
Name = y.Key.Name,
Positives = y.Count(z => z.Analysis.ResultPositive == true),
Negatives = y.Count(z => z.Analysis.ResultPositive == false),
IsPositive = y.Any(z => z.Analysis.ResultPositive == true)
}).ToList() //Crashes if ToList() is omitted. If using statement is removed, ToList() is not needed
})
.ToListAsync();
}
}
Первая проблема заключается в том, что базовый SQL-запрос очень неэффективен, потому что GROUP BY почти никогда не применяется к базе данных (но выполняется в памяти), за исключением самых простых случаев, но это не тема.
Тема заключается в том, что по какой-то причине приложение выходит из строя, если ToList() опущен (5-я строка от конца кода, который комментируется). Кроме того, нет ошибки, если я удаляю оператор using . Все мои запросы используют этот шаблон с использованием, и до сих пор у меня не было ни одной проблемы. Я знаю, что мне не нужно использовать using , но, по моему мнению, при использовании таким образом это не должно создавать проблем, поскольку я никогда не пытаюсь получить доступ к экземпляру DbContext за пределами этой области. Отображается эта ошибка:
Майкрософт.EntityFrameworkCore: не удается получить доступ к удаленному объекту. Распространенной причиной этой ошибки является удаление контекста, который был разрешен при внедрении зависимостей, а затем последующая попытка использовать тот же экземпляр контекста в другом месте вашего приложения. Это может произойти, если вы вызываете Dispose() для контекста или переносите контекст в оператор using . Если вы используете внедрение зависимостей, вы должны позволить контейнеру внедрения зависимостей позаботиться об удалении экземпляров контекста. Имя объекта: ‘Context’.
Итак, очевидно, что DbContext снова используется после уничтожения, но я не делаю этого в своем коде явно. Очевидно, что это имеет какое-то отношение к async / await, и по некоторым причинам добавление упомянутого ToList() решает проблему, но я понятия не имею, что происходит и почему ошибка существует без нее.
Комментарии:
1. Контекст удаляется в конце
using
, но запрос начинается сToListAsync
continue в асинхронной задаче.2. Можете ли вы опубликовать список исключений?
3.без
ToList()
SamplePointContamination.RiskTypes
isIQueryable
, который не «материализуется», поэтому доступ к нему вызовет это исключение (поскольку он будет запрашивать базу данных)4. я бы предложил переписать ether с использованием правильных объединений или
ToListAsync
данных, а затем поработать с in mem над возвращаемыми даннымиvar data = await dbContext.Analyses .Where(x => buildingZoneIds.Contains(x.Sample.SamplePoint.BuildingZoneId) amp;amp; x.CreatedAt >= timeFrom amp;amp; x.Sample.SamplePoint.Top.HasValue amp;amp; x.Sample.SamplePoint.Left.HasValue) .Include(x => x.Sample.SamplePoint) .Include(x => x.AnalysisRisks) .ThenInclude(x => x.Risk).ToListAsync()
, и @Selvin, вероятно, прав.5. Насколько я помню, в версиях ядра EF до 3 — EF может решить оценить любую часть запроса на клиенте, если он не может преобразовать ее в правильный запрос к базе данных. Я бы предложил начать с полного отключения этой функции (например, как описано здесь: compiledexperience.com/blog/posts/ef-core-client-side-eval ), потому что если EF не может оценить ваш запрос — он должен выдать исключение. В этом случае, скорее всего, последний выбор не может быть преобразован в запрос и поэтому оценивается на клиенте, что потенциально очень дорого, а также приводит к вашей проблеме.