我正在使用 .NET Core 3.1 和 Entity Framework Core 创建一个包含 IdentityServer4 的应用程序。它的目的是可以针对多种数据库类型。
在各种示例之后,除了包含 DbContexts 的 IdentityServer 项目本身之外,我还通过使用两个单独的迁移程序集,设法让它与 SQL Server 和 MySQL 一起工作。
在 Startup.cs 我有以下内容:
var migrationsAssembly = $"IdentityServer.Migrations.{serverType}";
services.AddDbContext<Data.IdentityServerContext>(options =>
{
switch (serverType)
{
case ServerType.SqlServer:
options.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
break;
case ServerType.MySql:
options.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
break;
}
});
其中 ServerType 是“SqlServer”或“MySql”的配置枚举
如果我使用 dotnet 命令行,迁移工作正常
dotnet ef database update -c IdentityServerContext
我希望能够使用配置或命令行来让 IdentityServer 自己应用迁移。
例如在 Main()
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var provider = scope.ServiceProvider;
try
{
var configuration = provider.GetRequiredService<IConfiguration>();
if (configuration.GetValue<bool>("migrateDatabases"))
{
var identityContext = provider.GetRequiredService<IdentityServerContext>();
identityContext.Database.Migrate();
}
}
catch
{
}
}
host.Run();
但是,如果我这样做,我会得到一个例外:
System.IO.FileNotFoundException: Could not load file or assembly 'IdentityServer.Migrations.MySql, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'IdentityServer.Migrations.MySql, Culture=neutral, PublicKeyToken=null'
at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, RuntimeAssembly assemblyContext, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, AssemblyLoadContext assemblyLoadContext)
at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, StackCrawlMark& stackMark, AssemblyLoadContext assemblyLoadContext)
at System.Reflection.Assembly.Load(AssemblyName assemblyRef, StackCrawlMark& stackMark, AssemblyLoadContext assemblyLoadContext)
at System.Reflection.Assembly.Load(AssemblyName assemblyRef)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsAssembly..ctor(ICurrentDbContext currentContext, IDbContextOptions options, IMigrationsIdGenerator idGenerator, IDiagnosticsLogger`1 logger)
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.GetRelationalService[TService](IInfrastructure`1 databaseFacade)
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
at IdentityServer.Program.Main(String[] args)
迁移程序集与项目 DLL 和 EXE 位于同一目录中。
我无法解决的是为什么它无法加载迁移程序集。
作为一个实验,我连接了AssemblyLoadContext.Default.Resolving
回调并用于Assembly.LoadFrom(<path>)
手动加载程序集。这可以正常工作并正确应用迁移,但这是一个非常混乱的解决方法。
任何人都可以解释为什么在调用时程序集加载失败context.Database.Migrate()
,但在使用 ef 工具或在Resolving
回调中手动加载时却没有?
编辑
我想我找到了一种解决方法,它与我们在同一解决方案中的另一个项目中所做的相匹配。
我们在该项目的运行时将模块动态加载到 Dot Net Core DI 框架中,并且模块中的迁移似乎工作正常。
我已将Scutor
库添加到项目中,以将手动加载的迁移程序集扫描到 DI 服务集合中。尽管它没有注册任何类,但这似乎足以将程序集加载到 中,AssemblyLoadContext
以便迁移代码能够加载它。
然而,这并不能解释为什么它不能自动加载迁移程序集。