Entity Framework Core is trying to insert duplicated records when SaveChanges() is called

8.6k Views Asked by At

My Entities are:

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

The add method is:

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

The entity that I am adding is:

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

When I call SaveChanges() the EF is trying to insert two identical records for ShoppingCartItem. The ShoppingCartItem is created and added to the context only once. Any ideas what possibly could be wrong?

EDIT:

This is how I call the AddSync method:

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

There are 2 best solutions below

7
On BEST ANSWER

UPDATE: I did the following:

  • Cloned your repo link: SmartStoreNETCore
  • Migrated to .NET Core 1.1 with the new EF tooling (preview4)
  • Added the configuration as specified in EF ModelBuilder below
  • Applied Migration and updated database

Commands:

dotnet ef --startup-project ../SmartStoreNetCore.Web/ migrations add ChangeShoppingCartItemKey
dotnet ef --startup-project ../SmartStoreNetCore.Web/ database update
  • Removed the following duplicate tag in _Layout.cshtml

    <script src="~/js/site.js" asp-append-version="true"></script>

site.js contains the click event handler for the Add To Cart functionality

  • Started the site and everything worked as expected, no duplicate shopping cart items and the quantity is updated as expected

enter image description here

In summary

I can fully confirm the following method was being called twice before removing the duplicate reference to site.js:

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

It is a mistery to me why you didn't catch this before via debugging

EF ModelBuilder

Your configuration should look like:

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

Having a value generated automatically does not define it as the primary key

10
On

DbContext is not thread safe. By - pointlessly, as noted in the comments - performing your .Add() in what is quite likely a different thread, you're confusing the DbContext. Add() is purely an in-memory operation; there is no reason to try to make it async. Change that and I reckon it will resolve the issue.

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

If you have any other similar usages that are not shown in your question, change those to sync too.

You can do "proper" async with DbContext but it is only for the methods that actually talk to the database, not the in-memory ones, and will not usually involve Task.<anything>, just the provided async methods.

Edit: for completeness, the link above is for EF6, but in EF Core, DbContext is also not thread-safe.