是否可以通过 Fluent API 建立这种关系?
Is it possible to set up this relationship via the Fluent API?
我有两个代码优先实体,Package 和 PackageEntry,我在 EF Core 中设置时遇到了问题。
我正在尝试使用代码优先实体和 Fluent API 实现以下目标:
- 一个包可以包含任意数量的包条目
- 每个 PackageEntry 都有对单个包实体的引用(包的不同实例,与包含 PackageEntries 集合的父包引用无关)
两个实体:
public class Package{
public Package()
{
_packageEntries = new List<PackageEntry>();
}
//trimmed other properties
private readonly List<PackageEntry> _packageEntries;
[NotMapped]
public IReadOnlyCollection<PackageEntry> PackageEntries => _packageEntries.ToList().AsReadOnly();
}
和
public class PackageEntry
{
public int DisplayOrder { get; set; }
public int PackageID { get; set; }
public Package Package { get; set; }
public int Quantity { get; set; }
public Package ParentPackage { get; set; }
public int ParentPackageID { get; set; }
}
我目前使用 Fluent API 时无法使用的是:
modelBuilder.Entity<Package>().HasMany(x => x.PackageEntries).WithOne();
modelBuilder.Entity<PackageEntry>().HasOne(x => x.Package).WithOne().HasForeignKey(typeof(PackageEntry), "PackageID");
它没有抛出错误,但我看到的是,当将 PackageEntry 添加到包中时,在上下文中调用 SaveChanges 时它没有被保存。
我是不是用 Fluent API 或其他东西做错了什么?
编辑
我错过了将顶级包添加到上下文中,一旦完成,添加到其中的包条目将被保存。我仍然会感谢对 Fluent API 设置和任何最佳实践的评论。
从 PackageEntry 实体,我需要知道父包和包含的包,它们将是对同一类型的单独引用。我似乎无法设置它使用 Fluent API,当通过 EF 加载父包时,它不包含任何 PackageEntry 对象,即使它们的 ParentPackageID 设置正确。
您的 Package.PackageEntries
collection 标记为 [NotMapped]
,并且没有 setter。无论如何,EntityFramework 都不会接受它。
我从未尝试过将 IReadonlyCollection<T>
与 EntityFramework 一起使用,但我想 EF 也不会喜欢那样。
您的第一个尝试应该是删除属性并像这样排列 属性:
public virtual IReadOnlyCollection<PackageEntry> PackageEntries {
get {
return _packageEntries.ToList().AsReadonly();
}
protected internal set {
_packageEntries = value;
}
}
当然,这需要您从私有成员变量中删除 readonly
。
也就是说,我不确定 EF 是否有最终分配给 属性 的内部列表,但我想它只会调用 Add()
上的方法collection(这就是为什么您的属性必须是 ICollection<T>
而不是 IEnumerable<T>
。
因此,如果这一切仍然无效,您应该制作 _packageEntries
protected internal
并将其用作您的 EF collection。那你只能像现在这样公开曝光你的PackageEntries
。
根据 EF 专家的一些离线建议,我通过删除 PackageEntry.Package 的导航 属性 并简单地手动处理该包实体的外键来解决这个问题。
一旦我这样做了,现在当加载父包实体时,它会正确加载子包条目。
因此,PackageEntry class 现在看起来像这样:
public class PackageEntry
{
public int DisplayOrder { get; set; }
public int PackageID { get; set; }
//public Package Package { get; set; } //Handle manually
public int Quantity { get; set; }
public Package ParentPackage { get; set; }
public int ParentPackageID { get; set; }
}
和 Fluent API 代码:
navigation = builder.Metadata.FindNavigation(nameof(Package.PackageEntries));
//EF access the PackageEntries collection property through its backing field
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
modelBuilder.Entity<Package>().HasMany(x => x.PackageEntries)
.WithOne("ParentPackage")
.HasForeignKey(nameof(PackageEntry.ParentPackageID))
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
我有两个代码优先实体,Package 和 PackageEntry,我在 EF Core 中设置时遇到了问题。
我正在尝试使用代码优先实体和 Fluent API 实现以下目标:
- 一个包可以包含任意数量的包条目
- 每个 PackageEntry 都有对单个包实体的引用(包的不同实例,与包含 PackageEntries 集合的父包引用无关)
两个实体:
public class Package{
public Package()
{
_packageEntries = new List<PackageEntry>();
}
//trimmed other properties
private readonly List<PackageEntry> _packageEntries;
[NotMapped]
public IReadOnlyCollection<PackageEntry> PackageEntries => _packageEntries.ToList().AsReadOnly();
}
和
public class PackageEntry
{
public int DisplayOrder { get; set; }
public int PackageID { get; set; }
public Package Package { get; set; }
public int Quantity { get; set; }
public Package ParentPackage { get; set; }
public int ParentPackageID { get; set; }
}
我目前使用 Fluent API 时无法使用的是:
modelBuilder.Entity<Package>().HasMany(x => x.PackageEntries).WithOne();
modelBuilder.Entity<PackageEntry>().HasOne(x => x.Package).WithOne().HasForeignKey(typeof(PackageEntry), "PackageID");
它没有抛出错误,但我看到的是,当将 PackageEntry 添加到包中时,在上下文中调用 SaveChanges 时它没有被保存。
我是不是用 Fluent API 或其他东西做错了什么?
编辑 我错过了将顶级包添加到上下文中,一旦完成,添加到其中的包条目将被保存。我仍然会感谢对 Fluent API 设置和任何最佳实践的评论。
从 PackageEntry 实体,我需要知道父包和包含的包,它们将是对同一类型的单独引用。我似乎无法设置它使用 Fluent API,当通过 EF 加载父包时,它不包含任何 PackageEntry 对象,即使它们的 ParentPackageID 设置正确。
您的 Package.PackageEntries
collection 标记为 [NotMapped]
,并且没有 setter。无论如何,EntityFramework 都不会接受它。
我从未尝试过将 IReadonlyCollection<T>
与 EntityFramework 一起使用,但我想 EF 也不会喜欢那样。
您的第一个尝试应该是删除属性并像这样排列 属性:
public virtual IReadOnlyCollection<PackageEntry> PackageEntries {
get {
return _packageEntries.ToList().AsReadonly();
}
protected internal set {
_packageEntries = value;
}
}
当然,这需要您从私有成员变量中删除 readonly
。
也就是说,我不确定 EF 是否有最终分配给 属性 的内部列表,但我想它只会调用 Add()
上的方法collection(这就是为什么您的属性必须是 ICollection<T>
而不是 IEnumerable<T>
。
因此,如果这一切仍然无效,您应该制作 _packageEntries
protected internal
并将其用作您的 EF collection。那你只能像现在这样公开曝光你的PackageEntries
。
根据 EF 专家的一些离线建议,我通过删除 PackageEntry.Package 的导航 属性 并简单地手动处理该包实体的外键来解决这个问题。
一旦我这样做了,现在当加载父包实体时,它会正确加载子包条目。
因此,PackageEntry class 现在看起来像这样:
public class PackageEntry
{
public int DisplayOrder { get; set; }
public int PackageID { get; set; }
//public Package Package { get; set; } //Handle manually
public int Quantity { get; set; }
public Package ParentPackage { get; set; }
public int ParentPackageID { get; set; }
}
和 Fluent API 代码:
navigation = builder.Metadata.FindNavigation(nameof(Package.PackageEntries));
//EF access the PackageEntries collection property through its backing field
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
modelBuilder.Entity<Package>().HasMany(x => x.PackageEntries)
.WithOne("ParentPackage")
.HasForeignKey(nameof(PackageEntry.ParentPackageID))
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);