Implementation of ModelCacheKey in EF Core
The official documentation of Microsoft says, and I quote, ” It is a key that uniquely identifies the model for a given context. This is used to store and lookup a cached model for a given context. “
This means that every DBContext, has a ModelCacheKey attached to it that is cached. In the world of EF Core Global Query Filters this really good, because once a session starts with a number of filters it is just business as usual and all the object are filtered. But what happens if you want to switch between the filters in the same session? Well, here comes the tricky part because you will have to find a way to invalidate that cache, so the model will be regenerated. Also, you must not to this every time something happens in your app because you will have a huge decrease in performance.
So, what if we can attach the TenantId and IsDeleted, in a hashed manner, to the CacheKey so the app can detect automatically that something changed and regenerate the model? And that is what we did in 4 steps :
1. Add the .ReplaceService method to your options builder:
protected void OnConfiguringFinal(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(connectionString).ReplaceService<IModelCacheKeyFactory, MasterModelCacheKeyFactory>(); }
2. Add a new variable in your extension of the DBContext:
public int hashCode;
3. Create two new classes in which you verify if something changed in your DBContext cache ( checking the hashCode does the trick ):
class MasterModelCacheKeyFactory : IModelCacheKeyFactory { public object Create(DbContext context) => new MasterModelCacheKey(context); } class MasterModelCacheKey : ModelCacheKey { int _hashcode; public MasterModelCacheKey(DbContext context) : base(context) { _hashcode = (context as MasterContext).hashCode; } protected override bool Equals(ModelCacheKey other) => base.Equals(other) && (other as MasterModelCacheKey)?._hashcode == _hashcode; //force to regerate this if there are different tenants public override int GetHashCode() { //get the hashCode that is attached right now to the context var hashCode = base.GetHashCode(); //if what is in the base.GetHashCode() it's the same with what is in context that means there are the same tenants & soft delete option and we are not regenerating the model if (_hashcode != hashCode) { hashCode = _hashcode; } return hashCode; } }
4. Generate the value of the hashCode, at the run time, whenever you see fitted ( we are doing it in the GET method ) with something that looks like this:
hashCode = 0; foreach (var userTenantClaim in userTenantClaims) { hashCode += userTenantClaim.Value.GetHashCode(); }
By all means, this is not an easy exercise so we will just recap it a little bit :
- Modify OnConfiguringFinal
- Add the hashCode field in the DBContext extension class
- Add 2 new classes that check if your current hashCode is the same with the cached one
- Get the new hashCode every time you do a call, to make sure you are in the right EF Core Global Query Filters scenario.
We used this in a Multitenant implementation scenario, but this can be extrapolated in any project you need to regenerate the DBContext for various reasons.
Chapter 5 : Implementation of the Global Query Filters, Multitenant and Soft Delete in EF Core