繁体   English   中英

实体框架 - 迁移 - 代码优先 - 每次迁移播种

[英]Entity Framework - Migrations - Code First - Seeding per Migration

我正在研究迁移,以便清理我们的部署过程。 将更改推向生产时所需的人工干预越少越好。

我在迁移系统中遇到了3个主要障碍。 如果我无法找到一个干净的方式,他们是show stoppers。

1.如何为每次迁移添加种子数据:

我执行命令“add-migration”,它使用Up和Down函数来构建一个新的迁移文件。 现在,我想通过Up和Down更改自动更改数据。 我不希望将种子数据添加到Configuration.Seed方法,因为它运行所有以各种重复问题结束的迁移。

2.如果无法满足上述要求,我该如何避免重复?

我有一个枚举,我循环,以将值添加到数据库。

foreach(var enumValue in Enum.GetValues(typeof(Access.Level)))
{
    context.Access.AddOrUpdate(
        new Access { AccessId = ((int)enumValue), Name = enumValue.ToString() }
    );
}
context.SaveChanges();

即使我使用的是AddOrUpdate,我仍然会在数据库中获得重复项。 上面的代码将我带到了第三个也是最后一个问题:

3.如何为主键提供种子?

我用以上代码列举的是:

public class Access
{
    public enum Level
    {
        None = 10,
        Read = 20,
        ReadWrite = 30
    }
    public int AccessId { get; set; }
    public string Name { get; set; }
}

我指定了我想要的值作为我的主键,但实体框架似乎忽略了它。 他们最终仍然是1,2,3。 我怎么能得到10,20,30?

目前这些EF的局限性还是有意限制,以防止我没有看到的其他类型的灾难?

  1. 当我想要通过迁移插入固定数据时,我使用对Sql("Insert ...")调用Sql("Insert ...")将插入直接放入Up()迁移中。 请参阅本页中间的注释: 如何插入固定数据
  2. 您可以通过调用AddOrUpdate重载来防止Seed方法中的重复项,该重载采用指定自然键的标识符表达式 - 请参阅此答案此博客条目
  3. 默认情况下,作为整数的主键创建为标识字段。 要另行指定,请使用[DatabaseGenerated(DatabaseGeneratedOption.None)]属性

我认为这是初始化器和种子方法的一个很好的解释

以下是如何使用AddOrUpdate方法的示例:

foreach(var enumValue in Enum.GetValues(typeof(Access.Level)))
{
    context.Access.AddOrUpdate(
        x => x.Name, //the natural key is "Name"
        new Access { AccessId = ((int)enumValue), Name = enumValue.ToString() }
    );
}

作为第1项的可能解决方案,我实现了IDatabaseInitializer策略,该策略仅运行每个待处理迁移的Seed方法,您需要在每个DbMigration类中实现自定义IMigrationSeed接口,然后Seed方法将是在每个迁移类的UpDown方法之后Up实现。

这有助于为我解决两个问题:

  1. 使用数据库数据迁移(或种子)进行组数据库模型迁移
  2. 检查Seed迁移代码应该在哪个部分运行,而不是检查数据库中的数据,而是使用已知数据,这些数据是刚刚创建的数据库模型。

界面看起来像这样

public interface IMigrationSeed<TContext>
{
    void Seed(TContext context);
}

下面是将调用此Seed方法的新实现

public class CheckAndMigrateDatabaseToLatestVersion<TContext, TMigrationsConfiguration>
    : IDatabaseInitializer<TContext>
    where TContext : DbContext
    where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
{
    public virtual void InitializeDatabase(TContext context)
    {
        var migratorBase = ((MigratorBase)new DbMigrator(Activator.CreateInstance<TMigrationsConfiguration>()));

        var pendingMigrations = migratorBase.GetPendingMigrations().ToArray();
        if (pendingMigrations.Any()) // Is there anything to migrate?
        {
            // Applying all migrations
            migratorBase.Update();
            // Here all migrations are applied

            foreach (var pendingMigration in pendingMigrations)
            {
                var migrationName = pendingMigration.Substring(pendingMigration.IndexOf('_') + 1);
                var t = typeof(TMigrationsConfiguration).Assembly.GetType(
                    typeof(TMigrationsConfiguration).Namespace + "." + migrationName);

                if (t != null 
                   && t.GetInterfaces().Any(x => x.IsGenericType 
                      && x.GetGenericTypeDefinition() == typeof(IMigrationSeed<>)))
                {
                    // Apply migration seed
                    var seedMigration = (IMigrationSeed<TContext>)Activator.CreateInstance(t);
                    seedMigration.Seed(context);
                    context.SaveChanges();
                }
            }
        }
    }
}

这里的好处是你有一个真正的EF上下文来操作种子数据,就像标准的EF种子实现一样。 但是,如果您决定删除先前迁移中的种子表,则可能会出现这种情况,您必须相应地重构现有的种子代码。

编辑:作为在Up和Down之后实现种子方法的替代方法,您可以创建相同Migration类的部分类,我发现这很有用,因为它允许我在我想重新播种时安全地删除迁移类同样的迁移。

您好我在此链接中找到了有关您的问题的非常有用的信息: Safari Books Online

“1.如何为每次迁移添加种子数据:”正如您在示例中所看到的,您需要为种子创建新的混淆。 迁移后必须调用此种子配置。

public sealed class Configuration : DbMigrationsConfiguration
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }

    protected override void Seed(SafariCodeFirst.SeminarContext context)
    {
        //  This method will be called after migrating to the latest version.

        //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
        //  to avoid creating duplicate seed data. E.g.
        //
        //    context.People.AddOrUpdate(
        //      p => p.FullName,
        //      new Person { FullName = "Andrew Peters" },
        //      new Person { FullName = "Brice Lambson" },
        //      new Person { FullName = "Rowan Miller" }
        //    );
        //
    }
}

“2.如果上述情况不可能,我该如何避免重复?”

AddOrUpdate如果你在这里收到错误,必须帮助你准确地复制重复项,你可能会在调用堆栈后发生配置错误。 看看这个例子!

“3.我怎样才能为主键提供种子?”

这也是你的关键定义。 如果您的密钥DatabaseGenerated(DatabaseGeneratedOption.Identity)比您不必提供它。 在其他一些senarios中,您需要创建一个新的,取决于密钥类型。

“目前EF的这些限制还是他们故意限制,以防止我看不到其他类型的灾难?”
不是我知道的!

好吧,所以有点抨击我已经设法将EF击败提交。 这是我做的:

1.我找不到特定迁移的数据。 这一切都必须进入常见的Configuration.Seed方法。

2.为了避免重复,我不得不做两件事情。 对于我的枚举,我写了以下种子代码:

foreach (var enumValue in Enum.GetValues(typeof(Access.Level)))
{
    var id = (int)enumValue;
    var val = enumValue.ToString();

    if(!context.Access.Any(e => e.AccessId == id))
        context.Access.Add(
            new Access { AccessId = id, Name = val }
        );
}
context.SaveChanges();

所以基本上,只检查它是否存在,如果不存在则添加

3.为了使上述功能正常工作,您需要能够插入主键值。 幸运的是,这个表将始终具有相同的静态数据,因此我可以取消激活自动增量。 为此,代码如下所示:

public class Access
{
    public enum Level
    {
        None = 10,
        Read = 20,
        ReadWrite = 30
    }

    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int AccessId { get; set; }
    public string Name { get; set; }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM