简体   繁体   English

EF Core:可以添加和保存父实体,但根本无法保存子实体

[英]EF Core: Can add and save parent entity, but fail to save child entities at all

I have a very basic scenario, outlined in the complete test case below.我有一个非常基本的场景,在下面的完整测试用例中进行了概述。 I am using .NET core 5.0.2, EF Core 5.0.3, running via vscode on win10.我正在使用 .NET core 5.0.2,EF Core 5.0.3,在 win10 上通过 vscode 运行。 I am working with nullable reference types enabled, as well (as the code below illustrates).我也在使用启用的可空引用类型(如下面的代码所示)。

If you jump all the way to the bottom and look at the unit test, it is failing based on the entities that should be added in the InjectRecords() method.如果您一直跳到底部并查看单元测试,则基于应在InjectRecords()方法中添加的实体,它失败了。 The Child entries are never added.永远不会添加子条目。

I have tried all combinations of "add child via the parent's navigation collection, add child via directly adding to DbSet and setting parent FK as object OR integer key" and it only works when I reconfigure the model to destroy all of the relationships between Parent and Child and keep things reduced to weak references.我已经尝试了“通过父级导航集合添加子级,通过直接添加到 DbSet 添加子级并将父级 FK 设置为 object 或 integer 键”的所有组合,它仅在我重新配置 Z20F35E630DAF44DBFA84C3F 之间的所有 F 关系时才有效孩子并将事情简化为弱引用。 There is obviously some very basic issue I am missing, because this isn't an interesting or expansive scenario at all.显然我遗漏了一些非常基本的问题,因为这根本不是一个有趣或广泛的场景。 Very basic.很基础。 I have also scoured google and SO for explanations, but have hit upon nothing to illustrate why this is not working.我还搜索了谷歌和 SO 以获得解释,但没有找到任何东西来说明为什么这不起作用。

Everything is in the test case below.一切都在下面的测试用例中。 On my system, when I run this, it fails starting at the second assertion.在我的系统上,当我运行它时,它从第二个断言开始失败。 I would expect that all three assertions would pass (Pulling at Parents, pulling at Childs from the Childs DbSet, accessing the Childs via the Parent they were added to in InjectRecords() ).我希望所有三个断言都会通过(拉动父母,从 Childs DbSet 拉动 Childs,通过在InjectRecords()中添加的 Parent 访问 Childs)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace TIL.Tests
{

  public class Parent
  {
    public int Id { get; set; }
    public ICollection<Child> Childs => new List<Child>();
  }
  public class Child
  {
    public int Id { get; set; }

    public int ParentId { get; set; }
    public Parent Parent { get; set; } = default!;
  }

  public class TestDbContext : DbContext
  {
    public TestDbContext(DbContextOptions<TestDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
      modelBuilder.Entity<Parent>()
        .HasMany(gd => gd.Childs)
        .WithOne(ge => ge.Parent);
    }

    public virtual DbSet<Child> Childs => Set<Child>();
    public virtual DbSet<Parent> Parents => Set<Parent>();
  }

  public static class TestDatabaseInitializer
  {
    public static string SqliteConnectionString = "DataSource=myshareddb;mode=memory;cache=shared";
    public static void ConfigureServices(IServiceCollection services)
    {
      var kaConn = new SqliteConnection(SqliteConnectionString);
      kaConn.Open();
      services.AddDbContext<TestDbContext>(options =>
      {
        options.UseSqlite(SqliteConnectionString);
      });
    }

    public async static Task DoCtxAction(DbContextOptions<TestDbContext> options, Func<TestDbContext, Task> action)
    {
      using (var ctx = new TestDbContext(options))
      {
        await action(ctx);
      }
    }

    public async static Task InjectRecords(IServiceProvider svcProvider)
    {
      var options = svcProvider.GetRequiredService<DbContextOptions<TestDbContext>>();
      await DoCtxAction(options, async (ctx) =>
      {
        await ctx.Database.EnsureCreatedAsync();

        var parent1 = new Parent { };
        var parent2 = new Parent { };
        ctx.Parents.AddRange(parent1, parent2);
        await ctx.SaveChangesAsync();
      });

      await DoCtxAction(options, async (ctx) =>
      {
        var parent1 = await ctx.Parents.FindAsync(1);
        var child1 = new Child
        {
        };
        var child2 = new Child
        {
        };
        parent1.Childs.Add(child1);
        parent1.Childs.Add(child2);
        await ctx.SaveChangesAsync();
      });
    }
  }


  public class DataLayerIntegrationTests
  {
    [Fact]
    public async Task DataLayerWorks()
    {
      var services = new ServiceCollection();
      TestDatabaseInitializer.ConfigureServices(services);
      using (var scope = services.BuildServiceProvider().CreateScope())
      {
        var serviceProvider = scope.ServiceProvider;
        try
        {
          await TestDatabaseInitializer.InjectRecords(serviceProvider);
        }
        catch (Exception e)
        {
          var f = e.Message;
        }

        var options = serviceProvider.GetRequiredService<DbContextOptions<TestDbContext>>();
        await TestDatabaseInitializer.DoCtxAction(options, async ctx => {
          var parents = await ctx.Parents.Include(x=>x.Childs).ToListAsync();
          Assert.Equal(2, parents.Count);

          var children = await ctx.Childs.ToListAsync();
          Assert.Equal(2, children.Count());

          Assert.Equal(2, parents.Where(x=>x.Id == 1).Single().Childs.Count());
        });
      }
    }
  }
}

