Используйте оба метода расширения AddDbContextFactory () и AddDbContext () в одном проекте.
Я пытаюсь использовать новый
DbContextFactory
шаблон, обсуждаемый в разделе конфигурации DbContext документации EF Core.
У меня есть
DbContextFactory
и успешно работает в моем приложении Blazor, но я хочу сохранить возможность вводить экземпляры
DbContext
напрямую, чтобы мой существующий код работал.
Однако, когда я пытаюсь это сделать, я получаю сообщение об ошибке:
System.AggregateException: некоторые службы не могут быть созданы (ошибка при проверке дескриптора службы ServiceType:Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory
1[MyContext]': невозможно использовать службу с заданной областью'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext]'.) ---> System.InvalidOperationException: Ошибка при проверке дескриптора службы' ServiceType:Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory
1[MyContext]': невозможно использовать службу с заданной областью'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext]'. ---> System.InvalidOperationException: невозможно использовать службу с областью действия 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[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()));