I have an ASP.NET 5 application which still uses EF6 and I register my services with Autofac as follows:
builder.RegisterType<UnitOfWork>()
.As(typeof(IUnitOfWork))
.InstancePerDependency();
builder.RegisterAssemblyTypes(assemblies)
.Where(t => t.FullName.StartsWith("MyApp") && t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerDependency();
Then in controllers I'm doing:
//Save object of type SomeObject
using (var scope = itemScope.BeginLifetimeScope())
{
var someService = itemScope.Resolve<ISomeService>();
var savedObject = await someService.SaveAsync(objectToSave);
}
The above runs in a loop in a background thread.
Originally I was not using child scopes. But then, using the diagnostic tools, I noticed that the SomeObject references are increasing and not being removed from the data context.
So I decided to add child scopes to have new instances every time. But this doesn't the problem. It stays as is.
If I'm using a child scope why does this happen, and how can I solve it?
You are still using itemScope to resolve the service. Note that scope was not used inside the using at all.
//Save object of type SomeObject
using (var scope = itemScope.BeginLifetimeScope())
{
//var someService = itemScope.Resolve<ISomeService>();
var someService = scope.Resolve<ISomeService>();
var savedObject = await someService.SaveAsync(objectToSave);
}
Related
In a previous question about how I visualize the graph of my dependencies I got the foundation for the code I now use to visualize my dependency graph as it is resolved by Autofac.
Running the code I get a tree that results in code like the following.
Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController (3851,7 ms. / 0,0 ms.) Depth: 0
Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController (3851,7 ms. / 0,4 ms.) Depth: 1
Usd.Utilities.WebApi.Controllers.UnikOwinContext (0,1 ms. / 0,0 ms.) Depth: 2
Usd.Utilities.WebApi.Controllers.UnikOwinContext (0,1 ms. / 0,0 ms.) Depth: 3
In the start I thought there was a problem with the code, and that it for some reason resulted in the components getting resolved multiple times. As Steven points out, this could happen when a component is registered as InstancePerDependency. But as several of my components are registered as InstancePerLifetime or SingleInstance dependencies, those dependencies shouldn't be resolved twice in the graph.
Steven does mention that "the first resolve of the InstancePerDependency dependency seems to have more dependencies than the next resolve, because this graph only shows resolves. Perhaps this is what's going on." But as I'm seeing InstancePerLifetime components being registered multiple times, on several occasions throughout the graph, I have the feeling that there's something else going on here.
What could be going on here?
How the dependencies are registered
The following code is the one we use to register our assemblies:
public static void RegisterAssemblies(this ContainerBuilder containerBuilder, IList<Assembly> assemblies, params Type[] typesToExclude)
{
if (containerBuilder != null && assemblies.Any())
{
var allTypes = assemblies.SelectMany(assembly => assembly.GetTypes()).Where(t => !typesToExclude.Any(t2 => t2.IsAssignableFrom(t))).ToList();
RegisterAllClassesWithoutAttribute(containerBuilder, allTypes);
RegisterClassesThatAreSingleton(containerBuilder, allTypes);
RegisterClassesThatAreInstancePerLifetimeScope(containerBuilder, allTypes);
RegisterGenericInterfaces(containerBuilder, allTypes);
RegisterRealOrTestImplementations(containerBuilder, allTypes);
RegisterAutofacModules(containerBuilder, allTypes);
containerBuilder.Register(c => UnikCallContextProvider.CurrentContext).As<IUnikCallContext>();
}
}
private static void RegisterAutofacModules(ContainerBuilder containerBuilder, List<Type> allTypes)
{
var modules = allTypes.Where(type => typeof(IModule).IsAssignableFrom(type) && type.GetCustomAttribute<DoNotRegisterInIocAttribute>() == null);
foreach (var module in modules)
{
containerBuilder.RegisterModule((IModule) Activator.CreateInstance(module));
}
}
private static void RegisterRealOrTestImplementations(ContainerBuilder containerBuilder, List<Type> allTypes)
{
if (StaticConfigurationHelper.UseRealImplementationsInsteadOfTestImplementations)
{
var realTypes = allTypes.Where(type => type.GetCustomAttribute<RealImplementationAsInstancePerLifetimeScopeAttribute>() != null).ToArray();
containerBuilder.RegisterTypes(realTypes).AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
else
{
var testTypes = allTypes.Where(type => type.GetCustomAttribute<TestImplementationAsInstancePerLifetimeScopeAttribute>() != null).ToArray();
containerBuilder.RegisterTypes(testTypes).AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
private static void RegisterGenericInterfaces(ContainerBuilder containerBuilder, List<Type> allTypes)
{
var typesAsGenericInterface = allTypes.Where(type => type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>() != null).ToArray();
foreach (var type in typesAsGenericInterface)
{
var attribute = type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>();
containerBuilder.RegisterGeneric(type).As(attribute.Type);
}
}
private static void RegisterClassesThatAreInstancePerLifetimeScope(ContainerBuilder containerBuilder, List<Type> allTypes)
{
var typesAsInstancePerDependency = allTypes.Where(type => type.GetCustomAttribute<InstancePerLifetimeScopeAttribute>() != null).ToArray();
containerBuilder.RegisterTypes(typesAsInstancePerDependency).InstancePerLifetimeScope().AsImplementedInterfaces();
}
private static void RegisterClassesThatAreSingleton(ContainerBuilder containerBuilder, List<Type> allTypes)
{
var typesAsSingleton = allTypes.Where(type => type.GetCustomAttribute<SingletonAttribute>() != null).ToArray();
containerBuilder.RegisterTypes(typesAsSingleton).SingleInstance().AsImplementedInterfaces();
}
private static void RegisterAllClassesWithoutAttribute(ContainerBuilder containerBuilder, List<Type> allTypes)
{
var types = allTypes.Where(type => !typeof(IModule).IsAssignableFrom(type) &&
type.GetCustomAttribute<DoNotRegisterInIocAttribute>() == null &&
type.GetCustomAttribute<SingletonAttribute>() == null &&
type.GetCustomAttribute<RealImplementationAsInstancePerLifetimeScopeAttribute>() == null &&
type.GetCustomAttribute<TestImplementationAsInstancePerLifetimeScopeAttribute>() == null &&
type.GetCustomAttribute<InstancePerLifetimeScopeAttribute>() == null &&
type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>() == null).ToArray();
containerBuilder.RegisterTypes(types).AsSelf().AsImplementedInterfaces();
}
Where the assemblies that are delivered to the RegisterAssemblies method could be fetched like this:
private List<Assembly> GetAssemblies()
{
var assemblies = AssemblyResolveHelper.LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory,
new Regex(#"Usd.EA.*\.dll"),
SearchOption.TopDirectoryOnly);
assemblies.AddRange(AssemblyResolveHelper.LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory,
new Regex(#"Usd.Utilities.*\.dll"),
SearchOption.TopDirectoryOnly));
assemblies.Add(GetType().Assembly);
return assemblies.Distinct().ToList();
}
The attributes
The attributes used in RegisterAllClassesWithoutAttribute are custom attributes that we manually assign to individual classes
using System;
[AttributeUsage(AttributeTargets.Class)]
public class DoNotRegisterInIocAttribute : Attribute
{
}
Used like this
[ExcludeFromCodeCoverage]
[DoNotRegisterInIoc]
public sealed class TestClass : ITestClass
When I'm not overwriting Autofacs MaxResolveDepth I get the following error
Failed An error occurred when trying to create a controller of type
'BogfoerController'. Make sure that the controller has a parameterless
public constructor. An exception was thrown while activating λ:Usd.EA
.Bogfoering.WebApi.Controllers.BogfoerController ->
Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController -> ......
Probable circular dependency between factory-scoped components. Chain
includes 'Activator = DomainWrapper (DelegateActivator), Services =
SomeService, Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime,
Sharing = None, Ownership = ExternallyOwned'
Short answer:
This is casused by the Autofac behaviour when resolving services from a child ILifetimeScope created by calling BeginLifetimeScope(Action<ContainerBuilder> configurationAction).
Long answer:
I have set up a simple test to prove above statement. I have generated a 51 test classes referencing themselves.
public class Test0
{
public Test0() { }
}
public class Test1
{
public Test1(Test0 test) { }
}
(...)
public class Test50
{
public Test50(Test49 test) { }
}
Registered them in a newly created container and tried to resolve the "Test50" class directly from the container. As you already found out. There is hard coded limit of 50 dependencies depth in the Autofac library, which you can see it on the GitHub page. After reaching this limit the DependencyResolutionException is thrown stating "Probable circular dependency between factory-scoped components." And this is exactly what happened in my first test.
Now you have asked, why are you seeing multiple registrations of the same dependencies. So here comes the fun part. When you are trying to resolve your instance, you are probably gonna use the BeginLifetimeScope function to create new ILifetimeScope. This would be still ok, unless you are going to add some new registrations to the child scope using one of the overloads. See example below:
using (var scope = container.BeginLifetimeScope(b => { }))
{
var test = scope.Resolve<Test49>();
}
I'm resolving only 50 dependencies (which have previously worked), but now, it yields an exception:
As you can see, this is exactly the same behaviour as you previously described. Each dependency is now showed 2 times. On that image, you can also see that the dependency graph has only reached the Test25 class. This has effectively reduced the previous max depth by a half (whole 25 dependencies!). We can test this by successuflly resolving Test24 class, but exception is thrown when trying to resolve the Test25. This goes even funnier, how do you think, what happens if we add another scope?
using (var scope1 = container.BeginLifetimeScope(b => { }))
{
using (var scope2 = scope1.BeginLifetimeScope(b => { }))
{
var test2 = scope2.Resolve<Test49>();
}
}
You probably guessed it, now you can only resolve the dependencies of depth 50 / 3 = ~16.
Conclusion: Creating nested scopes is limiting the actual available maximum depth of the dependencies graph N times, where the N is the depth of the scope. To be honest, scopes created without extending the container builder do not affect this number. In my opinion, this is a huge absurd, to have hard-coded magic number, which is nowhere in the documentation, cannot be easily configured, doesn't even represent the actual maximum depth and when overflowed, it throws misleading exception stating that you have circular dependencies in the graph somewhere.
Solutions: As a resolution to this issue you could not use this overload of this function. This could be not possible due to architecture limitations, or even the 3rd party framework which could be using the Autofac as DI container.
Another solution that you have already mentioned is overwriting the MaxResolveDepth using dirty reflection.
string circularDependencyDetectorTypeName = typeof(IContainer).AssemblyQualifiedName.Replace(typeof(IContainer).FullName, "Autofac.Core.Resolving.CircularDependencyDetector");
Type circularDependencyDetectorType = Type.GetType(circularDependencyDetectorTypeName);
FieldInfo maxResolveDepthField = circularDependencyDetectorType.GetField("MaxResolveDepth", BindingFlags.Static | BindingFlags.NonPublic);
maxResolveDepthField.SetValue(null, 500);
On the Autofac's GitHub you can also read that they are already planning to change the behaviour of the CircularDependencyDetector, so it could handle the infinite depth of dependencies, but those plans were mentioned in 2018 and they even couldn't change that exception message by this date.
I'm trying to add a test to an ASP.NET Core project where an object is created in one scope and then read in another scope. This is to simulate a user creating an object in one POST request and then reading it in another GET Request. However, I'm having trouble properly simulating this scenario.
I have this in my test code
SomeDbContext firstContext;
bool isSame;
using (var scope = someServiceProvider.CreateScope()) {
firstContext = someServiceProvider.GetService<SomeDbContext>();
}
using (var scope = someServiceProvider.CreateScope()) {
var secondContext = someServiceProvider.GetService<SomeDbContext>();
isSame = firstContext == secondContext; //should be false, right?
}
I expect isSame to have a value of false when the code above executes but it's actually true. Why is that? SomeDbContext has a lifetime of scoped when registering it with AddDbContext() so it should be destroyed when its scope is disposed and recreated in the second scope.
Your test is incorrect. Although you are creating two separate scopes, you're not actually using them. Here's a working version:
SomeDbContext firstContext;
bool isSame;
using (var scope = someServiceProvider.CreateScope()) {
firstContext = scope.ServiceProvider.GetService<SomeDbContext>();
}
using (var scope = someServiceProvider.CreateScope()) {
var secondContext = scope.ServiceProvider.GetService<SomeDbContext>();
isSame = firstContext == secondContext; //should be false, right?
}
Note how scope.ServiceProvider is used instead of someServiceProvider when resolving dependencies.
The closest thing I can find in the docs is Call services from main. Although the example shows the Main method, it does also demonstrate how the IServiceProvider that gets used comes from the scope itself.
I have a web forms application in the Global.asax of which I am buiding the Simple Injector container like below. The reason I am doing two is because I am using Hangfire to schedule recurring jobs and it does not take the Scoped lifestyle which I currently have for the application since it runs as a background worked thread. I am getting the below error when I am creating two instances of the container for my EF entities.
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects
Can someone please tell me how I can have two containers with different lifestyles registered in my web forms applictaion.
ContainerConfig.BuildContainer();
var container = ContainerConfig.BuildContainerJobs();
public static Container BuildContainer()
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
container.Register<TraceTimer>(Lifestyle.Scoped);
container.Register<Entities>(() => new Entities(), Lifestyle.Scoped);
container.Register<ReferenceDataCache>(
() => ReferenceDataCacheFactory.Create(), Lifestyle.Scoped);
var adapter = new SimpleInjectorAdapter(container);
ServiceLocator.SetLocatorProvider(() => (IServiceLocator)adapter);
ExecutionContextScopeManager.Current = (IExecutionContextScopeManager)adapter;
return container;
}
public static Container BuildContainerJobs()
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
container.Register<Entities>(() => new Entities(), Lifestyle.Transient);
container.Register<ReferenceDataCache>(
() => ReferenceDataCacheFactory.Create(), Lifestyle.Transient);
var adapter = new SimpleInjectorAdapter(container);
ServiceLocator.SetLocatorProvider(() => (IServiceLocator)adapter);
ExecutionContextScopeManager.Current = (IExecutionContextScopeManager)adapter;
return container;
}
Global.asax code for registering
ContainerConfig.BuildContainer();
var container = ContainerConfig.BuildContainerJobs();
var options = new SqlServerStorageOptions
{
QueuePollInterval = TimeSpan.FromMinutes(5) // Default value
};
GlobalConfiguration.Configuration
.UseSqlServerStorage("Jobs",options);
GlobalConfiguration.Configuration.UseDefaultActivator();
GlobalConfiguration.Configuration.UseActivator(new SimpleInjectorJobActivator(container));
GlobalJobFilters.Filters.Add(new SimpleInjectorAsyncScopeFilterAttribute(container));
JobsHelper.SetRecurringJob();
_backgroundJobServer = new BackgroundJobServer();
This exception is not thrown by Simple Injector but by Entity Framework. This exception is typically caused by using instances of entities that are created with one DbContext inside another DbContext.
Unfortunately I can't be more specific and pinpoint where you are going wrong and how to fix this, because your question does not contain the appropriate details.
I have the following class declaration and unit test:
public class Blah { }
[TestMethod]
public void TestMethod1()
{
var container = new Container();
var instance1 = container.GetInstance<Blah>();
var instance2 = container.GetInstance<Blah>();
var areBothInstancesSame = instance1 == instance2;
var nested = container.GetNestedContainer();
var nestedInstance1 = nested.GetInstance<Blah>();
var nestedInstance2 = nested.GetInstance<Blah>();
var areBothNestedInstancesSame = nestedInstance1 == nestedInstance2;
}
When I run this test, areBothInstancesSame is false but areBothNestedInstancesSame is true.
I also tested this inside a Web Api controller action:
public class Blah { }
public IHttpActionResult GetBlah()
{
var scope = this.Request.GetDependencyScope();
var instance1 = (Blah)scope.GetService(typeof(Blah));
var instance2 = (Blah)scope.GetService(typeof(Blah));
var areBothInstancesSame = instance1 == instance2;
return this.Ok();
}
And again, areBothInstancesSame is true.
I see this described in Structuremap's documentation, so I believe it's working as intended, but I don't understand why this is intended or how to get the nested container that Web Api automatically creates to return a new instance for each service with a Transient lifecycle.
Can anyone help me understand: 1) why this is the intended default behavior and how to make the nested container return a new instance every time; or 2) why it's obvious that I should never want the nested container to return a new instance every time?
Thanks
The best answer I can give is that the word nested refers rather to container's services and not necessarily to container hierarchy as it may seem (that is why child containers exist also)
Getting a service instance from a normal container will create a new instance along with the full object graph, with all required nested services inside. No mater how may times some transient service is nested inside the object graph, only one instance is created for that service type and reused within the entire graph.
For a nested container the transient instances behave as they belong to(are nested inside) the same object graph because it's purpose is to be used within one logical request.
maybe this example will help with the usage of nested containers http://structuremap.github.io/the-container/nested-containers/#sec5
basically nested containers exist to ensure transient services will not get a new instance with each GetService call.
To make a nested container return a new instance every time you should register the service as AlwaysUnique
Got a small confusion here.
I'm not sure if I am handling my DbContext throughout the WebApi properly.
I do have some controllers that do some operations on my DB (Inserts/Updates with EF) and after doing these actions I do trigger an event.
In my EventArgs (I have a custom class which inherits from EventArgs) I pass my DbContext and I use it in the event handler to log these operations (basically I just log authenticated user API requests).
In the event handler when I am trying to commit my changes (await SaveChangesAsync) I get an error : "Using a disposed object...etc" basically noticing me that at the first time I use await in my async void (fire and forget) I notify the caller to dispose the Dbcontext object.
Not using async works and the only workaround that I've mangaged to put out is by creating another instance of DbContext by getting the SQLConnectionString of the EventArgs passed DbContext.
Before posting I did made a small research based on my issue
Entity Framework disposing with async controllers in Web api/MVC
This is how I pass parameters to my OnRequestCompletedEvent
OnRequestCompleted(dbContext: dbContext,requestJson: JsonConvert.SerializeObject);
This is the OnRequestCompleted() declaration
protected virtual void OnRequestCompleted(int typeOfQuery,PartnerFiscalNumberContext dbContext,string requestJson,string appId)
{
RequestCompleted?.Invoke(this,new MiningResultEventArgs()
{
TypeOfQuery = typeOfQuery,
DbContext = dbContext,
RequestJson = requestJson,
AppId = appId
});
}
And this is how I process and use my dbContext
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = miningResultEventArgs.DbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery).Result;
var apiUserRequester = miningResultEventArgs.DbContext.ApiUsers.FirstAsync(x => x.AppId == appId).Result;
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
miningResultEventArgs.DbContext.ApiUserRequests.Add(apiRequest);
await miningResultEventArgs.DbContext.SaveChangesAsync();
By using SaveChanges instead of SaveChangesAsync everything works.
My only idea is to create another dbContext by passing the previous DbContext's SQL connection string
var dbOptions = new DbContextOptionsBuilder<PartnerFiscalNumberContext>();
dbOptions.UseSqlServer(miningResultEventArgs.DbContext.Database.GetDbConnection().ConnectionString);
using (var dbContext = new PartnerFiscalNumberContext(dbOptions.Options))
{
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
dbContext.ApiUserRequests.Add(apiRequest);
await dbContext.SaveChangesAsync();
}
The latter code excerpt is just a small test to check my supposition, basically I should pass the SQL connection string instead of the DbContext object.
I am not sure (in terms of best practice) if I should pass a connection string and create a new dbContext object (and dispose it by using a using clause) or if I should use/have another mindset for this issue.
From what I know, using a DbContext should be done for a limited set of operations and not for multiple purposes.
EDIT 01
I'm going to detail more thorough what I've been doing down below.
I think I got an idea of why this error happens.
I have 2 controllers
One that receives a JSON and after de-serializing it I return a JSON to the caller and another controller that gets a JSON that encapsulates a list of objects that I iterate in an async way, returning an Ok() status.
The controllers are declared as async Task<IActionResult> and both feature an async execution of 2 similar methods.
The first one that returns a JSON executes this method
await ProcessFiscalNo(requestFiscalView.FiscalNo, dbContext);
The second one (the one that triggers this error)
foreach (string t in requestFiscalBulkView.FiscalNoList)
await ProcessFiscalNo(t, dbContext);
Both methods (the ones defined previously) start an event OnOperationComplete()
Within that method I execute the code from my post's beginning.
Within the ProcessFiscalNo method I DO NOT use any using contexts nor do I dispose the dbContext variable.
Within this method I only commit 2 major actions either updating an existing sql row or inserting it.
For edit contexts I select the row and tag the row with the modified label by doing this
dbContext.Entry(partnerFiscalNumber).State = EntityState.Modified;
or by inserting the row
dbContext.FiscalNumbers.Add(partnerFiscalNumber);
and finally I execute an await dbContext.SaveChangesAsync();
The error always gets triggered within the EventHandler ( the one detailed # the beginning of the thread) during the await dbContext.SaveChangedAsync()
which is pretty weird since 2 lines before that I do await reads on my DB with EF.
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
dbContext.ApiUserRequests.Add(new ApiUserRequest() { ApiUser = apiUserRequester, RequestJson = requestJson, RequestType = requestType });
//this throws the error
await dbContext.SaveChangesAsync();
For some reason calling await within the Event Handler notifies the caller to dispose the DbContext object.
Also by re-creating the DbContext and not re-using the old one I see a huge improvement on access.
Somehow when I use the first controller and return the info the DbContext object appears to get flagged by the CLR for disposal but for some unknown reason it still functions.
EDIT 02
Sorry for the bulk-ish content that follows, but I've placed all of the areas where I do use dbContext.
This is how I'm propagating my dbContext to all my controllers that request it.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMemoryCache();
// Add framework services.
services.AddOptions();
var connection = #"Server=.;Database=CrawlerSbDb;Trusted_Connection=True;";
services.AddDbContext<PartnerFiscalNumberContext>(options => options.UseSqlServer(connection));
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("PowerUser",
policy => policy.Requirements.Add(new UserRequirement(isPowerUser: true)));
});
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthorizationHandler, UserTypeHandler>();
}
In Configure I'm using the dbContext for my custom MiddleWare
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
var context = app.ApplicationServices.GetService<PartnerFiscalNumberContext>();
app.UseHmacAuthentication(new HmacOptions(),context);
app.UseMvc();
}
In the custom MiddleWare I'm only using it for a query.
public HmacHandler(IHttpContextAccessor httpContextAccessor, IMemoryCache memoryCache, PartnerFiscalNumberContext partnerFiscalNumberContext)
{
_httpContextAccessor = httpContextAccessor;
_memoryCache = memoryCache;
_partnerFiscalNumberContext = partnerFiscalNumberContext;
AllowedApps.AddRange(
_partnerFiscalNumberContext.ApiUsers
.Where(x => x.Blocked == false)
.Where(x => !AllowedApps.ContainsKey(x.AppId))
.Select(x => new KeyValuePair<string, string>(x.AppId, x.ApiHash)));
}
In my controller's CTOR I'm passing the dbContext
public FiscalNumberController(PartnerFiscalNumberContext partnerContext)
{
_partnerContext = partnerContext;
}
This is my Post
[HttpPost]
[Produces("application/json", Type = typeof(PartnerFiscalNumber))]
[Consumes("application/json")]
public async Task<IActionResult> Post([FromBody]RequestFiscalView value)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var partnerFiscalNo = await _fiscalNoProcessor.ProcessFiscalNoSingle(value, _partnerContext);
}
Within the ProcessFiscalNoSingle method I have the following usage, If that partner exists then I'll grab him, if not, create and return him.
internal async Task<PartnerFiscalNumber> ProcessFiscalNoSingle(RequestFiscalView requestFiscalView, PartnerFiscalNumberContext dbContext)
{
var queriedFiscalNumber = await dbContext.FiscalNumbers.FirstOrDefaultAsync(x => x.FiscalNo == requestFiscalView.FiscalNo && requestFiscalView.ForceRefresh == false) ??
await ProcessFiscalNo(requestFiscalView.FiscalNo, dbContext, TypeOfQuery.Single);
OnRequestCompleted(typeOfQuery: (int)TypeOfQuery.Single, dbContextConnString: dbContext.Database.GetDbConnection().ConnectionString, requestJson: JsonConvert.SerializeObject(requestFiscalView), appId: requestFiscalView.RequesterAppId);
return queriedFiscalNumber;
}
Further down in the code, there's the ProcessFiscalNo method where I use the dbContext
var existingItem =
dbContext.FiscalNumbers.FirstOrDefault(x => x.FiscalNo == partnerFiscalNumber.FiscalNo);
if (existingItem != null)
{
var existingGuid = existingItem.Id;
partnerFiscalNumber = existingItem;
partnerFiscalNumber.Id = existingGuid;
partnerFiscalNumber.ChangeDate = DateTime.Now;
dbContext.Entry(partnerFiscalNumber).State = EntityState.Modified;
}
else
dbContext.FiscalNumbers.Add(partnerFiscalNumber);
//this gets always executed at the end of this method
await dbContext.SaveChangesAsync();
Also I've got an Event called OnRequestCompleted() where I pass my actual dbContext (after it ends up with SaveChangesAsync() if I update/create it)
The way I initiate the event args.
RequestCompleted?.Invoke(this, new MiningResultEventArgs()
{
TypeOfQuery = typeOfQuery,
DbContextConnStr = dbContextConnString,
RequestJson = requestJson,
AppId = appId
});
This is the notifier class (where the error occurs)
internal class RequestNotifier : ISbMineCompletionNotify
{
public async void UploadRequestStatus(object source, MiningResultEventArgs miningResultArgs)
{
await RequestUploader(miningResultArgs);
}
/// <summary>
/// API Request Results to DB
/// </summary>
/// <param name="miningResultEventArgs">EventArgs type of a class that contains requester info (check MiningResultEventArgs class)</param>
/// <returns></returns>
private async Task RequestUploader(MiningResultEventArgs miningResultEventArgs)
{
//ToDo - fix the following bug : Not being able to re-use the initial DbContext (that's being used in the pipeline middleware and controller area),
//ToDo - basically I am forced by the bug to re-create the DbContext object
var dbOptions = new DbContextOptionsBuilder<PartnerFiscalNumberContext>();
dbOptions.UseSqlServer(miningResultEventArgs.DbContextConnStr);
using (var dbContext = new PartnerFiscalNumberContext(dbOptions.Options))
{
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
dbContext.ApiUserRequests.Add(apiRequest);
await dbContext.SaveChangesAsync();
}
}
}
Somehow when the dbContext reaches the Event Handler CLR gets notified to dispose the dbContext object (because I'm using await?)
Without recreating the object I was having huge lag when I wanted to use it.
While writing this I have an idea, I did upgrade my solution to 1.1.0 and I'm gonna try to see if it behaves similarly.
Concerning Why you get the error
As pointed out at the Comments by #set-fu DbContext is not thread safe.
In addition to that, since there is no explicit lifetime management of your DbContext your DbContext is going to get disposed when the garbage collector sees fit.
Judging from your context, and your mention about Request scoped DbContext
I suppose you DI your DbContext in your controller's constructor.
And since your DbContext is request scoped it is going to be disposed as soon as your Request is over,
BUT since you have already fired and forgot your OnRequestCompleted events there is no guarantee that your DbContext won't be disposed.
From there on , the fact that one of our methods succeeds and the other fails i think is seer "Luck".
One method might be faster than the other and completes before the Garbage collector disposes the DbContext.
What you can do about this is to change the return type of your Events from
async void
To
async Task<T>
This way you can wait your RequestCompleted Task within your controller to finish and that will guarantee you that your Controller/DbContext will not get Disposed until your RequestCompleted task is finished.
Concerning Properly handling DbContexts
There are two contradicting recommendations here by microsoft and many people use DbContexts in a completely divergent manner.
One recommendation is to "Dispose DbContexts as soon as posible"
because having a DbContext Alive occupies valuable resources like db
connections etc....
The other states that One DbContext per request is highly
reccomended
Those contradict to each other because if your Request is doing a lot of unrelated to the Db stuff , then your DbContext is kept for no reason.
Thus it is waste to keep your DbContext alive while your request is just waiting for random stuff to get done...
So many people who follow rule 1 have their DbContexts inside their "Repository pattern" and create a new Instance per Database Query
public User GetUser(int id)
{
User usr = null;
using (Context db = new Context())
{
usr = db.Users.Find(id);
}
return usr;
}
They just get their data and dispose the context ASAP.
This is considered by MANY people an acceptable practice.
While this has the benefits of occupying your db resources for the minimum time it clearly sacrifices all the UnitOfWork and "Caching" candy EF has to offer.
So Microsoft's recommendation about using 1 Db Context per request it's clearly based on the fact that your UnitOfWork is scoped within 1 request.
But in many cases and i believe your case also this is not true.
I consider Logging a separate UnitOfWork thus having a new DbContext for your Post-Request Logging is completely acceptable (And that's the practice i also use).
An Example from my project i have 3 DbContexts in 1 Request for 3 Units Of Work.
Do Work
Write Logs
Send Emails to administrators.