Your expression-bodied property is creating a new empty List<Child> on every call.您的expression-bodied 属性在每次调用时都会创建一个新的空List<Child>
This这个

  public class Parent
  {
    public int Id { get; set; }
    public ICollection<Child> Childs => new List<Child>();
  }

is equivilent to相当于

public class Parent
{
    public int Id { get; set; }
    public ICollection<Child> Childs 
    { 
        get
        {
            return new List<Child>();
        }
     } 
}

And instead should be an auto-initialized read-only property :而应该是一个自动初始化的只读属性

public class Parent
{
    public int Id { get; set; }
    public ICollection<Child> Childs { get; } = new List<Child>();
}

Change the Parent model to -Parent model 更改为 -

public class Parent
{
    public Parent()
    {
        this.Childs = new List<Child>();
    }
    
    public int Id { get; set; }
    
    public ICollection<Child> Childs { get; set; }
}

so that the Childs collection gets initialized only once, during the Parent instantiation.这样在Parent实例化期间, Childs集合只被初始化一次。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 EF Core 在父实体保存时更新子实体 - EF Core upsert child entities upon parent entity save EF Core 在循环中更新实体和子实体 - 在第一次通过时保存提交整个列表的更改 - EF Core update entity and child entities in loop - save changes commiting entire list on first pass EF Core 通过 Id 或实体引用添加/保存 - EF Core add/save by Id OR entity reference 避免使用 EF Core 在子实体上附加父实体 - Avoid attaching parent entity on child entities using EF Core 如何使用已存在于 EF 核心中的子实体保存实体? - How to save an entity with a child entity which already exists in EF core? EF Core 3.1删除具有子关系的父实体+保存更改+在新父中重用相同的子ID =并发异常 - EF Core 3.1 Removing parent entity with child relations + save changes + reuse same child id in new parent = concurrent exception 无法在EF4中将父实体与现有子实体一起添加 - Unable to add parent entity with existing child entities in EF4 如何在 EF 中更新父实体时添加/更新子实体 - How to add/update child entities when updating a parent entity in EF 在NHibernate中的一个保存操作中向父实体添加实体列表 - Add list of entities to a parent entity in one save operation in NHibernate EF Core子实体自动添加到父级 - EF Core child entities automatically added to parent
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM