当使用迁移时,使用代码优先的模式更改时,种子数据将无法正常工作 [英] Seeding data will not work when schema changes with Code First when using migrations

查看:144
本文介绍了当使用迁移时,使用代码优先的模式更改时,种子数据将无法正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好,所以我正在使用Entity Framework 6.1,并且首先尝试使用播种代码。我有一个总是默认的工作的滴滴。但是我想使用我设置的数据库迁移,它的工作原理。 UNTIL我正常化一个表,然后尝试种子,我得到一个主键错误。



基本上在我的上下文中,当我取消注释TODO部分中的更改并且当尝试新归一化的表的数量时,模式更改会导致主键违规。它将适用于始终执行drop的初始化程序,但是我想要我的迁移数据表,而不是每次我进行更改时都删除数据库,以防我想要回滚。我已经尝试将PersonId的属性更改为Identity,并将其重新设置为None。所以要注意的是,如果它被设置为身份,它将会工作,但值将不断增加到更高的值每次1,2,3,4然后5,6,7,8等。如果我将它设置为无,它首次工作,然后当它在映射中被分割并且被归一化时,它会被击倒。我已经尝试过自定义dbcc命令,它也不喜欢这样,即使使用dbcc来重新设置两个不喜欢它的新表。有没有人知道如何明确地完成新表的种植。



有没有人知道如何做一个种子过程,如果你规范化,模型可以处理将对象映射到多个表?我正在尝试一堆不同的模式,并且无处不在。



所以POCO对象

 code> public class Person 
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int PersonId {get;组; }
[Column(TypeName =varchar)]
[必需]
[MaxLength(32)]
public string FirstName {get;组; }
[Column(TypeName =varchar)]
[必需]
[MaxLength(32)]
public string LastName {get;组; }

[Column(TypeName =varchar)]
public string OverlyLongDescriptionField {get;组; }
}

代码的上下文首先:

  public class EasyContext:DbContext 
{
public EasyContext():base(name = EasyEntity)
{
//Database.SetInitializer<EasyContext>(new EasyInitializer());

Database.SetInitializer(新的MigrateDatabaseToLatestVersion< EasyContext,Migrations.Configuration>(EasyEntity));
}

public DbSet< ProductOrder> ProductOrder {get;组; }
public DbSet< Person>人{get;组; }
public DbSet< Product>产品{get;组; }
public DbSet< Audit>备份{get;组;

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(dbo);
modelBuilder.Conventions.Remove< PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove< ManyToManyCascadeDeleteConvention>();

// TODO让我们规范化一个长的描述字段
//modelBuilder.Entity<Person>()
//.Map(m =>
/ / {
// m.Properties(p => new {p.FirstName,p.LastName});
// m.ToTable(Person);
//} )
//.Map(m =>
// {
// m.Properties(p => new {p.OverlyLongDescriptionField});
// m .ToTable(PersonDescription);
//});
}
}

DropCreateAlways的初始化器:

  public class EasyInitializer:DropCreateDatabaseAlways< EasyContext> 
{
protected override void Seed(EasyContext context)
{
SeedingValues.SeedingForDatabaseDrop(context);

base.Seed(context);
}
}

迁移配置:

 内部密封类配置:DbMigrationsConfiguration< EasyEntity.EasyContext> 
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
ContextKey =EasyEntity.EasyContext;
}

protected override void Seed(EasyContext context)
{
SeedingValues.SeedingWithoutDatabaseDrop(context);

base.Seed(context);
}
}

基础种子类:

 内部静态类SeedingValues 
{
public static void SeedingForDatabaseDrop(EasyContext context)
{
BaseSeed );
}

public static void SeedingWithoutDatabaseDrop(EasyContext context)
{
context.Person.ClearRange();

BaseSeed(context);
}

private static void BaseSeed(EasyContext context)
{
IList< Person> persons =新列表< Person>
{
new Person {PersonId = 1,FirstName =Brett,LastName =Guy,OverlyLongDescriptionField =OMG Look我有一堆文本通过放置一堆东西来对表进行非规范化与主表相关。 },
new Person {PersonId = 2,FirstName =Neil,LastName =Person},
new Person {PersonId = 3,FirstName =Ryan,LastName =Other},
new Person {PersonId = 4,FirstName =Aaron,LastName =Dude},
};

foreach(var person in persons)
context.Person.AddOrUpdate(person);
}
}

ClearingHelper

  public static void ClearRange< T>(此DbSet< T> dbSet)其中T:class 
{
using(var context = new EasyContext ))
{
dbSet.RemoveRange(dbSet);
}
}


