EntityFramework.Docs icon indicating copy to clipboard operation
EntityFramework.Docs copied to clipboard

Saving a graph of entities doesn't work for depth greater than 1

Open litoj opened this issue 1 year ago • 7 comments

Type of issue

Code doesn't work

Description

I am trying to save a graph just like in the example shown. But there are multiple issues with the example:

  • whatever I do, it throws an error when saving the Post, because the executed INSERT has still BlogId=0
  • providing the Blog to the Post didn't help
  • using Sync/Async processing didn't change anything
  • the Post has many objects that are expected to be not null, yet aren't required to be set in constructor - this generates compilator warnings

Page URL

https://learn.microsoft.com/en-us/ef/core/saving/related-data#adding-a-graph-of-new-entities

Content source URL

https://github.com/dotnet/EntityFramework.Docs/blob/main/entity-framework/core/saving/related-data.md

Document Version Independent Id

c7517e7d-f011-a24c-53da-913771913998

Article author

@ajcvickers

litoj avatar Nov 02 '24 17:11 litoj

I tried to reproduce this, and I initially received the following error

Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert the value NULL into column 'Content', table 'EFDocs.dbo.Posts'; column does not allow nulls. UPDATE fails.

This occurs because I created a new project with Nullable reference types enabled. Reading the comment "this generates compilator warnings", I expect this is also the case for you.

To fix the example, either remove the <Nullable>enable</Nullable> config in your csproj file, or set the content of the posts.

using (var context = new BloggingContext())
{
    var blog = new Blog
    {
        Url = "http://blogs.msdn.com/dotnet",
        Posts = new List<Post>
        {
            new Post { Title = "Intro to C#", Content = string.Empty},
            new Post { Title = "Intro to VB.NET", Content = ""},
            new Post { Title = "Intro to F#", Content = "Intro to F#"},
        }
    };

    context.Blogs.Add(blog);
    context.SaveChanges();
}

If wanted, I can update the docs by assigning a value to the content properties.

timdeschryver avatar Nov 06 '24 17:11 timdeschryver

The primary issue is rather that there is no clarification what happens with the post.BlogId - does it just magically figure out it is from the object above? Also that part in particular did not work for me - I wasn't able to save it like that and instead it always threw an error about breaking FK constraint - precisely because the posts didn't get the BlogId set.

litoj avatar Nov 06 '24 17:11 litoj

@litoj it's normal that the entity has id 0, during the insert it should receive the appropriate id. For example, with Sql Server the Id column will be the Primary Key, which is auto incremented ([BlogId] [int] IDENTITY(1,1) NOT NULL)

timdeschryver avatar Nov 06 '24 19:11 timdeschryver

Yes, but how does the post.BlogId get set? Because in my tests it didn't and that's why I got the errors.

litoj avatar Nov 06 '24 19:11 litoj

Sorry @litoj , I don't know - normally, that should also work automatically.

timdeschryver avatar Nov 07 '24 07:11 timdeschryver

I resolved to using a workaround by saving each entity manually. After getting the parent entity ID by saving it, I only then added the child collection to it and then it worked.

After finding out only the root entity needs to get saved separately, I simplified the code to this:

// duplicate `entity` while duplicating all of its children (.Bundles and .Bundles.Slots)
// ...

        await DbContext.TestTemplate.AddAsync(entity);
        var bundles = entity.Bundles;
        entity.Bundles = [];
        await DbContext.SaveChangesAsync();
        entity.Bundles = bundles;
        await DbContext.SaveChangesAsync();

litoj avatar Nov 24 '24 17:11 litoj

In my previous comment, I actually am using this feature successfully, but only for the grandchildren (Bundles->Slots). It seems that it works fine for one layer of children, but doesn't cascade for multiple layers (Template->Bundles->Slots).

Is that maybe something that is planned to enable?

litoj avatar Jan 16 '25 09:01 litoj