Как отключить кэширование модели в Entity Framework 6 (подход Code First)

Следуя документации MSDN, мы можем прочитать:

Модель для этого контекста затем кэшируется и предназначена для всех остальных экземпляров контекста в домене приложения. Это кэширование можно отключить, установив свойство ModelCaching на данный ModelBuidler, но обратите внимание, что это может серьезно ухудшить производительность.

Проблема заключается в том, что модельный конструктор не содержит никакого свойства с именем ModelCaching.

Как можно отключить кэширование модели (например, для изменения конфигурации модели во время выполнения)?

Ответ 1

Может быть, старый EF4 Docu еще в EF6. см. modelBuilder.CacheForContextType недоступен

См. также конструктор DbContext для передачи в модели DbContext Constructor

Я не пробовал, но теоретически вы можете проходить в модели каждый раз.

Ответ 2

Предупреждение о переходе: само собой разумеется, что приведенный ниже механизм будет охватывать ваши потребности, если вам не нужно выполнять объединения между таблицами, которые поступают из разных контекстов. Если вам нужны такие операции, вам придется дополнительно уточнить механизм, показанный ниже, с помощью небольшого API, чтобы вы могли динамически связывать указанные таблицы с некоторой строкой или номером (чтобы вы могли динамически получать и комбинировать свои соответствующие DBS файлы во время выполнения), Выполнение такого рода -больше общего - немного сложное и выходит за рамки этого ответа.

Здесь полномасштабная реализация механизма, выдвинутого байрогом, - все это относится к нему. Обратите внимание, что мы получаем подключение к базе данных через новый DbContext по причинам, объясняемым в комментариях:

     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; }
         }
     }

Ответ 3

Здесь похож вопрос

Единственный доступный подход исходит от диспетчера программ команды Entity Framework (Rowan Miller (MSFT)):

Мы удалили CacheForContextType в CTP5, мы изначально предполагали, что он будет использоваться, когда люди захотят использовать один и тот же контекст в том же AppDomain с разными моделями. Проблема в том, что он создавал бы модель при каждой инициализации и не позволял бы каким-либо образом кэшировать ряд моделей и выбирать, какой из них использовать при каждой инициализации. Создание модели дорого, поэтому мы хотели продвинуть лучшую модель.

Мы рекомендуем использовать внешний конструктор ModelBuilder → DbDatabaseMapping → DbModel для каждой модели, которую вы хотите использовать. DbModel следует кэшировать и использовать для создания экземпляров контекста. ModelBuilder → Рабочий процесс DbModel немного запутан и имена классов невелики, они будут убраны для RTM.

Я пробовал следующий подход:

  • Переместите все операции, которые находились внутри обработчика события OnModelCreating, к новой функции, которая создает DbModelBuilder (скорее всего вы передадите DbConnection как параметр этой функции)
  • Получить DbModel через DbModelBuilder.Build(DbConnecton)
  • Получить DbCompiledModel через DbModel.Compile()
  • Создайте новый конструктор для DbContext с параметрами (DbConnection, DbCompileModel, bool) и передайте ранее созданный DbCompiledModel внутри него

В результате я смог изменить параметры DbCompiledModel каждый раз, когда я вызываю конструктор DbContext. Это все, что мне нужно.

Ответ 4

У меня такая же проблема: один контекст db, 2 или более разные модели db (только по именам таблиц)

Мое решение для EF6: все еще можно использовать внутреннее кэширование структуры Entity Framework модели db, но сделать дифференциацию между DbModel (s) в одном и том же DbContext, реализовав IDbModelCacheKeyProvider Interface в производном DbContext.

MSDN doc находится здесь: https://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.idbmodelcachekeyprovider(v=vs.113).aspx И он говорит:

Внедрите этот интерфейс в свой контекст, чтобы использовать пользовательскую логику для вычисления ключа, используемого для поиска уже созданной модели в кеше. Этот интерфейс позволяет вам иметь один тип контекста, который может использоваться с разными моделями в одном и том же приложении AppDomain или с несколькими типами контекстов, которые используют одну и ту же модель.

Надеюсь, что это поможет кому-то.