I have the following model
public class Rate
{
public Guid Id { get; set; } // PK
public int SpecialId { get; set; }
[Column(TypeName = "Money")]
public decimal Rental { get; set; }
public virtual Vehicle Vehicle { get; set; }
}
And the following...
public class Vehicle
{
public Guid Id { get; set; } // PK
public int SpecialId { get; set; }
public string Manufacturer { get; set; }
public string Model { get; set; }
}
I am joining the two on SpecialId using a GroupJoin using the following query.
var query = _context.Vehicle.AsNoTracking()
.GroupJoin(_context.Rate.AsNoTracking(),
vehicle => vehicle.SpecialId,
rate => rate.SpecialId,
(v, r) => new VehicleQuery
{
Rates = r,
Vehicle = v
}).AsNoTracking();
... and returning a VehicleQuery model (Show below)
public class VehicleQuery
{
public Vehicle Vehicle { get; set; }
public IEnumerable<Rate> Rates { get; set; }
}
However, even though I have AsNoTracking on the Rate table and the final query, when I look at the results in the debugger.
I can see that the Rates are still tracked (DynamicProxies). However the Vehicle is fine and showing as normal class and not a dynamic proxy/tracked.
I have even tried r.ToList() in the VehicleQuery but it's still showing as a DynamicProxie?
Any help appreciated.
Related
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; }
}
Suppose I have an entity:
public class Table {
[Key]
[Column]
public long TableId { get; set; }
[ForeignKey("TableId")]
[JsonIgnore]
[Association("Diners", "TableId", "TableId")]
public List<Diner> DinersAtTable { get; set; }
[NotMapped]
public int Total { get; set; }
}
public class Diner {
[Key]
[Column]
public long DinerId { get; set; }
public long TableId { get; set; }
}
public class Item {
[Key]
[Column]
public long ItemId { get; set; }
public long DinerId { get; set; }
public long Price { get; set; }
}
I want to fill Total with the sum of the Prices of all the Items ordered by the diners, but since I'm not really interested in the Items themselves, I would like to fill the Total in the query which gets me the Table entity.
So, I'd like something like:
var query = ctx.Tables.Include(t => t.DinersAtTable)
.Include(t = t.DinersAtTable.Select(d => d.Include(Items)))
.AssignToUnmapped(t => t.Total = t.Diners.Items.Sum()).Select(t => t.TableId, t.Total)
Obviously AssignToUnmapped and the final Select is what I'm not sure how to achieve. Any idea how I should do that?
I am in need of some assistance with an Entity Framework query. I have the following entities:
public class Invoice
{
public Guid Id { get; set; }
public DateTime CreateDate { get; set; }
[ForeignKey("CreatedById")]
public virtual ApplicationUser CreatedBy { get; set; }
public Guid CreatedById { get; set; }
public bool Approved { get; set; }
public virtual ICollection<InvoiceDetail> Details { get; set; }
}
public class InvoiceDetail
{
public Guid Id { get; set; }
[ForeignKey("InvoiceId")]
public virtual Invoice Invoice { get; set; }
public Guid InvoiceId { get; set; }
public string Item { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
public decimal UnitCost { get; set; }
public decimal Discount { get; set; }
}
I am trying to sum the total of all the items where an invoice is Approved. The problem I'm having is the syntax on getting to the InvoiceDetail level once I filter for the Approved = 1:
var myInvoices = context.Invoices.Where(i => i.CreatedById == userId).Include(i => i.CreatedBy).Include(i => i.Details);
var approvedTotal = myInvoices.Where(i => i.Approved == 1).Select([GET TO DETAILS???]);
// my goal is to get the following sum for each detail for all of the approved invoices:
// ((d.Quantity * d.UnitCost) - d.Discount)
Assuming that you wanted to get a sum total of all details after the given calculation had been performed you could do something like this:
var approvedTotal = invoices.Where(invoice => invoice.Approved)
.Select(invoice => invoice.Details.Sum(detail => ((detail.Quantity * detail.UnitCost) - detail.Discount)));
So this gives you one total per invoice.
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;
}
}
}
This will create two tables "Ingredient" and "Recipe" and an additional table for many-to-many mapping.
public class DC : DbContext {
public DbSet<Ingredient> Ingredients { get; set; }
public DbSet<Recipe> Recipes { get; set; }
}
public class Ingredient {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
}
public class Recipe {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Ingredient> Ingredients { get; set; }
}
Question: I want to include additional column "quantity" in the third mapping table that will be created by Entity Framework. How to make that possible? Thanks in advance.
When you've got some extra information, I suspect it won't really count as a mapping table any more - it's not just a many-to-many mapping. I think you should just model it as another table:
public class Ingredient {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<RecipePart> RecipeParts { get; set; }
}
public class RecipePart {
public int Id { get; set; }
public Ingredient { get; set; }
public Recipe { get; set; }
// You'll want to think what unit this is meant to be in... another field?
public decimal Quantity { get; set; }
}
public class Recipe {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<RecipePart> Parts { get; set; }
}
So now you don't really have a many-to-many mapping - you have two ordinary many-to-one mappings. Do you definitely need to "ingredient to recipes" mapping exposed in your model at all? If you want to find out all the recipes which use a particular ingredient, you could always do a query such as:
var recipies = DB.Recipies.Where(r => r.Parts
.Any(p => p.Ingredient == ingredient));