A second operation was started ... (But there is no parallel access) - c#

I have a userRepo which holds my users data. Over night I check if the display names of the users changed. In the task scheduler class I am only using the the userRepository context in the functions below and I quite don't understand how I get a second operation exception in the SaveChangesAsync() Function.
Am I missing somethings if the context is not called over the Web-API instead its called internally from my task scheduler class?
This is the repository class
public class UserRepository
{
private RTBMContext context;
public UserRepository(RTBMContext context)
{
this.context = context;
}
public async Task<TUser> Update(TUser entity)
{
if (entity == null)
{
return null;
}
var item = await context.TUsers.FindAsync(entity.PkUserNt);
this.context.Entry(item).CurrentValues.SetValues(entity);
await this.context.SaveChangesAsync(); //Crash !!!!!!!!!!!!!!!!!!!!!
return entity;
}
}
public partial class RTBMContext : DbContext
{
public RTBMContext(DbContextOptions<RTBMContext> options)
: base(options)
{
}
...
Startup class:
public void ConfigureServices(IServiceCollection services)
{
// Register DbContext
services.AddDbContextPool<Models.RTBMContext>(options =>
{
options.UseNpgsql(this.Configuration.GetConnectionString("DBConnection"));
});
// Repository registration
services.AddScoped<UserRepository>();
services.AddScoped<TaskScheduler>();
var serviceProvider = services.BuildServiceProvider();
TaskScheduler _siteService = serviceProvider.GetService<TaskScheduler>();
_siteService.Run();
}
And this is the scheduler that uses it
public class TaskScheduler : ControllerBase
{
private readonly UserRepository userRepository;
private readonly IServiceScopeFactory serviceScopeFactory;
public TaskScheduler(UserRepository userRepository, IServiceScopeFactory serviceScopeFactory)
{
this.userRepository = userRepository;
this.serviceScopeFactory = serviceScopeFactory;
}
public void Run() //run at startup for debugging purpose
{
Thread threadRunAt2_O_Clock = new Thread(delegate ()
{
this.UpdateAllUserDisplayNames().Wait();
});
threadRunAt2_O_Clock.IsBackground = true;
threadRunAt2_O_Clock.Start();
}
protected async Task<bool> UpdateAllUserDisplayNames()
{
List<TUser> allUsers = (await this.userRepository.GetAll()).ToList();
foreach (TUser user in allUsers)
{
string newDisplayName = this.GetDisplayName(user.PkUserNt);
if (newDisplayName != "" && newDisplayName != user.Name)
{
user.Name = newDisplayName;
await this.userRepository.Update(user);
}
}
return true;
}
}
Exception:
A second operation was started on this context before a previous
operation completed. This is usually caused by different threads
concurrently using the same instance of DbContext. For more
information on how to avoid threading issues with DbContext, see
https://go.microsoft.com/fwlink/?linkid=2097913.

Related

A second operation was started on this context instance before a previous operation completed .... concurrently using the same instance of DbContext

I got this error when I run unit test.
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. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
This is my code. Job2 is similar to Job1. The difference is just the model.
public class MyServices : IMyServices
{
private readonly MyContext _myContext;
public CallQEtlServices(MyContext myContext)
{
_myContext = myContext;
}
public async Task Process(CancellationToken cancellation)
{
await Task.WhenAll(Job1(cancellation),
Job2(cancellation));
}
public async Task Job1(CancellationToken cancellation)
{
// code removed for brevity
_myContext.RemoveRange(myContext.Students);
await _myContext.SaveChangesAsync(cancellation);
await _myContext.AddRangeAsync(Students, cancellation);
var result = await _myContext.SaveChangesAsync(cancellation);
}
}
In program.cs
builder.Services.AddDbContextFactory<MyContext>(options =>
options.UseSqlServer(myServicesConnStr),
ServiceLifetime.Transient);
and context
public class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(MyContext).Assembly);
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
return await base.SaveChangesAsync(cancellationToken);
}
public async Task ExecuteSqlRawAsync(string sql)
{
await base.Database.ExecuteSqlRawAsync(sql);
}
}

Dispose context instance error when trying to connect to my DB after Azure service bus message is consumed

I'm listening for an incoming Azure service bus message. Following the documentation and receiving the message, I parse the message body and then I want to connect to my DB to edit an entry and then save. But I'm getting this error below when trying to make the call
var ybEvent = await _unitOfWork.Repository<YogabandEvent>().GetEntityWithSpec(spec);
Error
Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\nObject name: 'DataContext'.
Here is the full service with the method that listens for and picks up incoming Azure messages.
Error is on the last line of MessageHandler()
FYI - If I remove the 'await' on the DB call, I still get the same error for a disposed context.
QUESTION - how do I fix this?
public class ServiceBusConsumer : IServiceBusConsumer
{
private readonly IConfiguration _config;
private readonly ServiceBusClient _queueClient;
private readonly ServiceBusProcessor _processor;
private readonly IUnitOfWork _unitOfWork;
private readonly IEventConsumer _eventConsumer;
public ServiceBusConsumer(IConfiguration config, IEventConsumer eventConsumer, IUnitOfWork unitOfWork)
{
_config = config;
_unitOfWork = unitOfWork;
_eventConsumer = eventConsumer;
_queueClient = new ServiceBusClient(_config["ServiceBus:Connection"]);
_processor = _queueClient.CreateProcessor(_config["ServiceBus:Queue"], new ServiceBusProcessorOptions());
}
public void RegisterOnMessageHandlerAndReceiveMessages() {
_processor.ProcessMessageAsync += MessageHandler;
_processor.ProcessErrorAsync += ErrorHandler;
_processor.StartProcessingAsync();
}
private async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
JObject jsonObject = JObject.Parse(body);
var eventStatus = (string)jsonObject["EventStatus"];
await args.CompleteMessageAsync(args.Message);
var spec = new YogabandEventWithMessageIdSpecification(args.Message.SequenceNumber);
// error here...
var ybEvent = await _unitOfWork.Repository<YogabandEvent>().GetEntityWithSpec(spec);
// do something then save
}
private Task ErrorHandler(ProcessErrorEventArgs args)
{
var error = args.Exception.ToString();
return Task.CompletedTask;
}
}
Here is my unit of work
public IGenericRepository<TEntity> Repository<TEntity>() where TEntity : class // : BaseEntity
{
if(_repositories == null)
_repositories = new Hashtable();
var type = typeof(TEntity).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(TEntity)), _context);
_repositories.Add(type, repositoryInstance);
}
return (IGenericRepository<TEntity>) _repositories[type];
}
I tried to call my generic repo directly inside the handler but that still fails with the dispose error.
Here is the call I changed in the handler, now I call the gen repo instead of the unit of work
var ybEvent = await _eventsRepo.GetEntityWithSpec(spec);
Here is GetEntityWIthSpec() from my generic repo
public async Task<T> GetEntityWithSpec(ISpecification<T> spec)
{
return await ApplySpecification(spec).FirstOrDefaultAsync();
}
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
{
return SpecificationEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), spec);
}
FYI - here is how I init my repo call
private readonly IGenericRepository<YogabandEvent> _eventsRepo;
then I inject it into the constructor
public ServiceBusConsumer(IConfiguration config, IEventConsumer eventConsumer, IUnitOfWork unitOfWork, IGenericRepository<YogabandEvent> eventsRepo)
{
_config = config;
_unitOfWork = unitOfWork;
_eventConsumer = eventConsumer;
_eventsRepo = eventsRepo;
_queueClient = new ServiceBusClient(_config["ServiceBus:Connection"]);
_processor = _queueClient.CreateProcessor(_config["ServiceBus:Queue"], new ServiceBusProcessorOptions());
}
Code that starts the ServiceBusConsumer it's in Main()
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
try
{
// do some work here
// https://stackoverflow.com/questions/48590579/cannot-resolve-scoped-service-from-root-provider-net-core-2
var bus = services.GetRequiredService<IServiceBusConsumer>();
bus.RegisterOnMessageHandlerAndReceiveMessages();
}
catch (Exception ex)
{
var logger = loggerFactory.CreateLogger<Program>();
logger.LogError(ex, "An error occured during migration");
}
}
host.Run();
}
Here is my unit of work
public class UnitOfWork : IUnitOfWork
{
private readonly DataContext _context;
private Hashtable _repositories;
public UnitOfWork(DataContext context)
{
_context = context;
}
public async Task<int> Complete()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
public IGenericRepository<TEntity> Repository<TEntity>() where TEntity : class // : BaseEntity
{
if(_repositories == null)
_repositories = new Hashtable();
var type = typeof(TEntity).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(TEntity)), _context);
_repositories.Add(type, repositoryInstance);
}
return (IGenericRepository<TEntity>) _repositories[type];
}
}
I cannot verify this but I had a similar situation where creating a response call to a service actually disposed my UoW and the datacontext and I faced the same error
I'm suspecting that the this call await args.CompleteMessageAsync(args.Message); is doing the disposing somewhere between the lines, (you can continue to trace here CompleteMessageAsync calls
this and a lot of disposing is going on)
to verify that, you can try to postpone that call till after you use the repository to save the changes.
// await args.CompleteMessageAsync(args.Message); <-- comment this line
var spec = new YogabandEventWithMessageIdSpecification(args.Message.SequenceNumber);
// error here...
var ybEvent = await _unitOfWork.Repository<YogabandEvent>().GetEntityWithSpec(spec);
// do something then save
await args.CompleteMessageAsync(args.Message); // <-- add it here
Remove this dispose from UnitOfWork:
public void Dispose()
{
_context.Dispose();
}
Simple rule: if you have not created object - do not dispose. It will be disposed automatically when scope diposed.
Also consider to remove this boilerplate. DbContext is already UoW, DbSet is already repository.

