目录
正文
一、LINQ概述
1、LINQ简介
● LINQ的全称:Language Integrated Query。语言集成查询。
● 不采用特定于关系数据库或者XML的专有方案,而采用通用方案来解决各种信息资源的访问与整合问题。
(处理数据库,就必须理解SQL;处理XML,就必须理解Xpath、Xquery、XSLT这样的技术;处理DataSet,则需要了解ADO.NET中可以使用的各种类和特性。而LINQ提供了统一的数据视图,从而不需要考虑数据的形式和结构。)
● 在LINQ中,查询成为编程语言的一部分。这使得查询表达式可以得到编译时的语法检查,智能感知(InteliSense)的好处。
2、体系结构
如图所示:
● 最底层包含应用程序可以操作的各种数据源。包括:对象、关系数据库、XML。
● 倒数第二层是LINQ支持的数据源:LINQ to Objects、LINQ to DataSet、LINQ to SQL、LINQ to Entities和LINQ to XML。
● LINQ支持的数据源也称为LINQ提供程序;这些LINQ提供程序将以VB或C#表达的查询转换为该数据源的本地语言。为了通过LINQ访问所有这些数据源,开发人员可以使用C#或VB并编写LINQ查询。
3、基础回顾(匿名类型、扩展方法、拉姆达表达式)
● 匿名类型 :在不需要正式定义类的情况下定义数据类型。实际上,匿名类型是MS专门为LINQ设计的新功能。更多内容参见:匿名类型的相关文档。
● 扩展方法:扩展已有类的功能,而不需要创建该类的子类。扩展方法的出现,很重要的一个原因也是LINQ的出现。更多内容参见:扩展方法的相关文档。
● 拉姆达表达式:实现类似于匿名方法同样的效果。匿名方法,将代码直接与委托实例相关联。更多内容参见:匿名方法和Lambda表达式的相关文档。
4、LINQ查询幕后的实现
(1)、查询语法(LINQ查询)
string[] names ={
"Burke","Connor","Frank","Everett","Albert","George","Harris","David" };
IEnumerable<string> query =
from s in names
where s.Length == 5
orderby s
select s.ToUpper();
(2)、方法语法(Lambda表达式)----与(1)等价
IEnumerable<string> query = names
.Where(s => s.Length == 5)
.OrderBy(s => s)
.Select(s => s.ToUpper());
● s => s.Length == 5 | s => s | s => s.ToUpper() 是三个Lambda表达式。
● Where()、OrderBy()、Select()是静态类Enumerable的静态方法,是泛型接口IEnumerable<T>的扩展方法。
调用静态类的扩展方法 | 扩展方法允许这样调用 | 返回值类型 |
Enumerable.Where (names, s => s.Length == 5); | names.Where (s => s.Length == 5) | IEnumerable<T> |
Enumerable.OrderBy (names, s => s); | names.OrderBy (s => s) | IOrderedEnumerable<T> (实现了IEnumerable<T>接口) |
Enumerable.Select (names, s => s.ToUpper()); | names.Select (s => s.ToUpper()) | IEnumerable<T> |
◆ Where()方法需要的参数类型为: Func<TSource, bool>(委托类型)。
TSource:集合中元素的类型。bool:返回值类型。
◆ OrderBy()方法需要的参数类型为:Func<TSource, TKey>(委托类型)。
TSource: 集合中元素的类型。Tkey:返回值类型。
◆ Select()方法需要的参数类型为: Func<TSource, TResult>(委托类型)。
TSource: 集合中元素的类型。TResult:返回值类型。
● 更多内容参见:Where()、OrderBy()、Select()方法的定义。
(3)、方法语法(委托类型/匿名方法)----与(1)等价
Func<string, bool> filter = delegate(string s) { return s.Length == 5; };
Func<string, string> extract = delegate(string s) { return s; };
Func<string, string> project = delegate(string s) { return s.ToUpper(); };
IEnumerable<string> query =
names.Where(filter).OrderBy(extract).Select(project);
(4)、方法语法(委托类型/具体方法)----与(1)等价
Func<string, bool> filter = Method1;
Func<string, string> extract = Method2;
Func<string, string> project = Method3;
IEnumerable<string> query =
names.Where(filter).OrderBy(extract).Select(project);
bool Method1(string s) { return s.Length == 5; }
string Method2(string s) { return s; }
string Method3(string s) { return s.ToUpper(); }
5、LINQ延迟执行
(1)、LINQ延迟执行
var result =
from c in Products
where c.Price > 500
select c;
foreach (Product p in result)
{
……
}
● 查询变量result只是对linq查询的一个描述,而非执行结果。换言之,linq查询并不立即执行得到结果,而是将查询缓存在变量result中。那么result是什么类型呢?IQueryable<T>。public interface IQueryable<T> : IEnumerable<T>, IQueryable, IEnumerable
● IQueryable<T>只是查询表示,类似于SqlCommand等命令表示,真正的查询执行要依赖于在其上的调用操作。SqlCommand调用ExecuteNonQuery()、ExecuteReader()等方法将导致查询的执行;foreach语句会导致IQueryable<T>上的方法GetEnumerator()执行,从而导致查询的执行,并将返回结果IEnumerator<T>。
● 缓存查询,而不立即执行的这种现象称作延迟执行。
● 延迟执行如果使用不当会导致各种程序效率问题。
(2)、改进LINQ延迟执行
var result =
from c in Products
where c.Price > 500
select c;
var list = result.ToList<Product>;
foreach (Product p in list)
{
……
}
通过调用ToList或者ToArray方法,可以直接执行linq查询,将查询结果缓存在list变量中。
二、LINQ to SQL
1、LINQ to SQL 简介
● 面向对象领域,一切围绕对对象(字段、方法、引用)。
关系型数据库领域,一切围绕表格数据(行、列、关系)。
● LINQ将关系数据库映射为对象模型。开发人员不是直接操作数据库,而是操作表示数据库的对象模型。对对象模型执行更改之后,将其提交给数据库执行。
● LINQ To SQL是ADO.NET的一个重要组件,而非完全替代品。
2、实体类映射
[Table(Name = "Category")]
public class Category
{
[Column(IsPrimaryKey=true)]
public string CategoryId;
[Column]
public string Name;
[Column]
public string Descn;
}
[Table]特性表示将类Category与数据库中的表Category相对应。
[Column]特性则对应数据库中的列。
3、DataContext数据上下文对象
DataContext db = new DataContext(@"server=./sqlexpress;database=pubs;uid=sa;pwd=123;");
Table<Category> Categorys = db.GetTable<Category>();
var result =
from c in Categorys
select c;
DataContext类非常类似于数据库连接对象,但它为LINQ to SQL提供了更多支持。
4、定义基本关系
(1)、一对多关系
[Table(Name = "Category")]
public class Category
{
……
private EntitySet<Product> products;
[Association(Storage="products",OtherKey="CategoryId")]
public EntitySet<Product> Products
{
get { return this.products; }
set { this.products.Assign(value); }
}
}
Products映射了Category与Product之间的一对多关系。
(2)、多对一关系
[Table(Name="Product")]
public class Product
{
……
private EntityRef<Category> category;
[Association(Storage = "category", ThisKey = "CategoryId")]
public Category Category
{
get { return this.category.Entity; }
set { this.category.Entity = value; }
}
}
Category映射了Product与Category之间的多对一关系。
5、对象关系设计器
● 对象关系设计器将自动完成实体类映射、定义基本关系、创建DataContext子类等工作。
● 在项目中添加新项“LINQ to SQL类”,将创建默认名称为“DataClasses1.dbml”的文件。
(1)、查询代码片段
//查询居住在加利福尼亚的所有作者
void Method1()
{
DataClasses1DataContext database = new DataClasses1DataContext();
var authors = from a in database.authors
where a.state == "CA"
select new
{
Name = a.au_fname + " " + a.au_lname
};
foreach (var a in authors)
Console.WriteLine(a.Name);
}
(2)、新增代码片段
//插入新行(一个表)
void Method2()
{
DataClasses1DataContext database = new DataClasses1DataContext();
authors a = new authors()
{
au_id = "123-45-6789",
au_fname = "wang",
au_lname = "cheng",
phone = "13701362855"
};
database.authors.InsertOnSubmit(a);//InsertOnSubmit()方法只影响对象模型
database.SubmitChanges();//使用SubmitChanges()方法将更改保存到数据库
}
//插入新行(多个表)
void Method3()
{
//插入来自于新作者(author)的新书(title)的书名(authortitle)
DataClasses1DataContext db = new DataClasses1DataContext();
authors a = new authors()
{
au_id = "234-56-7890",
au_fname = "zhang",
au_lname = "ting",
phone = "13811765843"
};
titles t = new titles()
{
title_id = "ZT5555",
title = "C#高级编程(第六版)",
pubdate = System.DateTime.Now,
type = "computer"
};
titleauthor ta = new titleauthor()
{
authors = a,
titles = t
};
db.titleauthor.InsertOnSubmit(ta);//需要指示titleauthor表中的title_id和author_id,LINQ to SQL会自动执行该操作。
db.SubmitChanges();
}
(3)、更新代码片段
//更新行
void Method4()
{
DataClasses1DataContext db = new DataClasses1DataContext();
titles bookTitle =
(from t in db.titles
where t.title_id == "ZT5555"
select t).Single();//Single()方法返回序列中的唯一元素。如果该序列中一个元素也没有,则会跑出异常。
bookTitle.title = "SQL Server 核心技术";
db.SubmitChanges();
}
(4)、删除代码片段
//删除行(一个表)
void Method5()
{
DataClasses1DataContext db = new DataClasses1DataContext();
var author = from a in db.authors
where a.au_id == "123-45-6789"
select a;
if (author.Count() > 0)
{
db.authors.DeleteOnSubmit(author.First());
db.SubmitChanges();
}
}
//删除行(多个表)
void Method6()
{
DataClasses1DataContext db = new DataClasses1DataContext();
string au_id_to_remove = "234-56-7890";
//删除author
var author = from a in db.authors
where a.au_id == au_id_to_remove
select a;
foreach (var a in author)
db.authors.DeleteOnSubmit(a);
//删除titleauthor
var titleauthor = from ta in db.titleauthor
where ta.au_id == au_id_to_remove
select ta;
foreach (var ta in titleauthor)
db.titleauthor.DeleteOnSubmit(ta);
//程序里无论先删除author,还是先删除titleauthor都无所谓
//LINQ会自动先删除titleauthor,再删除author
db.SubmitChanges();
}
(5)、LINQ to SQL示例
三、LINQ to XML
1、.NET Framework3.5中新的XML对象
● XDocument对象:这个对象可以创建XML文档。它替代了.NET3.5之前的XmlDocument对象。
Xdocument对象的一个重要成员是Load()方法。这个方法将XML文档加载到内存中。
XDocument xdoc = XDocument.Load(@"C:/Hamlet.xml");
另一个重要成员是Save()方法。可以将xml文档保存到物理文件中。
XDocument xdoc = XDocument.Load(@"C:/Hamlet.xml");
xdoc.Save(@"C:/CopyOfHamlet.xml");
● XElement对象:表示单个元素的对象。
● XNamespace对象:表示XML命名空间。
● XComment对象:将注释添加到XML文档。
● XAttribute对象:用于添加和使用属性。
2、LINQ查询XML代码片段
XDocument LibraryBooks = new XDocument();
LibraryBooks = XDocument.Load("Books.xml");
var query =
from book in LibraryBooks.Descendants("Book")
where book.Element("Publisher").Value == "Wrox"
select book.Element("Title").Value;
Console.WriteLine("Wrox出版的所有书籍");
foreach (var book in query)
Console.WriteLine(book);
四、LINQ to DataSet
1、查询一个表代码片段
EnumerableRowCollection<DataRow> authors =
from author in ds.Tables[0].AsEnumerable()
where author.Field<string>("state") == "CA"
select author;
foreach (DataRow dr in authors)
{
Console.WriteLine("{0} - {1} {2}", dr["au_id"].ToString(), dr["au_fname"].ToString(), dr["au_lname"].ToString());
}
2、使用聚合函数代码片段
var query =
ds.Tables[0].AsEnumerable()
.Count(a => a.Field<string>("state") == "CA");
Console.WriteLine("共{0}个结果", query);
int[] nums = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var maxOddNum = nums.Where(n => n % 2 == 1).OrderByDescending(n => n).Max();
Console.WriteLine("最大的奇数是:{0}", maxOddNum);
var sumOfNums = nums.Where(n => n % 2 == 1).OrderByDescending(n => n).Sum();
Console.WriteLine("所有奇数的和为:{0}", sumOfNums);
3、查询两个表代码片段
var query =
(from customer in customersTable.AsEnumerable()
join order in ordersTable.AsEnumerable()
on customer.Field<string>("customerID") equals order.Field<string>("customerID")
select new
{
id=customer.Field<string>("customerID"),
CompanyName = customer.Field<string>("CompanyName"),
ContactName = customer.Field<string>("ContactName"),
OrderDate = order.Field<DateTime>("OrderDate"),
ShipCountry = order.Field<string>("ShipCountry")
}).ToList();
dataGridView1.DataSource = query;
4、查询类型化DataSet
向项目中添加新建项DataSet,命名为:TypedCustomerDataSet.xsd。
在服务器资源管理器窗口中,打开希望使用的数据库连接。该示例使用Northwind数据库。
将Customer表拖到TypedCustomerDataSet.xsd的设计界面上。
SqlConnection conn;
SqlCommand cmd;
SqlDataAdapter da;
TypedCustomerDataSet ds = new TypedCustomerDataSet();
conn = new SqlConnection(@"server=.;database=northwind;uid=sa;pwd=sa");
cmd = new SqlCommand("select * from customers;", conn);
da = new SqlDataAdapter(cmd);
da.Fill(ds,"Customers");
var query =
(from customer in ds.Customers
where customer.Country == "USA"
select new
{
customer.CustomerID,
customer.CompanyName,
customer.ContactName,
customer.ContactTitle
}).ToList();
dataGridView1.DataSource = query;
5、检测空字段代码片段
var query =
(from customer in ds.Customers
//where语句中的条件求值时使用了短路方式,因此必须将IsNull()方法放在其他条件之前
//如果没有使用类型化的DataSet,就不需要显示地检查空值
where !customer.IsNull("Region") && customer.Region == "WA"
select new
{
customer.CustomerID,
customer.CompanyName,
customer.ContactName,
customer.ContactTitle
}).ToList();
dataGridView1.DataSource = query;
6、将结果保存到DataTable代码片段
var query =
from customer in ds.Customers
where customer.Country == "USA"
select customer;
//CopyToDataTable()不适用于计划使用匿名类型的查询或执行表连接的查询。
DataTable USACustomers = query.CopyToDataTable();
dataGridView1.DataSource = USACustomers;
五、其他
1、LINQ to SQL示例
创建一个Windows应用程序。
在项目中添加新项LINQ to SQL类,对该项使用默认名称DataClasses1.dbml。
在服务器资源管理器中打开希望使用的数据库连接,在该示例中使用pubs样本数据库。
将以下表拖到DataClasses1.dbml的设计界面上:authors、publishers、titleauthor、titles。
保存DataClasses1.dbml文件。
下面的代码包括查询、新增、更新和删除的功能。
private void Form1_Load(object sender, EventArgs e)
{
Method1();
}
//查询居住在加利福尼亚的所有作者
void Method1()
{
DataClasses1DataContext database = new DataClasses1DataContext();
var authors = from a in database.authors
where a.state == "CA"
select new
{
Name = a.au_fname + " " + a.au_lname
};
foreach (var a in authors)
Console.WriteLine(a.Name);
}
//插入新行(一个表)
void Method2()
{
DataClasses1DataContext database = new DataClasses1DataContext();
authors a = new authors()
{
au_id = "123-45-6789",
au_fname = "wang",
au_lname = "cheng",
phone = "13701362855"
};
database.authors.InsertOnSubmit(a);//InsertOnSubmit()方法只影响对象模型
database.SubmitChanges();//使用SubmitChanges()方法将更改保存到数据库
}
//插入新行(多个表)
void Method3()
{
//插入来自于新作者(author)的新书(title)的书名(authortitle)
DataClasses1DataContext db = new DataClasses1DataContext();
authors a = new authors()
{
au_id = "234-56-7890",
au_fname = "zhang",
au_lname = "ting",
phone = "13811765843"
};
titles t = new titles()
{
title_id = "ZT5555",
title = "C#高级编程(第六版)",
pubdate = System.DateTime.Now,
type = "computer"
};
titleauthor ta = new titleauthor()
{
authors = a,
titles = t
};
db.titleauthor.InsertOnSubmit(ta);//需要指示titleauthor表中的title_id和author_id,LINQ to SQL会自动执行该操作。
db.SubmitChanges();
}
//更新行
void Method4()
{
DataClasses1DataContext db = new DataClasses1DataContext();
titles bookTitle =
(from t in db.titles
where t.title_id == "ZT5555"
select t).Single();//Single()方法返回序列中的唯一元素。如果该序列中一个元素也没有,则会跑出异常。
bookTitle.title = "SQL Server 核心技术";
db.SubmitChanges();
}
//删除行(一个表)
void Method5()
{
DataClasses1DataContext db = new DataClasses1DataContext();
var author = from a in db.authors
where a.au_id == "123-45-6789"
select a;
if (author.Count() > 0)
{
db.authors.DeleteOnSubmit(author.First());
db.SubmitChanges();
}
}
//删除行(多个表)
void Method6()
{
DataClasses1DataContext db = new DataClasses1DataContext();
string au_id_to_remove = "234-56-7890";
//删除author
var author = from a in db.authors
where a.au_id == au_id_to_remove
select a;
foreach (var a in author)
db.authors.DeleteOnSubmit(a);
//删除titleauthor
var titleauthor = from ta in db.titleauthor
where ta.au_id == au_id_to_remove
select ta;
foreach (var ta in titleauthor)
db.titleauthor.DeleteOnSubmit(ta);
//程序里无论先删除author,还是先删除titleauthor都无所谓
//LINQ会自动先删除titleauthor,再删除author
db.SubmitChanges();
}
2、杂项
● 数据库中的一行数据,用linq查询多次,转换成的对象是同一个对象(引用相等)。
● 两次查询同一行数据的中间在数据库对该行数据进行了更改,内存中的对象的值是不发生变化的。
● 过滤泛型List示例:
List<Customer> listCustomerAll;
Customer model;
List<Customer> listTemp = new List<Customer>();
listTemp = listCustomerAll.FindAll(
new Predicate<Customer>(delegate(Customer c) { return c.customerID == model.ID; })
);