具有多个数据库上下文的 .NET Core 应用不会在所有 DbContext 中生成表

.NET Core app with multiple database contextes does not generate tables in all DbContexts

我用 .NET core 3.1 制作了一个 OData 服务器。

在这个应用程序中,我有 2 个数据库上下文。其中之一用于应用程序本身 (AppDbContext),另一个用于帐户管理和身份验证 (AccountDbContext)。

我有一个 ServiceExtensions 这样的:

public static class ServiceExtensions
{
            
    public static void ConfigureMySqlContext(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddDbContext<AppDbContext>(options =>
        {
            options.UseMySql(configuration.GetConnectionString("MysqlConnection"),
                mysqlOptions => {
                    mysqlOptions.ServerVersion(new ServerVersion(new Version(5, 4, 3), ServerType.MySql));
                });
        });

        services.AddDbContext<AccountDbContext>(options =>
        {
            options.UseMySql(configuration.GetConnectionString("MysqlConnection"),
                mysqlOptions => {
                    mysqlOptions.ServerVersion(new ServerVersion(new Version(5, 4, 3), ServerType.MySql));
                });
        });

    }
        
}

startup.cs 中,我将按如下方式调用 ConfigureMySqlContext 函数:


public class Startup
{
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. 
        // I use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            services.ConfigureMySqlContext(Configuration);
        }
}

public static class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args)
            .Build()
            .CreateDbIfNotExists()
            .Run();
    }

    private static IHost CreateDbIfNotExists(this IHost host)
    {
        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;

            var accountDbContext = services.GetRequiredService<AccountDbContext>();
            accountDbContext.Database.EnsureCreated();

            var appDbContext = services.GetRequiredService<AppDbContext>();
            appDbContext.Database.EnsureCreated();
                
        }

        return host;
    }
}

此代码的目的是在数据库内的两个上下文中生成所需的表。 问题是,当我将这个应用程序发布到服务器时,当应用程序第一次 运行 时,它只会生成和启动 AppDbContext 中的表。为什么?因为放在AccountDbContext.

之前

如果我按如下方式更改 ConfigureMySqlContext 函数并再次发布应用程序:

public static class ServiceExtensions
{
            
    public static void ConfigureMySqlContext(this IServiceCollection services, IConfiguration configuration)
    {

        services.AddDbContext<AccountDbContext>(options =>
        {
            options.UseMySql(configuration.GetConnectionString("MysqlConnection"),
                mysqlOptions => {
                    mysqlOptions.ServerVersion(new ServerVersion(new Version(5, 4, 3), ServerType.MySql));
                });
        });

        services.AddDbContext<AppDbContext>(options =>
        {
            options.UseMySql(configuration.GetConnectionString("MysqlConnection"),
                mysqlOptions => {
                    mysqlOptions.ServerVersion(new ServerVersion(new Version(5, 4, 3), ServerType.MySql));
                });
        });       
    }        
}

然后就是AccountDbContext里面的表,会在第一个运行之后在数据库中启动。

问题在于,EnsureCreated 的核心是语义非常简单,可以建模为:

if (!database.exists()) {
   database.create();
}

其中 exists() 检查数据库是否存在,而不检查数据库中的表。

它的最终结果是,如果数据库没有完全清空,EnsureCreated()将没有任何效果。

有两种方法可以解决这个问题:

  1. 将数据库一分为二。这是EntityFramework最喜欢的解决方案。

但是,如果不行,请查看:

  1. 查看 IRelationalDatabaseCreator.CreateTables() 方法:

https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.storage.irelationaldatabasecreator?view=efcore-5.0

这是一个可能的解决方案:

using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Infrastructure;
public static class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args)
            .Build()
            .CreateDbIfNotExists()
            .Run();
    }

    private static IHost CreateDbIfNotExists(this IHost host)
    {
        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;
            try
            {
                var accountDbContext = services.GetRequiredService<AccountDbContext>();
                accountDbContext.Database.EnsureCreated();
                var accountDbCreator = accountDbContext.GetService<IRelationalDatabaseCreator>();
                accountDbCreator.CreateTables();
            }
            catch (Exception e) { }

            try
            {
                var appDbContext = services.GetRequiredService<AppDbContext>();
                appDbContext.Database.EnsureCreated();
                var appDbCreator = appDbContext.GetService<IRelationalDatabaseCreator>();
                appDbCreator.CreateTables();
            }
            catch (Exception e) { }
        }

        return host;
    }
}