EF Core DbContext being disposed of before Async methods are complete

I am having issues where my DbContext is being disposed of early. It is only ever apparent when calling any of the *Async methods, such as ToListAsync() - if i call any of the syncronous methods everything is fine.
I can't figure out what I'm doing wrong.
Any advise please?
Here is as much of the code as i believe is needed.
The DbContext and its interface
public interface IMyDbContext
{
DbSet<MyModel> MyModels { get; set; }
}
public class MyDbContext : DbContext, IMyDbContext
{
public DbSet<MyModel> MyModels { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new MyModelConfig());
}
}
A Repository using this DbContext
public class MyModelRepository : IMyModelRepository
{
private readonly IMyDbContext _dbContext;
private string _baseSql = "Some SQL here ";
public MyModelRepository(IMyDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<IList<MyModel>> GetAllAsync(Paging paging, Permission permission)
{
if (permission == null)
throw new ArgumentNullException("permission");
string sql = ApplyFilter(_baseSql, permission);
try
{
// THIS FAILS
return await _dbContext.MyModels.FromSql(sql).Skip(paging.Skip).Take(paging.Take).ToListAsync();
// THIS FAILS
return await _dbContext.MyModels.FromSql(sql).ToListAsync();
// THIS WORKS
return await _dbContext.MyModels.FromSql(sql).ToList();
}
catch (Exception e)
{
throw new InvalidOperationException("Could not retrieve data", e);
}
}
}
I'm calling the repo via a service that looks like this:
public class GetAllMyModelQuery : IGetAllMyModelQuery
{
private readonly IMyModelRepository _myModelRepository;
private readonly IPermissionService _permissionService;
private readonly ILogger _logger;
public GetAllAbsenceQuery(IMyModelRepository myModelRepository, IPermissionService permissionService, ILogger<GetAllMyModelQuery> logger)
{
_myModelRepository = myModelRepository;
_permissionService = permissionService;
_logger = logger;
}
public async Task<IList<Emp_AbsenceEtrac>> Execute(Paging paging)
{
if (_permissionService.Permission == null)
{
_logger.LogInformation("No permission to the requested resource");
return null;
}
// if external?
// call external repo
//TODO//
// else
return await _myModelRepository.GetAllAsync(paging, _permissionService.Permission);
}
}
This in turn is called by the controller
public class MyModelController : Controller
{
private readonly IQueryStore _queryStore;
public MyModelController(IQueryStore queryStore)
{
_queryStore = queryStore;
}
[HttpGet]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(BadRequestObjectResult), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Index([FromQuery] int offset = 0, [FromQuery] int limit = 25)
{
Paging paging = new Paging(offset, limit);
return Ok(_queryStore.GetAllMyModelQuery.Execute(paging));
}
}
Finally, it's all wired together in the startup:
services.AddScoped<IMyDbContext, MyDbContext>();
services.AddScoped<IMyModelRepository, MyModelRepository>();
// Everything else above is also added as scope..
services.AddDbContext<MyDbContext>(opts =>
{
opts.UseSqlServer(Configuration.GetConnectionString("MyDb"),
sqlServerOptions =>
{
sqlServerOptions.CommandTimeout(600);
// required to allow skip/take on sql server 2008
sqlServerOptions.UseRowNumberForPaging(true);
});
});
Is there anything jumping out that would cause my Async calls to result in a closed Db connection?
Error is:
You should await the GetAllMyModelQuery.Execute method in your Index controller action:
[HttpGet]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(BadRequestObjectResult), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Index([FromQuery] int offset = 0, [FromQuery] int limit = 25)
{
Paging paging = new Paging(offset, limit);
return Ok(await _queryStore.GetAllMyModelQuery.Execute(paging).ConfigureAwait(false));
}

access dbcontext inside ExceptionFilter

I've written an ExceptionFilter attribute which in that i need to access dbContext class to do database affairs. but i receive null reference in my filter attribute.
Is there any way that i can get working reference of dbContext?
public class AppExceptionAttribute : ExceptionFilterAttribute
{
AppIdentityDbContext _context;
public AppExceptionAttribute(AppIdentityDbContext context)
{
_context = context;
}
public AppExceptionAttribute()
{ }
public override async Task OnExceptionAsync(ExceptionContext context)
{
var exception = context.Exception;
while (exception != null)
{
//here _context is null, that is a dbContext class
_context.Errors.Add(new Entities.Error {
Message = exception.Message,
StackTrace = exception.StackTrace,
Date = DateTime.Now
});
exception = exception.InnerException;
}
await _context.SaveChangesAsync();
}
}
i need to mention that is an asp.net core application
You can access the IServiceProvider from the ExceptionContext.
public override async Task OnExceptionAsync(ExceptionContext context)
{
var db = context.HttpContext.RequestServices.GetService<AppIdentityDbContext>();
...
await db.SaveChangesAsync();
}

asp.net core A second operation started on this context before a previous operation completed

I have an ASP.Net Core 2 Web application.
I'm trying to create a custom routing Middleware, so I can get the routes from a database.
In ConfigureServices() I have:
services.AddDbContext<DbContext>(options =>
options.UseMySQL(configuration.GetConnectionString("ConnectionClient")));
services.AddScoped<IServiceConfig, ServiceConfig>();
In Configure():
app.UseMvc(routes =>
{
routes.Routes.Add(new RouteCustom(routes.DefaultHandler);
routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}");
});
In the RouteCustom
public class RouteCustom : IRouteCustom
{
private readonly IRouter _innerRouter;
private IServiceConfig _serviceConfig;
public RouteCustom(IRouter innerRouter)
{
_innerRouter = innerRouter ?? throw new ArgumentNullException(nameof(innerRouter));
}
public async Task RouteAsync(RouteContext context)
{
_serviceConfig = context.HttpContext
.RequestServices.GetRequiredService<IServiceConfig>();
/// ...
// Operations inside _serviceConfig to get the route
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
_serviceConfig = context.HttpContext
.RequestServices.GetRequiredService<IServiceConfig>();
// ...
// Operations inside _serviceConfig to get the route
}
}
The IServiceConfig it is just a class where I access the database to get data, in this case the routes, but also other configuration data I need for the application.
public interface IServiceConfig
{
Config GetConfig();
List<RouteWeb> SelRoutesWeb();
}
public class ServiceConfig : IServiceConfig
{
private readonly IMemoryCache _memoryCache;
private readonly IUnitOfWork _unitOfWork;
private readonly IServiceTenant _serviceTenant;
public ServiceConfig(IMemoryCache memoryCache,
IUnitOfWork unitOfWork,
IServiceTenant serviceTenant)
{
_memoryCache = memoryCache;
_unitOfWork = unitOfWork;
_serviceTenant = serviceTenant;
}
public Config GetConfig()
{
var cacheConfigTenant = Names.CacheConfig + _serviceTenant.GetId();
var config = _memoryCache.Get<Config>(cacheConfigTenant);
if (config != null)
return config;
config = _unitOfWork.Config.Get();
_memoryCache.Set(cacheConfigTenant, config,
new MemoryCacheEntryOptions()
{
SlidingExpiration = Names.CacheExpiration
});
return config;
}
public List<RouteWeb> SelRoutesWeb()
{
var cacheRoutesWebTenant = Names.CacheRoutesWeb + _serviceTenant.GetId();
var routesWebList = _memoryCache.Get<List<RouteWeb>>(cacheRoutesWebTenant);
if (routesWebList != null)
return routesWebList;
routesWebList = _unitOfWork.PageWeb.SelRoutesWeb();
_memoryCache.Set(cacheRoutesWebTenant, routesWebList,
new MemoryCacheEntryOptions()
{
SlidingExpiration = Names.CacheExpiration
});
return routesWebList;
}
}
The problem is I'm getting this message when I test with multiple tabs opened and try to refresh all at the same time:
"A second operation started on this context before a previous operation completed"
I'm sure there is something I'm doing wrong, but I don't know what. It has to be a better way to access the db inside the custom route middleware or even a better way for doing this.
For example, on a regular Middleware (not the routing one) I can inject the dependencies to the Invoke function, but I can't inject dependencies here to the RouteAsync or the GetVirtualPath().
What can be happening here?
Thanks in advance.
UPDATE
These are the exceptions I'm getting.
An unhandled exception occurred while processing the request.
InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
And this one:
An unhandled exception occurred while processing the request.
MySqlException: There is already an open DataReader associated with this Connection which must be closed first.
This is the UnitOfWork
public interface IUnitOfWork : IDisposable
{
ICompanyRepository Company { get; }
IConfigRepository Config { get; }
// ...
void Complete();
}
public class UnitOfWork : IUnitOfWork
{
private readonly DbContext _context;
public UnitOfWork(DbContext context)
{
_context = context;
Company = new CompanyRepository(_context);
Config = new ConfigRepository(_context);
// ...
}
public ICompanyRepository Company { get; private set; }
public IConfigRepository Config { get; private set; }
// ...
public void Complete()
{
_context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
UPDATE 2
After reviewing the comments and making a lot of tests, the best clue I have is when I remove the CustomRoute line the problem disappear. Removing this line from Configure function on Startup.cs
routes.Routes.Add(new RouteCustom(routes.DefaultHandler));
Also I have tried removing, first the RouteAsync and then the GetVirtualPath() methods, but if one of those is present I get an error, so it is clear that the problem is in this CustomRoute class.
In the TenantMiddleware, which is called first for any request, I'm injecting the UnitOfWork and I have no problem. This Middleware is create in the Configure function:
app.UseMiddleware<TenantMiddleware>();
And inside, I'm injecting the UnitOfWork, and using it on every request, like this:
public async Task Invoke(HttpContext httpContext, IServiceTenant serviceTenant)
{
// ...performing DB operations to retrieve the tenent's data.
}
public class ServiceTenant : IServiceTenant
{
public ServiceTenant(IHttpContextAccessor contextAccessor,
IMemoryCache memoryCache,
IUnitOfWorkMaster unitOfWorkMaster)
{
_unitOfWorkMaster = unitOfWorkMaster;
}
// ...performing DB operations
}
SO, the problem with the CustomRoute is I can't inject the dependencies by adding to the Invoke function like this:
public async Task Invoke(HttpContext httpContext, IServiceTenant serviceTenant)
So I have to call the corresponding Service (Inside that service I inject the UnitOfWork and perform the DB operations) like this, and I think this can be the thing that is causing problems:
public async Task RouteAsync(RouteContext context)
{
_serviceConfig = context.HttpContext
.RequestServices.GetRequiredService<IServiceConfig>();
// ....
}
because this is the only way I know to "inject" the IServiceConfig into the RouteAsync and GetVirtualPath()...
Also, I'm doing that in every controller since I'm using a BaseCOntroller, so I decide which os the injection services I use...
public class BaseWebController : Controller
{
private readonly IMemoryCache _memoryCache;
private readonly IUnitOfWork _unitOfWork;
private readonly IUnitOfWorkMaster _unitOfWorkMaster;
private readonly IServiceConfig _serviceConfig;
private readonly IServiceFiles _serviceFiles;
private readonly IServiceFilesData _serviceFilesData;
private readonly IServiceTenant _serviceTenant;
public BaseWebController(IServiceProvider serviceProvider)
{
_memoryCache = serviceProvider.GetRequiredService<IMemoryCache>();
_unitOfWork = serviceProvider.GetRequiredService<IUnitOfWork>();
_unitOfWorkMaster = serviceProvider.GetRequiredService<IUnitOfWorkMaster>();
_serviceConfig = serviceProvider.GetRequiredService<IServiceConfig>();
_serviceFiles = serviceProvider.GetRequiredService<IServiceFiles>();
_serviceFilesData = serviceProvider.GetRequiredService<IServiceFilesData>();
_serviceTenant = serviceProvider.GetRequiredService<IServiceTenant>();
}
}
And then in every controller, instead of referencing all of the injected services, I can do it only for those I need, like this:
public class HomeController : BaseWebController
{
private readonly IUnitOfWork _unitOfWork;
public HomeController(IServiceProvider serviceProvider) : base(serviceProvider)
{
_unitOfWork = serviceProvider.GetRequiredService<IUnitOfWork>();
}
public IActionResult Index()
{
// ...
}
}
I don't know if this has something to do with my problem, but I'm just showing you what I think can be the problem, so you can have more information.
Thanks.
UPDATE 3
This is the code of the db to retrieve the routes:
public class PageWebRepository : Repository<PageWeb>, IPageWebRepository
{
public PageWebRepository(DbContext context) : base(context) { }
public List<RouteWeb> SelRoutesWeb()
{
return Context.PagesWebTrs
.Include(p => p.PageWeb)
.Where(p => p.PageWeb.Active)
.Select(p => new RouteWeb
{
PageWebId = p.PageWebId,
LanguageCode = p.LanguageCode,
Route = p.Route,
Regex = p.PageWeb.Regex.Replace("<route>", p.Route),
Params = p.PageWeb.Params,
Area = p.PageWeb.Area,
Controller = p.PageWeb.Controller,
Action = p.PageWeb.Action,
Type = p.PageWeb.Type,
Sidebar = p.PageWeb.Sidebar,
BannerIsScript = p.PageWeb.BannerIsScript,
Title = p.Title,
Description = p.Description,
Keywords = p.Keywords,
ScriptHead = p.ScriptHead,
ScriptBody = p.ScriptBody,
BannerScript = p.BannerScript,
BannerUrl = p.BannerUrl,
})
.ToList();
}
}
Where PagesWebTrs are the translations of the pages (multi language) and PagesWeb is the main table.
This issue is indeed within the route middleware.
Per definition, a middleware is a singleton, so a single instance handles all requests. This results into the instance state (the IServiceConfigwith hooked up DbContext) being accessed and changed by multiple simultaneous requests; it's a well disguished classical concurrency issue.
An example.
Request A executes RouteAsync, sets the _serviceConfig and executes a query on the DbContext. Nano seconds (or less :)) later, request B does the same. While request B's query is being executed, request A executes GetVirtualPath, but this time on the DbContext set by request B. This results in a second query being executed on the DbContext of request B which still has one running and you get the mentionned error.
The solution is to prevent shared state, by retrieving the IServiceConfig at the start of each method.
As you already said, getting such a dependency injected via the Invoke method does not work; the Invokemethod does not get executed.
Here below is the reworked RouteCustom.
public class RouteCustom : IRouteCustom
{
private readonly IRouter _innerRouter;
public RouteCustom(IRouter innerRouter)
{
_innerRouter = innerRouter ?? throw new ArgumentNullException(nameof(innerRouter));
}
public async Task RouteAsync(RouteContext context)
{
var serviceConfig = context.HttpContext.RequestServices.GetRequiredService<IServiceConfig>();
// ...
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
var serviceConfig = context.HttpContext.RequestServices.GetRequiredService<IServiceConfig>();
// ...
}
}

Categories