解决方案

所以问题是新创建的表未被填充并且旧表被填充。所以如果我跟随我的例子,一个POCO类人并且被删除了。没有任何明确的映射,只是一个DbSet将创建一个表Person。如果我做我的映射到拆分表。

  modelBuilder.Entity< Person>()
.Map =>
{
m.Properties(p => new {p.PersonId,p.FirstName,p.LastName});
m.ToTable(Person);
})
.Map(m =>
{
m.Properties(p => new {p.PersonId,p.OverlyLongDescriptionField});
m .ToTable(PersonDescription);
});

我的种子进程违反了主键违规。这是因为如果我查看新更新的数据库,它仍然保留旧表并创建一个新的表。然而,它不知道如何使用这种方法删除数据:

  public static void ClearRange< T>(此DbSet&T ; dbSet)其中T:class 
{
using(var context = new EasyContext())
{
dbSet.RemoveRange(dbSet);
}
}

所以我在想:如果我的数据包含在这个时候,我需要改变它向前移动我理论上可以直接用SQL命令清理表。这不是我想要的方式,但它的工作正常。



所以我添加更多的数据到我的清算助手:

  public static void ResetIdentity(string tableName)
{
using(var context = new EasyContext())
{
context .Database.ExecuteSqlCommand($DBCC CHECKIDENT('{tableName}',RESEED,0));
}
}

public static void DeleteTable(string tableName)
{
using(var context = new EasyContext())
{
context.Database.ExecuteSqlCommand($DELETE {tableName});
}
}

public static void DeleteTableAndResetIdentity(string tableName)
{
using(var context = new EasyContext())
{
context.Database.ExecuteSqlCommand($DELETE {tableName});
context.Database.ExecuteSqlCommand($DBCC CHECKIDENT('{tableName}',RESEED,0));
}
}

然后我将其添加到我的播种例程的清理部分:

  ClearingHelper.DeleteTable(dbo.PersonDescription); 
ClearingHelper.DeleteTableAndResetIdentity(dbo.Person);

这是不幸的,有两种方法可以这样做:


  1. 逐行删除更慢。

  2. 如果我向后迁移,我将不得不改变这一点。

但它有效!现在我可以对POCO进行规范化的模式进行更改,并且仍然运行一个播种例程。


Okay so I am using Entity Framework 6.1 and attempting code first with seeding. I have a drop always intializer that ALWAYS WORKS. However I want to use database migration which I have set up and it works. UNTIL I normalize out a table and then try to seed it I get a primary key error.

Basically in my context when I uncomment out the changes in the 'TODO' section and the schema changes I get a primary key violation when attempting population of the newly normalized out table. It will work for the initializer that does the drop always, but I want my migration data table and not to drop the database everytime I make changes in case I want to rollback ever. I have tried changing the attribute of the 'PersonId' to Identity and to None and back to Identity. So the caveat is if it is set to 'Identity' it will work but the values will keep incrementing to higher values each time 1,2,3,4 then 5,6,7,8;etc. If I set it to none it works the first time and then when it is split in the mapping and normalized it blows up. I have tried custom dbcc commands and it does not like that either, as even with setting dbcc to reseed with the two new tables it does not like it. It is as if it has no idea about seeding the new table when being done explicitly.

Does anyone know how to do a seeding process that the model can handle if you normalize out the mapping of an object to multiple tables? I am trying a bunch of different patterns and getting nowhere fast.

So POCO Object

public class Person
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int PersonId { get; set; }
        [Column(TypeName = "varchar")]
        [Required]
        [MaxLength(32)]
        public string FirstName { get; set; }
        [Column(TypeName = "varchar")]
        [Required]
        [MaxLength(32)]
        public string LastName { get; set; }

        [Column(TypeName = "varchar")]
        public string OverlyLongDescriptionField { get; set; }
}

Context for code First:

public class EasyContext : DbContext
    {
        public EasyContext() : base("name=EasyEntity")
        {
            //Database.SetInitializer<EasyContext>(new EasyInitializer());

            Database.SetInitializer(new MigrateDatabaseToLatestVersion<EasyContext, Migrations.Configuration>("EasyEntity"));
        }

        public DbSet<ProductOrder> ProductOrder { get; set; }
        public DbSet<Person> Person { get; set; }
        public DbSet<Product> Product { get; set; }
        public DbSet<Audit>  Backup { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.HasDefaultSchema("dbo");
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

            //TODO Let's normalize out a long descriptive field
            //modelBuilder.Entity<Person>()
            //.Map(m =>
            //{
            //    m.Properties(p => new { p.FirstName, p.LastName });
            //    m.ToTable("Person");
            //})
            //.Map(m =>
            //{
            //    m.Properties(p => new { p.OverlyLongDescriptionField });
            //    m.ToTable("PersonDescription");
            //});
        }
    }

