( Related Posting: Why is DBContext is disposed after putting it in IMemoryCache (.NET Core / EF Core) )
( Related Posting: Cannot access a disposed object with MemoryCache )
Here is info about our technical development environment :
Microsoft Visual Studio Enterprise 2019
.NET Core 3.1
DevExpress v21.2.6 platform
PostgreSQL 14.2, compiled by Visual C++ build 1914, 64-bit
Microsoft.AspNet.Providers.Core 2.0.0
Microsoft.EntityFrameworkCore 5.0.15
Microsoft.EntityFrameworkCore.Design 5.0.15
Microsoft.EntityFrameworkCore.Tools 5.0.15
Microsoft.OpenApi 1.2.3
Npgsql 5.0.12
Npgsql.EntityFrameworkCore.PostgreSQL 5.0.10
Swashbuckle.AspNetCore 6.3.0
Here is a UML Class diagram showing the relationship between the relevant classes:
In our Startup.cs’s public void ConfigureServices(IServiceCollection servicesCollection), we have the following “Dependency injected” configurations which is mostly configured to be Transient:
servicesCollection.AddMemoryCache();
servicesCollection.AddDbContext<DatabaseContext>(ServiceLifetime.Transient);
// Register the service and implementation for the database context
servicesCollection.AddTransient<IDatabaseContext, DatabaseContext>();
servicesCollection.AddTransient<IDataAccessLayer, DataAccessLayer>();
servicesCollection.AddScoped<IFaxActivityProcessor, FaxActivityProcessor>();
servicesCollection.AddScoped<IFaxService, FaxtService>();
In the FaxService.cs class, we have the AddToCache method that adds data to the MemoryCache and a ProcessMessagesCallBack which is the callback method that is registered as the PostEviction Callback in our MemoryCache:
private void AddToCache(IList<string> messagesIdsCollection)
{
this._logger.LogInformation("AddToCache ");
var expirationToken =
new CancellationChangeToken(
new CancellationTokenSource(TimeSpan.FromMilliseconds(Int32.Parse(this._apiConfigSettings.MemoryCacheWait)
+ 10)).Token);
var entry = new MemoryCacheEntryOptions();
entry.SetAbsoluteExpiration(TimeSpan.FromMilliseconds(Int32.Parse(this._apiConfigSettings.MemoryCacheWait)));
entry.AddExpirationToken(expirationToken);
entry.RegisterPostEvictionCallback(ProcessMessagesCallBack);
this._memoryCache.Set("messageIdsCollection", messagesIdsCollection, entry);
}
protected void ProcessMessagesCallBack(object key, object value, EvictionReason reason, object state)
{
// Invoke other new class from here:
IList<string> messagesIdsBatch = (IList<string>)value;
string hostName = Dns.GetHostName();
IList<string> estrangedMessages = null;
this._logger.LogInformation("Processing messageIds List.... : " + String.Join(",", messagesIdsBatch));
estrangedMessages = this._faxActivityProcessor.ProcessMessages(messagesIdsBatch);
}
Ultimately, the ProcessMessagesCallBack callback method will invoke FaxActivityProcessor.cs class’s ProcessMessages method:
public IList<string> ProcessMessages(IList<string> messagesIdsColl)
{
HashSet<string> noDupesMessagesIdsHashSet = new HashSet<string>(messagesIdsColl);
IList<string> noDupesMessagesIds = new IList<string>(noDupesMessagesIdsHashSet.ToList());
IList<EmailEvent> reunifyingChildEmailEventsCollection = new List<EmailEvent>();
IList<string> estrangedMessagesIds = null;
try
{
IDictionary<string, long> messagesIdsEmailRecordsIdsDictionary = this._dataAccessLayer.GetMessagesIdsEmailRecordsIdsDictionary(noDupesMessagesIds);
estrangedMessagesIds = this._dataAccessLayer.ReconcileMisplaced(messagesIdsEmailRecordsIdsDictionary, messagesIdsBatch);
}
catch (Exception ex)
{
this._logger.Log(ex.StackTrace);
}
return estrangedMessagesIds;
}
Correct me if I’m wrong, but the MemoryCache’s configured callback method runs on its own thread that is different from the Main thread of the application. Therefore, DataAccessLayer’s DatabaseContext (i.e Entity Framework’s DbContext ) gets disposed because it’s No longer in the Main thread.
So, when I run the aforementioned code, the ProcessMessages method throws an error when this._dataAccessLayer.ReconcileMisplaced method is invoked by throwing:
{"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.\r\nObject name: 'DatabaseContext'."}
However, in our Startup.cs’s ConfigureServices, most of the classes were “dependency injected” as Transients so I was expecting that the callback method’s new Thread would get a new DatabaseContext instance. But, it’s strange that the application tries to access a disposed context instance.
Could someone please tell me what changes/additions I have to make to the aforementioned code that will ensure that the “Cannot access a disposed context instance” error stops?