简体   繁体   中英

How to disable model caching in Entity Framework 6 (Code First approach)

Following MSDN documentation we can read:

The model for that context is then cached and is for all further instances of the context in the app domain. This caching can be disabled by setting the ModelCaching property on the given ModelBuidler , but note that this can seriously degrade performance.

The problem is the model builder does not contain any property named ModelCaching .

How it is possible to disable the model caching (eg for changing model configuration in a run-time)?

I have the same kind of issue: one db context, 2 or more different db models (different by table names, only)

My solution for EF6: One can still use the internal Entity Framework caching of db model but make a differentiation between DbModel(s) on the same DbContext by implementing IDbModelCacheKeyProvider Interface on derived DbContext.

MSDN doc is here: https://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.idbmodelcachekeyprovider(v=vs.113).aspx And it says:

Implement this interface on your context to use custom logic to calculate the key used to lookup an already created model in the cache. This interface allows you to have a single context type that can be used with different models in the same AppDomain, or multiple context types that use the same model.

Hope it helps someone.

Maybe old EF4 Docu still in EF6. see modelBuilder.CacheForContextType not available

See also the DbContext Constructor to pass in a model DbContext Constructor

I haven't tried it, but theoretically you can pass in a model each time.

Forward Warning: It goes without saying that the mechanism shown below will cover your needs as long as you don't need to perform joins between tables that come from different contexts. If you need such operations then you will have to further refine the mechanism shown below with a small API so that you can dynamically associate said tables with some string or number (so that you may access and combine their respective DBSets dynamically at will in runtime). Doing this sort of thing -altough more general- is a bit complex and falls outside the scope of this answer.

Here's a full-blown implementation of the mechanism put forward by bairog - all credit goes to him. Notice that we get the connection to the database via new DbContext for reasons explained in the comments:

     using System;
     using System.Collections.Concurrent;
     using System.ComponentModel.DataAnnotations;
     using System.Data.Common;
     using System.Data.Entity;
     using System.Data.Entity.Infrastructure;
     using System.Data.Entity.ModelConfiguration;

     namespace Utilities
     {
         // usage:
         //
         // var context1 = new FooContext("Schema1", "PingTable1", "PongTable1");
         // context1.Ping.Select(x => x.Id > 10).ToList();     
         // context1.Pong.Select(x => x.Id > 20).ToList();

         public class FooContext : DbContext
         {
             public DbSet<Ping> Ping { get; set; }
             public DbSet<Pong> Pong { get; set; }

             static public FooContext Spawn(string nameOrConnectionString, string pingTablename, string pongTablename, string schemaName = null) //minifactory
             {
                 //if (string.IsNullOrWhiteSpace(schemaName?.Trim())) throw new ArgumentException(nameof(schemaName)); //canbe
                 if (string.IsNullOrWhiteSpace(pingTablename?.Trim())) throw new ArgumentException(nameof(pingTablename));
                 if (string.IsNullOrWhiteSpace(pongTablename?.Trim())) throw new ArgumentException(nameof(pongTablename));

                 var dummyDbContext = new DbContext(nameOrConnectionString); //0 stupidhack for retrieving the connection

                 return new FooContext(dummyDbContext, GetModelBuilderAndCacheIt(dummyDbContext.Database.Connection, pingTablename, pongTablename, schemaName));
             }
             //0 stupidhack over EntityConnection("name=NameOfConnectionStringFromWebConfig") which wasnt working because it demands metadata on the
             //  codefirst connection to an oracle db (at least oracledb ver11 - go figure ...)
             //
             //  update: I finally had success using the *managed* driver oracle.manageddataaccess with oracle-odac ver12+    one may now use:
             //
             //  var connectionString = ConfigurationManager.ConnectionStrings[nameOrConnectionString];
             //  if (connectionString == null) return null;
             //  
             //  var factory = DbProviderFactories.GetFactory(connectionString.ProviderName);
             //  var connection = factory.CreateConnection();
             //  connection.ConnectionString = connectionString.ConnectionString; //vital
             //  
             //  new FooContext(dummyDbContext, GetModelBuilderAndCacheIt(connection, pingTablename, pongTablename, schemaName));

             private static readonly object DbCompiledModelRegistrarLocker = new object(); // ReSharper disable InconsistentlySynchronizedField
             private static readonly ConcurrentDictionary<Tuple<string, string, string>, DbCompiledModel> DbModelBuilderCache = new ConcurrentDictionary<Tuple<string, string, string>, DbCompiledModel>();

             static private DbCompiledModel GetModelBuilderAndCacheIt(DbConnection databaseConnection, string pingTablename, string pongTablename, string schemaName) //0
             {
                 var key = Tuple.Create(pingTablename, pongTablename, schemaName);
                 if (DbModelBuilderCache.ContainsKey(key))
                     return DbModelBuilderCache[key];

                 lock (DbCompiledModelRegistrarLocker)
                 {
                     if (DbModelBuilderCache.ContainsKey(key))
                         return DbModelBuilderCache[key];

                     var modelBuilder = new DbModelBuilder();
                     modelBuilder.Configurations.Add(new PingFluentConfiguration(schemaName, pingTablename));
                     modelBuilder.Configurations.Add(new PongFluentConfiguration(schemaName, pongTablename));

                     //setting a maxsize for the cache so that least used dbmodels get flushed away is left as an exercise to the reader
                     return DbModelBuilderCache[key] = modelBuilder.Build(databaseConnection).Compile();
                 }
             }

             //0 building the same model over and over is very expensive operation and this is why we resorted to caching the modelbuilders
             // ReSharper restore InconsistentlySynchronizedField

             private DbContext _dummyDbContext;

             private FooContext(DbContext dummyDbContext, DbCompiledModel compiledModel)
                 : base(dummyDbContext.Database.Connection, compiledModel, contextOwnsConnection: true)
             {
                 _dummyDbContext = dummyDbContext;

                 Database.SetInitializer<FooContext>(strategy: null); //0
             }

             //0 http://stackoverflow.com/a/39710954/863651   ef by default attempts to create the database if it doesnt exist
             //  however in this case we want ef to just do nothing if the underlying database doesnt exist

             //protected override void OnModelCreating(DbModelBuilder modelBuilder) //0 here be dragons   beware that this approach wont work as intended down the road
             //{
             //    modelBuilder.Configurations.Add(new PingFluentConfiguration(_schemaName, _tablename)); //0 here be dragons   beware that this approach wont work as intended down the road
             //    base.OnModelCreating(modelBuilder);
             //}

             protected override void Dispose(bool disposing)
             {
                 if (disposing)
                 {
                     _dummyDbContext?.Dispose();
                     _dummyDbContext = null;
                 }

                 base.Dispose(disposing);
             }
         }

         public sealed class PingFluentConfiguration : EntityTypeConfiguration<Ping>
         {
             public PingFluentConfiguration(string schemaName, string tableName)
             {
                 HasKey(t => t.Id);

                 ToTable(schemaName: schemaName, tableName: tableName);
             }
         }

         public sealed class PongFluentConfiguration : EntityTypeConfiguration<Pong>
         {
             public PongFluentConfiguration(string schemaName, string tableName)
             {
                 HasKey(t => t.Id);

                 ToTable(schemaName: schemaName, tableName: tableName);
             }
         }

         public class Ping
         {
             [Key]
             [Required]
             public string Id { get; set; }

             [Required]
             public string Name { get; set; }
         }

         public class Pong
         {
             [Key]
             [Required]
             public string Id { get; set; }

             [Required]
             public string Name { get; set; }
         }
     }

Here is similar question

The only available approach comes from the Program Manager of the Entity Framework team ( Rowan Miller (MSFT) ):

We removed CacheForContextType in CTP5, we originally intended it to be used when folks wanted to use the same context in the same AppDomain with different models. The issue is that it would create the model on every initialization and didn't allow any way to cache a series of models and choose which one to use during each initialization. Model creation is expensive so we wanted to promote a better pattern.

The pattern we recommend is to externally create a ModelBuilder -> DbDatabaseMapping -> DbModel for each model you want to use. The DbModel should be cached and used to create context instances. The ModelBuilder -> DbModel workflow is a little messy and the class names aren't great, they will be tidied up for RTM.

I've tried the following approach:

  1. Move all operations which were inside OnModelCreating event handler to a new function which creates DbModelBuilder (it's more likely you will pass DbConnection as a parameter to this function)
  2. Get DbModel via DbModelBuilder.Build(DbConnecton)
  3. Get DbCompiledModel via DbModel.Compile()
  4. Create new constructor for DbContext with parameters (DbConnection, DbCompileModel, bool) and pass previously created DbCompiledModel inside it

The result was that I can change parameters of DbCompiledModel every time I call DbContext constructor. That was all I needed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM