【问题标题】:Entity Framework Code First AddOrUpdate method insert Duplicate valuesEntity Framework Code First AddOrUpdate 方法插入重复值
【发布时间】:2012-04-17 22:31:38
【问题描述】:

我有简单的实体:

public class Hall
{
    [Key]
    public int Id {get; set;}

    public string Name [get; set;}
}

然后在Seed 方法中我使用AddOrUpdate 来填充表格:

var hall1 = new Hall { Name = "French" };
var hall2 = new Hall { Name = "German" };
var hall3 = new Hall { Name = "Japanese" };

context.Halls.AddOrUpdate(
    h => h.Name,
    hall1,
    hall2,
    hall3
);

然后我在包管理控制台中运行:

Add-Migration Current
Update-Database

没关系:我在“Hall”表中有三行。但如果我再次在包管理控制台Update-Database 中运行,我已经有五行了:

Id  Name
1   French
2   Japaneese
3   German
4   French
5   Japanese

为什么?我认为应该是三排,而不是五排。我尝试使用Id 属性而不是Name,但这并没有什么不同。

更新:

此代码产生相同的结果:

var hall1 = new Hall { Id = 1, Name = "French" };
var hall2 = new Hall { Id = 2, Name = "German" };
var hall3 = new Hall { Id = 3, Name = "Japanese" };

context.Halls.AddOrUpdate(
                h => h.Id,
                hall1);

context.Halls.AddOrUpdate(
                h => h.Id,
                hall2);

context.Halls.AddOrUpdate(
                h => h.Id,
                hall3);

我还通过 nuget 安装了最新的 EntityFramework。

【问题讨论】:

  • 如果您对每条记录单独调用AddOrUpdate 会发生什么?你有5条记录很奇怪。这意味着它工作过一次,所以你一定有一些特别的东西。
  • @Ladislav Mrnka:单独通话也是如此。
  • 非常感谢。我已经看过这个页面了。不幸的是,我在这篇文章中没有发现任何有趣的东西可以解决我的问题。
  • 在下面查看我的答案。我花了几个小时调试这个,这是因为我通过覆盖我的上下文文件中的 SaveChanges() 方法来修改实体状态信息。完整解释如下。使用种子方法填充数据库不需要通过 Id 进行任何查找——重点是让事情变得简单,最后我能够通过查找除 Id 之外的属性名称来弄清楚如何使这项工作正常进行。我们不应该在这个种子方法中管理 Id 列。

标签: c# .net entity-framework-4


【解决方案1】:

好吧,我用这个把脸从键盘上敲了一个小时。如果您的表的 Id 字段是 Identity 字段,那么它将不起作用,因此请为 identifierExpression 使用不同的字段。我使用了 Name 属性,还从 new Hall {...} 初始化程序中删除了 Id 字段。

对 OPs 代码的这种调整对我有用,所以我希望它对某人有所帮助:

protected override void Seed(HallContext context)
{
    context.Halls.AddOrUpdate(
        h => h.Name,   // Use Name (or some other unique field) instead of Id
        new Hall
        {
            Name = "Hall 1"
        },
        new Hall
        {
            Name = "Hall 2"
        });

    context.SaveChanges();
}

【讨论】:

  • 它的工作方式与设计的一样。如果您将一个值插入到表中,则 SQL 会分配数据库中的下一个值。如果您不希望 SQL/EF 计算下一个要使用的密钥,那么您需要为您的实体关闭它。我已经在下面发布了一个答案来解释这一点。
  • 对我不起作用。您使用的是什么版本的 EF?
  • @Campinho EF 4.1 Code First 我记得
  • 如果您正在寻找一种方法来执行多列唯一行,请参阅此问题...stackoverflow.com/questions/35452497/…
【解决方案2】:

我知道这是一个老问题,但正确的答案是,如果您自己设置 id # 并且您想使用 AddOrUpdate 那么您需要告诉 EF/SQL 您不希望它生成 ID #.

modelBuilder.Entity<MyClass>().Property(p => p.Id)
    .HasDatabaseGeneratedOption(System.ComponentModel
    .DataAnnotations.Schema.DatabaseGeneratedOption.None); 

这样做的缺点是,当您插入一个新项目时,您需要设置它的 Id,所以如果这是在运行时动态完成的(而不是从种子数据中),那么您将需要计算出下一个 Id。 Context.MyClasses.Max(c=&gt;c.Id) + 1 效果很好。

