Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Saving related entities does not work #19761

Closed
joaopgrassi opened this issue Jan 31, 2020 · 3 comments
Closed

Saving related entities does not work #19761

joaopgrassi opened this issue Jan 31, 2020 · 3 comments
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported

Comments

@joaopgrassi
Copy link
Member

joaopgrassi commented Jan 31, 2020

We've found a strange behavior while unit testing some code which saves related data as shown here: Saving Related Data. The principal entity holds a list of dependents, just like in the docs example.

Steps to reproduce

// Models
public class WorkItem
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public List<WorkItemComment> Comments { get; set; } = new List<WorkItemComment>();
}

public class WorkItemComment
{
    public Guid Id { get; set; }
    public string Comment { get; set; }
    public WorkItem Parent { get; set; }
    public Guid ParentId { get; set; }
}

I have this sample code for inserting one work item along with one comment:

var workItem = new WorkItem
{
    Id = Guid.NewGuid(),
    Title = "Some work item"
};

// Save the workItem first
wiContext.WorkItems.Add(workItem);
await wiContext.SaveChangesAsync();

var comment = new WorkItemComment
{
    Id = Guid.NewGuid(),
    Comment = "Some comment"
};

// Add the comment to the saved comment instance
workItem.Comments.Add(comment);

// State is unchanged
var workItemState = wiContext.Entry(workItem).State;

// PROBLEM: The comment has a state of Modified.
var state2 = wiContext.Entry(comment).State;

// Call fails with UPDATE comment instead of insert
await wiContext.SaveChangesAsync();

Odd, but okay. I then tried doing this: (simulated disconnected scenario)

var workItem = new WorkItem
{
    Id = Guid.NewGuid(),
    Title = "Some work item"
};

// Save the workItem first with the passed in DbContext
// Like a request to an endpoint "CreateWorkItem"
wiContext.WorkItems.Add(workItem);
await wiContext.SaveChangesAsync();

// simulates a "disconnected scenario" where I only want to add a comment
// Like a request to an endpoint "AddComment"
using (var newContext = new WorkItemDbContext())
{
    var existingWorkItem = await newContext.WorkItems.Include(wi => wi.Comments)
    	.FirstAsync(wi => wi.Id == workItem.Id);
    
    var comment = new WorkItemComment
    {
        Id = Guid.NewGuid(),
        Comment = "SOme comment",
    };
    
    existingWorkItem.Comments.Add(comment);
    
    // State is "Unchanged"
    var workItemState = newContext.Entry(existingWorkItem).State;
    
    // State is "Detached"
    var state2 = newContext.Entry(comment).State;
    
    // Fails as well
    await newContext.SaveChangesAsync();
}

In both cases I get:

Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: '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.'

   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected)
   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.<ConsumeResultSetWithoutPropagationAsync>d__6.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.<ConsumeAsync>d__2.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.<ExecuteAsync>d__29.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.<ExecuteAsync>d__29.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.<ExecuteAsync>d__8.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.<ExecuteAsync>d__8.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<SaveChangesAsync>d__97.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<SaveChangesAsync>d__101.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.<ExecuteAsync>d__7`2.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.EntityFrameworkCore.DbContext.<SaveChangesAsync>d__54.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at EFCore31.Program.<GenerateWorkItemWithComment3>d__3.MoveNext() in C:\github\efcore-childupdate-bugrepro\src\EFCore31\Program.cs:line 137
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at EFCore31.Program.<Main>d__0.MoveNext() in C:\github\efcore-childupdate-bugrepro\src\EFCore31\Program.cs:line 23

The only way it works (generating the new insert) is if I do this:

var workItem = new WorkItem
{
    Id = Guid.NewGuid(),
    Title = "Some work item"
};

var comment = new WorkItemComment
{
    Id = Guid.NewGuid(),
    Comment = "Some comment",
};

workItem.Comments.Add(comment);

// Add workitem to DbSet with comments
wiContext.WorkItems.Add(workItem);

// WORKS: Generates two inserts
await wiContext.SaveChangesAsync();

But of course, this is not great. The real scenario is more like having case1 or case2. Case 2 is even more real because it's the same as if I had two actions in a controller which handle both cases separately. Same as explained in the docs: Adding a related entity

The same code works on EF Core 2.2.6.

I have created a repro here: https://github.com/joaopgrassi/efcore-childupdate-bugrepro

It contains two console apps one using EF Core 2.2.6 and the other with EF Core 3.1.1. The code is the same. The 2.2.6 version works with 3 approaches above. The 3.1.1 just works with approach case 2

Further technical details

EF Core version: 3.1.1
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: NET Core 3.1
Operating system: Windows 10 1903
IDE: Visual Studio 2019 16.4.2

@joaopgrassi
Copy link
Member Author

I think it has to do with the type of the Id property. For instance, I just tried this sample from the docs (Blog/Post example) https://github.com/aspnet/EntityFramework.Docs/tree/master/samples/core/GetStarted
and out of the box the code works.

But then I changed the PostId to be of type Guid and when I do:

var post = new Post
{
    PostId = Guid.NewGuid(),
    Title = "Hello World",
    Content = "I wrote an app using EF Core!"
};

blog.Posts.Add(post);

// *** State here is Modified instead of Added
var postState = db.Entry(post);

Causing the same error again.

@AndriySvyryd
Copy link
Member

@joaopgrassi That's because you are assigning a value to a store-generated key, see https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#dc

@AndriySvyryd AndriySvyryd added closed-no-further-action The issue is closed and no further action is planned. and removed type-bug labels Feb 3, 2020
@joaopgrassi
Copy link
Member Author

Oh I overlooked that. Guess it makes sense. Thanks @AndriySvyryd for pointing it out.

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported
Projects
None yet
Development

No branches or pull requests

3 participants