Используйте оба метода расширения AddDbContextFactory () и AddDbContext () в одном проекте.

Я пытаюсь использовать новый DbContextFactoryшаблон, обсуждаемый в разделе конфигурации DbContext документации EF Core.

У меня есть DbContextFactory и успешно работает в моем приложении Blazor, но я хочу сохранить возможность вводить экземпляры DbContext напрямую, чтобы мой существующий код работал.

Однако, когда я пытаюсь это сделать, я получаю сообщение об ошибке:

System.AggregateException: некоторые службы не могут быть созданы (ошибка при проверке дескриптора службы ServiceType:Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory1[MyContext]': невозможно использовать службу с заданной областью'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext]'.) ---> System.InvalidOperationException: Ошибка при проверке дескриптора службы' ServiceType:Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory1[MyContext]': невозможно использовать службу с заданной областью'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext]'. ---> System.InvalidOperationException: невозможно использовать службу с областью действия 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext]'.

Мне также удалось получить эту ошибку однажды во время экспериментов:

Не удается разрешить службу с заданной областью "Microsoft.EntityFrameworkCore.DbContextOptions`1[MyContext]" от корневого поставщика.

Теоретически возможно ли использовать оба AddDbContext и AddDbContextFactory все вместе?

4 ответа

Все дело в том, чтобы понять время жизни различных элементов в игре и правильно их настроить.

По умолчанию DbContextFactory созданный AddDbContextFactory()extension метод имеет срок жизни Singleton. Если вы используете AddDbContext() метод расширения с настройками по умолчанию, он создаст DbContextOptions с Scopedсрок службы (см. исходный код здесь), и как Singleton не могу использовать что-то с более коротким Scoped срок службы, выдается ошибка.

Чтобы обойти это, нам нужно изменить срок службы DbContextOptionsтакже быть "Синглтоном". Это можно сделать с помощью явной установки области видимости DbContextOptions параметр AddDbContext()

services.AddDbContext<FusionContext>(options => ApplyOurOptions(options, connectionString),
    contextLifetime: ServiceLifetime.Transient, 
    optionsLifetime: ServiceLifetime.Singleton);

Здесь есть действительно хорошее обсуждение этого в основном репозитории GitHub EF. Также стоит взглянуть на исходный код для DbContextFactory здесь.

Кроме того, вы также можете изменить время жизни DbContextFactoryпутем установки параметра ServiceLifetime в конструкторе:

services.AddDbContextFactory<FusionContext>(options => 
    ApplyOurOptions(options, connectionString), ServiceLifetime.Scoped);

Важная точка:

Оба AddDbContextFactory а также AddDbContext внутренняя регистрация DbContextOptions<T> внутри общего частного метода AddCoreServices с использованием TryAdd. (источник)

Это фактически означает, что тот, который находится в вашем коде первым , будет использоваться первым .

Таким образом, вы можете сделать это для более чистой настройки:

      services.AddDbContext<RRStoreContext>(options => {

   // apply options

});

services.AddDbContextFactory<RRStoreContext>(lifetime: ServiceLifetime.Scoped);

Я на самом деле использую это прямо сейчас, чтобы доказать себе, что это действительно не достигается :-)

      services.AddDbContextFactory<RRStoreContext>(options =>
{
   throw new Exception("Oops!");  // this is never reached

}, ServiceLifetime.Scoped);    

К сожалению, у меня есть некоторые перехватчики запросов, которые не являются потокобезопасными (и это вся причина, по которой я хотел создать несколько экземпляров с фабрикой), поэтому я думаю, что мне нужно создать свою собственную фабрику контекста, потому что у меня есть отдельная инициализация для контекста vs.ContextFactory.


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

влияние: ИспыталDbContextFactory

      public class SmartRRStoreContextFactory : IDbContextFactory<RRStoreContext>
{
    private readonly IServiceProvider _serviceProvider;

    public SmartRRStoreContextFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public virtual RRStoreContext CreateDbContext()
    {
        // need a new options object for each 'factory generated' context
        // because of thread safety isuess with Interceptors
        var options = (DbContextOptions<RRStoreContext>) _serviceProvider.GetService(typeof(DbContextOptions<RRStoreContext>));
        return new RRStoreContext(options);
    }
}

Примечание. У меня есть только один контекст, которому это нужно, поэтому я жестко кодирую новый контекст в своем методе. Альтернативой было бы использование отражения - что-то вроде этого DbContextFactorySource .

Затем в моем Startup.cs у меня есть:

      services.AddDbContext<RRStoreContext>(options => 
{
    var connection = CONNECTION_STRING;

    options.UseSqlServer(connection, sqlOptions =>
    {
        sqlOptions.EnableRetryOnFailure();
    });

    // this is not thread safe
    options.AddInterceptors(new RRSaveChangesInterceptor());

}, optionsLifetime: ServiceLifetime.Transient);

// add context factory, this uses the same options builder that was just defined
// but with a custom factory to force new options every time
services.AddDbContextFactory<RRStoreContext, SmartRRStoreContextFactory>();  

И я закончу предупреждением. Если вы используете фабрику ( CreateDbContext) в дополнение к «нормальному» введенному DbContext, убедитесь, что не смешиваете сущности. Если, например, вы вызываете SaveChanges в неправильном контексте, ваши объекты не будут сохранены.

При работе с EF Core 6 (.NET 6) в большинстве случаев вам не нужно использовать оба, посколькуAddDbContextFactoryтакже регистрирует сам тип контекста как службу с ограниченной областью действия.

Итак, в сервисах с пожизненным внедрениемIDbContextFactory<MyDbContext>, и в услугах сScopedсрок службы (например, контроллеры MVC или API)MyDbContextнапрямую.

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

См. также:
Связанная проблема с улучшением Github.

У меня была аналогичная ошибка, но я решил ее другим способом, потому что для AddDbContextFactory мне понадобился Sigleton. Я добавил службы в другом порядке, сначала AddDbContextFactory, а затем AddDbContext. Вот как это выглядит в моем коде:

      builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
    options.UseNpgsql(connectionString, c => c.UseNodaTime()));

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseNpgsql(connectionString, c => c.UseNodaTime()));
Другие вопросы по тегам