Entity Framework Core — отключить кэширование модели, вызвать onModelCreating() для каждого экземпляра dcontext

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

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

Я нашел эту ссылку, в которой говорится, что одним из способов достижения этого является использование DBModelBuilding - добавление модели вручную в контекст, но это для Entity Framework, не помогло для EF Core.

Entity Framework 6. Отключить кеширование моделей

Я надеюсь, что у кого-то есть решение для этого.

Спасибо


person Kavitha Gowda    schedule 08.10.2020    source источник


Ответы (2)


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

  1. Реализуйте интерфейс IDbModelCacheKeyProvider в производном DbContext. Проверьте это https://docs.microsoft.com/en-us/dotnet/api/system.data.entity.infrastructure.idbmodelcachekeyprovider?redirectedfrom=MSDN&view=entity-framework-6.2.0

  2. Создайте модель вне DbContext, а затем предоставьте его в настройках.

person MGot90    schedule 04.02.2021

После успешного создания модели EF Core будет кэшировать ее навсегда, если только вы не внедрите диспетчер кэширования, способный определить, эквивалентна ли модель другой, и, следовательно, можно ли ее кэшировать или нет.

Точка входа — реализовать менеджер кеша:

internal sealed class MyModelCacheKeyFactory : IModelCacheKeyFactory
{
    public object Create([NotNull] DbContext context)
    {
        return GetKey(context);
    }
}

Метод GetKey, который вам нужно написать, должен возвращать объект, который будет использоваться в качестве ключа. Этот метод должен проверять предоставленный контекст и возвращать один и тот же ключ, если модели одинаковы, и что-то другое, если они не совпадают. Подробнее об интерфейсе IModelCacheKeyFactory.

Я понимаю, это может быть непонятно (и мне тоже было непонятно), поэтому пишу полный пример того, что у меня есть в продакшене.

Рабочий пример

Моя цель — использовать один и тот же контекст для разных схем. Что нам нужно сделать, это

  1. создать новую контекстную опцию
  2. реализовать логику в контексте
  3. создать фабрику ключей кэша
  4. сделать метод расширения, чтобы указать схему
  5. вызвать метод расширения в контексте БД

1. Создайте новый параметр контекста

Здесь есть шаблон, содержащий только _schemaName. Шаблон необходим, так как опция расширения неизменяема по дизайну, и нам нужно сохранить контракт.

internal class MySchemaOptionsExtension : IDbContextOptionsExtension
{
    private DbContextOptionsExtensionInfo? _info;
    private string _schemaName = string.Empty;

    public MySchemaOptionsExtension()
    {
    }

    protected MySchemaOptionsExtension(MySchemaOptionsExtension copyFrom)
    {
        _schemaName = copyFrom._schemaName;
    }

    public virtual DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this);

    public virtual string SchemaName => _schemaName;

    public virtual void ApplyServices(IServiceCollection services)
    {
        // not used
    }

    public virtual void Validate(IDbContextOptions options)
    {
        // always ok
    }

    public virtual MySchemaOptionsExtension WithSchemaName(string schemaName)
    {
        var clone = Clone();

        clone._schemaName = schemaName;

        return clone;
    }

    protected virtual MySchemaOptionsExtension Clone() => new(this);

    private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
    {
        private const long ExtensionHashCode = 741; // this value has chosen has nobody else is using it

        private string? _logFragment;

        public ExtensionInfo(IDbContextOptionsExtension extension) : base(extension)
        {
        }

        private new MySchemaOptionsExtension Extension => (MySchemaOptionsExtension)base.Extension;

        public override bool IsDatabaseProvider => false;

        public override string LogFragment => _logFragment ??= $"using schema {Extension.SchemaName}";

        public override long GetServiceProviderHashCode() => ExtensionHashCode;

        public override void PopulateDebugInfo([NotNull] IDictionary<string, string> debugInfo)
        {
            debugInfo["MySchema:" + nameof(DbContextOptionsBuilderExtensions.UseMySchema)] = (ExtensionHashCode).ToString(CultureInfo.InvariantCulture);
        }
    }
}

2. Логика в контексте

Здесь мы навязываем схему всем реальным объектам. Схема получается опцией, прикрепленной к контексту

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   var options = this.GetService<IDbContextOptions>().FindExtension<MySchemaOptionsExtension>();
   if (options == null)
   {
       // nothing to apply, this is a supported scenario.
       return;
   }

   var schema = options.SchemaName;

   foreach (var item in modelBuilder.Model.GetEntityTypes())
   {
       if (item.ClrType != null)
           item.SetSchema(schema);
   }
}

3. Создайте фабрику ключей кеша

Здесь нам нужно создать фабрику кеша, которая сообщит EF Core, что он может кэшировать все модели в одном контексте, т. е. все контексты с одной и той же схемой будут использовать одну и ту же модель:

internal sealed class MyModelCacheKeyFactory : IModelCacheKeyFactory
{
    public object Create([NotNull] DbContext context)
    {
        const string defaultSchema = "dbo";
        var extension = context.GetService<IDbContextOptions>().FindExtension<MySchemaOptionsExtension>();

        string schema;
        if (extension == null)
            schema = defaultSchema;
        else
            schema = extension.SchemaName;

        if (string.IsNullOrWhiteSpace(schema))
            schema = defaultSchema;
        // ** this is the magic **
        return (context.GetType(), schema.ToUpperInvariant());
    }
}

Магия здесь в этой строке

return (context.GetType(), schema.ToUpperInvariant());

что мы возвращаем кортеж с типом нашего контекста и схемы. Хэш кортежа объединяет хеш каждой записи, поэтому логическим дискриминатором здесь являются тип и имя схемы. Когда они совпадают, модель используется повторно; когда это не так, создается новая модель, а затем кэшируется.

4. Сделайте метод расширения

Метод расширения просто скрывает добавление опции и замену службы кеша.

public static DbContextOptionsBuilder UseMySchema(this DbContextOptionsBuilder optionsBuilder, string schemaName)
{
    if (optionsBuilder == null)
        throw new ArgumentNullException(nameof(optionsBuilder));
    if (string.IsNullOrEmpty(schemaName))
        throw new ArgumentNullException(nameof(schemaName));

    var extension = optionsBuilder.Options.FindExtension<MySchemaOptionsExtension>() ?? new MySchemaOptionsExtension();

    extension = extension.WithSchemaName(schemaName);

    ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);

    optionsBuilder.ReplaceService<IModelCacheKeyFactory, MyModelCacheKeyFactory>();

    return optionsBuilder;
}

В частности, следующая строка применяет наш менеджер кеша:

optionsBuilder.ReplaceService<IModelCacheKeyFactory, MyModelCacheKeyFactory>();

5. Вызовите метод расширения

Вы можете вручную создать контекст следующим образом:

var options = new DbContextOptionsBuilder<DataContext>();

options.UseMySchema("schema1")
options.UseSqlServer("connection string omitted");

var context = new DataContext(options.Options)

В качестве альтернативы вы можете использовать IDbContextFactory с внедрением зависимостей. Подробнее о Интерфейс IDbContextFactory.

person Yennefer    schedule 06.07.2021