一、使用多个from子句
当使用多个from子句时,不自然的就会想到需要使用join等连接符,然而这需要看它们之间是否存在相互关系。相互关系是指关系中用以说明一个序列如何与另一个序列相关联的部分,此时,可以通过join子句中的equals关键字,或通过不等式以及多个表达式来实现。
当使用多个from子句时,不自然的就会想到需要使用join等连接符,然而这需要看它们之间是否存在相互关系。相互关系是指关系中用以说明一个序列如何与另一个序列相关联的部分,此时,可以通过join子句中的equals关键字,或通过不等式以及多个表达式来实现。
接下来举一个示例,用于说明,当我们使用多个from子句时,由于没有相互关系,故无须使用join
【示例】
【示例】
using System;
using System.Linq;
namespace 使用多个from子句
{
class Program
{
static void Main(string[] args)
{
//创建一个字符串数组
string[] myStrings =
{
"internet is an important communication tool.",
"I believe in you.",
"your behavior is illegal,you know?"
};
//myString叫做范围(range),相当于for循环中的迭代变量
//关键字let用于定义变量范围
var result = from myString in myStrings
let strings = myString.Split(new char[] { ' ', ',', '?','.'},
StringSplitOptions.RemoveEmptyEntries)
from s in strings
let one = s.ToUpper()
where one.Contains('I')
select s;
Array.ForEach(result.ToArray(), w => Console.Write(w + " "));
Console.ReadKey();
}
}
}
二、定义内联接
给定两个数据源,姑且称之为左源和右源,内联接只返回满足匹配条件的那些对象,即含有满足条件的左侧对象和右侧对象,换言之,只返回第一个from子句中的那些在第二个from子句中存在匹配项的元素。值得注意的是,第一个序列和第二个序列中的元素数量不必相等,两个序列中满足条件的元素会被返回以组成一个新序列。
一般而言,内联接基于主从关系或者是父子关系。鉴于此,就可这样描述,对于从源(子源)中的每一个或多个元素而言,主源(父源)中都有一个元素与之对应。
给定两个数据源,姑且称之为左源和右源,内联接只返回满足匹配条件的那些对象,即含有满足条件的左侧对象和右侧对象,换言之,只返回第一个from子句中的那些在第二个from子句中存在匹配项的元素。值得注意的是,第一个序列和第二个序列中的元素数量不必相等,两个序列中满足条件的元素会被返回以组成一个新序列。
一般而言,内联接基于主从关系或者是父子关系。鉴于此,就可这样描述,对于从源(子源)中的每一个或多个元素而言,主源(父源)中都有一个元素与之对应。
接下来,将举一个常见的例子,即典型的基于单键的内联接
【示例】
【示例】
using System;
using System.Collections.Generic;
using System.Linq;
namespace 定义内联接
{
class Program
{
static void Main(string[] args)
{
//先定义两个实体类,然后进行实例化并将其对象存在在相应类型的集合中
List<Customer> listC = new List<Customer>
{
new Customer {ID=1,CompanyName="company1" },
new Customer {ID=2,CompanyName="company2" },
new Customer {ID=3,CompanyName="company3" }
};
List<Order> listO = new List<Order>
{
new Order {ID=11,CustomerID=1,ItemScription="徐帅哈哈" },
new Order {ID=22,CustomerID=6,ItemScription="注销多少多少" },
new Order {ID=44,CustomerID=3,ItemScription="xutaohdshdis" },
new Order { ID=3,CustomerID=22,ItemScription="住hi的hi i姐姐第四"}
};
//内联接
var result = from c in listC
join o in listO on c.ID equals o.CustomerID
select new { Name = c.CompanyName, OrderID = o.ID, Item = o.ItemScription };
Array.ForEach(result.ToArray(), item =>
Console.WriteLine("Name:{0}, OrderID:{1}, Item:{2}",item.Name,item.OrderID,item.Item));
Console.ReadKey();
}
}
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 ItemScription { get; set; }
}
}
三、使用自定义(非等式)联接
上述例子使用的是等式连接,其中需要用到join、on以及equal,那么,有没有办法不使用join就能达到相同的效果呢?答案是必须的啊!当我们编写基于非等式的自定义连接时,就可以让关键字join滚蛋了。
那么,如何编写非等式连接?当联接的谓词基于不等式(或多个不等式,或多个等式)这样的表达式时,可以用不等式联接(不用join关键字)以实现交叉连接和自定义连接。当然,为了达到这样不可告人的卑鄙行径,此时需要首先在where子句中年定义两个序列之间的关联关系。
上述例子使用的是等式连接,其中需要用到join、on以及equal,那么,有没有办法不使用join就能达到相同的效果呢?答案是必须的啊!当我们编写基于非等式的自定义连接时,就可以让关键字join滚蛋了。
那么,如何编写非等式连接?当联接的谓词基于不等式(或多个不等式,或多个等式)这样的表达式时,可以用不等式联接(不用join关键字)以实现交叉连接和自定义连接。当然,为了达到这样不可告人的卑鄙行径,此时需要首先在where子句中年定义两个序列之间的关联关系。
3.1 实现非等式自定义连接
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.Data.SqlClient;
namespace 实现非等式自定义联接
{
class Program
{
static void Main(string[] args)
{
var productSelectWanted = new int[] { 1, 2, 3 };
List<Supplier> suppliers = new List<Supplier>();
List<Product> products = new List<Product>();
//List<Product> products = GetProducts();
const string SelectSQL = "select * from Products;select * from Suppliers";
using (SqlConnection con = new SqlConnection(connectionString))
{
con.Open();
SqlCommand com = new SqlCommand(SelectSQL, con);
IDataReader reader = com.ExecuteReader();
products=reader.ReadProducts();
//当读取批处理 SQL 语句的结果时,使数据读取器前进到下一个结果。
if (reader.NextResult())
{
suppliers = reader.ReadSuppliers();
}
}
//var result = from p in products
// from id in productSelectWanted
// where p.SupplierID == id
// && id > 2 && id <5
// orderby p.SupplierID
// select p;
var result = from p in products
let s = from supplier in suppliers
select supplier.SupplierID
where s.Contains(p.SupplierID)
orderby p.SupplierID
select new { SupplierID = p.SupplierID, ProductName = p.ProductName };
Array.ForEach(result.ToArray(), o => Console.WriteLine(
"Supplier ID={0,4},Product={1}", o.SupplierID, o.ProductName));
Console.ReadKey();
}
private static readonly string connectionString = "xxxxxxxxx";
//private static List<Product> GetProducts()
//{
// const string SelectSQL = "xxxxxxxx";
// using(SqlConnection con=new SqlConnection(connectionString))
// {
// con.Open();
// SqlCommand com = new SqlCommand(SelectSQL, con);
// IDataReader reader = com.ExecuteReader();
// return reader.ReadProducts();
// }
//}
private static List<Supplier> ReadSuppliers()
{
const string SelectSQL = "xxxxxxxx";
using (SqlConnection con = new SqlConnection(connectionString))
{
con.Open();
SqlCommand com = new SqlCommand(SelectSQL, con);
IDataReader reader = com.ExecuteReader();
return reader.ReadSuppliers();
}
}
}
/// <summary>
/// 产品类
/// </summary>
public class Product
{
public int? ProductID { get; set; }
public string ProductName { get; set; }
public int? SupplierID { get; set; }
}
/// <summary>
/// 提供商类
/// </summary>
public class Supplier
{
public int? SupplierID { get; set; }
public string CompanyName { get; set; }
}
/// <summary>
/// 扩展方法
/// </summary>
public static class ExtendsReader
{
public static List<Product> ReadProducts(this IDataReader reader)
{
List<Product> list = new List<Product>();
while (reader.Read())
{
Product p = new Product();
p.ProductID = reader.GetInt32(0);
p.ProductName = reader.GetString(1);
p.SupplierID = reader.GetInt32(2);
list.Add(p);
}
return list;
}
public static List<Supplier> ReadSuppliers(this IDataReader reader)
{
List<Supplier> list = new List<Supplier>();
while (reader.Read())
{
Supplier s = new Supplier();
s.SupplierID = reader["SupplierID"] == System.DBNull.Value ? -1 : reader.GetInt32(0);
s.CompanyName = reader["CompanyName"] == System.DBNull.Value ? "" : reader.GetString(1);
list.Add(s);
}
return list;
}
}
}
【分析】
结合上述两个例子来看,如果两个例子均含有键,那么基于这些键的等式联接就相当明显,可以考虑使用join,但本示例虽然两个序列亦含有键,但第二个序号是作为搜索条件的,或者是由用户直接输入的,换言之,第二个序列并不是由对象组成的,自然也就没有用于联接的键了。此时,我们就可以使用自定义联接和where谓词。
结合上述两个例子来看,如果两个例子均含有键,那么基于这些键的等式联接就相当明显,可以考虑使用join,但本示例虽然两个序列亦含有键,但第二个序号是作为搜索条件的,或者是由用户直接输入的,换言之,第二个序列并不是由对象组成的,自然也就没有用于联接的键了。此时,我们就可以使用自定义联接和where谓词。
from子句中in关键字之前的那个变量相当于for循环中的迭代变量,而我们通过关键字let可以添加额外的范围变量。试想一下,如果我们将所引入的范围变量作为内联接右侧的话,那么是不是就可以不用使用join on equal了呢?答案是必须的啊!此时,我们只能使用带有where子句和谓词的自定义联接。
如果有多个筛选条件,可以给自定义联接的where子句增加额外的谓词。
【注释 NextResult()方法】来源于网友
使用数据访问类时遇到这样一种需求:在执行一个存储过程时希望返回两个结果集.按以往的做法,必定是写成两个存储过程来实现,这样数据库往返两趟,牺牲了一点效率.由于数据量大,在使用IDataReader接口来读取结果集时,发现有NextResult方法,很少用它.于是看了一下MSDN,有这样一段介绍:"当读取批处理 SQL 语句的结果时,使数据读取器前进到下一个结果。用于处理多个结果,这些结果可通过执行批处理 SQL 语句获得。默认情况下,数据读取器定位在第一项结果上。"根据这个说法,尝试在一个存储过程连续执行两个查询,然后在访问数据的方法中,第一次使用 IDataReader.Read()方法作循环,读取出前一个结果集来,然后在不断连接的情况下,使用IDatareader.NextResult()判断一下是否有另一个结果集,再继续使用IDataReader.Read()作循环,完整取出后一个结果集无误,由此,可证明,在一个存储过程里执行多个查询语句,其结果集均能用这种办法取出,由此减少了数据连接的次数,而且可以在数据访问类中执行更为复杂的操作,例如借用前一个方法返回的多个结果执行批量操作而不用重新取得数据源,也能满足有连环数据操作的需求。
使用数据访问类时遇到这样一种需求:在执行一个存储过程时希望返回两个结果集.按以往的做法,必定是写成两个存储过程来实现,这样数据库往返两趟,牺牲了一点效率.由于数据量大,在使用IDataReader接口来读取结果集时,发现有NextResult方法,很少用它.于是看了一下MSDN,有这样一段介绍:"当读取批处理 SQL 语句的结果时,使数据读取器前进到下一个结果。用于处理多个结果,这些结果可通过执行批处理 SQL 语句获得。默认情况下,数据读取器定位在第一项结果上。"根据这个说法,尝试在一个存储过程连续执行两个查询,然后在访问数据的方法中,第一次使用 IDataReader.Read()方法作循环,读取出前一个结果集来,然后在不断连接的情况下,使用IDatareader.NextResult()判断一下是否有另一个结果集,再继续使用IDataReader.Read()作循环,完整取出后一个结果集无误,由此,可证明,在一个存储过程里执行多个查询语句,其结果集均能用这种办法取出,由此减少了数据连接的次数,而且可以在数据访问类中执行更为复杂的操作,例如借用前一个方法返回的多个结果执行批量操作而不用重新取得数据源,也能满足有连环数据操作的需求。
四、实现分组联接和左外联接
分组联接和左外联接其实都是分组联接,不过它们产生的结果可是不同的哦。当LINQ查询中含有into子句时,该查询就是一个分组联接,在MSIL中是一个扩展方法GroupJoin。
分组就是一个主从关系,即主序列中的单个元素及其所对应的从属序列,这个从属序列也叫做子序列。左外联接也使用了GroupJoin方法以及into子句,不过其数据是平面的,它将两个序列中的元素反规范化。值得注意的是,那些没有子序列的主序列元素也会被获取出来。
分组联接和左外联接其实都是分组联接,不过它们产生的结果可是不同的哦。当LINQ查询中含有into子句时,该查询就是一个分组联接,在MSIL中是一个扩展方法GroupJoin。
分组就是一个主从关系,即主序列中的单个元素及其所对应的从属序列,这个从属序列也叫做子序列。左外联接也使用了GroupJoin方法以及into子句,不过其数据是平面的,它将两个序列中的元素反规范化。值得注意的是,那些没有子序列的主序列元素也会被获取出来。
4.1 定义分组联接
分组就是从第一个序列中返回元素,然后将每个元素跟它们在第二个序列中相关的元素联系起来。实际上,分组联接是一种用于将关系型数据库中的规范化关系呈现出来的方式,而内联接则将数据反规范化(冗余)
分组就是从第一个序列中返回元素,然后将每个元素跟它们在第二个序列中相关的元素联系起来。实际上,分组联接是一种用于将关系型数据库中的规范化关系呈现出来的方式,而内联接则将数据反规范化(冗余)
有了分组联接,我们就有了另外一种处理数据的方式。外层序列表示分组,每个分组元素中的内层序列则表示该分组中的元素。其结果就是一个由返回类型所表示的主从关系。严格地说,从分组联接查询中所得到的返回结果仍然是一个IEnumerable<T>,只是每个分组都用一个键值关联了一个从属序列。
4.2 实现左外连接
左外连接就是内联接的结果再加上所有在第二个序列中没有匹配项对的第一个序列中的元素,因此,左外连接的结果数量大于或等于内联接的结果数量。
左外连接就是内联接的结果再加上所有在第二个序列中没有匹配项对的第一个序列中的元素,因此,左外连接的结果数量大于或等于内联接的结果数量。
左外连接是基于分组联接的。可以使用join...on...equals...into这样的形式来编写左外联接,如果我们想要将结果平面化,就要在分组的基础上再加上一个from子句。值得注意的是,如果我们做到这里就停下的话,那么我们只不过是绕了一大圈又实现了一个内联接而已。为了得到没有子元素的主元素,需要使用DefaultEmpty扩展方法,并指明要如何处理缺失的子元素。
4.3 实现交叉联接
交叉联接,又称笛卡尔联接,是两个源之间没有任何相互关系谓词的联接。
【示例】
using System;
using System.Collections.Generic;
using System.Linq;
namespace 定义分组联接
{
class Program
{
static void Main(string[] args)
{
List<Customer> customers = new List<Customer>
{
new Customer {ID=1,CompanyName="company001" },
new Customer {ID=2,CompanyName="company002" },
new Customer {ID=3,CompanyName="company003" },
new Customer {ID=4,CompanyName="" }
};
List<Order> orders = new List<Order>
{
new Order { ID=1,CustomerID=1,ItemDescription="12134546"},
new Order { ID=2,CustomerID=1,ItemDescription="99999999"},
new Order { ID=3,CustomerID=2,ItemDescription="88888888"},
new Order { ID=4,CustomerID=2,ItemDescription="00000000"},
new Order {ID=5,CustomerID=4,ItemDescription="" },
new Order {ID=6,CustomerID=5,ItemDescription="" }
};
//交叉连接(笛卡尔连接)
var test = from c in customers
from o in orders
select new
{
c.CompanyName,
o.CustomerID,
o.ItemDescription
};
foreach (var item in test)
{
Console.WriteLine(item.CompanyName+" "+item.CustomerID+" "+item.ItemDescription);
}
//group join,分组连接,相当于以第一层的字段来分组
var group = from customer in customers
join order in orders on customer.ID equals order.CustomerID
into children
select new { Customer = customer.CompanyName, Orders = children };
string separator = new string('*', 40);
foreach (var customer in group)
{
Console.WriteLine("Customer: {0}", customer.Customer);
Console.WriteLine(separator);
foreach (var order in customer.Orders)
{
Console.WriteLine("\tID={0},Item={1}", order.ID, order.ItemDescription);
}
Console.WriteLine(Environment.NewLine);
}
Console.WriteLine(separator);
//实现左外连接
//返回指定序列中的元素;如果序列为空,则返回单一实例集合中的指定值。
var groups = from customer in customers
join order in orders
on customer.ID equals order.CustomerID into children
from child in children.DefaultIfEmpty(new Order { ItemDescription = "此值为空" })
select new { Customer = customer.CompanyName, Item = child.ItemDescription };
foreach (var customer in groups)
{
Console.WriteLine("Customer={0},Item={1}", customer.Customer, customer.Item);
}
//实现右外连接
var groupRj = from order in orders
join customer in customers
on order.CustomerID equals customer.ID into children
from child in children.DefaultIfEmpty(new Customer { })
select new { ID = child.ID != null ? child.ID : -1, Customer = child.CompanyName != null ? child.CompanyName : "空值" };
Console.WriteLine(separator);
foreach (var item in groupRj)
{
Console.WriteLine("ID={0},CompanyName={1}", item.ID, item.Customer);
}
Console.ReadKey();
}
}
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; }
}
}
【错误】
原因:
未使用new实例化一个Customer类对象
五、在组合键上定义联接
一般而言,一个键就是一个字段,它表示的是一个值,这个值跟另外某个地方的某个值相对应。通常来说,这个键在整个记录中所扮演的角色是举足轻重的。而组合键就是由多个字段组合而成的键,而在SQL中,组合键即一个基于多列的索引,这和Linq的中的组合键是一个吊样。
在Linq中,可以使用单个值来表示一个关联关系,也可以使用组合键来表示。当使用组合键时,需要用到new关键字和多个字段。
【示例】
using System;
using System.Collections.Generic;
using System.Linq;
namespace 在组合键上定义连接
{
class Program
{
static void Main(string[] args)
{
List<Customer> customers = new List<Customer>
{
new Customer {ID=1,OrderID=1, CompanyName="company001" },
new Customer {ID=2,OrderID=3, CompanyName="company002" },
new Customer {ID=3,OrderID=4, CompanyName="company003" },
new Customer {ID=4,OrderID=5, CompanyName="company004" }
};
List<Order> orders = new List<Order>
{
new Order { ID=1,CustomerID=1,ItemDescription="12134546"},
new Order { ID=2,CustomerID=1,ItemDescription="99999999"},
new Order { ID=3,CustomerID=2,ItemDescription="88888888"},
new Order { ID=4,CustomerID=2,ItemDescription="00000000" },
new Order {ID=5,CustomerID=4,ItemDescription="434343434" },
new Order {ID=6,CustomerID=5,ItemDescription="" }
};
//组合键上的类型推理取决于这些键中属性的名称,以及属性的出现顺序。
//如果源序列中属性的名称不同,您必须在键中分配新名称。
var group = from customer in customers
join order in orders
on new { ID=customer.ID, OrderID=customer.OrderID } equals new { ID=order.CustomerID, OrderID=order.ID }
select new { CompanyName = customer.CompanyName, ItemDescription = order.ItemDescription };
Array.ForEach(group.ToArray(), s => Console.WriteLine(s.CompanyName + " " + s.ItemDescription));
Console.ReadKey();
}
}
public class Customer
{
public int ID { get; set; }
public int OrderID { 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; }
}
}