Initializer for DropCreateAlways:

public class EasyInitializer : DropCreateDatabaseAlways<EasyContext>
    {
        protected override void Seed(EasyContext context)
        {
            SeedingValues.SeedingForDatabaseDrop(context);

            base.Seed(context);
        }
    }

Configuration for migrations:

internal sealed class Configuration : DbMigrationsConfiguration<EasyEntity.EasyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
            ContextKey = "EasyEntity.EasyContext";
        }

        protected override void Seed(EasyContext context)
        {
            SeedingValues.SeedingWithoutDatabaseDrop(context);

            base.Seed(context);
        }
    }

Base Seeding class:

internal static class SeedingValues
{
    public static void SeedingForDatabaseDrop(EasyContext context)
    {
        BaseSeed(context);
    }

    public static void SeedingWithoutDatabaseDrop(EasyContext context)
    {
        context.Person.ClearRange();

        BaseSeed(context);
    }

    private static void BaseSeed(EasyContext context)
    {
        IList<Person> persons = new List<Person>
        {
            new Person { PersonId = 1, FirstName = "Brett", LastName = "Guy", OverlyLongDescriptionField = "OMG Look I have a bunch of text denormalizing a table by putting a bunch of stuff only side related to the primary table." },
            new Person { PersonId = 2, FirstName = "Neil", LastName = "Person"},
            new Person { PersonId = 3, FirstName = "Ryan", LastName = "Other"},
            new Person { PersonId = 4, FirstName = "Aaron", LastName = "Dude"},
        };

        foreach (var person in persons)
            context.Person.AddOrUpdate(person);
    }
}

ClearingHelper

public static void ClearRange<T>(this DbSet<T> dbSet) where T : class
{
    using (var context = new EasyContext())
    {
       dbSet.RemoveRange(dbSet);
    }
 }

解决方案

Okay so the issue is with a newly created table being not populated and the old table being populated. So if I have following off of my example a POCO class 'Person' and have plurilization removed. Without any explicit mapping and just a DbSet will create a table Person. If I then do my mapping to split tables.

modelBuilder.Entity<Person>()
.Map(m =>
{
    m.Properties(p => new { p.PersonId, p.FirstName, p.LastName });
    m.ToTable("Person");
})
.Map(m =>
{
    m.Properties(p => new { p.PersonId, p.OverlyLongDescriptionField });
    m.ToTable("PersonDescription");
});

I get a Primary Key violation with my seeding process. This is due to if I look at the database newly updated it still is retaining the old table and created a new one. However it does not know how to remove the data with this method:

public static void ClearRange<T>(this DbSet<T> dbSet) where T : class
{
    using (var context = new EasyContext())
    {
        dbSet.RemoveRange(dbSet);
    }
}

Becase the set is partial. So I am thinking: "Well if my data for seeding is contained at this point and I need to alter it moving forward I can in theory just clean up the tables directly with SQL commands." This is not the approach I wanted per say but it does work.

So I add more data to my clearing helper:

public static void ResetIdentity(string tableName)
{
    using (var context = new EasyContext())
    {
        context.Database.ExecuteSqlCommand($"DBCC CHECKIDENT('{tableName}', RESEED, 0)");
    }
}

public static void DeleteTable(string tableName)
{
    using (var context = new EasyContext())
    {
        context.Database.ExecuteSqlCommand($"DELETE {tableName}");
    }
}

public static void DeleteTableAndResetIdentity(string tableName)
{
    using (var context = new EasyContext())
    {
        context.Database.ExecuteSqlCommand($"DELETE {tableName}");
        context.Database.ExecuteSqlCommand($"DBCC CHECKIDENT('{tableName}', RESEED, 0)");
    }
}

Then I add this to my Seeding routine's cleanup portion:

ClearingHelper.DeleteTable("dbo.PersonDescription");
ClearingHelper.DeleteTableAndResetIdentity("dbo.Person");

This is unfortunate in two ways to do it this way in that:

  1. It is slower doing a delete as it goes row by row.
  2. If I migrate backwards I will have to change this.

But it works! I can now have changes to the schema with normalizing out POCO's and still run a seeding routine.

这篇关于当使用迁移时,使用代码优先的模式更改时,种子数据将无法正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