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

时间:2013-09-12 08:54:17

标签: c# entity-framework .net-4.5 ef-migrations seeding

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

我遇到了迁移系统的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; }
}

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

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

4 个答案:

答案 0 :(得分:27)

  1. 当我想要通过迁移插入固定数据时,我使用对Sql("Insert ...")的调用将插入直接放入Up()迁移中。请参阅此页面中间的注释:how to insert fixed data
  2. 您可以通过调用AddOrUpdate重载来防止Seed方法中出现重复,该重载采用指定自然键的标识符表达式 - 请参阅this answerthis blog entry
  3. 默认情况下,作为整数的主键创建为标识字段。要另行指定,请使用[DatabaseGenerated(DatabaseGeneratedOption.None)]属性
  4. 我认为这是Initializer and Seed methods

    的一个很好的解释

    以下是如何使用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 :(得分:12)

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

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

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

    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类的部分类,我发现这很有用,因为它允许我在我想重新播种相同的迁移时安全地删除迁移类

答案 2 :(得分: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)比您不必提供密钥。在其他一些senarios中,您需要创建一个新的,取决于密钥类型。

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

答案 3 :(得分: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; }
}