43

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

我在迁移系统中遇到了 3 个主要问题。如果我想不出一个干净的方式绕过它们,它们就是表演的终结者。

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; }
}

我正在指定我想要作为主键的值,但 Entity Framework 似乎忽略了它。他们最终仍然是 1,2,3。我如何让它成为 10,20,30?

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

4

4 回答 4

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

我认为这是对Initializer 和 Seed 方法的一个很好的解释

下面是如何使用 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() }
    );
}
于 2013-09-12T16:23:09.797 回答
16

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

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

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

界面看起来像这样

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 之后实现种子方法的替代方法,您可以创建同一迁移类的部分类,我发现这很有用,因为它允许我在想要重新播种时安全地删除迁移类同样的迁移。

于 2015-11-07T17:47:08.860 回答
3

嗨,我在此链接中找到了一个非常有用的信息来解决您的问题: 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)比您不必提供它。在其他一些场景中,您需要创建一个新的,这取决于密钥类型。

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

于 2013-09-12T09:30:01.157 回答
3

好的,所以通过一些抨击,我设法将 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; }
}
于 2013-09-12T13:35:21.877 回答