C#EF6:相同类型的两个导航属性

时间:2017-08-30 22:50:41

标签: c# entity-framework-6

简单地说,在C#EF6中,如何将两个导航属性映射到同一个表,同时保持结果集分开?用简单的英语,我有一个班级,我想要在另一个班级的两个集合。换句话说,我想要两个相同类型但具有不同元素的集合。不幸的是,EF6似乎对两个集合都是一样的,并且给它们两个相同的元素(表中的每个记录)。

我从几十个StackOverflow答案中找到的最好的是这个,但它有问题描述。在这个例子中,有许多儿子和许多女儿,他们每人都有一个。理想情况下, Sons Daughters 都可以存储在同一个表 Child 中。

class Father
{
    [Key]
    public long Id { get; set; }

    public virtual ICollection<Child> Sons { get; set; }

    public virtual ICollection<Child> Daughters { get; set; }
}

class Child
{
    [Key]
    public long Id { get; set; }

    public long FatherId_1 { get; set; }  

    public Father Father_1 { get; set; }

    public long FatherId_2 { get; set; }  // One for each collection???

    public Father Father_2 { get; set; }
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Child>()
            .HasRequired(e => e.Father_1)
            .WithMany(e => e.Sons)
            .HasForeignKey(e => e.FatherId_1);
    modelBuilder.Entity<Child>()
            .HasRequired(e => e.Father_2)
            .WithMany(e => e.Daughters)
            .HasForeignKey(e => e.FatherId_2);
}

问题在于,当从儿童表中读取数据时,它不区分儿子女儿。也就是说, Sons 集合不仅包含 Sons ,还包含 Daughters Daughters 集合也是如此。我可能期望EF6尝试使用鉴别器列,但事实并非如此。

问题:如何将两个导航属性映射到同一个表,并且仍然能够将其记录读回相应的导航属性?或者,示例是否正确,我的问题在其他地方?或者,这是不可能的,并且它们需要映射到它们自己的表(具有相同的模式)。

4 个答案:

答案 0 :(得分:1)

我有点困惑,你的解释。但我找到了一些我理解的代码:

对于&#34;它是一个模型&#34; Child&#34;需要分成两个集合&#34; :

 modelBuilder.Entity<Child>()
.Map(m =>
  {
    m.Properties(t => new { t.Id /*other props*/ });
    m.ToTable("Sons");
  })
.Map(m =>
  {
    m.Properties(t => new { t.Id /*other props*/});
    m.ToTable("Daughters");
  });

答案 1 :(得分:0)

我找到了一种使用domain object backed by a state object解决此问题的方法。基本上,您使用状态对象以EF喜欢的方式存储数据,而您的域对象是将数据公开给应用程序其余部分的对象。例如:

public class Father
{
    //--- Constructor ---

    internal Father(FatherState state)
    {
        State = state;
    }

    //--- Properties ---

    public long Id => State.Id;

    public IList<Child> Sons => Children.Where(child => child.Type == ChildType.Son).ToList().AsReadOnly();

    public IList<Child> Daughters => Children.Where(child => child.Type == ChildType.Daughter).ToList().AsReadOnly();

    //--- Methods ---

    public void AddChild(Child child)
    {
        State.Children.Add(child);
    }

    public void RemoveChild(Child child)
    {
        State.Children.Remove(child);
    }
}

internal class FatherState
{
    [Key]
    public long Id { get; set; }

    public virtual ICollection<Child> Children { get; set; }
}

public class Child
{
    [Key]
    public long Id { get; set; }

    public long FatherId { get; set; }  

    public Father Father { get; set; }

    public ChildType Type { get; set; }
}

public enum ChildType
{
    Son,
    Daughter
}

当然,这只能与存储库模式一起使用,因为它必须将EF提供的FatherState对象转换为应用程序使用的Father对象。

我认为真正的解决方案是切换到fully featured ORM like NHibernate。 EF太不发达了,而且只有.NET Core才会变得更糟。我甚至不知道如何用EF做适当的DDD。许多开发人员必须妥协他们的模型。 NHibernate有很多优点,我发现唯一的警告是它没有提供异步方法,但有onetwo个参数反对它,它can be done anyway。还有forks的NHibernate将它们作为最后的手段提供。

答案 2 :(得分:0)

我认为你不需要单独的映射来为儿子和女儿分岔同样的父亲。更好的解决方案是在Child Table中创建唯一标识符,例如enum,如前面的答案所示,或在Gender模型中添加Child字段,并且只保留{Father的一个映射1}}模型。 (我不知道您的确切要求,但FatherChild也可以在严格的面向对象术语中的一个超类humanperson中进行推广。

但由于我不确定你的要求,如果你真的想继续你的映射,那么我建议的解决方案就是这个。

  class Father
    {
        [Key]
        public long Id { get; set; }

        public int SonId{get;set;}
        public int DaughterId{get;set;}


        [ForeignKey("SonId")]
        public virtual Child Child_Son{get;set;}

        [ForeignKey("DaughterId")]
        public virtual Child Child_Son{get;set;}
    }

    class Child
    {
        [Key]
        public long Id { get; set; }

        public string Gender{get;set;}
    }

<强>解释

Son类中同一DaughterChildFather的两个外键可以轻松帮助您使用基本Linq或任何SQL查询实现单独的集合。此外,这保留了规则“如果父母只存在,那么孩子就存在”。

答案 3 :(得分:0)

我在你的建模中可以看到,子实体需要具有属性fatherId和gender。检查以下代码:

public class Child
{
   [Key]
   public long Id {get; set;}
   public string Name {get; set;}
   public Gender Gender{get; set;} //Usually use Enum containing Female and Male
   public long FatherId{get; set;}

   public virtual Father Father {get; set;}
}

public class Father
{
  public Father()
  {
     Children = new HashSet<Child>();
  }  

  [Key]
  public long Id {get; set;}
  public string Name {get; set;}

  public virtual ICollection<Child> Children{get; set;}
}

以性别为基础的孩子将适用于您想要做的事情。