【讨论】:

    【解决方案3】:

    此代码有效:

    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
    }
    
    protected override void Seed(HallContext context)
    {
        context.Halls.AddOrUpdate(
            h => h.Id,
            new Hall
            {
                Id = 1,
                Name = "Hall 1"
            },
            new Hall
            {
                Id = 2,
                Name = "Hall 2"
            });
    
        context.SaveChanges();
    }
    

    【讨论】:

    • 它可能有效,但它不正确。 ID 在服务器上生成。运行它,SQL 将使用 1 和 2 作为前两个 ID。然后删除这些行并再次运行,Id = 1 将被忽略,SQL 将在下一次插入它们时使用 ID=3、4。或者在上面的代码中将 Id 更改为 99、100。删除数据库,运行种子方法,它不会使用 99、100,但 SQL 将使用 1 和 2。此外,如果这些种子行被删除,每次种子运行都会再次插入相同的数据,因为 ID 不匹配.使用 Name 作为唯一字段要好得多。
    • 所以您建议将 Ciaran Bruen 的答案标记为正确?我现在无法检查它
    【解决方案4】:

    如果您错误地设置了实体状态,也可能会导致这种情况。当我运行 update-database 时,我不断收到以下错误...“序列包含多个匹配元素。”

    例如,我在每个 update-database 命令上创建了重复的行(这当然不应该在播种数据时发生),然后下一个 update-database 命令根本不起作用,因为它发现了更多比一个匹配(因此序列错误说我有不止一个匹配的行)。那是因为我在我的上下文文件中使用了对 ApplyStateChanges 的方法调用覆盖了 SaveChanges...

    public override int SaveChanges()
    {
        this.ApplyStateChanges();
        return base.SaveChanges();
    }
    

    我使用 ApplyStateChanges 来确保在添加对象图时,Entity Framework 明确知道对象是处于添加状态还是修改状态。关于我如何使用 ApplyStateChanges 的完整说明可以在 here 找到。

    这很好用(但需要注意!!)...如果您还使用 CodeFirst 迁移为数据库播种,那么上述方法将对 Seed 方法中的 AddOrUpdate() 调用造成严重破坏。因此,在其他任何事情之前,只需检查您的 DBContext 文件并确保您没有以上述方式覆盖 SaveChanges,否则您最终将获得第二次运行 update-database 命令的重复数据,然后根本无法工作第三次,因为每个匹配的项目不止一行。

    归根结底,您不需要在 AddOrUpdate() 中配置 Id...这违背了简单和初始数据库播种的整个目的。它可以通过以下方式正常工作:

    context.Students.AddOrUpdate(
        p => p.StudentName,
        new Student { StudentName = "Bill Peters" },
        new Student { StudentName = "Jandra Nancy" },
        new Student { StudentName = "Rowan Miller" },
        new Student { StudentName = "James O'Dalley" },
    

    只要我没有通过调用 ApplyStateChanges 覆盖上下文文件中的 SaveChanges 方法。希望这会有所帮助。

    【讨论】:

      【解决方案5】:

      这些步骤对我有用

      1. 删除表中的所有行。
      2. 将增量标识重置为 0。DBCC CHECKIDENT (yourtablename, RESEED, 0)Seed() 中指定的主键必须与数据库表中的主键匹配,以免重复。)
      3. 在“种子”方法中指定主键。
      4. 多次运行Seed() 方法,然后检查它们是否重复。

      【讨论】:

      • 我唯一的选择是在唯一的 Id 列上播种过滤,因为我的其他字段重复,所以这是不可能的。感谢您的回答!
      【解决方案6】:

      我发现AddOrUpdate 可以很好地处理不是 ID 的字段。如果这对你有用:context.Halls.AddOrUpdate(h =&gt; h.Name, hall1, hall2, hall3)

      您可能希望使用“French_test_abc_100”、“German_test_abc_100”等大厅名称。

      这可以防止硬编码的测试数据在您测试应用时搞砸。

      【讨论】:

        【解决方案7】:

        如果 object(hall) 的 id 为 0,则为插入。我认为您需要仔细检查大厅对象的 id 字段

        【讨论】:

        • 它应该可以工作,逻辑似乎是正确的。可能是缺少提交,或者您未显示的代码中的其他一些错误。可能会在新列中创建时间戳来确认这种情况。
        【解决方案8】:

        您的 ID 字段是身份字段吗?我遇到了同样的问题。当我从我的 ID 字段中删除身份状态并将 ID 设置到数据库中时,问题就解决了。

        这对我有用,因为这些是查找表,不应该是身份字段。

        【讨论】:

        • 是的,它是一个身份字段。如果表不为空,我添加了条件然后是种子表。
        【解决方案9】:

        我使用 ID 字段作为 Identity/Key 并添加属性而不是由服务器分配 Id。这解决了我的问题。

        public class Hall
        {
            [Key]
            [Required]
            [DatabaseGenerated(DatabaseGeneratedOption.None)]
            public int Id {get; set;}
        
            public string Name [get; set;}
         }
        

        【讨论】:

          【解决方案10】:

          就 Ciaren 的回答而言,以下重置 ModelCreating 上下文的代码帮助我解决了类似的问题。确保将“ApplicationContext”更改为您的 DbContext 名称。

          public class ApplicationContext : DbContext, IDbContext
              {
                  public ApplicationContext() : base("ApplicationContext")
                  {
                       
                  }
          
                  protected override void OnModelCreating(DbModelBuilder modelBuilder)
                  {
                      modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
                      Database.SetInitializer<ApplicationContext>(null);
                      base.OnModelCreating(modelBuilder);
                  }
               }

          【讨论】:

            【解决方案11】:

            我发现要让它工作,当种子第一次运行时,标识位置应该是 0。您可以使用以下方法重置它:

            DBCC CHECKIDENT (tableName, RESEED, 0)
            

            【讨论】:

              【解决方案12】:

              我认为您可能需要使用Update-Database TargetMigration:0 后跟Update-Database 之类的方式退出现有的数据库迁移(即从头开始启动数据库)。

              事实上,您不会删除现有的表或值,而只是添加/更新这些值。这需要发生才能获得您想要的结果。

              【讨论】:

                【解决方案13】:

                你也可以这样做:

                 context.Halls.AddOrUpdate(new Hall[]{hall1,hall2, hall3});
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2011-04-06
                  • 1970-01-01
                  • 2013-11-02
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多