简体   繁体   中英

Necessity of navigation properties in EF Core models

Let's say in our project we use C# and MsSQL and we have one Products table with two columns ( ID , Name )

One day we decided to save product information given by Company1 , so we created a new table ProductInfoFromCompany1 because it has custom columns ( ProductID , Price , CurrentScore )

The next day, we agreed with Company2 and now we need to save their data as well. So, new table -> ProductInfoFromCompany2 with different columns ( ProductID , Year , Rating )

Another day, we agreed with Company3 and so on...

So, we have no idea how the data given by new companies will look like. That's why we need to create a new table because if we use one Details table, it will be too wide with numerous null columns

In Entity Framework Core we have these models:

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

You can see my question is commented in the Product class.

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

The reason why I'm asking is that in all books or documentation which I've read, people use navigation property, but in this case, it violates open-closed principle because whenever we add new company, we need to modify Product class as well.

PS if we want to query ProductInfoFromCompany1 data and we have product Id , we can simply start querying from ProductInfoFromCompany1 , like this

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

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

You are the only one who can answer the question if you need something or not.

If the question is does EF Core require navigation properties, the answer is no. Reference: Relationships - Single Navigation Property EF Core documentation topic:

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

In fact EF Core fluent API and shadow properties allow defining relationship without any navigation or FK property. How useful it would be is another story. The main point (which is the question as I read it) is that none of them is mandatory.

Of course the lack of a navigation property imposes some limitations on the type of LINQ queries you can create - like you said, you can't start a query from Product and apply filter on associated ProductInfoFromCompany1 , or eager/explicit/lazy load it.

But if you don't need all that, eg as you said, you can build your queries starting from ProductInfoFromCompany1 , then omitting the navigation property in Product is perfectly fine.

As I mentioned in my comment a design change is required to achieve what you want. Here is my suggestion:

Since your issue is with the structure of the product table because you don't know what each company wants to store as info for their product you can do it this way : (I ll explain later).

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

Explanation of the code:

This approach gives you more control over your data without having to create a table for each product info.

Company: I started by creating a company table with a navigation property that will lazy load all the products related to it.(if lazy loading is enabled) Then In the product table I added a FK to reference the company.

Field: Since you mentioned that you don't know what a company will have as product info , you can create a new field and link it to a product using the ProductField table .

ProductField: This table will act as a "Many to Many" between your product, and field as a result you can add as many field to a new product without having to modify the structure of your product table or create a new one . You can also reuse the same field if company number 3 needs it.

USAGE:

Given we have a company named MyCompany . MyCompany has a product named Car and the info required to be added to the car is Make, and Color. We create two new fields called Make, and Color, then in the ProductField Table we add two new entries: The first one will have: The ID of the field "Make", The value "BMW", and a reference to the product with its id which is Car. We do the same thing for color by referencing the the field "Color" and the product "Car".

Querying: Now querying is simpler than having a table for each company product info.

Example:

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

Again that's my take on it. Hope it helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM