EF Core 模型中导航属性的必要性

Necessity of navigation properties in EF Core models

假设在我们的项目中我们使用 C# 和 MsSQL,并且我们有一个 Products table 有两列 (ID,Name)

有一天我们决定保存 Company1 给出的产品信息,所以我们创建了一个新的 table ProductInfoFromCompany1 因为它有自定义列(ProductID, Price, CurrentScore)

第二天,我们同意了Company2,现在我们也需要保存他们的数据。因此,新的 table -> ProductInfoFromCompany2 具有不同的列 (ProductIDYearRating)

另一天,我们同意Company3等等...

所以,我们不知道新公司给出的数据会是什么样子。这就是我们需要创建一个新的 table 的原因,因为如果我们使用一个 Details table,它将太宽且包含许多空列

在 Entity Framework Core 中,我们有这些模型:

public class ProductInfoFromCompany1
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public decimal Price { get; set; }
    public double CurrentScore { get; set; }

    public Product Product { get; set; }
}

public class ProductInfoFromCompany2
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public int Year { get; set; }
    public double Rating { get; set; }

    public Product Product { get; set; }
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }

    //Do we need these navigation properties in this class?
    //public ProductInfoFromCompany1 ProductInfoFromCompany1 { get; set; } 
    //public ProductInfoFromCompany2 ProductInfoFromCompany2 { get; set; }
}

你可以看到我的问题在Product class中有评论。

我们是否需要在 Product class 中添加导航属性?

我问的原因是,在我读过的所有书籍或文档中,人们都使用导航属性,但在这种情况下,它违反了开闭原则,因为每当我们添加新的公司,我们也需要修改Productclass

P.S。如果我们想查询 ProductInfoFromCompany1 数据并且我们有产品 Id,我们可以简单地从 ProductInfoFromCompany1 开始查询,像这样

var info = _db.ProductInfoesFromCompany1.Where(c=>c.ProductId == productId);

正如我在评论中提到的那样,需要进行设计更改才能实现您想要的效果。 这是我的建议:

由于您的问题与产品结构有关 table 因为您不知道每个公司想要存储什么作为其产品的信息,您可以这样做:(我稍后会解释) .

 public class Company
    {
        [Key]
        public int Id { get; set; }

        [Display(Name = "Name")]
        [Required]
        public string Name { get; set; }

        [Display(Name = "Description")]
        public string Description { get; set; }

        [Required]
        [Display(Name = "Created date")]
        [DataType(DataType.DateTime)]
        public DateTime CreatedDate { get; set; }

        public virtual ICollection<Product> Prodcuts { get; set; }

    }

    public class Product
    {
        [Key]
        public int Id { get; set; }

        [Display(Name="Name")]
        [Required]
        public string Name { get; set; }

        [Required]
        [Display(Name = "Created date")]
        [DataType(DataType.DateTime)]
        public DateTime CreatedDate { get; set; }

        [Required]
        [ForeignKey("Company")]
        [Display(Name = "Company")]
        public int CompanyID { get; set; }
        public virtual Company Company { get; set; }

        public virtual ICollection<ProductField> Fields { get; set; }

    }

    public class ProductField
    {
        [Key]
        public int Id { get; set; }

        [Display(Name = "Value")]
        [Required]
        public string Value { get; set; }

        [Required]
        [ForeignKey("Product")]
        [Display(Name = "Product")]
        public int ProductID { get; set; }
        public virtual Product Product { get; set; }

        [Required]
        [ForeignKey("Field")]
        [Display(Name = "Field")]
        public int FieldID { get; set; }
        public virtual Field Field { get; set; }

        [Required]
        [Display(Name = "Created date")]
        [DataType(DataType.DateTime)]
        public DateTime CreatedDate { get; set; }
    }


public class Field
    {
        [Key]
        public int ID { get; set; }

        [MaxLength(100)]
        [Index("ActiveAndUnique", 1, IsUnique = true)]
        [Required]
        [Display(Name = "Name")]
        public string Name { get; set; }

        [Display(Name = "Description")]
        public string Description { get; set; }

        [Required]
        [Display(Name = "Created date")]
        [DataType(DataType.DateTime)]
        public DateTime CreatedDate { get; set; }
    }

代码解释:

这种方法可以让您更好地控制数据,而无需为每个产品信息创建 table。

公司: 我首先创建了一家公司 table,其导航 属性 将延迟加载与其相关的所有产品。(如果启用了延迟加载) 然后在产品中table我加了一个FK来引用公司。

字段: 由于您提到您不知道公司将拥有什么作为产品信息,您可以创建一个新字段并 link 使用 ProductField table 将其添加到产品。

产品字段: 此 table 将充当您的产品和字段之间的 "Many to Many",因此您可以向新产品添加尽可能多的字段,而无需修改产品结构 table 或创建一个新的。如果 3 号公司需要,您也可以重复使用相同的字段。

用法:

鉴于我们有一家名为 MyCompany 的公司。 MyCompany 有一个名为 Car 的产品,需要添加到汽车的信息是 Make 和 Color。 我们创建了两个新字段,分别称为“品牌”和“颜色”,然后在 ProductField Table 中添加了两个新条目: 第一个将有: 字段的 ID "Make"、值 "BMW" 以及对 ID 为 Car 的产品的引用。 我们通过引用字段 "Color" 和产品 "Car".

对颜色做同样的事情

正在查询: 现在查询比为每个公司产品信息设置 table 更简单。

示例:

var myProducts = _db.Products.Where(p=>p.CompanyID== "1").Include(p=>p.Fields).Tolist()

这也是我的看法。希望对你有帮助。

Do we need to add navigation properties in the Product class?

如果您是否需要,您是唯一可以回答问题的人。

如果问题是EF Core 是否需要导航属性,答案是否定的。参考:Relationships - Single Navigation Property EF Core 文档主题:

Including just one navigation property (no inverse navigation, and no foreign key property) is enough to have a relationship defined by convention.

事实上,EF Core fluent API 和阴影属性允许在没有任何导航或 FK 属性 的情况下定义关系。它有多大用处是另一回事。要点(这是我读到的问题)是其中 none 是强制性的。

当然,缺少导航 属性 对您可以创建的 LINQ 查询类型施加了一些限制 - 正如您所说,您不能从 Product 开始查询并应用过滤器在关联的 ProductInfoFromCompany1 或 eager/explicit/lazy 上加载它。

但是如果您不需要所有这些,例如如您所说,您可以从 ProductInfoFromCompany1 开始构建查询,然后在 Product 中省略导航 属性 就完全可以了。