如何在运行时为C#中的测试生成,编译和应用迁移?

问题描述

如何在运行时为C#中的测试生成,编译和应用迁移?

我想我已经很接近要回答我的问题了。我能够生成和编译迁移,但无法应用它们。在互联网上搜索两天没有任何结果。所以,有人可以帮我解决这个问题吗?

这是我用来生成和编译迁移的代码

using (var scope = testServer.Host.Services.CreateScope())
{
    var dbContext = (MyDbContext)scope.ServiceProvider.GetService(typeof(MyDbContext));

    var migrationsDirName = "Migrations";
    var migrationsAssemblyName = "My.Migrations";
    var sep = Path.DirectorySeparatorChar;
    var migrationsAssemblyPath = $"{migrationsDirName}{sep}{migrationsAssemblyName}.dll";

    //prepare a directory
    Directory.CreateDirectory(migrationsDirName);
    Directory.GetFiles(migrationsDirName).ToList().ForEach(File.Delete);

    //generate the migration files
    var designTimeServices = new ServiceCollection()
        .AddSingleton(dbContext.GetService<IHistoryRepository>())
        .AddSingleton(dbContext.GetService<IMigrationsIdGenerator>())
        .AddSingleton(dbContext.GetService<IMigrationsModelDiffer>())
        .AddSingleton(dbContext.GetService<IMigrationsAssembly>())
        .AddSingleton(dbContext.Model)
        .AddSingleton(dbContext.GetService<ICurrentDbContext>())
        .AddSingleton(dbContext.GetService<IDatabaseProvider>())
        .AddSingleton(dbContext.GetService<IRelationalTypeMappingSource>())
        .AddSingleton(dbContext.GetService<IMigrator>())
        .AddSingleton<IOperationReporter>(new OperationReporter(null))
        .AddSingleton<MigrationsCodeGeneratorDependencies>()
        .AddSingleton<ICSharpHelper,CSharpHelper>()
        .AddSingleton<CSharpMigrationoperationGeneratorDependencies>()
        .AddSingleton<ICSharpMigrationoperationGenerator,CSharpMigrationoperationGenerator>()
        .AddSingleton<CSharpSnapshotGeneratorDependencies>()
        .AddSingleton<ICSharpSnapshotGenerator,CSharpSnapshotGenerator>()
        .AddSingleton<CSharpMigrationsGeneratorDependencies>()
        .AddSingleton<IMigrationsCodeGenerator,CSharpMigrationsGenerator>()
        .AddSingleton<MigrationsScaffolderDependencies>()
        .AddSingleton<MigrationsScaffolder>()
        .AddSingleton<ISnapshotModelProcessor,SnapshotModelProcessor>()
        .AddSingleton<IMigrationsCodeGeneratorSelector,MigrationsCodeGeneratorSelector>()
        .BuildServiceProvider();
    var scaffolder = designTimeServices.GetrequiredService<MigrationsScaffolder>();
    var migration = scaffolder.ScaffoldMigration("MyMigration","My.Data");

    var migrationFileName = $"{migrationsDirName}{sep}{migration.MigrationId}{migration.FileExtension}";
    var designerFileNmae = $"{migrationsDirName}{sep}{migration.MigrationId}.Designer{migration.FileExtension}";
    var snapshotFileName = $"{migrationsDirName}{sep}{migration.SnapshotName}{migration.FileExtension}";
    File.WriteallText(migrationFileName,migration.MigrationCode);
    File.WriteallText(designerFileNmae,migration.MetadataCode);
    File.WriteallText(snapshotFileName,migration.SnapshotCode);

    //compile the migration files
    using (var migrationStream = File.OpenRead(migrationFileName))
    using (var designerStream = File.OpenRead(designerFileNmae))
    using (var snapshotStreamStream = File.OpenRead(snapshotFileName))
    {
        var compilation = CSharpCompilation
            .Create(migrationsAssemblyName)
            .WithOptions(new CSharpCompilationoptions(OutputKind.DynamicallyLinkedLibrary))
            .AddReferences(new[]
            {
                MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),MetadataReference.CreateFromFile(typeof(MigrationBuilder).GetTypeInfo().Assembly.Location),MetadataReference.CreateFromFile(typeof(BaseScenario).GetTypeInfo().Assembly.Location),MetadataReference.CreateFromFile(typeof(ModelBuilder).GetTypeInfo().Assembly.Location),MetadataReference.CreateFromFile(typeof(Action).GetTypeInfo().Assembly.Location),MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).GetTypeInfo().Assembly.Location),MetadataReference.CreateFromFile(typeof(System.Linq.Expressions.BinaryExpression).GetTypeInfo().Assembly.Location),MetadataReference.CreateFromFile(
                    AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name == "netstandard").Single().Location
                )
            })
            .AddSyntaxTrees(new[] 
            {
                CSharpSyntaxTree.ParseText(SourceText.From(migrationStream),path: migrationFileName),CSharpSyntaxTree.ParseText(SourceText.From(designerStream),path: designerFileNmae),CSharpSyntaxTree.ParseText(SourceText.From(snapshotStreamStream),path: snapshotFileName)
            });
        var result = compilation.Emit(migrationsAssemblyPath);
        TestMyDbContext.MigrationAssembly = migrationsAssemblyPath;
    }

    //apply the compiled migration to the database
    dbContext = (MyDbContext)scope.ServiceProvider.GetService(typeof(MyDbContext));
    dbContext.Database.EnsureDeleted();
    dbContext.Database.Migrate();
}

这是我的TestMyDbContext班:

public class TestMyDbContext : MyDbContext
{
    public static string MigrationAssembly { get; set; } = null;

    public TestMyDbContext(IConfiguration configuration) : base(configuration)
    {

    }
    
    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options
            .Usesqlite("Data Source=Data/my.db",c =>
            {
                if(MigrationAssembly != null)
                {
                    c.MigrationsAssembly(MigrationAssembly);
                }
            })
            .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
    }
}

正如我在研究运行时迁移生成过程中发现的那样,DbContext实例对于生成迁移是必不可少的。而且,还要在DbContext方法中创建OnConfiguring实例期间设置迁​​移程序集。

因此,我对上面的代码有以下期望:创建第一个DbContext实例,并使用它来获取迁移程序集。之后,我创建了另一个DbContext实例,但是这次在创建期间,我可以抓住迁移程序集,因此,新创建的DbContext实例上的代码应应用编译后的迁移:

dbContext = (MyDbContext)scope.ServiceProvider.GetService(typeof(MyDbContext));
dbContext.Database.EnsureDeleted();
dbContext.Database.Migrate();

但是这没有发生,迁移也不会更新数据库。我在这里可能想念什么?

更新

我刚刚发现了一件有趣的事情。我下载了DB Browser for SQLite并检查了my.db文件。它没有应用迁移。因此,我决定检查我的迁移.dll。我只是将其复制到bin(在运行时生成后),然后注释了创建迁移程序集的代码部分(即,从var migrationsDirName = "Migrations";行到//apply the compiled migration to the database行),并对{ {1}}为TestMyDbContext.MigrationAssembly名称。现在,迁移已按预期应用。但这不是我想要的,因为我不需要手动移动迁移.dll并在每次数据模型发生更改时重新运行测试。简而言之,我能够应用迁移,但是无法以这种方式生成和编译它。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)