Search code examples
schemaentity-framework-core

How to implement IModelCacheKeyFactory in EF Core


The story: in our multi-tenant app (one PostgreSql db, multiple schemas) we need to use one DbContext against multiple schemas.

What I tried: holding a cache (Dictionary, where key is schema name, value is the context for that schema). When instatiating new context for another schema I can see that dbContext schema is still set to previous schema provided. I assume the model in context is cached internally by context type, so that is the reason I see this behavior?

So above doesn't seem to work and I found that implementing IModelCacheKeyFactory should do the trick. Does anyone know what should go into Create method though? There are no samples nor documentation anywhere.

What I found: Dynamically changing schema in Entity Framework Core but it is for EF6, so not much help.


Solution

  • Here is an example.

    Derived DbContext that replaces it's ModelCacheKey (and factory) with a Custom one.

    class MyDbContext : DbContext
    {
        public MyDbContext(string schema)
        {
            Schema = schema;
        }
    
        public string Schema { get; }
    
        protected override void OnConfiguring(DbContextOptionsBuilder options)
            => options
                .UseSqlServer("...")
                .ReplaceService<IModelCacheKeyFactory, MyModelCacheKeyFactory>();
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.HasDefaultSchema(Schema);
    
            // ...
        }
    }
    

    The factory that creates the Context with a specific key.

    class MyModelCacheKeyFactory : IModelCacheKeyFactory
    {
        public object Create(DbContext context)
            => new MyModelCacheKey(context);
    }
    

    The custom ModelCacheKey per context.

    class MyModelCacheKey : ModelCacheKey
    {
        string _schema;
    
        public MyModelCacheKey(DbContext context)
            : base(context)
        {
            _schema = (context as MyDbContext)?.Schema;
        }
    
        protected override bool Equals(ModelCacheKey other)
            => base.Equals(other)
                && (other as MyModelCacheKey)?._schema == _schema;
    
        public override int GetHashCode()
        {
            var hashCode = base.GetHashCode() * 397;
            if (_schema != null)
            {
                hashCode ^= _schema.GetHashCode();
            }
    
            return hashCode;
        }
    }