【问题标题】:C# EF6: Two navigation properties of the same typeC# EF6:相同类型的两个导航属性
【发布时间】:2018-03-22 18:47:47
【问题描述】:

简单地说,在 C# EF6 中,如何将两个导航属性映射到同一个表,同时保持它们的结果集分开?用简单的英语来说,我有一门课,我想在另一个课上收藏两个。换句话说,我想要两个相同类型但元素不同的集合。不幸的是,EF6 似乎将两个集合视为相同,并为它们提供相同的元素(表中的每条记录)。

我从几十个 StackOverflow 答案中找到的最好的是这个,但它有描述的问题。在这个例子中,一个父亲有许多儿子和许多女儿,他们每个人都有一个父亲。理想情况下,SonsDaughters都可以存储在同一个表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);
}

这样做的问题是,当从 Child 表中读取数据时,它不会区分 SonsDaughters。也就是说,Sons 集合不仅包含 Sons,还包含 DaughtersDaughters 集合也是如此。我可能预计 EF6 会尝试使用鉴别器列,但它没有。

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

【问题讨论】:

    标签: c# entity-framework-6


    【解决方案1】:

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

    对于“这是一个模型“孩子”需要分成两个集合”:

     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");
      });
    

    【讨论】:

    • 这是一个模型“Child”需要拆分为两个集合,而不是两个模型需要存储在一个表中。为什么要在模型中为相同的逻辑类型创建两个类?你能用代码说明你是如何解决上述问题的吗?
    • 如果我没看错,它会将一些属性映射到一个表,而将其他属性映射到另一个表。那正确吗?我在第一段中澄清了我的问题。基本上,我有一个班级,我想在另一个班级中有两个集合。这似乎是一种常见的模式。我很困惑为什么关于它的信息很少。
    【解决方案2】:

    我认为您不需要完全单独的映射来为儿子和女儿分叉相同的 FatherId。更好的解决方案是在Child Table 中创建一个唯一标识符,例如enum,就像之前的答案所建议的那样,或者在您的Child 模型中添加一个Gender 字段并只保留Father 模型的一个映射。 (我不知道您的确切要求,但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;}
        }
    

    解释

    Father 类中的 SonDaughter 的两个外键将轻松帮助您使用基本的 Linq 或任何 sql 查询实现单独的集合。此外,这保留了“如果父项仅存在则子项存在”的规则。

    【讨论】:

    • 谢谢,我会试试这个并报告。
    • 我在实现它时遇到了麻烦,但我无法确认或否认它。如果我尝试更长的时间,我可能已经让它工作了。我时间紧迫,决定将所有记录映射到单个 ICollection 属性,然后使用访问器属性对结果进行划分和排序(类似于我的答案)。它使我的数据库设计保持简洁,代码易于理解。
    • 子类应该有fatherId,而父类应该有子类的集合。添加公共虚拟 ICollection 孩子 {get; set;} 到父类。
    【解决方案3】:

    我找到了使用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。 NHibernate 的 forks 也提供它们作为最后的手段。

    【讨论】:

      【解决方案4】:

      我在您的建模中看到的是,子实体需要具有属性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;}
      }
      

      根据性别获取孩子将适用于您想做的事情。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-14
        • 1970-01-01
        • 2016-07-02
        • 2023-03-19
        • 1970-01-01
        • 2022-12-23
        相关资源
        最近更新 更多