[英]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.