Reputation: 997
I have been battling with what is proving to be a diffuclt to track down issue involving awaited tasks within a LINQ Lambda select.
Note: The code below has been simplified and uses demo naming i.e. Town, Region etc. But the workflow is the same as the original code in which the issue occurs.
await this.CheckTownSpecialProperty(t)
function is called, I end up getting A second operation was started on this context before a previous operation completed.
. public async Task<IEnumerable<TownSearchOutputDto>> GetTowns(TownSearchInputDto input)
{
var numResults = 25;
var query = _townRepository.GetAll()
.Include(t => t.Tests)
.Include(t => t.Region.Select(s => s.SubRegion))
.Include(t => t.Province); // Eager load relations.
// Filter based on CountyId.
if (input.CountyId > 0)
{
query = query.Where(t => t.CountyId == input.CountyId);
}
var townList = await query
.OrderBy(t => t.Name)
.Take(numResults)
.ToListAsync();
IEnumerable<Task<TownSearchOutputDto>> tasks = townList.Select(async t => {
var townLevel = this.GetTownLevel(t);
bool hasSomeSpecialTownProperty = false;
if (townLevel.Key > 1)
{
hasSomeSpecialTownProperty = await this.CheckTownSpecialProperty(t);
}
return new TownSearchOutputDto
{
Id = t.Id,
CountyId = t.CountyId,
RegionName = t.Region.Name,
SubRegionName = t.SubRegion.Name,
ProvinceName = t.Province.Name,
TownLevelOrder = townLevel.Key,
TownLevelName = townLevel.Value,
HasSomeSpecialTownProperty = hasSomeSpecialTownProperty,
Disabled = townLevel.Key > 2 || t.Tests.Any(a => a.TownId == t.Id)
};
}).ToList();
var towns = await Task.WhenAll(tasks);
return towns;
}
private KeyValuePair<int, string> GetTownLevel(Town town)
{
if (!string.IsNullOrWhiteSpace(town.Tiny))
{
return new KeyValuePair<int, string>(3, "Tiny");
}
else if (!string.IsNullOrWhiteSpace(town.Small))
{
return new KeyValuePair<int, string>(2, "Small");
}
else if (!string.IsNullOrWhiteSpace(town.Big))
{
return new KeyValuePair<int, string>(1, "Big");
}
return new KeyValuePair<int, string>(-1, null);
}
private async Task<bool> CheckTownSpecialProperty(Town town)
{
// If no town is found then we cannot continue.
if (town is null)
{
throw new UserFriendlyException("Town not found. Cannot continue!");
}
var townLevel = this.GetTownLevel(town);
if(townLevel.Key == 1) // This is big town level, exit early. We are just testing against smaller towns.
{
return false;
}
// Find any big town of the same name also being tested.
// This is where the issue occurs.
// In debug mode all works well but when running freely, I get:
// "A second operation was started on this context before a previous operation completed."
var townList = await _townRepository.GetAll()
.Include(t => t.Tests)
.Include(t => t.Region.Select(s => s.SubRegion))
.Include(t => t.Province); // Eager load relations.
.Where(t => t.Id != town.Id
&& t.Region.Name == town.Region.Name
&& t.Name == town.Name
&& t.SubRegion.Name == town.SubRegion.Name
&& t.Tests.Any(a => a.TownId == t.Id))
.ToListAsync();
// If we have found a big town also being tested return true.
if (townList.Any(t => this.GetTownLevel(t).Key == 1))
{
return true;
}
return false;
}
CheckTownSpecialProperty()
synchronous. But this defeats the purpose of GetTowns being asynchronous to begin with, it was really just to let me know the funtion was working OK."The operation cannot be completed because the DbContext has been disposed."
"The operation cannot be completed because the DbContext has been disposed."
error is thrown. List<Town> townList = null;
using (var context = _abpContext.GetDbContext())
{
townList = await context.Towns
.Include(t => t.Tests)
.Include(t => t.Region.Select(s => s.SubRegion))
.Include(t => t.Province); // Eager load relations.
.Where(t => t.Id != town.Id
&& t.Region.Name == town.Region.Name
&& t.Name == town.Name
&& t.SubRegion.Name == town.SubRegion.Name
&& t.Tests.Any(a => a.TownId == t.Id))
.ToListAsync();
}
context.Configuration.LazyLoadingEnabled = false;
. I was hoping that fully materialising the objects would prevent the error, but this made no difference.AsNoTracking()
to the queries. This was a last try to see if I could fully materialise the objects and prevent the concurrency issues. Sadly it did not work.Upvotes: 1
Views: 203
Reputation: 456477
Your problem is in GetTowns
. Here's what that code is doing:
query
._townRepository
to get townList
.CheckTownSpecialProperty
.Task.WhenAll
.So CheckTownSpecialProperty
may be called concurrently, and all those concurrent executions use the same _townRepository
. This is not allowed.
To fix this, either:
query
more complex (e.g., doing a join) so that a second lookup isn't necessary.CheckTownSpecialProperty
, so that each of the concurrent executions have their own repository.CheckTownSpecialProperty
are run one at a time. They can still be asynchronous, but serial.Upvotes: 4