1

Trying to perform an update on an entity with nested list. I keep getting this error no matter what I do. I've tried this: Updating Nested Objects in Entity Framework and this: entity framework update nested objects

Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.

"TypeName": "Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException"

Inserting a new row works just fine. But not update.

public class BuyGroup
{
    [Key]
    public Guid Id { get; set; }

    [Required]
    [Column(TypeName = "nvarchar(150)")]
    public string Name { get; set; }

    public virtual ICollection<Item> Items { get; set; } = new List<Item>();
}

public class Item
{
    [Key]
    public Guid Id { get; set; }

    [Required]
    [Column(TypeName = "nvarchar(100)")]
    public string Name { get; set; }

    public BuyGroup BuyGroup { get; set; }
}

Repository:

public async Task<Guid> Save(Entities.BuyGroup model)
{
    using (var dc = _dataContext())
    {
        // this is ok, i get existing item with Items collection populated
        var existing = await Get(model.Id); 

        if (existing != null)
        {
            existing.Name = model.Name;
            ... // overwrite properties

            existing.Items = model.Items; // overwrite Items colletion

            dc.BuyGroups.Update(existing).State = EntityState.Modified;
        } 
        else 
        {
            await dc.BuyGroups.AddAsync(model);
        }

        // blows up here when existing != null
        await dc.SaveChangesAsync();
    }
}

EDIT:

Adding Get() method

{
    using (var dc = _dataContext())
    {
        return await dc.BuyGroups.FirstOrDefaultAsync(x => x.Id == id);
    }
}

EDIT2:

Using the same context still does not solve my issue:

using (var dc = _dataContext())
{
    var existing = await dc.BuyGroups.FirstOrDefaultAsync(x => x.Id == id); // same context
    if (existing != null)
    {

        existing.Items.Add(new Item{ .....}):
        dc.ByGroups.Entry(existing).State = EntityState.Modified;


    } else {
        await dc.BuyGroups.AddAsync(model);
    }
    await dc.SaveChangesAsync();
}
ShaneKm
  • 20,823
  • 43
  • 167
  • 296
  • Does it work if you make `Items` non virtual? – mxmissile Jan 10 '20 at 16:47
  • nope :(. doesn't work – ShaneKm Jan 10 '20 at 16:51
  • Why do you need both 1) dc.BuyGroups.Update(existing).State = EntityState.Modified; 2) await dc.SaveChangesAsync(); – jdweng Jan 10 '20 at 16:55
  • Replace `dc.BuyGroups.Update(existing).State = EntityState.Modified;` with `dc.BuyGroups.Update(existing);` – TanvirArjel Jan 10 '20 at 16:55
  • tried that. didn't work – ShaneKm Jan 10 '20 at 16:56
  • Why load `existing`, why not just update `model`? – mxmissile Jan 10 '20 at 16:57
  • Actually `Database operation expected to affect 1 row(s) but actually affected 0 row(s)` error occurs when you are trying to updated an entity but the entity does not exist in the database. – TanvirArjel Jan 10 '20 at 16:59
  • I've tried this as @mxmissile suggested, without using existing and simply updating a model (as it has the right ID). model.Items = new List{new Item { Id = Guid.NewGuid(), Name = "Test" }}; dc.BuyGroups.Update(model); ..But I still get Database operation exception – ShaneKm Jan 10 '20 at 17:05
  • Update your code above, and show how `model` gets loaded. – mxmissile Jan 10 '20 at 17:10
  • Updated. It gets existing item with 1 item in Items collection. All i am trying to do is add or remove items from that collection via update – ShaneKm Jan 10 '20 at 17:22
  • Is it possible that due to the using statements that Getting the item under one _dataContext, disposing, and then trying to edit under another is causing the issue? Would it be possible to do all the operations under the same _dataContext? – ZaChickster Jan 10 '20 at 17:44
  • tried getting the item with CUrrent => using (var dc = _dataContext()) { var existing = await dc.BuyGroups.Include("Items").FirstOrDefaultAsync(x => x.Id == model.Id); – ShaneKm Jan 10 '20 at 17:54
  • @ShaneKm `existing.Items = model.Items;` That looks shady, are you sure the items you are updating are attached to the context? Maybe you're tricking ef since your putting them in the ICollection and they're not attached... – johnny 5 Jan 10 '20 at 18:03
  • @johnny5, they are showing up in 'existing.Items'. i simply add another row. – ShaneKm Jan 10 '20 at 18:34
  • @ShaneKm see my answer – johnny 5 Jan 10 '20 at 19:38

3 Answers3

2

You want to assign All the Items ins your model, to just one Entity in another table? This is a design error.

Also I'm not sure you can replace an entire collection in one entity. You have to clear them and add new items. You are assigning a DbSet to existing. Items, that's definitely impossible.

Edit Another thing: You create a new Context for 'existing' and you attach something from an other source. Maybe your Items come from another context. They need to be attached to this dc context.

Roman Marusyk
  • 23,328
  • 24
  • 73
  • 116
Holger
  • 2,446
  • 1
  • 14
  • 13
  • what do you mean not possible? How would you perform an update to a nested collection? – ShaneKm Jan 10 '20 at 16:54
  • What you do is, you throw the entire collection away, you put it actually in trash, with overwriting `existing.Items`. You can call Add/Remove to insert or delete something. Elements already in the collection you don't need to update, they are always uptodate - cause there are no two instances of them. Are you sure your nested collection is not up-to-date ? Cause if everything is well configured, this is done automaticly. You insert something in model.Items, and the Item will appear in all related entities. – Holger Jan 10 '20 at 17:02
2

I'm under the impression that since you're invoking a function _dataContext(), instead of calling to a protected instance of the just _dataContext, You're probably creating a new context in the get and save methods.

Since, get and save methods are using seperate DbContexts so when you existing.Items = model.Items; You're using items that are attached to a seperate context.

There are a lot of ways to solve this, but personally I would just create protected methods that accept the dbContext, so you don't have to worry about attaching the entities to the context.

protected BuyGroup GetImplementation(MyDbContext context, int id)
{
    return await context.BuyGroups.FirstOrDefaultAsync(x => x.Id == id);
}

Then in your save method you can just call instead:

var existing = await this.GetImplementation(dc, model.Id);

Edit For Edit

You're setting the new item to modified instead of added

existing.Items.Add(new Item{ .....}):
//You shouldn't do this for added items
//dc.ByGroups.Entry(existing).State = EntityState.Modified;
johnny 5
  • 19,893
  • 50
  • 121
  • 195
  • But I am using the same context for get. Please see Edit2. – ShaneKm Jan 10 '20 at 19:43
  • @ShaneKm you're overriding the added state to modified after – johnny 5 Jan 10 '20 at 19:46
  • still getting the same exception after commenting out Modified line state(). Also tried State = Added but that throws different exception – ShaneKm Jan 10 '20 at 21:38
  • @ShaneKm it sounds like you're attaching to the context multiple times. Each entity *must have the proper state when applying to the context. Please post your full code, without omitted details – johnny 5 Jan 11 '20 at 01:28
  • E.g see this [answer](https://stackoverflow.com/a/39462283/1938988) – johnny 5 Jan 11 '20 at 01:29
1

This is a long shot, I'm not liable if your PC explodes, try this:

public async Task<Guid> Save(Entities.BuyGroup model)
{
    using (var dc = _dataContext())
    {
        var existing = await Get(model.Id); 
        if (existing != null)
        {
            dc.ByGroups.Entry(model).State = EntityState.Modified;

        } else {
            await dc.BuyGroups.AddAsync(model);
        }
        await dc.SaveChangesAsync();
    }
}
anastaciu
  • 23,467
  • 7
  • 28
  • 53
  • doesn't work. I've tried add a new Item to Items collection like so: existing.Items.Add(new Item { Id = Guid.NewGuid(), Name = "Test"} ); then Entry(model).State = Modified. But I still get an exception. IF I remove Add(new item) it goes through and only updates simple props of BuyGroup class BUT NOT Items collection – ShaneKm Jan 10 '20 at 17:23
  • Well, too bad it didn't work, you can try the [MS docs page](https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-3.1&tabs=visual-studio) it has methods for ASP.NET Core Web API, specífically the PUT method might be helpful as it has some similarities with what you want to do. – anastaciu Jan 10 '20 at 21:24