【问题标题】:Entity Framework Core is trying to insert duplicated records when SaveChanges() is calledEntity Framework Core 在调用 SaveChanges() 时尝试插入重复的记录
【发布时间】:2016-12-19 11:02:30
【问题描述】:

我的实体是:

public class Customer
{
   ...
   public virtual ICollection<ShoppingCartItem> ShoppingCartItems { get; set; }
   ...
}

public class ShoppingCartItem
{
   public string CustomerId { get; set; }
   public int ProductId { get; set; }
   public virtual Customer { get; set; }
   public virtual Product{ get; set; }
   ...
}

添加方法是:

public async Task AddAsync(TEntity entity)
{
    await Task.Factory.StartNew(() => this.entities.Add(entity));
}

我要添加的实体是:

ShoppingCartItem()
{
    CustomerId = "xxxxx",
    ProductId = 1,
    Customer = null,
    Product = null 
}

当我调用 SaveChanges() 时,EF 试图为 ShoppingCartItem 插入两条相同的记录。 ShoppingCartItem 仅被创建一次并添加到上下文中。有什么想法可能是错的吗?

编辑:

这就是我调用 AddSync 方法的方式:

public async Task AddNewCartItem(ShoppingCartItem shopingCartItem)
    {
        await this.ShoppingCartItemRepository.AddAsync(shopingCartItem);
        await this.SmartStoreWorkData.CompleteAsync();
    }

【问题讨论】:

  • EF 只能在您拥有唯一主键的情况下进行更新。否则它无法知道什么是重复的。
  • 我们能看看你是怎么调用 Addasync 方法的吗?
  • 为什么add方法是异步的?这根本没有意义。
  • @SamAxe ShoppingCartItem 有主键。 @DavidG EF 尝试添加两条记录的任何一种方式。
  • 你怎么称呼那个 AddNewCartItem?

标签: c# entity-framework entity-framework-core


【解决方案1】:

DbContextnot thread safe。通过 - 毫无意义,如 cmets 中所述 - 在很可能是不同的线程中执行你的 .Add(),你会混淆 DbContextAdd() 纯粹是内存操作;没有理由尝试使其异步。改变它,我认为它会解决问题。

public void Add(TEntity entity)
{
    this.entities.Add(entity);
}

如果您有任何其他未在问题中显示的类似用法,请将其更改为同步。

可以DbContext 进行“适当的”异步,但这仅适用于实际与数据库对话的方法,而不是内存中的方法,并且通常不会涉及Task.&lt;anything&gt; , 只是提供的async 方法。

编辑:为了完整起见,上面的链接适用于 EF6,但在 EF Core 中,DbContext 也是 not thread-safe

【讨论】:

  • 感谢您在必须使用异步时进行澄清。但这并不能解决我在 cmets 中提到的问题。我更改了 Add 方法,EF 仍然尝试在同一 SaveChanges() 调用中第二次添加同一记录。
  • @Kos - 你可能不会通过this.entities.Add(entity) 将项目添加到ShoppingCartItems 集合 是吗?如果没有,请向我们展示CompleteAsync() 的代码(通过编辑原始问题)。还向我们展示您的DbContext 的创建位置。一般来说,如果您显示更多可能相关的代码,我们可以为您提供更好的帮助。
  • 我没有将ShoppingCartItem 添加到Customer.ShoppingCartItems 集合中。 CompleteAsync() 方法看起来像 await this.dbContext.SaveChangesAsync();。我在这里注入DbContext services.AddTransient&lt;ISmartStoreWorkData, SmartStoreWorkData&gt;(); 这是我的UnitOfWork。似乎双记录插入发生在具有多对多关系的表中。
  • 您暗示ShoppingCartItem 的主键是DB 生成的ID,但在流利的配置中(在您的链接代码中)您实际上将其设置为{ CustomerId, ProductId }builder.Entity&lt;ShoppingCartItem&gt;().HasKey(x =&gt; new { x.CustomerId, x.ProductId });。您能否确认表中没有具有该主键的其他行?
【解决方案2】:

更新:我做了以下事情:

  • 克隆了你的回购链接:SmartStoreNETCore
  • 使用新的 EF 工具迁移到 .NET Core 1.1(预览版4)
  • 添加了如下EF ModelBuilder中指定的配置
  • 应用迁移和更新数据库

命令:

dotnet ef --startup-project ../SmartStoreNetCore.Web/ migrations add ChangeShoppingCartItemKey
dotnet ef --startup-project ../SmartStoreNetCore.Web/ database update
  • 删除_Layout.cshtml中的以下重复标签

    &lt;script src="~/js/site.js" asp-append-version="true"&gt;&lt;/script&gt;

site.js 包含 添加到购物车 功能的点击事件处理程序

  • 启动了网站,一切都按预期工作,没有重复的购物车商品,并且数量按预期更新

总结

我可以完全确认在删除对site.js 的重复引用之前,以下方法被调用了两次

 public async Task AddNewCartItem(ShoppingCartItem shopingCartItem)
 {
     await this.ShoppingCartItemRepository.AddAsync(shopingCartItem);
     await this.SmartStoreWorkData.CompleteAsync();
 }

为什么你之前没有通过调试发现这个问题,这对我来说是个谜

EF 模型构建器

您的配置应如下所示:

builder.Entity<ShoppingCartItem>().HasKey(x => x.Id); // Notice this!
builder.Entity<ShoppingCartItem>().Property(x => x.Id).ValueGeneratedOnAdd(); // Also this!
builder.Entity<ShoppingCartItem>().HasOne(s => s.Customer).WithMany(b => b.ShoppingCartItems).OnDelete(DeleteBehavior.Restrict);
builder.Entity<ShoppingCartItem>().HasOne(s => s.Product).WithMany().OnDelete(DeleteBehavior.Restrict);

自动生成一个值不将其定义为主键

【讨论】:

  • 在这种情况下,我将第二条重复记录插入到数据库中。
  • 您使用的是哪个数据库引擎?
  • SQL Server 数据库。只是为了清除...重复的记录,但 ID 不同。
  • 嗯...此时只能想到一个bug。我不知道...您使用的是最新的 EF Core 吗? Check this answer I gave the other day
  • 最新版本是 1.1.0,但我不确定您遇到的是否是以前的 EF Core 版本、当前版本的 EF 或您的代码中的错误 :(,所以我什至不能告诉你升级会解决它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-29
  • 2021-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-14
  • 1970-01-01
相关资源
最近更新 更多