一个LINQ查询中有多个from子句,并不意味着一定要用join。相互关系是指查询中用以说明一个序列如何与另一个序列相关联的部分。这个可以通过join子句中的equal关键字。也可以通过不等式或多个表达式来实现。
string[] willRogers = { "Don't gamble; take all your savings ", "and buy some good stock and hold it ", "till it goes up, then sell it. If it ", "don't go up, don't buy it. Will Rogers" }; // part, word, words, and one are all called range variables var horseSense = from part in willRogers let words = part.Split(';', '.', ' ', ',') from word in words let one = word.ToUpper() where one.Contains('I') select word; Array.ForEach(horseSense.ToArray(), w => Console.WriteLine(w));给定两个数据源,内连接只返回满足匹配条件的那些对象。
class Program { static void Main(string[] args) { // 客户源 List<Customer> customers = new List<Customer> { new Customer{ID=1, CompanyName="Tom's Toffees"}, new Customer{ID=2, CompanyName="Karl's Coffees"}, new Customer{ID=3, CompanyName="Mary's Marshmallow Cremes"} }; // 订单源 List<Order> orders = new List<Order> { new Order{ID=1, CustomerID=1, ItemDescription="Granulated Sugar"}, new Order{ID=2, CustomerID=1, ItemDescription="Molasses"}, new Order{ID=3, CustomerID=2, ItemDescription="French Roast Beans"}, new Order{ID=4, CustomerID=2, ItemDescription="Ceramic Cups"} }; // 内连接,返回客户表中的ID属性与订单表中的客户ID属性相等的那些项 var join1 = from customer in customers join order in orders on customer.ID equals order.CustomerID select new { Name = customer.CompanyName, Item = order.ItemDescription }; Array.ForEach(join1.ToArray(), o => Console.WriteLine("Name: {0}, Item: {1}", o.Name, o.Item)); } } public class Customer { public int ID { get; set; } public string CompanyName { get; set; } } public class Order { public int ID { get; set; } public int CustomerID { get; set; } public string ItemDescription { get; set; } }结果 Name: Tom’s Toffees, Item: Granulated Sugar Name: Tom’s Toffees, Item: Molasses Name: Karl’s Coffees, Item: French Roast Beans Name: Karl’s Coffees, Item: Ceramic Cups
普通的join子句执行额是一个等式联接,使用join, on, equal关键字。当联接的谓词基于不等式(或多个等式,或多个不等式)这样的表达式时,或者当let子句为右侧序列引入了一个临时范围变量时,可以用不等式联接(不用join关键字)以实现交叉连接和自定义连接。为了实现自定义连接,首先要在where子句中定义两个序列之间的关联关系。
class Program { // 连接字符串 private string connectionString = "Server=localhost;Database=Northwind;Trusted_Connection=True;"; static void Main() { var productIDsWanted = new int[] { 1, 2, 3, 4, 5, 6 }; List<Product> products = GetProducts(); List<Supplier> suppliers = GetSuppliers(); // 实现非等式自定义连接(没有使用equal关键字) var results1 = from p in products from id in productIDsWanted where p.SupplierID == id orderby p.SupplierID select p; Array.ForEach(results1.ToArray(), target => Console.WriteLine("Supplier ID={0}, Product={1}", target.SupplierID, target.ProductName)); // 实现带有多个谓词的自定义连接 var results2 = from p in products from id in productIDsWanted where p.SupplierID == id orderby p.SupplierID select p; Array.ForEach(results2.ToArray(), target => Console.WriteLine("Supplier ID={0}, Product={1}", target.SupplierID, target.ProductName)); // 显示供应商的公司名称,即投影出新的类型 var results3 = from p in products join s in suppliers on p.SupplierID equals s.SupplierID from id in productIDsWanted where p.SupplierID == id && id > 2 && id < 5 orderby p.SupplierID select new { SupplierName = s.CompanyName, ProductName = p.ProductName }; Array.ForEach(results3.ToArray(), target => Console.WriteLine("Supplier ID={0}, Product={1}", target.SupplierName, target.ProductName)); } private static List<Product> GetProducts() { const string SQL = "SELECT * FROM PRODUCTS"; using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); SqlCommand command = new SqlCommand(SQL, connection); IDataReader reader = command.ExecuteReader(); return reader.ReadProducts(); } } private static List<Supplier> GetSuppliers() { const string SQL = "SELECT * FROM Suppliers"; List<Supplier> suppliers = new List<Supplier>(); using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); SqlCommand command = new SqlCommand(SQL, connection); IDataReader reader = command.ExecuteReader(); return reader.ReadSuppliers(); } } } /// <summary>对应于NorthWind数据库中的Product表的实体类</summary> public class Product { public int? ProductID { get; set; } public string ProductName { get; set; } public int? SupplierID { get; set; } public int? CategoryID { get; set; } public string QuantityPerUnit { get; set; } public decimal? UnitPrice { get; set; } public int? UnitsInStock { get; set; } public int? UnitsOnOrder { get; set; } public int? ReorderLevel { get; set; } public bool? Discontinued { get; set; } } /// <summary>对应于NorthWind数据库中的Supplier表的实体类</summary> public class Supplier { public int? SupplierID { get; set; } public string CompanyName { get; set; } public string ContactName { get; set; } public string ContactTitle { get; set; } public string Address { get; set; } public string City { get; set; } public string Region { get; set; } public string PostalCode { get; set; } public string Country { get; set; } public string Phone { get; set; } public string Fax { get; set; } public string HomePage { get; set; } } /// <summary>IDataReader扩展方法静态类</summary> public static class ExtendsReader { /// <summary>IDataReader读取Product表的扩展方法</summary> public static List<Product> ReadProducts(this IDataReader reader) { List<Product> list = new List<Product>(); while (reader.Read()) { Product product = new Product(); product.ProductID = reader.GetInt32(0); product.ProductName = reader.GetString(1); product.SupplierID = reader.GetInt32(2); product.CategoryID = reader.GetInt32(3); product.QuantityPerUnit = reader.GetString(4); product.UnitPrice = reader.GetDecimal(5); product.UnitsInStock = reader.GetInt16(6); product.UnitsOnOrder = reader.GetInt16(7); product.ReorderLevel = reader.GetInt16(8); product.Discontinued = reader.GetBoolean(9); list.Add(product); } return list; } /// <summary>IDataReader读取Supplier表的扩展方法</summary> public static List<Supplier> ReadSuppliers(this IDataReader reader) { List<Supplier> list = new List<Supplier>(); while (reader.Read()) { Supplier supplier = new Supplier(); supplier.SupplierID = reader.GetInt32(0); supplier.CompanyName = reader.GetString(1); supplier.ContactName = reader.GetString(2); supplier.ContactTitle = reader.GetString(3); supplier.Address = reader.GetString(4); supplier.City = reader.GetString(5); supplier.Region = reader["Region"] == System.DBNull.Value ? "" : reader.GetString(6); supplier.PostalCode = reader.GetString(7); supplier.Country = reader.GetString(8); supplier.Phone = reader.GetString(9); supplier.Fax = reader["Fax"] == System.DBNull.Value ? "" : reader.GetString(10); supplier.HomePage = reader["HomePage"] == System.DBNull.Value ? "" : reader.GetString(11); list.Add(supplier); } return list; } }from子句中in关键字之前的那个变量称为范围变量。其功能类似于for循环中的迭代变量。通过let关键字,还可以添加额外的范围变量。如果所引入的范围变量将作为内连接右侧的话,那么就不能使用join on equal子句了;只能使用带有where子句和谓词的自定义连接。
class Program { static void Main() { const string connectionString = "Server=localhost;Database=Northwind;Trusted_Connection=True;"; const string SQL = "SELECT * FROM PRODUCTS; SELECT * FROM SUPPLIERS"; List<Product> products = new List<Product>(); List<Supplier> suppliers = new List<Supplier>(); using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); SqlCommand command = new SqlCommand(SQL, connection); IDataReader reader = command.ExecuteReader(); products = reader.ReadProducts(); if (reader.NextResult()) suppliers = reader.ReadSuppliers(); } // let 关键字用于右侧源,那么就不能使用`join on equal`子句了;只能使用带有`where`子句和谓词的自定义连接 var nonEquiJoin = from p in products let master = from s in suppliers select s.SupplierID where master.Contains(p.SupplierID) orderby p.SupplierID select new { SupplierID = p.SupplierID, ProductName = p.ProductName }; Array.ForEach(nonEquiJoin.ToArray(), target => Console.WriteLine("Supplier ID={0,4}, Product={1}",target.SupplierID, target.ProductName)); } }分组联接和左外联接其实都是分组联接,不过他们所产生的结果不同。当LINQ查询中含有into子句时,该查询就是一个分组联接,在Microsoft中间语言中是一个扩展方法GroupJoin。分组就是一个主从关系,即序列中的单个元素及其所对应的从属序列,这个从属序列也叫做子序列。左外联接也使用了GroupJoin方法,不过其数据的平面的,它将两个序列中元素反规范化(指数据冗余之类的东西)。不过它跟内联接不同,那么没有子序列的主列元素也会被获取出来。
左外联接就是内连接的结果再加上所有在第二个序列中没有匹配项的第一个序列中的元素,因此,左外连接的结果数量总是大于或等于内联接的结果数量。 左外联接其实是基于分组连接的。可以使用join ... on ... equal ... into这样的形式来编写左外联接,不过你可能还想着要把结果集平面化。这了使结果集平面化,就要再分组的基础上再加上一个from子句。当然,如果做到这里就停下的话,那么你只不过使饶了一大圈有实现了一个内连接而已。为了得到没有子元素的主元素,需要使用DefaultIfEmpty拓展方法,并指明要如何处理缺失的子元素。
class Program { static void Main(string[] args) { List<Customer> customers = new List<Customer> { new Customer{ID=1, CompanyName="Tom's Toffees"}, new Customer{ID=2, CompanyName="Karl's Coffees"}, new Customer{ID=3, CompanyName="Mary's Marshmallow Cremes"} }; List<Order> orders = new List<Order> { new Order{ID=1, CustomerID=1, ItemDescription="Granulated Sugar"}, new Order{ID=2, CustomerID=1, ItemDescription="Molasses"}, new Order{ID=3, CustomerID=2, ItemDescription="French Roast Beans"}, new Order{ID=4, CustomerID=2, ItemDescription="Ceramic Cups"} }; // group join var group = from customer in customers join order in orders on customer.ID equals order.CustomerID into children from child in children.DefaultIfEmpty(new Order()) select new { Customer = customer.CompanyName, Item=child.ItemDescription }; foreach (var customer in group) { Console.WriteLine("Customer={0}, Item={1}", customer.Customer, customer.Item); } } }交叉联接是其他任何联接的基础。交叉联接是两个源之间没有任何相互关系谓词的联接。
class Program { static void Main(string[] args) { #region collection initializations var customers = new Customer[] { new Customer{CustomerID="ALFKI", CompanyName="Alfreds Futterkiste"}, new Customer{CustomerID="ANATR", CompanyName="Ana Trujillo Emparedados y helados"}, new Customer{CustomerID="ANTON", CompanyName="Antonio Moreno Taquería"}, new Customer{CustomerID="AROUT", CompanyName="Around the Horn"}, new Customer{CustomerID="BERGS", CompanyName="Berglunds snabbköp"}, new Customer{CustomerID="SPECD", CompanyName="Spécialités du monde"} }; var products = new Product[] { new Product{ProductID=1, ProductName="Chai"}, new Product{ProductID=2, ProductName="Chang"}, new Product{ProductID=3, ProductName="Aniseed Syrup"}, new Product{ProductID=4, ProductName="Chef Anton's Cajun Seasoning"}, new Product{ProductID=5, ProductName="Chef Anton's Gumbo Mix"}, new Product{ProductID=6, ProductName="Grandma's Boysenberry Spread"}, new Product{ProductID=7, ProductName="Uncle Bob's Organic Dried Pears"}, new Product{ProductID=8, ProductName="Northwoods Cranberry Sauce"}, new Product{ProductID=9, ProductName="Mishi Kobe Niku"}, new Product{ProductID=10, ProductName="Ikura"} }; var orders = new Order[] { new Order{ OrderID= 10278, CustomerID="BERGS"}, new Order{ OrderID= 10280, CustomerID="BERGS"}, new Order{ OrderID= 10308, CustomerID="BERGS"}, new Order{ OrderID= 10355, CustomerID="AROUT"}, new Order{ OrderID= 10365, CustomerID="ANTON"}, new Order{ OrderID= 10383, CustomerID="AROUT"}, new Order{ OrderID= 10384, CustomerID="BERGS"}, new Order{ OrderID= 10444, CustomerID="BERGS"}, new Order{ OrderID= 10445, CustomerID="BERGS"}, new Order{ OrderID= 10453, CustomerID="AROUT"}, new Order{ OrderID= 10507, CustomerID="ANTON"}, new Order{ OrderID= 10524, CustomerID="BERGS"}, new Order{ OrderID= 10535, CustomerID="ANTON"}, new Order{ OrderID= 10558, CustomerID="AROUT"}, new Order{ OrderID= 10572, CustomerID="BERGS"}, new Order{ OrderID= 10573, CustomerID="ANTON"}, new Order{ OrderID= 10625, CustomerID="ANATR"}, new Order{ OrderID= 10626, CustomerID="BERGS"}, new Order{ OrderID= 10643, CustomerID="ALFKI"} }; var orderDetails = new OrderDetail[] { new OrderDetail{OrderID=10278, ProductID=1, UnitPrice=15.5000M, Quantity=16, Discount=0}, new OrderDetail{OrderID=10278, ProductID=2, UnitPrice=44.0000M, Quantity=15, Discount=0}, new OrderDetail{OrderID=10278, ProductID=3, UnitPrice=35.1000M, Quantity=8, Discount=0}, new OrderDetail{OrderID=10278, ProductID=4, UnitPrice=12.0000M, Quantity=25, Discount=0}, new OrderDetail{OrderID=10280, ProductID=5, UnitPrice=3.6000M, Quantity=12, Discount=0}, new OrderDetail{OrderID=10280, ProductID=6, UnitPrice=19.2000M, Quantity=20, Discount=0}, new OrderDetail{OrderID=10280, ProductID=7, UnitPrice=6.2000M, Quantity=30, Discount=0}, new OrderDetail{OrderID=10308, ProductID=1, UnitPrice=15.5000M, Quantity=5, Discount=0}, new OrderDetail{OrderID=10308, ProductID=9, UnitPrice=12.0000M, Quantity=5, Discount=0}, new OrderDetail{OrderID=10355, ProductID=3, UnitPrice=3.6000M, Quantity=25, Discount=0}, new OrderDetail{OrderID=10355, ProductID=5, UnitPrice=15.6000M, Quantity=25, Discount=0}, new OrderDetail{OrderID=10365, ProductID=7, UnitPrice=16.8000M, Quantity=24, Discount=0}, new OrderDetail{OrderID=10383, ProductID=9, UnitPrice=4.8000M, Quantity=20, Discount=0}, new OrderDetail{OrderID=10383, ProductID=2, UnitPrice=13.0000M, Quantity=15, Discount=0}, new OrderDetail{OrderID=10383, ProductID=4, UnitPrice=30.4000M, Quantity=20, Discount=0} }; #endregion var orderInfo = from customer in customers from product in products // inner data from data in ((from order in orders join detail in orderDetails on order.OrderID equals detail.OrderID group detail by new { order.CustomerID, detail.ProductID, detail.Discount } into groups from item in groups let Quantity = groups.Sum(d => d.Quantity) select new { groups.Key.CustomerID, item.ProductID, item.UnitPrice, Quantity, item.Discount }).Distinct()) where customer.CustomerID == data.CustomerID && product.ProductID == data.ProductID orderby product.ProductID, customer.CompanyName select new { customer.CustomerID, customer.CompanyName, product.ProductID, product.ProductName, data.Quantity, data.UnitPrice, data.Discount, Total = (data.Quantity * data.UnitPrice * (1 - data.Discount)) }; Dump(orderInfo); } public static void Dump(object data) { if (data is IEnumerable) { IEnumerable list = data as IEnumerable; IEnumerator enumerator = list.GetEnumerator(); int i = 0; while (enumerator.MoveNext()) { i++; Console.WriteLine(enumerator.Current); Console.WriteLine(Environment.NewLine); } Console.WriteLine("Item count: {0}", i); } } } public class Customer { public string CustomerID { get; set; } public string CompanyName { get; set; } } public class Product { public int ProductID { get; set; } public string ProductName { get; set; } } public class Order { public int OrderID { get; set; } public string CustomerID { get; set; } } public class OrderDetail { public int OrderID { get; set; } public int ProductID { get; set; } public decimal UnitPrice { get; set; } public int Quantity { get; set; } public decimal Discount { get; set; } }