I'm registering my DbContext using Simple Injector on per web api request basis:
container.RegisterWebApiRequest<IModelContext, ModelContext>();
But also have a delegate filter created per web request as well:
container.RegisterAll<DelegatingHandler>(
...
typeof(DelegatingHandlerProxy<TraceRequestHandler>)
...
);
which saves some info about a request in the db:
public class TraceRequestHandler(ITraceRequestRepository r) : DelegateHandler
public class EntityTraceRequestRepository(IModelContext c) : ITraceRequestRepository
what causes all kinds of EF multi-threading exceptions:
The context cannot be used while the model is being created. This exception may be thrown if the context is used inside the OnModelCreating method or if the same context instance is accessed by multiple threads concurrently. Note that instance members of DbContext and related classes are not guaranteed to be thread safe.
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
The connection is not open.
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection
in a controller/repository such as:
public ProductController(IProductRepository r) : ApiController
public EntityProductRepository(IModelContext c) : IProductRepository
Once request tracing is removed, everything works fine.
How to register db context separately for 'general purpose' modules and for infrastructure modules?
The bug is in the code that you didn't show in your question, the InfrastructureDelegatingHandler
:
internal sealed class InfrastructureDelegatingHandler : DelegatingHandler {
private readonly IDbContext _dbContext;
public InfrastructureDelegatingHandler(IDbContext dbContext) {
_dbContext = dbContext;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken) {
Task.Factory.StartNew(() => TraceRequest(request));
return base.SendAsync(request, cancellationToken);
}
private void TraceRequest(HttpRequestMessage request) {
DbRawSqlQuery<string> query =
((EfDbContext)_dbContext).Database.SqlQuery<string>(
"select name from sys.indexes");
Console.WriteLine(String.Join(",", query.ToArray()));
}
}
The problem is in the following line:
Task.Factory.StartNew(() => TraceRequest(request));
In this line you start new task that calls TraceRequest. TraceRequest however uses the _dbContext
and you never await for that created task, which means that it will run in the background. In other words, your EfDbContext
is used from multiple thread simultaneously.
Your problem will disappear when you change the code to the following:
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken) {
await Task.Factory.StartNew(() => TraceRequest(request));
var message = await base.SendAsync(request, cancellationToken);
return message;
}