如何表示同一类型的 parent 和多个 child 属性之间的关系?
How to represent a relationship between a parent and multiple child properties of the same type?
考虑以下两个实体*:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Child PrimaryChild { get; set; }
public virtual Child SecondaryChild { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
// Foreign keys to be added here or
// in the other class (that's what I'm trying to figure out)
}
如代码所示,parent 需要有一个主要的 child 和一个次要的 child;并且(最好)当 parent 被删除时,两个 children 也应该被删除。
甚至可以用 EF 表示这种关系吗?
如果 parent 只有一个 child,我可以通过转动 [=15] 的主键轻松使用 one-to-one 关系(1 to 0..1
) =] 分为 PK 和 FK。显然,这不适用于当前情况,因为一方面,一个主键不能有多个值。
感觉这种情况很常见,应该有一个 well-known 解决方案。然而,翻了无数帖子,还是没有找到。我能找到的最接近的东西是 但它不起作用,因为:
它创建 * to 0..1
关系。
当我忽略这个事实并尝试执行以下代码时:
var c1 = new Child { Name = "C1" };
var c2 = new Child { Name = "C2" };
context.Parents.Add(new Parent { Name = "P1", PrimaryChild = c1, SecondaryChild = c2 });
...它抛出 DbUpdateException 消息:
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
如何解决这个问题?
* 这是一个简化的例子。在现实生活场景中,parent 和其他 child 实体之间存在关系,每个实体都有两个或多个引用。
此代码已在 Visual Studio 中测试并且可以正常工作
var c1 = new Child { Name = "C1" };
var c2 = new Child { Name = "C2" };
_context.Parents.Add(new Parent { Name = "P1", PrimaryChild = c1, SecondaryChild = c2 });
var parents = _context.Parents
.Include(i => i.PrimaryChild)
.Include(i => i.SecondaryChild)
.ToList();
要尝试它,您必须将关系添加到您的 类
public class Parent
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int? PrimaryChildId { get; set; }
public int? SecondaryChildId { get; set; }
[ForeignKey(nameof(PrimaryChildId))]
[InverseProperty("PrimaryChildParents")]
public virtual Child PrimaryChild { get; set; }
[ForeignKey(nameof(SecondaryChildId))]
[InverseProperty("SecondaryChildParents")]
public virtual Child SecondaryChild { get; set; }
}
public class Child
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[InverseProperty(nameof(Parent.PrimaryChild))]
public virtual ICollection<Parent> PrimaryChildParents { get; set; }
[InverseProperty(nameof(Parent.SecondaryChild))]
public virtual ICollection<Parent> SecondaryChildParents { get; set; }
}
如果你想一对一,因为你使用的是 EF6
你可以给外键添加一些约束
[Index("IX_Parents_PrimaryChildId", 1, IsUnique = true)]
public int? PrimaryChildId { get; set; }
[Index("IX_Parents_SecondaryChildId", 2, IsUnique = true)]
public int? SecondaryChildId { get; set; }
我想出了一个很糟糕的解决方案,但我仍然希望有一个更好的解决方案。
而不是 one-to-one 关系,我创建了一个很好的旧 one-to-many 关系(Parent
有很多 Child
)在 属性 =14=] class 存储类型 (primary/secondary),并在 Parent
class 中创建未映射的属性以替换原始导航属性。
下面是问题示例的代码:
Child
class:
public enum ChildType { Primary, Secondary }
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public ChildType ChildType { get; set; }
[ForeignKey("Parent")]
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
Parent
class:
public class Parent
{
public Parent()
{
Children = new HashSet<Child>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Child> Children { get; set; }
[NotMapped]
public Child PrimaryChild
{
get
{
return Children.SingleOrDefault(c => c.ChildType == ChildType.Primary);
}
set
{
var existingChild =
Children.SingleOrDefault(c => c.ChildType == ChildType.Primary);
if (existingChild != null) Children.Remove(existingChild);
Children.Add(value);
}
}
[NotMapped]
public Child SecondaryChild
{
get
{
return Children.SingleOrDefault(c => c.ChildType == ChildType.Secondary);
}
set
{
var existingChild =
Children.SingleOrDefault(c => c.ChildType == ChildType.Secondary);
if (existingChild != null) Children.Remove(existingChild);
Children.Add(value);
}
}
}
用法:
var c1 = new Child { Name = "C1", ChildType = ChildType.Primary };
var c2 = new Child { Name = "C2", ChildType = ChildType.Secondary };
context.Parents.Add(new Parent { Name = "P1", PrimaryChild = c1, SecondaryChild = c2 });
显然,这不会强制执行 parent 可以拥有的 children 的最小或最大数量,或者它们必须是 ChildType
的数量。不过这是我能想到的最好的了。
创建此类关系的方法有两种:
- Parent有两个外键指向各自的children,接近你所做的。
- Children 有指向 parent 的外键,这将是一个简单的一对多。
第一个选项很容易将一个parent限制为两个child人,并且很容易区分主次child。
第二个选项,如果需要区分主从,则需要在child objects中添加类型。使用 parent 的 ID 和类型作为唯一索引将允许您将 children 限制为单个主 child 每个 parent.
现在就删除 parent 而言,如果您对关系进行级联删除,它将自动发生。我知道在第二个选项中,简单的 one-to-many,级联删除将按预期工作。第一个选项,基本上是两个 one-to-one 关系,你可以设置级联删除,但我怀疑你需要确保它是一个 one-way 级联。例如如果 parent 被删除,children 也会被删除,但是如果 child 被删除,parent 应该保留。
我倾向于 children 具有外键和类型。所以沿着这些方向:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
[Index("IX_Child_Parent_Type", 1, IsUnique = true)]
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
[Index("IX_Child_Parent_Type", 2, IsUnique = true)]
public int ChildTypeId { get; set; }
public virtual ChildType Type { get; set; }
}
public class ChildType
{
public int Id { get; set; }
public string Name { get; set; }
}
另一个选项是这样的:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Child PrimaryChild { get; set; }
public virtual Child SecondaryChild { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
}
对实体进行一些小修改并使用流畅的api来指定一对多关系
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IEnumerable<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsPrimary {get; set;}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasKey(p => p.Id)
.WithMany(p => p.Child)
.HasForeignKey(s => s.ParentId);
}
public DbSet<Child> Children{ get; set; }
试试这个方法,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp2
{
// this class will represent a person, a parent or a child since they have the same attributes
class Person
{
protected int _id;
protected string _name;
public int Id { get => _id; }
public string Name { get => _name; }
// Any object wether it's a parent or a child must be instantiated with those properties
public Person(int id, string name)
{
this._id = id;
this._name = name;
}
}
// this represents a parent which is a persone but have two persons reprenseting it's children. the children can't be instanciated alone.
class Parent : Person
{
private Person _primaryChild;
private Person _secondaryChild;
public Person PrimaryChild { get => _primaryChild; }
public Person SecondaryChild { get => _secondaryChild; }
// this creates the parent with it's children. one the parent dispose its children will be deleted.
public Parent(Person parent, Person primaryChild, Person secondaryChild) : base( parent.Id, parent.Name)
{
// this primaryChild enforce that a parent must have two different children to represents a primary and a secondry child.
if(primaryChild.Id != secondaryChild.Id)
{
this._id = parent.Id;
this._name = parent.Name;
this._primaryChild = primaryChild;
this._secondaryChild = secondaryChild;
}
else
{
throw new Exception("Children must not be tweens.");
}
}
}
class Program
{
static void Main(string[] args)
{
// instanciating a person does'nt represent an actor in the process. A parent must be instianciated.
try
{
// creating a new parent with its related children
var parent = new Parent(new Person(1, "Parent"), new Person(1, "ChildOne"), new Person(2, "ChildTwo"));
// diplaying children names
Console.WriteLine($"Primary Child is {parent.PrimaryChild.Name}. Secondary Child is {parent.SecondaryChild.Name}.");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
子 class 应包含父 class 的外键,在本例中,我为一对一关系使用了复合键。
public class Child
{
[Key]
[Column(Order = 1)]
public int Id { get; set; }
public string Name { get; set; }
[Key, ForeignKey("Parent")]
[Column(Order = 2)]
public int ParentId { get; set; }
}
然后在上下文的 OnModelCreating 中设置约束。
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Parent>()
.HasRequired(e => e.PrimaryChild)
.WithRequiredPrincipal(e => e.Parent);
modelBuilder.Entity<Parent>()
.HasRequired(e => e.SecondaryChild)
.WithRequiredPrincipal(e => e.Parent);
modelBuilder.Entity<Child>()
.HasKey(e => e.ParentId);
}
您可以根据需要扩展它。
考虑以下两个实体*:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Child PrimaryChild { get; set; }
public virtual Child SecondaryChild { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
// Foreign keys to be added here or
// in the other class (that's what I'm trying to figure out)
}
如代码所示,parent 需要有一个主要的 child 和一个次要的 child;并且(最好)当 parent 被删除时,两个 children 也应该被删除。
甚至可以用 EF 表示这种关系吗?
如果 parent 只有一个 child,我可以通过转动 [=15] 的主键轻松使用 one-to-one 关系(1 to 0..1
) =] 分为 PK 和 FK。显然,这不适用于当前情况,因为一方面,一个主键不能有多个值。
感觉这种情况很常见,应该有一个 well-known 解决方案。然而,翻了无数帖子,还是没有找到。我能找到的最接近的东西是
它创建
* to 0..1
关系。当我忽略这个事实并尝试执行以下代码时:
var c1 = new Child { Name = "C1" }; var c2 = new Child { Name = "C2" }; context.Parents.Add(new Parent { Name = "P1", PrimaryChild = c1, SecondaryChild = c2 });
...它抛出 DbUpdateException 消息:
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
如何解决这个问题?
* 这是一个简化的例子。在现实生活场景中,parent 和其他 child 实体之间存在关系,每个实体都有两个或多个引用。
此代码已在 Visual Studio 中测试并且可以正常工作
var c1 = new Child { Name = "C1" };
var c2 = new Child { Name = "C2" };
_context.Parents.Add(new Parent { Name = "P1", PrimaryChild = c1, SecondaryChild = c2 });
var parents = _context.Parents
.Include(i => i.PrimaryChild)
.Include(i => i.SecondaryChild)
.ToList();
要尝试它,您必须将关系添加到您的 类
public class Parent
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int? PrimaryChildId { get; set; }
public int? SecondaryChildId { get; set; }
[ForeignKey(nameof(PrimaryChildId))]
[InverseProperty("PrimaryChildParents")]
public virtual Child PrimaryChild { get; set; }
[ForeignKey(nameof(SecondaryChildId))]
[InverseProperty("SecondaryChildParents")]
public virtual Child SecondaryChild { get; set; }
}
public class Child
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[InverseProperty(nameof(Parent.PrimaryChild))]
public virtual ICollection<Parent> PrimaryChildParents { get; set; }
[InverseProperty(nameof(Parent.SecondaryChild))]
public virtual ICollection<Parent> SecondaryChildParents { get; set; }
}
如果你想一对一,因为你使用的是 EF6 你可以给外键添加一些约束
[Index("IX_Parents_PrimaryChildId", 1, IsUnique = true)]
public int? PrimaryChildId { get; set; }
[Index("IX_Parents_SecondaryChildId", 2, IsUnique = true)]
public int? SecondaryChildId { get; set; }
我想出了一个很糟糕的解决方案,但我仍然希望有一个更好的解决方案。
而不是 one-to-one 关系,我创建了一个很好的旧 one-to-many 关系(Parent
有很多 Child
)在 属性 =14=] class 存储类型 (primary/secondary),并在 Parent
class 中创建未映射的属性以替换原始导航属性。
下面是问题示例的代码:
Child
class:
public enum ChildType { Primary, Secondary }
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public ChildType ChildType { get; set; }
[ForeignKey("Parent")]
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
Parent
class:
public class Parent
{
public Parent()
{
Children = new HashSet<Child>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Child> Children { get; set; }
[NotMapped]
public Child PrimaryChild
{
get
{
return Children.SingleOrDefault(c => c.ChildType == ChildType.Primary);
}
set
{
var existingChild =
Children.SingleOrDefault(c => c.ChildType == ChildType.Primary);
if (existingChild != null) Children.Remove(existingChild);
Children.Add(value);
}
}
[NotMapped]
public Child SecondaryChild
{
get
{
return Children.SingleOrDefault(c => c.ChildType == ChildType.Secondary);
}
set
{
var existingChild =
Children.SingleOrDefault(c => c.ChildType == ChildType.Secondary);
if (existingChild != null) Children.Remove(existingChild);
Children.Add(value);
}
}
}
用法:
var c1 = new Child { Name = "C1", ChildType = ChildType.Primary };
var c2 = new Child { Name = "C2", ChildType = ChildType.Secondary };
context.Parents.Add(new Parent { Name = "P1", PrimaryChild = c1, SecondaryChild = c2 });
显然,这不会强制执行 parent 可以拥有的 children 的最小或最大数量,或者它们必须是 ChildType
的数量。不过这是我能想到的最好的了。
创建此类关系的方法有两种:
- Parent有两个外键指向各自的children,接近你所做的。
- Children 有指向 parent 的外键,这将是一个简单的一对多。
第一个选项很容易将一个parent限制为两个child人,并且很容易区分主次child。
第二个选项,如果需要区分主从,则需要在child objects中添加类型。使用 parent 的 ID 和类型作为唯一索引将允许您将 children 限制为单个主 child 每个 parent.
现在就删除 parent 而言,如果您对关系进行级联删除,它将自动发生。我知道在第二个选项中,简单的 one-to-many,级联删除将按预期工作。第一个选项,基本上是两个 one-to-one 关系,你可以设置级联删除,但我怀疑你需要确保它是一个 one-way 级联。例如如果 parent 被删除,children 也会被删除,但是如果 child 被删除,parent 应该保留。
我倾向于 children 具有外键和类型。所以沿着这些方向:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
[Index("IX_Child_Parent_Type", 1, IsUnique = true)]
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
[Index("IX_Child_Parent_Type", 2, IsUnique = true)]
public int ChildTypeId { get; set; }
public virtual ChildType Type { get; set; }
}
public class ChildType
{
public int Id { get; set; }
public string Name { get; set; }
}
另一个选项是这样的:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Child PrimaryChild { get; set; }
public virtual Child SecondaryChild { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
}
对实体进行一些小修改并使用流畅的api来指定一对多关系
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IEnumerable<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsPrimary {get; set;}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasKey(p => p.Id)
.WithMany(p => p.Child)
.HasForeignKey(s => s.ParentId);
}
public DbSet<Child> Children{ get; set; }
试试这个方法,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp2
{
// this class will represent a person, a parent or a child since they have the same attributes
class Person
{
protected int _id;
protected string _name;
public int Id { get => _id; }
public string Name { get => _name; }
// Any object wether it's a parent or a child must be instantiated with those properties
public Person(int id, string name)
{
this._id = id;
this._name = name;
}
}
// this represents a parent which is a persone but have two persons reprenseting it's children. the children can't be instanciated alone.
class Parent : Person
{
private Person _primaryChild;
private Person _secondaryChild;
public Person PrimaryChild { get => _primaryChild; }
public Person SecondaryChild { get => _secondaryChild; }
// this creates the parent with it's children. one the parent dispose its children will be deleted.
public Parent(Person parent, Person primaryChild, Person secondaryChild) : base( parent.Id, parent.Name)
{
// this primaryChild enforce that a parent must have two different children to represents a primary and a secondry child.
if(primaryChild.Id != secondaryChild.Id)
{
this._id = parent.Id;
this._name = parent.Name;
this._primaryChild = primaryChild;
this._secondaryChild = secondaryChild;
}
else
{
throw new Exception("Children must not be tweens.");
}
}
}
class Program
{
static void Main(string[] args)
{
// instanciating a person does'nt represent an actor in the process. A parent must be instianciated.
try
{
// creating a new parent with its related children
var parent = new Parent(new Person(1, "Parent"), new Person(1, "ChildOne"), new Person(2, "ChildTwo"));
// diplaying children names
Console.WriteLine($"Primary Child is {parent.PrimaryChild.Name}. Secondary Child is {parent.SecondaryChild.Name}.");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
子 class 应包含父 class 的外键,在本例中,我为一对一关系使用了复合键。
public class Child
{
[Key]
[Column(Order = 1)]
public int Id { get; set; }
public string Name { get; set; }
[Key, ForeignKey("Parent")]
[Column(Order = 2)]
public int ParentId { get; set; }
}
然后在上下文的 OnModelCreating 中设置约束。
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Parent>()
.HasRequired(e => e.PrimaryChild)
.WithRequiredPrincipal(e => e.Parent);
modelBuilder.Entity<Parent>()
.HasRequired(e => e.SecondaryChild)
.WithRequiredPrincipal(e => e.Parent);
modelBuilder.Entity<Child>()
.HasKey(e => e.ParentId);
}
您可以根据需要扩展它。