如果表不存在,则创建表?

14

使用Entity Framework Core,是否有一种方法来创建表,如果它还不存在?即使在上下文中调用 EnsureCreated,也会抛出异常:

使用Entity Framework Core,是否有办法在尚不存在的情况下创建表格?即使在上下文中调用EnsureCreated,也会抛出异常:

DbSet<Ticker> Ticker { get; set }
Database.EnsureCreated();

Ticker.Add(...);
dbctx.SaveChanges(); <== exception

异常结果:

System.Data.SqlClient.SqlException: Invalid object name 'Ticker'

是否有办法在插入数据之前创建表Ticker?

==编辑==

这个问题不是要创建/迁移整个数据库,数据库已经存在,并且大部分表格也存在,但其中一些表格可能不存在。因此,我只需要在运行时创建一个或两个表格。


可能是使用LINQ检查数据库中是否存在表的重复问题。 - Guy
@Guy,所以你是在暗示说没有办法做到吗? - fluter
我建议你可以检查表是否存在,如果不存在则创建它。 - Guy
2
@Guy 是的,那是个好主意,但如何在EntityFramework中实现呢? - fluter
https://dev59.com/M2025IYBdhLWcg3wST6Q 这个问题怎么样?有很多例子。 - Guy
1
好的,使用EFCore可以优雅地检查表是否存在,但是创建表必须使用原始的SQL查询吗?我问这个问题是因为我想避免在EFCore应用程序中直接使用SQL语句。 - fluter
4个回答

11
在 Entity Framework Core(版本为 2.2.4)中,您可以使用以下代码在 DbContext 中创建数据库表,如果它们不存在的话:
try
{
    var databaseCreator = (Database.GetService<IDatabaseCreator>() as RelationalDatabaseCreator);
    databaseCreator.CreateTables();
}
catch (System.Data.SqlClient.SqlException)
{
    //A SqlException will be thrown if tables already exist. So simply ignore it.
}

如果数据库已经存在,Database.EnsureCreated() 不会创建模式(也就是你的表),这就是你收到异常的原因。

你可以查看该方法的文档

PS:如果在 Entity Framework Core 新版本中更改了异常,请确保捕获正确的异常。


4
我的猜测是你的上下文定义错误。也许你忘记在上下文实现中添加DbSet了?
以下代码完美地工作,我也真诚地喜欢在实际DBContext实现的构造函数中使用EnsureCreated()。
internal class AZSQLDbContext : DbContext
{
    public AZSQLDbContext() {
        this.Database.EnsureCreated();
    }

    internal DbSet<TaskExecutionInformation> TaskExecutionInformation { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var dbUser = "your-user";
        var dbPW = "your-pw";
        optionsBuilder.UseSqlServer(
            $"Server=tcp:sample-sql.database.windows.net,1433;Initial Catalog=sample-db;Persist Security Info=False;User ID={dbUser};Password={dbPW};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;");
    }
}

TaskExecutionInformation只是一个简单的Poco,可以是任何东西。如果您需要一些指导,请参见以下内容。

public class TaskExecutionInformation
{
    public Guid Id { get; set; }
    public string Status { get; set; }
    public int Duration { get; set; }
}

这个方法没有考虑到当数据库存在但是缺少表的情况。this.Database.EnsureCreated();只会在整个数据库不存在时创建数据库,并不会检查已存在数据库中的表。 - cherry

2

在我的情况下,有两个应用程序使用同一个数据库,它们可以创建自己的 Code-First 表,如果这些表不存在的话。所以我解决这个问题的方法是,在 DbContext 中使用以下扩展方法:

using System;
using System.Linq;

using Microsoft.EntityFrameworkCore;

namespace Infrastructure.Extensions
{
    internal static class DbContextExtensions
    {
        internal static void EnsureCreatingMissingTables<TDbContext>(this TDbContext dbContext) where TDbContext : DbContext
        {
            var type = typeof(TDbContext);
            var dbSetType = typeof(DbSet<>);

            var dbPropertyNames = type.GetProperties().Where(p => p.PropertyType.Name == dbSetType.Name)
                .Select(p => p.Name).ToArray();

            foreach (var entityName in dbPropertyNames)
            {
                CheckTableExistsAndCreateIfMissing(dbContext, entityName);
            }
        }

        private static void CheckTableExistsAndCreateIfMissing(DbContext dbContext, string entityName)
        {
            var defaultSchema = dbContext.Model.GetDefaultSchema();
            var tableName = string.IsNullOrWhiteSpace(defaultSchema) ? $"[{entityName}]" : $"[{defaultSchema}].[{entityName}]";

            try
            {
                _ = dbContext.Database.ExecuteSqlRaw($"SELECT TOP(1) * FROM {tableName}"); //Throws on missing table
            }
            catch (Exception)
            {
                var scriptStart = $"CREATE TABLE {tableName}";
                const string scriptEnd = "GO";
                var script = dbContext.Database.GenerateCreateScript();

                var tableScript = script.Split(scriptStart).Last().Split(scriptEnd);
                var first = $"{scriptStart} {tableScript.First()}";

                dbContext.Database.ExecuteSqlRaw(first);
                Log.Information($"Database table: '{tableName}' was created.");
            }
        }
    }
}

这段代码没有处理依赖关系。如果尝试创建一个引用尚不存在的另一个表的外键约束,它将失败。 - cherry

1

这里有几个选项。最简单的方法是执行:

MyContext.Database.CreateIfNotExists();

或者,可以使用初始化方式,在您的上下文构造函数中添加以下内容:

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

但是,这两个方法都需要您在修改模型并重新创建数据库时手动删除架构。如果您不想这样做,可以使用以下初始化方式:

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

这将在每次运行您的程序时根据数据库检查您的模型,并在模型已被修改时自动删除和重新创建数据库。 编辑: 如果您不想删除数据库,而只是要更新它,则可以使用以下初始化:
Database.SetInitializer<MyContext>(new MigrateDatabaseToLatestVersion<MyContext, Config>());

2
我不会创建数据库,数据库已经存在了,我只需要在表不存在的情况下创建一个表。 - fluter
1
谢谢,但是当我尝试你的示例时,EFCore中不存在SetInitializer方法:https://learn.microsoft.com/en-us/ef/core/api/microsoft.entityframeworkcore.infrastructure.databasefacade#Microsoft_EntityFrameworkCore_Infrastructure_DatabaseFacade - fluter
看起来这个方法在最新版本的EFCore中被移除了。 - fluter
3
是的,我注意到你正在使用EF Core。不幸的是,微软已经移除了EF Core自动迁移功能,并且不打算再实现它们。 - stelioslogothetis
1
这个答案似乎不再适用于最新版本的EFCore。 - gpresland

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