6

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.

P.S. 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); 
4
  • I would suggest you think about another way of designing your DB. Imagine in 1 year from now you are dealing with 100 companies.This will lead to the creation of 100 tables serving the same purpose without mentioning the mess when querying , and maintaining the code. Commented Jan 18, 2019 at 13:56
  • @AdamChawki agree, but now it isn't possible to change the structure Commented Jan 18, 2019 at 13:58
  • @JackSparrow OCP has nothing to do with how your data looks. Does your Product entity have two different types of product information? Or does it have a list of the same kind of product information produced by different companies? It looks like the classes are misnamed and Product should have a Rating and a Pricing property. Whether those classes need Product and ProductID is another matter. Most likely they do Commented Jan 18, 2019 at 16:08
  • This question is a blessing. Commented Jan 21, 2021 at 9:52

2 Answers 2

5

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, e.g. as you said, you can build your queries starting from ProductInfoFromCompany1, then omitting the navigation property in Product is perfectly fine.

Sign up to request clarification or add additional context in comments.

Comments

0

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.