所以每个Customer 有零个或多个Invoices,每个Invoice 是恰好一个客户的发票,即外键CustomerId 所指的客户:一个简单的一对多关系。
Invoices 和 InvoiceLines 之间同样存在一对多的关系:每个 Invoice 都有零个或多个 InvoiceLines,每个 InvoiceLine 都属于一个 Invoice,使用外键。
产品 - InvoiceLines:也是使用外键的一对多关系。
如果您关注entity-framework coding conventions,,您将获得类似于以下的课程:
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;}
}
在实体框架中,表的列由非虚拟属性表示。虚拟属性表示表之间的关系。
外键 CustomerId 是 Invoices 表中的一个真实列。因此它是非虚拟的。可以在虚拟属性 Customer 上看到 Invoice 与 Customer 有关系。
在实体框架中,表之间的关系类型(一对一、一对多、多对多)可以通过virtual ICollection<...> 或virtual ... 看到
表和集合使用复数名词来标识;表的单行或集合中的一项由单数名词标识。稍后当我们讨论 lambda 时,您会发现这使得解释 lambda 变得更容易。
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;}
}
最后是 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;}
}
实际上,这就是实体框架检测表、表中的列以及表之间的关系所需要知道的全部内容。不需要属性或流利的 api。只有当你想要不同于默认标识符的标识符、表名或列名,或者你想在关系中定义一些特殊的东西时,你才需要流畅的 API。
回到你的问题
如果我收到一个 productID 作为参数 (selectedID),我将如何访问与该 productID 相关的客户名称、invoiceID 和 PriceTotal?
首先,如果您有一个 productId,可能有多个客户使用此 productId 购买了该产品,因此有多个发票和几个 priceTotals。因此,您不能说出客户名称、发票 ID。
要求
如果我有 productId,请提供购买此产品的所有客户的姓名,以及声明他们购买此产品的 InvoiceId,以及他们购买此产品的发票总价。
请注意,客户可能在不同的订单中多次购买了该产品,因此他们可以有多个发票。
如果您遵循我上面写的约定,则解决方案相当简单:
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(),
});
你在这里看到了几个 Lambda 表达式。因为我对标识符的使用非常严格,所以它们应该不会太难:
dbContext.Customers.Where(...)
.Select(customer => new
{
// Select only the Customer properties that you plan to use
Id = customer.Id,
Name = customer.Name,
...
在 Where 之后,我得到了一个客户序列,并且从这个序列中的每个客户,我都创建了一个新对象,其属性 Id 具有 customerId 的值,属性 Name 具有值客户姓名。
这个客户对象,也有一个属性 Invoices:
Invoices = customer.Invoices
.Where(...)
.Select(invoice => new{...}
您不想要该客户的所有发票,您只想要发票,其中至少一个发票行具有 ProductId 产品的外键:
.Where(invoice => invoice.InvoiceLines.Any(invoiceLine => invoiceLine.ProductId == productId)
这表示:只保留那些至少有一个 InvoiceLIne 属性 ProductId 等于 productId 的发票。我们想要哪种发票。
PriceTotal = invoice.InvoiceLines
.Select(invoiceLine => invoiceLine.Price)
.Sum(),
为了计算包含该产品的发票的 PriceTotal,我们取该发票中每个 InvoiceLine 的价格,并将所有这些价格相加。
最困难的一个:客户在哪里:
.Where(customer => customer.Invoices
.SelectMany(invoice => invoiceLines)
.Any(invoiceLine => invoiceLine.ProductId == productId))
所以输入是一个客户序列。我们只保留那些在该客户的 Invoices 序列中至少有一个 invoiceLine 且 productId 等于 productId 的客户。
如果您有一个序列,其中序列中的每个元素都有一个子序列,并且您想将所有这些子序列作为一个序列进行检查,请使用 SelectMany。
因此,如果您有学校,并且每个学校都有学生,并且您希望按顺序查看所有学校的所有学生,用户:schools.SelectMany(school => school.Students) 我对 Invoices 和 InvoiceLines 做了同样的事情,以获取所有 Invoices 的所有 InvoiceLines该客户的:invoices.SelectMany(invoice => invoice.InvoiceLines)
对了,你有没有看到,因为我对单复数名词都非常严格,所以 lambda 表达式中的标识符很容易理解它们代表什么?
希望这对理解 lambda 表达式有所帮助