join
子句可用于将来自不同源序列并且在对象模型中没有直接关系的元素相关联。 唯一的要求是每个源中的元素需要共享某个可以进行比较以判断是否相等的值。 例如,食品经销商可能拥有某种产品的供应商列表以及买主列表。 例如,可以使用 join
子句创建该产品同一指定地区供应商和买主的列表。
join
子句将 2 个源序列作为输入。 每个序列中的元素都必须是可以与其他序列中的相应属性进行比较的属性,或者包含一个这样的属性。 join
子句使用特殊 equals
关键字比较指定的键是否相等。 join
子句执行的所有联接都是同等联接。 join
子句的输出形式取决于执行的联接的具体类型。 以下是 3 种最常见的联接类型:
-
内部联接
-
分组联接
-
左外部联接
内部联接
以下示例演示了一个简单的内部同等联接。 此查询生成一个“产品名称/类别”对平面序列。 同一类别字符串将出现在多个元素中。 如果 categories
中的某个元素不具有匹配的 products
,则该类别不会出现在结果中。
var innerJoinQuery = from category in categories join prod in products on category.ID equals prod.CategoryID select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence
Group Join
含有 into
表达式的 join
子句称为分组联接。
var innerGroupJoinQuery = from category in categories join prod in products on category.ID equals prod.CategoryID into prodGroup select new { CategoryName = category.Name, Products = prodGroup };
分组联接会生成分层的结果序列,该序列将左侧源序列中的元素与右侧源序列中的一个或多个匹配元素相关联。 分组联接没有等效的关系术语;它本质上是一个对象数组序列。
如果在右侧源序列中找不到与左侧源中的元素相匹配的元素,则 join
子句会为该项生成一个空数组。 因此,分组联接基本上仍然是一种内部同等联接,区别在于分组联接将结果序列组织为多个组。
如果只选择分组联接的结果,则可访问各项,但无法识别结果所匹配的项。 因此,通常更为有用的做法是:选择分组联接的结果并将其放入一个也包含该项名的新类型中,如上例所示。
当然,还可以将分组联接的结果用作其他子查询的生成器:
var innerGroupJoinQuery2 = from category in categories join prod in products on category.ID equals prod.CategoryID into prodGroup from prod2 in prodGroup where prod2.UnitPrice > 2.50M select prod2;
左外部联接
在左外部联接中,将返回左侧源序列中的所有元素,即使右侧序列中没有其匹配元素也是如此。 若要在 LINQ 中执行左外部联接,请结合使用 DefaultIfEmpty
方法与分组联接,指定要在某个左侧元素不具有匹配元素时生成的默认右侧元素。 可以使用 null
作为任何引用类型的默认值,也可以指定用户定义的默认类型。 以下示例演示了用户定义的默认类型:
var leftOuterJoinQuery = from category in categories join prod in products on category.ID equals prod.CategoryID into prodGroup from item in prodGroup.DefaultIfEmpty(new Product { Name = String.Empty, CategoryID = 0 }) select new { CatName = category.Name, ProdName = item.Name };
组合键
可通过使用组合键测试多个值是否相等。 有关详细信息,请参阅如何:使用组合键进行联接。 还可以在 group
子句中使用组合键。
var query = from o in db.Orders from p in db.Products join d in db.OrderDetails on new {o.OrderID, p.ProductID} equals new {d.OrderID,d.ProductID} into details from d in details select new {o.OrderID, p.ProductID, d.UnitPrice};
if the Orders
table and OrderDetails
table each used different names for their columns, you could create composite keys by assigning identical names in the anonymous types:
join...on new {Name = o.CustomerName, ID = o.CustID} equals new {Name = d.CustName, ID = d.CustID }