So, I'm writing an e-commerce application and I'm trying to create a many to many relationship between the Product and Size classes. Entities looks like that:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class Size
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductSize
{
public int Id { get; set; }
public int ProductId { get; set; }
public int SizeId { get; set; }
public string Amount { get; set; }
public virtual Size Size { get; set; }
public virtual Product Product { get; set; }
}
Normally I wouldn't explicitly create this ProductSize table, because EF would create it for me. However, I need the Amount column to sit there. Now, for getting info about products with sizes and amounts I have to create a very explicit query (2 joins). If I hadn't manually created the relationship class and just give the Product class a virtual ICollection of Size, getting the complete info would be just a simple matter. So if I had selected a Product instance, then all sizes would be loaded into it's virtual ICollection<Size>.
The question is - can I achieve the same level of simplicity with the entities structure given above? Instead of writing a 2 join query where I explicitly mention every column I want, and then pack it into some ViewModel I'd like to use the simpler syntax for getting a Product instance and its related data (so also the data that's sitting in the intermediate table).
Add an Inverse Navigation Property to the ProductSize from Product and Size like this:
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public virtual ICollection<ProductSize> ProductSizes { get; set; }
}
public class Size {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductSize> ProductSizes { get; set; }
}
public class ProductSize {
public int Id { get; set; }
public int ProductId { get; set; }
public int SizeId { get; set; }
public string Amount { get; set; }
public virtual Size Size { get; set; }
public virtual Product Product { get; set; }
}
Doing so, you should be able to do like in the following example:
from p in Context.Products
where p.ProductSizes.Where(ps => ps.Amount > 0 && ps.Size.Name.Equals("Big")).Any()
select p;
Related
I am trying to code a payment process for an e-commerce solution and I want to gather all products in the shopping cart, but then inside a List<OrderProducts> and then store that list into an an object Order.
The POCO classes are below:
Order.cs
public class Order
{
public int Id { get; set; }
public double TotalAmount { get; set; }
public IdentityUser UserAccount { get; set; }
public virtual ICollection<OrderProduct> OrderProduct { get; set; }
public DateTime OrderDate { get; set; }
public bool IsComplete { get; set; }
}
OrderProduct.cs
public class OrderProduct
{
public int Id { get; set; }
public Order Order { get; set; }
public Product Product { get; set; }
public int Quantity { get; set; }
}
After running migrations and updating the database, it seems the OrderProduct column in the Order table does not even show in the database design.
Can someone please help or give a better solution based on the scenario above?
Change your OrderProduct class, then regenerate your migration:
public class OrderProduct
{
[Key]
public int Id { get; set; }
public int OrderId {get;set;}
[ForeignKey(nameof(OrderId))]
public virtual Order Order { get; set; }
public Product Product { get; set; }
public int Quantity { get; set; }
}
I create an application and as an example for testing I take a table of orders. I have questions about class modeling.
I have 3 classes:
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
public class Part
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
class Order
{
public Order()
{
Cars = new List<Car>();
Parts = new List<Part>();
}
public int OrderId { get; set; }
public int CarId { get; set; }
public int PartId { get; set; }
public ICollection<Car> Cars { get; set; }
public ICollection<Part> Parts { get; set; }
}
I do not know if this model is ok. What do you think? Because something does not go here: / In the application:
I can not add cars or parts to the order that I do not have in the database.
In the table of orders I would like to see only the order Id, the value of the order, and the Id of the car and Id of the part that was bought.
I would like the Car and Part tables to have no data about orders. I would like to only add parts or cars in the application, later only be able to select from them in the order section.
Let's start with the physical tables you will need:
Part { Id, Name, Price }
Car { Id, Name, Price }
Order { Id }
OrderPart* { OrderId, PartId }
OrderCar* { OrderId, CarId }
The last two tables are called "join tables" because you need them to be able to store multiple parts and multiple cars on the same order, but are not really tables you think of as being part of your model.
Entity Framework will automatically make these join tables if you set up your classes as follows:
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
public class Part
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
class Order
{
public Order()
{
Cars = new List<Car>();
Parts = new List<Part>();
}
public int OrderId { get; set; }
public virtual ICollection<Car> Cars { get; set; }
public virtual ICollection<Part> Parts { get; set; }
}
Note that the ICollection<> properties on the Car and Part table will be the clue to EF that it needs to make the join table. Also, remember that you need "virtual" on your navigation properties.
It is good model ?
One Pizza may have a few idgredience
One Pizza may have one sauce under the cheese
One Order may have a few idgredience and a few sauces.
It is my classes :
public class Suace
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
public class Pizza
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public ICollection<Idgredient> Idgredients { get; set; }
public Sauce Sauce {get;set;}
public virtual ICollection<Order> Orders { get; set; }
}
class Order
{
public Order()
{
Cars = new List<Car>();
Parts = new List<Part>();
}
public int OrderId { get; set; }
public virtual ICollection<Car> Suace { get; set; }
public virtual ICollection<Part> Pizza { get; set; }
}
public class Idgredient
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Pizza> Pizzas { get; set; }
}
I have 3 related objects (non relevant properties omitted for brevity):
public class Product
{
public int ID { get; set; }
public virtual ProductPrice Price { get; set; }
}
public class ProductPrice
{
public int ID { get; set; }
public int ProductID { get; set; }
public int VerticalID { get; set; }
public decimal Value { get; set; }
public virtual Product Product { get; set; }
public virtual Vertical Vertical { get; set; }
public override string ToString()
{
return Value.ToString("C");
}
}
public class Vertical
{
public int ID { get; set; }
public string Name { get; set; }
}
Product price varies based on the current "vertical". The current vertical will probably (eventually) be stored in a session, but for the time being, let's assume that this will be a query string parameter. (e.g. mydomain.com?VerticalID=2).
My question
When a user visits mydomain.com/products?VerticalID=2 or mydomain.com/products/?VerticalID=2 how can I get the Entity Framework to select/assign the correct price based on the ProductID and the VerticalID - making this possible?:
#Model.Price.ToString()
Update 1 (sample data and DB structure)
Here are my tables with dummy content:
Products
ProductPrices
Verticals
Relationship Explanation
There should be one price, per product, per vertical. The query would look something like:
-- Let's assume ProductID = 2 and VerticalID = 1 (e.g. mydomain.com/products/2?VerticalID=1)
SELECT * FROM ProductPrices WHERE ProductID = 2 AND VerticalID = 1
The above query would return 1 row (which is what it should always return)
Update 2 (another example)
For illustrative purposes I added the VerticalID property to Product:
public class Product
{
public int ID { get; set; }
public string Manufacturer { get; set; }
public string Model { get; set; }
public string PartNumber { get; set; }
public int CategoryID { get; set; }
public string Description { get; set; }
[NotMapped]
public int VerticalID = 1;
public virtual ProductCategory Category { get; set; }
public virtual ProductPrice Price { get; set; }
public virtual ICollection<ProductImage> Images { get; set; }
public virtual ICollection<ProductDocument> Documents { get; set; }
public virtual ICollection<ProductDetail> Details { get; set; }
public virtual ICollection<RelatedProduct> RelatedProducts { get; set; }
}
Now, when actually trying to execute this, I am getting the following error:
Unable to determine the principal end of an association between the types 'Print_Solutions.Models.ProductPrice' and 'Print_Solutions.Models.Product'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
How can I tell entity to use both VerticalID and Product.ID when retrieving the price? (using the test data I have, if this was product 1, this product should map to ID 1 of the product price table, and cost $100).
Supposing your DbContext has a collection of ProductPrice named ProductPrices, using LINQ you simply has to make this query:
var price = ctx.ProductPrices.Where(pp =>
pp.ProductId = productId && pp.VerticalId == verticalId).SingleOrDefault();
Where productId and verticalId are the available paramters that come from the action paramters, the session, or wherever they are.
The use of single or default warranties that there's only one value on the database, or that there is none, and, on that case, you get null as a result of the query.
As for your updates I see that your problem is also related to the definition of the relations in the model.
There are 3 ways to achieve it:
using EF conventions. To achive this, change the name of the ID properties of your entites: for example use ProductId, instead of ID and the conventions will build the model for you
using attributes. In this particular case use ForeignKeyAttribute where it applies
using the fluent API
You have some more info on relationships here, with a few simple samples.
I was never able to figure out how to fix the models or use the fluent API. I did some lazy loading instead. If anyone has a better solution, please post it.
public class Product
{
public int ID { get; set; }
public string Manufacturer { get; set; }
public string Model { get; set; }
public string PartNumber { get; set; }
public int CategoryID { get; set; }
public string Description { get; set; }
public int VerticalID = 1;
private ProductPrice _price;
public virtual ProductCategory Category { get; set; }
public virtual ICollection<ProductImage> Images { get; set; }
public virtual ICollection<ProductDocument> Documents { get; set; }
public virtual ICollection<ProductDetail> Details { get; set; }
public virtual ICollection<RelatedProduct> RelatedProducts { get; set; }
// Lazy Loading
public ProductPrice Price
{
get
{
if (_price == null)
{
var db = new ApplicationContext();
_price = db.Prices.FirstOrDefault(p => p.ProductID == ID && p.VerticalID == VerticalID);
}
return _price;
}
}
}
You can see my previous question which related with many to many relation but with auto generated mapping table.
I have 2 model, HrTraining and HrPerson. Any people can be assigned to one or more Trainings. You can see my model as below
public class HrTraining
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HrMapTrainingPerson> HrMapTrainingPerson { get; set; }
}
public class HrMapTrainingPerson
{
public int Id { get; set; }
public string Status { get; set; }
public int HrTrainingId { get; set; }
public int HrPersonId { get; set; }
public virtual HrTraining HrTraining { get; set; }
public virtual HrPerson HrPerson { get; set; }
}
public class HrPerson
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HrMapTrainingPerson> HrMapTrainingPerson { get; set; }
}
How can i take all training objects which assingned to a person with efficient way.
So you want to find a person, and get all the trainings assigned to it? There are lot of ways.. but using your models, this could be something like this
var trPersons = dbContext.HrPerson.Find(idPerson).HrMapTrainingPerson.ToList();
foreach(var trPerson in trPersons) {
var training = trPerson.HrTraining;
//do what you want, here you can get trPerson.HrTraining.Name for instance
}
In a MVC app I´m setting up a database with multiple tables that got the same structure and I want to use the same model for all of them instead of having to create a new model for each. When trying to use the same model creating multiple tables I get the following error:
"Multiple object sets per type are not supported"
Is it somehow possible to use the same model to create two tables?
I´m building a db with equitys historical prices. And the idea is one table per equity.
Stock1 Stock2
-Date -Date
-Price -Price
Or is best practice in db-design to have all data with the same structure in one table and then use annother table to connect data to the parent?
Instruments Data
-StockId -Date
-StockName -Price
-StockId(FK)
Code:
//Models for tables
public class Instruments
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class Equity
{
[Key]
public int Id { get; set; }
public DateTime Date { get; set; }
[ForeignKey("Instruments")]
public int InstrumentId { get; set; }
public virtual Instruments Instruments { get; set; }
public double Open { get; set; }
public double Close { get; set; }
public double High { get; set; }
public double Low { get; set; }
public int Volume { get; set; }
}
//Code to create tables when running "add-migration"
public DbSet<Instruments> Instruments { get; set; }
public DbSet<Equity> Stock1 { get; set; }
public DbSet<Equity> Stock2 { get; set; }
One Table per Stock is a bad Idea.
You can store data for all stocks in the Stock1 property of your DbContext class. Add a property/column to distinguish the stock name/StockId to your Equity table.
If you are using StockI, create a Stock table and StockId in your Equity table will have a foreign key connection to the Stock table.
public class Stock
{
public int Id { get; set; }
public string Ticker { set;get; }
public string Name { get; set; }
}
public class Equity
{
[Key]
public int Id { get; set; }
public DateTime Date { get; set; }
[ForeignKey("Instruments")]
public int InstrumentId { get; set; }
public virtual Instruments Instruments { get; set; }
public double Open { get; set; }
public double Close { get; set; }
public double High { get; set; }
public double Low { get; set; }
public int Volume { get; set; }
public int StockId { set;get;}
public virtual Stock Stock { set;get;}
}
Now in your DbContext class, you will only have one property of DbSet<Equity> type. You can access all Equity records via this or filter it based on your need (ex : Get records for a specific Stock)
public DbSet<Equity> Equities { get; set; }
To get data for specific Stock, You will usually use a filter. For example,
var stockIdOfMsft = 24;
var msftEquities = db.Equities.Where(a=>a.StockId == stockIdOfMsft).ToList();
Or
var msftEquities = db.Equities.Where(a=>a.Stock.Ticker == "MSFT").ToList();