如何在首次运行时自动创建数据库?

178

我的应用程序正在移植到.NET Core,并将使用EF Core与SQLite。我希望在首次运行应用程序时自动创建数据库和表。根据文档,这可以通过手动命令完成:

dotnet ef migrations add MyFirstMigration

dotnet ef database update

我不希望最终用户输入这些内容,而是希望应用程序在首次使用时创建并设置数据库。对于EF 6,有:

Database.SetInitializer(new CreateDatabaseIfNotExists<MyContext>());

但我找不到EF Core的相应功能。我已经有了模型类,所以我可以编写代码来根据模型初始化数据库,但如果框架可以自动完成这项工作,那就更容易了。我不想自动构建模型或迁移,只想在新数据库中创建表。

EF Core 缺少自动创建表的功能吗?

7个回答

241

如果你已经创建好了迁移,可以按照以下步骤在 Startup.cs 中执行它们。

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
 {
      using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
      {
            var context = serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
            context.Database.Migrate();
      }
      
      ...

这将使用您添加的迁移创建数据库和表。

如果您没有使用Entity Framework Migrations,而是只需要在第一次运行时完全按照上下文类中的DbContext模型进行创建,则可以使用:

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
 {
      using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
      {
            var context = serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
            context.Database.EnsureCreated();
      }
      
      ...

如果需要在确保数据库被创建之前删除它,请调用:

context.Database.EnsureDeleted();

在调用 EnsureCreated() 之前。

改编自:http://docs.identityserver.io/en/latest/quickstarts/7_entity_framework.html?highlight=entity


1
我对EF还比较新,我使用EF 6 Code First“从数据库创建”功能在Visual Studio中创建了定义数据结构的类,并使用当前数据库文件。然后,我将它们剪切/粘贴到新的dotnet core VS解决方案中 - 所以我猜这些不是迁移。这是否意味着在上述代码可以使用之前必须创建迁移文件? - deandob
2
很抱歉,我的回答有误。如果您没有使用迁移,可以使用命令context.Database.EnsureCreated()/EnsureDeleted(),更多细节请参考https://blogs.msdn.microsoft.com/dotnet/2016/09/29/implementing-seeding-custom-conventions-and-interceptors-in-ef-core-1-0/。 - Ricardo Fontana
4
如果你需要两种解决方案怎么办?我们刚刚将我们的应用程序移植到了UWP并开始使用EF Core,这意味着一些用户需要从头创建数据库,而其他用户已经有了数据库。有没有一种方法可以确保第一次迁移只在表不存在时创建初始表格?你的回答似乎没有涉及到这个问题,或者是我理解有误? - Lars Udengaard

46

我的答案与Ricardo的答案非常相似,但我觉得我的方法更直接一些,因为在他的using函数中有很多东西,我甚至不确定它在底层是如何工作的。

因此,对于那些想要简单清晰的解决方案,可以为您创建一个数据库,您可以确切地知道发生了什么,这个解决方案适用于您:

public Startup(IHostingEnvironment env)
{
    using (var client = new TargetsContext())
    {
        client.Database.EnsureCreated();
    }
}

这基本上意味着,您创建的 DbContext(在这种情况下,我的称为 TargetsContext)内部,可以使用 DbContext 的实例来确保在应用程序运行时的 Startup.cs 中创建了在类中定义的表。


23
这是一段关于Entity Framework Core的信息,来源在这里:EnsureCreated完全绕过了迁移并为您创建模式,您不能将其与迁移混合使用。EnsureCreated旨在进行测试或快速原型设计,在这些情况下,您可以每次放弃和重新创建数据库。如果您正在使用迁移并希望在应用程序启动时自动应用它们,则可以使用context.Database.Migrate() - Thomas Schneiter
只是想指出,我刚刚尝试将这个粘贴到我的启动文件中,然后VS2019告诉我IHostingEnvironment现在已经被弃用了,推荐使用Microsoft.AspNetCore.Hosting.IWebHostEnvironment作为替代方案。 - ctrl-z pls
@ThomasSchneiter 注意,并非所有的提供商都支持迁移,因此在这些情况下,这仍然是一个有趣的选择。例如,请参阅CosmosDB提供程序。 - julealgon

22

如果您通过Startup.cs中Configure方法的参数列表获取上下文,您可以改为执行以下操作:

public void Configure(IApplicationBuilder app, IHostingEnvironment env,  LoggerFactory loggerFactory,
    ApplicationDbContext context)
 {
      context.Database.Migrate();
      ...

2
在我看來,這是最好的解決方案:你不需要關心任何服務甚至是DB上下文,你只需要使用通過依賴注入獲得的上下文即可。很棒! - Tobias

20

对于EF Core 2.0+,我必须采用不同的方法,因为它们更改了API。截至2019年3月,Microsoft建议将数据库迁移代码放在应用程序入口类中,但在WebHost构建代码之外。

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateWebHostBuilder(args).Build();
        using (var serviceScope = host.Services.CreateScope())
        {
            var context = serviceScope.ServiceProvider.GetRequiredService<PersonContext>();
            context.Database.Migrate();
        }
        host.Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

请注意,如果您在运行迁移时需要使用具有写入数据库模式访问权限的数据库连接。使用此方法时,建议为查询使用不同的连接,而不是用于运行迁移的连接。如果 SQL 注入攻击以某种方式通过并且您正在使用具有 DCL 或 DDL 权限的连接字符串进行查询,则存在潜在攻击向量。 https://www.geeksforgeeks.org/sql-ddl-dql-dml-dcl-tcl-commands/ - Paul Stark

14

如果您还没有创建迁移,有两个选项

1.从应用程序“Main”创建数据库和表:

var context = services.GetRequiredService<YourRepository>();
context.Database.EnsureCreated();

2. 如果数据库已经存在,则创建表格:

var context = services.GetRequiredService<YourRepository>();
context.Database.EnsureCreated();
RelationalDatabaseCreator databaseCreator =
(RelationalDatabaseCreator)context.Database.GetService<IDatabaseCreator>();
databaseCreator.CreateTables();

感谢Bubi的回答


4
我的服务是什么? - MichaelMao
我正在阅读的所有文档都在迁移方面发出了大而明显的警告,指出“不要使用EnsureCreatedEnsureDeleted,否则Database.Migrate将失败”。 - mwilson

3
如果你想同时使用EnsureCreated和Migrate,请使用以下代码:
     using (var context = new YourDbContext())
            {
                if (context.Database.EnsureCreated())
                {
                    //auto migration when database created first time

                    //add migration history table

                    string createEFMigrationsHistoryCommand = $@"
USE [{context.Database.GetDbConnection().Database}];
SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
CREATE TABLE [dbo].[__EFMigrationsHistory](
    [MigrationId] [nvarchar](150) NOT NULL,
    [ProductVersion] [nvarchar](32) NOT NULL,
 CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY CLUSTERED 
(
    [MigrationId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY];
";
                    context.Database.ExecuteSqlRaw(createEFMigrationsHistoryCommand);

                    //insert all of migrations
                    var dbAssebmly = context.GetType().GetAssembly();
                    foreach (var item in dbAssebmly.GetTypes())
                    {
                        if (item.BaseType == typeof(Migration))
                        {
                            string migrationName = item.GetCustomAttributes<MigrationAttribute>().First().Id;
                            var version = typeof(Migration).Assembly.GetName().Version;
                            string efVersion = $"{version.Major}.{version.Minor}.{version.Build}";
                            context.Database.ExecuteSqlRaw("INSERT INTO __EFMigrationsHistory(MigrationId,ProductVersion) VALUES ({0},{1})", migrationName, efVersion);
                        }
                    }
                }
                context.Database.Migrate();
            }

1
自 EF Core v7.0 和 .NET 6.0 开始,根据 Ricardo 的回答,对 EF Core v7.0 中的对象进行了一些更改,因此提供的答案需要进行调整。
要获取服务范围,请使用以下代码行:
var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope()

然而,现在有点不同:
var serviceScope = app.Services.CreateScope()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接