使用Entity Framework Code First实现带有有效载荷的自引用父子关系。

5
我正在尝试使用实体框架中的代码优先设置,但遇到了困难。为了描述我想要完成的内容:
有一个名为Product的实体。该产品可以选择性地具有一个或多个相关的“子”产品。一个产品可以是一个或多个父产品的子级。
当我尝试生成与模型类“Product”绑定的控制器时,我收到一个错误消息:(更新,更加具体,与下面的代码匹配)
 There was an error running the selected code generator:
'Unable to retrieve metadata for 'ProductCatalog.Models.Product'.
 Multiple object sets per type are not supported. The object sets 
'Product' and 'Products' can both contain instances of type
'ProductCatalog.Models.Product'.

以下是不起作用的模型类:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;

namespace ProductCatalog.Models
{
    // Product
    public class Product
    {
        [Key]
        public int ProductId { get; set; } // ProductID (Primary key)
        public string ProductName { get; set; } // ProductName
        public string ProductSku { get; set; } // ProductSKU
        public int BaseQuantity { get; set; } // BaseQuantity
        public decimal BaseCost { get; set; } // BaseCost

        // Reverse navigation
        public virtual ICollection<RelatedProduct> ParentProducts { get; set; } // RelatedProduct.FK_RelatedProductChildID
        public virtual ICollection<RelatedProduct> ChildProducts { get; set; } // RelatedProduct.FK_RelatedProductParentID

        public virtual ICollection<RelatedProduct> RelatedProducts { get; set; }
    }


    // RelatedProduct
    public class RelatedProduct
    {
        [Key, Column(Order = 0)]
        public int ParentId { get; set; } // ParentID
        [Key, Column(Order = 1)]
        public int ChildId { get; set; } // ChildID
        public int Quantity { get; set; } // Quantity
        public bool Required { get; set; } // Required
        public bool Locked { get; set; } // Locked

        // Foreign keys
        public virtual Product ParentProduct { get; set; } //  FK_RelatedProductParentID
        public virtual Product ChildProduct { get; set; } //  FK_RelatedProductChildID
    }

    public class ProductDBContext : DbContext
    {
        public IDbSet<Product> Product { get; set; } // Product
        public IDbSet<RelatedProduct> RelatedProduct { get; set; } // RelatedProduct

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<RelatedProduct>()
                .HasRequired(a => a.ParentProduct)
                .WithMany(b => b.ChildProducts)
                .HasForeignKey(c => c.ParentId) // FK_RelatedProductParentID
                .WillCascadeOnDelete(false);

            modelBuilder.Entity<RelatedProduct>()
                .HasRequired(a => a.ChildProduct)
                .WithMany(b => b.ParentProducts)
                .HasForeignKey(c => c.ChildId); // FK_RelatedProductChildID

        }
    }
}

一些我尝试过的更新: a)首先作为数据库构建。这个方法可行,但我正在尝试以代码优先方式解决问题。 b)使用数据库结构进行反向工程。这将创建一些非常好的流畅API代码,基本上可以完成我上面所做的事情。但仍然出现相同的错误。我将继续调查。 - Bumble
你的 DbSet 是私有的。尝试将它们改为 public。或者这只是一个复制粘贴错误吗? - Slauma
我认为我的另一个答案不会有帮助,因为它是针对没有有效负载的多对多关系。您的方法(带有效负载)是正确的,映射是完美的。问题必须在其他地方(真的很奇怪的异常)。让我困扰的一件事是使用具有模式指定表名的 ToTable。通常,您会使用 ToTable 的第二个参数来定义模式,例如 ToTable("Product", "dbo")。或者干脆删除 "dbo."(仅使用 ToTable("Product")),因为 "dbo" 是默认模式。但是,我无法想象这解释了奇怪的异常。 - Slauma
@Slauma,是的,toTable并不是必要的。EF对表名的处理很好。反向工程将其添加到其中。但是,有好消息。我找到了解决方法。我很快会更新上面的代码,但这个修复的方法是将DbSet“Product”的复数形式命名为“Products”..只需要这样做(以及在父关系中添加willcascadeondelete(false))。 - Bumble
我刚刚也看到了 Product DbSet,本来想写的 :) 恭喜!(EF 异常通常不能很好地指向问题的根源...) - Slauma
显示剩余3条评论
1个回答

3

通过对DbSets进行复数化修正

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;

namespace ProductCatalog.Models
{
    // Product
    public class Product
    {
        [Key]
        public int ProductId { get; set; } // ProductID (Primary key)
        public string ProductName { get; set; } // ProductName
        public string ProductSku { get; set; } // ProductSKU
        public int BaseQuantity { get; set; } // BaseQuantity
        public decimal BaseCost { get; set; } // BaseCost

        // Reverse navigation
        public virtual ICollection<RelatedProduct> ParentProducts { get; set; } // RelatedProduct.FK_RelatedProductChildID
        public virtual ICollection<RelatedProduct> ChildProducts { get; set; } // RelatedProduct.FK_RelatedProductParentID

        public virtual ICollection<RelatedProduct> RelatedProducts { get; set; }
    }


    // RelatedProduct
    public class RelatedProduct
    {
        [Key, Column(Order = 0)]
        public int ParentId { get; set; } // ParentID
        [Key, Column(Order = 1)]
        public int ChildId { get; set; } // ChildID
        public int Quantity { get; set; } // Quantity
        public bool Required { get; set; } // Required
        public bool Locked { get; set; } // Locked

        // Foreign keys
        public virtual Product ParentProduct { get; set; } //  FK_RelatedProductParentID
        public virtual Product ChildProduct { get; set; } //  FK_RelatedProductChildID
    }

    public class ProductDBContext : DbContext
    {
        public IDbSet<Product> Products { get; set; } // Product
        public IDbSet<RelatedProduct> RelatedProducts { get; set; } // RelatedProduct

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<RelatedProduct>()
                .HasRequired(a => a.ParentProduct)
                .WithMany(b => b.ChildProducts)
                .HasForeignKey(c => c.ParentId) // FK_RelatedProductParentID
                .WillCascadeOnDelete(false);

            modelBuilder.Entity<RelatedProduct>()
                .HasRequired(a => a.ChildProduct)
                .WithMany(b => b.ParentProducts)
                .HasForeignKey(c => c.ChildId); // FK_RelatedProductChildID

        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接