2

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();
    }
8
  • EF can only do updates if you have a unique primary key. Otherwise it has no way of knowing what is a duplicate or not. Commented Dec 17, 2016 at 14:01
  • Can we see how you call Addasync method please? Commented Dec 17, 2016 at 14:01
  • Why is the add method async? There's no point in that at all. Commented Dec 17, 2016 at 14:07
  • @SamAxe the the ShoppingCartItem has primary key. @DavidG either ways the EF is trying to add two records. Commented Dec 17, 2016 at 14:13
  • How do you call that AddNewCartItem? Commented Dec 17, 2016 at 14:14

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

10 Comments

Thank you for the clarification when async have to be used. But this does not solve the problem as I mentioned in the comments . I changed the Add method and the EF still tries to add second time the same record in the same SaveChanges() call.
@Kos - you're not perhaps adding the item to the ShoppingCartItems collection and via this.entities.Add(entity) are you? If not, would you show us the code for CompleteAsync() please (by editing the original question). Also show us where your DbContext is created. In general we can help you better if you show quite a bit more code that might be relevant.
I do not add the ShoppingCartItem to the Customer.ShoppingCartItems collection. The CompleteAsync() method looks like await this.dbContext.SaveChangesAsync();. I am injecting the DbContext in here services.AddTransient<ISmartStoreWorkData, SmartStoreWorkData>(); which is my UnitOfWork. Seems like that double records insert happens with tables that have many-to-many relationship.
You imply the primary key for ShoppingCartItem is the DB-generated ID, but in the fluent config (in your linked code) you actually set it to { CustomerId, ProductId }: builder.Entity<ShoppingCartItem>().HasKey(x => new { x.CustomerId, x.ProductId });. Can you confirm there is no other row in the table with that primary key?
|
2

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

7 Comments

In this case I am getting the second duplicated record inserted into the database.
Which DB engine are you using?
SQL Server Database. Just to clear that... duplicated record but with different Id.
Mmm... can only think of a bug at this point. I don't know... Are you using the latest EF Core? Check this answer I gave the other day
The latest version is 1.1.0, but I'm not sure if this what you are experiencing is a bug in previous EF Core versions, in the current version of EF or in your code :(, so I can't even tell you an upgrade will fix it
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.