实体框架代码第一个AddOrUpdate方法插入重复的值

本文关键字:插入 方法 框架 代码 第一个 AddOrUpdate 实体 | 更新日期: 2023-09-27 18:30:09

我有一个简单的实体:

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

一切都很好:我在大厅的桌子上有三排。但如果我再次在包管理控制台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方法插入重复的值

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

这个对操作系统代码的调整对我来说很有效,所以我希望它能帮助到一些人:

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

我知道这是一个老问题,但正确的答案是,如果你自己设置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=>c.Id) + 1运行良好。

此代码有效:

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

如果实体状态设置不正确,也可能导致此情况。当我运行更新数据库时,我一直收到以下错误。。。"序列包含多个匹配元素。"

例如,我在每个更新数据库命令上都创建了重复的行(当然,在播种数据时不应该发生这种情况),然后下一个更新数据库命令根本不起作用,因为它发现了多个匹配项(因此出现了序列错误,说我有多个匹配行)。这是因为我在上下文文件中用对ApplyStateChanges的方法调用重写了SaveChanges。。。

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

我使用ApplyStateChanges来确保在添加对象图时,实体框架明确地知道对象是处于添加状态还是修改状态。关于我如何使用ApplyStateChanges的完整解释可以在这里找到。

这很有效(但需要注意的是!!)。。。如果您还使用CodeFirst迁移对数据库进行种子设定,那么上述方法将对种子方法中的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方法。希望这能有所帮助。

这些步骤对我来说很有效

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

我发现AddOrUpdate可以很好地处理非ID字段。如果这对您有效:context.Halls.AddOrUpdate(h => h.Name, hall1, hall2, hall3)

您可能需要使用大厅名称,如"French_test_abc_100"、"German_test_abc_100"等。

这可以防止硬编码的测试数据在你测试应用程序时把事情搞砸。

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

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

这对我很有效,因为这些都是查找表,无论如何都不应该是身份字段。

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

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

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

我发现,为了实现这一点,当种子第一次运行时,标识位置应该是0。您可以使用重置

DBCC CHECKIDENT (tableName, RESEED, 0)

我认为您可能需要用Update-Database TargetMigration:0Update-Database之类的东西来退出现有的数据库迁移(即从头开始数据库)。

实际上,您并不是删除现有的表或值,而是添加/更新这些值。为了得到你想要的结果,这是必须的。

您也可以这样做:

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