我正在使用.NET Core 3.1和Entity Framework Core创建一个包含IdentityServer4的应用程序。目的是可以针对多种数据库类型。
在下面的各种示例中,除了包含DbContext的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
中,以便迁移代码能够加载它。
但是,这并不能解释为什么它无法自动加载迁移程序集。