I am getting this error:
System.InvalidOperationException: "A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext".
This is my code in service:
public async Task<IQueryable<GetAllUsersDTO>> UsersById(Guid userId, CancellationToken cancellationToken)
{
try
{
var Log = await _db.Users
.Include(x => x.University)
.Include(x => x.Fields)
.Where(x => x.UserId == userId)
.OrderByDescending(x => x.CreatedAt)
.AsNoTracking()
.ToListAsync();
var logsDTO = await Task.WhenAll(Log.Select(async x =>
{
var userInDb = await _db.Users.FindAsync((Guid)x.UserId);
var user = await _userService.GetDirectoryInformation(userInDb, cancellationToken);
return new GetAllUsersDTO()
{
Description = x.Description,
PostDate = x.PostDate,
Id = x.Id,
RecieveUserId = x.RecieveUserId,
UniversityId = x.UniversityId,
IsVisible = x.IsVisible,
IsApproved = x.IsApproved,
UserIdOpen = x.UserId,
UserName = user.UserName,
LogTitle = x.LogTitle,
LogNr = x.LogNr,
Email = user.Email,
};
}));
return logsDTO.AsQueryable();
}
catch (Exception ex)
{
Console.WriteLine("Error=", ex.Message);
throw;
}
}
This is how I'm injecting DataContext
in this service:
private readonly DataContext _db;
public UserService(DataContext context) : base(context)
{
_db = context;
}
Note: This service only works when Log list returns only one data. But when it returns more than one data, then it triggers the error.
I have tried configuring my dependency injection setup with ServiceLifetime.Scoped
, and it looks like this:
builder.Services
.AddDbContext<DataContext>(opt =>
opt.UseSqlServer(builder.Configuration.GetConnectionString("connString"))
,ServiceLifetime.Scoped
);
Note: This service only works when Log list returns only one data. But when it returns more than one data, then it triggers the error.
_db.Users.FindAsync
and potentially GetDirectoryInformation
(it seems that it can use/uses database too) are the problem, the following:
var logsDTO = await Task.WhenAll(Log.Select(async x =>
{
var userInDb = await _db.Users.FindAsync((Guid)x.UserId);
var user = await _userService.GetDirectoryInformation(userInDb, cancellationToken);
// ...
}
Will schedule to execute as many tasks as there are logs in parallel. And EF Core database context is not supposed to be used from different threads simultaneously. That's it.
"Direct" workarounds:
var Log = await _db.Users ...
) and remove usage of _userService.GetDirectoryInformation
and the Task.WhenAll(Log.Select
completely (I would argue that it the best course of action)_userService
thereAlso note, since you gathering logs for single user there is completely no reason to query for it in multiple threads. Even if for some reason you don't want to (or can't) just use Select
to build the result, i.e.:
await _db.Users
.Where(x => x.UserId == userId)
.OrderByDescending(x => x.CreatedAt)
.Select(x => new GetAllUsersDTO
{
Description = x.Description,
// ...
UserName = x.User.UserName,
Email = x.User.Email,
})
.ToListAsync();
Then just fetch the data one time and that's it:
var Log = ...; // your original query
var userInDb = await _db.Users.FindAsync(userId);
var user = await _userService.GetDirectoryInformation(userInDb, cancellationToken);
var result = Logs
.Select(x => new GetAllUsersDTO {...}) // use user, no async/awaits in the select
// ...