So every Customer
has zero or more Invoices
, every Invoice
is the invoice of exactly one Customer, namely the Customer that the foreign key CustomerId refers to: a straightforward one-to-many relation.
Similarly there is a one-to-many relationship between Invoices and InvoiceLines: Every Invoice has zero or more InvoiceLines, every InvoiceLine belongs to exactly one Invoice, using a foreign key.
Products – InvoiceLines: also a one-to-many relation using a foreign key.
If you’ve followed the entity-framework coding conventions, you will have classes similar to the following:
class Customer
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every Customer has zero or more Invoices (one-to-many)
public virtual ICollection<Invoice> Invoices {get; set;}
}
class Invoice
{
public int Id {get; set;}
...
// Every invoice belongs to exactly one Customer, using foreign key:
public int CustomerId {get; set;}
public virtual Customer Customer {get; set;}
// Every Invoice has zero or more InvoiceLiness (one-to-many)
public virtual ICollection<InvoiceLine> InvoiceLiness {get; set;}
}
In entity framework columns of tables are represented by non-virtual properties. The virtual properties represent the relations between the tables.
The foreign key CustomerId is a real column in the Invoices table. Hence it is non-virtual. That an Invoice has a relation to a Customer can be seen on the virtual property Customer.
In entity framework the type of relation between tables (one-to-one, one-to-many, many-to-many) can be seen by the
virtual ICollection<...>
orvirtual ...
Tables and collections are identified using plural nouns; a single row of the table, or one item in the collection is identified by a singular noun. Later when we discuss lambda, you will see that this makes interpreting the lambda easier.
class InvoiceLIne
{
public int Id {get; set;}
...
// Every InvoiceLine belongs to exactly one Invoice, using foreign key:
public int InvoiceId{get; set;}
public virtual Invoice Invoice {get; set;}
// Every InvoiceLine has exacaly one Product, using foreign key:
public int ProductId {get; set;}
public virtual Product Product {get; set;}
}
class Product
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every Product is use in has zero or more InvoiceLines (one-to-many)
public virtual ICollection<InvoiceLine> InvoiceLInes {get; set;}
}
And finally the DbContext
class OrderingDbContext : DbContext
{
public DbSet<Customer> Customers {get; set;}
public DbSet<Invoice> Invoices {get; set;}
public DbSet<InvoiceLIne> InvoiceLines {get; set;}
public DbSet<Product> Products {get; set;}
}
In fact, this is all that entity framework needs to know to detect the tables, the columns in the tables and the relations between the tables. No need for attributes or fluent api. Only if you want identifiers that deviate from the default identifiers, for table names, or columnames, or if you want to define something special in the relations, you need fluent API.
Back to your question
If I received a productID as a parameter (selectedID), how would I go about accessing the customer name, invoiceID, and PriceTotal related to that productID?
First of all, if you have a productId, there are probably several Customers who bought the product with this productId, and thus several invoices, and several priceTotals. So you can’t say THE customer name, THE invoice id.
Requirement If I have a productId, give me the names of all Customers who bought this Product, together with the InvoiceIds where is stated that they bought this product and the total price of the invoice where they bought this product.
Note that a Customer might have bought the product several times in different orders, so they can have several Invoices.
If you’ve followed the conventions as I wrote above, the solution is fairly simple:
int productId = ...
var customersWhoBoughThisProduct = dbContext.Customers
// I only want Customers who have at least one Invoice that has at least one
// invoiceLine for this product
.Where(customer => customer.Invoices
.SelectMany(invoice => invoiceLines, invoice => invoice.ProductId)
.Contains(productId))
// from the customers who bough this product, select several properties:
.Select(customer => new
{
// Select only the Customer properties that you plan to use
Id = customer.Id,
Name = customer.Name,
...
// You want only the invoices that have this product as one of the InvoiceLines
Invoices = customer.Invoices
.Where(invoice => invoice.InvoiceLines
.Any(invoiceLine => invoiceLine.ProductId == productId)
.Select(invoice => new
{
// select the invoice properties that you plan to use, for example:
Id = invoice.Id,
OrderDate = invoice.OrderDate,
PriceTotal = invoice.InvoiceLines
.Select(invoiceLine => invoiceLine.Price)
.Sum(),
})
.ToList(),
});
Well you see several Lambda expressions here. Because I’m quite strict in my use of identifiers, they shouldn’t be too difficult:
dbContext.Customers.Where(...)
.Select(customer => new
{
// Select only the Customer properties that you plan to use
Id = customer.Id,
Name = customer.Name,
...
After the Where, I’ve got a sequence of Customers, and from every customer in this sequence, I make exactly one new object, with property Id, that has the value of customerId, and property Name, which has the value of customer.Name.
This customer object, also has a property Invoices:
Invoices = customer.Invoices
.Where(...)
.Select(invoice => new{...}
You don’t want all invoices of this customer, you only want the invoice, where at least one invoice line has the foreign key to the product with ProductId:
.Where(invoice => invoice.InvoiceLines.Any(invoiceLine => invoiceLine.ProductId == productId)
This says: keep only those invoices, that have at least one InvoiceLIne where property ProductId equals productId. Which is the kind of invoices we want.
PriceTotal = invoice.InvoiceLines
.Select(invoiceLine => invoiceLine.Price)
.Sum(),
To calculate the PriceTotal of the invoice that had this product, we take the Price of every InvoiceLine in this invoice, and we sum all these prices.
The most difficult one: the Where of the customers:
.Where(customer => customer.Invoices
.SelectMany(invoice => invoiceLines)
.Any(invoiceLine => invoiceLine.ProductId == productId))
So the input was a sequence of Customers. We keep only those Customers, that have at least an invoiceLine in the sequence of Invoices of this customer that with a productId equal to productId.
Whenever you have a sequence where every element in the sequence has a subsequence, and you want to check all these subsequences as one sequence, use SelectMany.
So if you have Schools, and every School has Students, and you want to see all Students of all Schools in one sequence, user: schools.SelectMany(school => school.Students)
I did the same with Invoices and InvoiceLines, to get all InvoiceLines of all Invoices of this Customer: invoices.SelectMany(invoice => invoice.InvoiceLines)
By the way, did you see that because I am very strict in singular and plural nouns, that the identifiers in the lambda expressions are quite easy to understand what they represent?
Hope this helped a bit in understanding lambda expressions
CLICK HERE to find out more related problems solutions.