LINQ(LINQ to Entities)
2014-01-11 20:28
316 查看
LINQtoEntities是LINQ中最吸引人的部分。它让你可以使用标准的C#对象与数据库的结构和数据打交道。使用LINQtoEntities时,LINQ查询在后台转换为SQL查询并在需要数据的时候执行,即开始枚举结果的时候执行。LINQtoEntities还为你获取的所有数据提供变化追踪,也就是说,可以修改查询获得的对象,然后整批同时把更新提交到数据库。
LINQtoEntities是EntityFramework的一部分并且取代LINQtoSQL作为在数据库上使用LINQ的标准机制。EntityFramework是行业领先的对象-关系映射(ORM)系统。可以和多种数据库一起使用,并支持各种灵活、复杂的数据模型。
注:
微软把开发的重点从LINQtoSQL转移到了LINQtoEntities,并且宣布LINQtoSQL不再提供更新,LINQtoSQL现在仍被支持单不推荐。
LINQtoEntities是一项令人印象深刻的技术,但对大多数开发人员而言只是一个小的进步。和DataSet一样,ASP.NET开发人员使用LINQ的查询特新远多于它的批量更新特性。这是因为通常Web应用程序的更新是单次的而不是批量的。他们更愿意在页面回发时立刻执行更新,同时可以获得原始值和新(更新)值,这使得通过ADO.NET命令提交更新更加方便。
简而言之,LINQtoEntities没有提供任何不能用ADO.NET代码、自定义对象、LINQtoObjects实现的特性,但是有时出于某些原因而需要考虑使用LINQtoEntities:
更少的代码。不必编写查询数据库的ADO.NET代码,可以通过一个工具生成需要的数据类。
灵活的查询能力。不必拼凑SQL语句,而是使用LINQ查询模型。一致的查询模型可访问众多不同的数据源(从数据库到XML)。
变更追踪以及批量更新。可以对查询的数据进行多项修改并提交批量更新,这不需要编写任何ADO.NET代码。
为了生成模型,右击App_Code目录,单击“添加新项”,“ADO.NET实体数据模型”,设置创建的文件名称后(这里是NorthwindModel.edmx),单击“确定”。
从一个已经存在的数据库生成模型,即微软的Northwind示例数据库。配置数据库连接,并可以选择表、视图、和存储过程。还可以选择使用复数还是单数形式的对象名(例如,Products表的行被命名为Product)、是否包含外键关系等。这里选择全部表并选中“所生成对象的单复数形式”。
VisualStudio会为你选择的数据库元素创建模型图,它显示了已经创建的映射对象、对象拥有的字段以及对象之间的关系。
项目中新增了下面这两个文件:
NorthwindModel.edmx:这个XML文件定义数据库模型的架构。
NorthwindModel.Designer.cs:这个C#代码文件包含数据模型的映射对象。
打开该文件,可以看到有两段代码区域:Contexts和Entities。
1.派生的对象上下文
NorthwindModel.Designer.cs文件中定义的第一个类从ObjectContext派生,其名称是NorthwindEntities。这个类的构造函数连接到所生成模型的数据库,或者你也可以指定连接字符串连接到其他数据库(必须具有相同的架构,否则模型无法工作)。
下面是一个简单的示例:
2.实体类
实体类用于把数据库表的记录映射到C#对象。如果选中了“确定所生成对象的单复数形式”选项,那么像Products这样的表创建的实体对象名称是Product。
每个实体对象包含如下的内容:
工厂方法:可以通过默认的构造函数或工厂方法创建实体对象新实例。工厂方法的参数是需要的字段,它是试图保存数据元素时防止架构错误的好办法。
字段属性:实体对象为它们派生的数据库表的每个列包含一个字段属性。
导航属性:如果数据模型中包含了外键关系,实体对象就会包含帮助访问关联数据的导航属性。
提示:
实体类被声明为分部类,因此可以创建扩展功能,在重新生成数据模型时它不会被覆盖。
示例:
ID=5的产品不符合条件被过滤掉了
这个查询使用Orders导航属性查询每个与Customer关联的所有Orders。我们使用Order实体类型的Employee导航属性检查下了订单的员工的姓并过滤掉了姓等于“King”的数据。
使用导航属性,不必为每个实体类创建单独的查询就可以在数据模型间导航。
1.一对多关系
一对多关系的导航属性通过强类型的EntityCollection来处理。针对某个关系选择合适记录的问题你不需要关心,它已经由外键关系处理了。因此选择某个用户的订单时,仅仅得到了那些CustomerID值和Customer的CustomerID属性值相同的Order实例。
使用SelectMany扩展方法进行LINQtoEntities查询,可以直接把EntityCollection类作为查询结果,它会在结果集合里包含所有匹配的结果。
示例如下:
也可以改写成隐式的LINQ表达式得到相同的结果:
2.一对一关系
对于一对一关系,有两个导航属性。
TReference:它返回的结果是EntityReference<T>,其中,T是关联关系引用的实体类型。例如,Order实体类型有一个名为EmployeeReference的导航属性,它返回EntityReference<Employee>。
T:这个属性更有用。T是它引用的实体类型。例如,Order实体类型有一个名为Employee的方便的导航属性。
选中存储过程,右键添加函数导入,导入界面还可以选择“创建新的复杂类型”,然后可以如下使用它:
此时返回的类型的名称就是先前选择自定义复杂类型的名称。
缺点是这种相似性来自对很多复杂性的隐藏。如果不小心,就会给数据库产生很大的负载。你应该花时间好好检查为了服务你的LINQtoEntities查询,究竟生成了什么样的SQL查询。通过EntityFramework查看SQL查询并不容易,需要把LINQtoEntities查询的结果转换为System.Data.Objects.ObjectQuery的实例并调用
ToTraceString()方法才行。
示例:
很多时候像这样打印SQL查询并不现实。如果使用的是非Express版本的SQLServer,可以使用SQLServerProfile工具。如果是Express版本的,那我们推荐使用Anjlab开发的开源免费的SQLProfile,它很不错。
1.迟到的过滤
一个导致不必要的数据库查询的常见原因是过滤查询中的数据太晚了,这是一个查询示例:
这里的问题是,第一个查询从数据库检索Country=UK的所有记录。第二个查询应用于第一个查询的结果,但是它使用的是LINQtoObjects,也就是说我们丢弃了从数据库请求的绝大部分数据(第一个查询就显得非常的浪费资源了)。
这个示例只会产生一条SQL查询,类似于于下面:
SELECT*FROMCustomersWHERECountry='UK'
解决方案是把过滤器融合到同一个查询里。
2.使用延迟和贪婪数据加载
为了让导航属性无缝地工作,LINQtoEntities使用了一项称作延迟加载的技术,只在需要的时候才从数据库加载数据。通过导航属性从某个实体类型转移到另一个实体类型时,第二个实体类型的实例仅在需要的时候才加载。
这个查询中,我们过滤出一组Customers,然后对其结果进行迭代,并导航到相关的Order实例,最终,我们得到了位于英国伦敦且订单多余两笔的公司名称。
由于延迟加载,Orders表的数据只在需要时加载,也就是说为了在循环中得到每个客户关联的订单,我们都生成了一条SQL查询。这产生了太多的查询。对于这个简单的示例,我们可以把所有这一切整合到一个LINQ查询里。
但其实我们要演示的是贪婪加载功能。它可以在查询中加载其他表的关联数据。示例如下:
使用Include()扩展方法包含关联的数据,它告诉LINQtoEntities引擎关联到我们查询的Customer的Order实例应该被加载,即便这个查询并没有直接关联到Orders表。最终,EntityFramework捕获了结果,也就是说当我们迭代Customer实例并检查关联的Orders时,它们都已经被加载了,不需要再生成额外的数据库查询。
3.使用显式加载
如果要完全控制加载的数据,可以使用显式加载。可以使用派生ObejectContext类禁用延迟加载,然后使用EntityCollection.Load()方法按需加载数据,可以通过IsLoaded方法检查所需的数据是否已经加载。
禁用了延迟加载,即导航属性要引用的数据不会被自动加载。
第一次迭代使用Load()显式加载那些符合条件的Orders数据,此时数据会从数据库加载到EntityFramework缓存里。
第二次迭代检查所有的Customers对象,用IsLoaded属性判断哪些Customers加载了Orders数据
First()方法可以把第一条Order添加到集合中
这个示例并不怎么自然,但它足以让你看出项目中实际使用显式加载所需的知识。
4.编译查询
LINQtoEntities另一个隐藏的功能是能够创建已编译的查询。已编译的查询是一个强类型的Func委托,它有一个用于查询的参数。编译查询时,执行翻译到SQL语句的动作,以后每次调用已编译的查询时都会对其重用。
这并不像使用存储过程那样高效,因为数据库还是要创建查询计划来执行SQL,但它确实避免了LINQtoEntities重复解析LINQ查询。
示例:
LINQtoEntities是EntityFramework的一部分并且取代LINQtoSQL作为在数据库上使用LINQ的标准机制。EntityFramework是行业领先的对象-关系映射(ORM)系统。可以和多种数据库一起使用,并支持各种灵活、复杂的数据模型。
注:
微软把开发的重点从LINQtoSQL转移到了LINQtoEntities,并且宣布LINQtoSQL不再提供更新,LINQtoSQL现在仍被支持单不推荐。
LINQtoEntities是一项令人印象深刻的技术,但对大多数开发人员而言只是一个小的进步。和DataSet一样,ASP.NET开发人员使用LINQ的查询特新远多于它的批量更新特性。这是因为通常Web应用程序的更新是单次的而不是批量的。他们更愿意在页面回发时立刻执行更新,同时可以获得原始值和新(更新)值,这使得通过ADO.NET命令提交更新更加方便。
简而言之,LINQtoEntities没有提供任何不能用ADO.NET代码、自定义对象、LINQtoObjects实现的特性,但是有时出于某些原因而需要考虑使用LINQtoEntities:
更少的代码。不必编写查询数据库的ADO.NET代码,可以通过一个工具生成需要的数据类。
灵活的查询能力。不必拼凑SQL语句,而是使用LINQ查询模型。一致的查询模型可访问众多不同的数据源(从数据库到XML)。
变更追踪以及批量更新。可以对查询的数据进行多项修改并提交批量更新,这不需要编写任何ADO.NET代码。
生成数据模型
EntityFramework依赖于一个数据模型来使用LINQtoEntities进行查询。表中的行被转换为C#对象的实例,表的列是这些对象的属性。数据库架构和数据模型对象的映射是EntityFramework的核心.为了生成模型,右击App_Code目录,单击“添加新项”,“ADO.NET实体数据模型”,设置创建的文件名称后(这里是NorthwindModel.edmx),单击“确定”。
从一个已经存在的数据库生成模型,即微软的Northwind示例数据库。配置数据库连接,并可以选择表、视图、和存储过程。还可以选择使用复数还是单数形式的对象名(例如,Products表的行被命名为Product)、是否包含外键关系等。这里选择全部表并选中“所生成对象的单复数形式”。
VisualStudio会为你选择的数据库元素创建模型图,它显示了已经创建的映射对象、对象拥有的字段以及对象之间的关系。
项目中新增了下面这两个文件:
NorthwindModel.edmx:这个XML文件定义数据库模型的架构。
NorthwindModel.Designer.cs:这个C#代码文件包含数据模型的映射对象。
数据模型类
我们将把大部分时间花在NorthwindModel.Designer.cs这个文件上。因为它包含了我们要用于LINQtoEntities查询的数据类型。(这个文件会被数据模型重新生成,因此不应该也不必要手工去修改这个文件,你的修改会丢失。)打开该文件,可以看到有两段代码区域:Contexts和Entities。
1.派生的对象上下文
NorthwindModel.Designer.cs文件中定义的第一个类从ObjectContext派生,其名称是NorthwindEntities。这个类的构造函数连接到所生成模型的数据库,或者你也可以指定连接字符串连接到其他数据库(必须具有相同的架构,否则模型无法工作)。
下面是一个简单的示例:
protectedvoidPage_Load(objectsender,EventArgse)
{
NorthwindEntitiesdb=newNorthwindEntities();
GridView1.DataSource=db.Products;
GridView1.DataBind();
}
2.实体类
实体类用于把数据库表的记录映射到C#对象。如果选中了“确定所生成对象的单复数形式”选项,那么像Products这样的表创建的实体对象名称是Product。
每个实体对象包含如下的内容:
工厂方法:可以通过默认的构造函数或工厂方法创建实体对象新实例。工厂方法的参数是需要的字段,它是试图保存数据元素时防止架构错误的好办法。
字段属性:实体对象为它们派生的数据库表的每个列包含一个字段属性。
导航属性:如果数据模型中包含了外键关系,实体对象就会包含帮助访问关联数据的导航属性。
提示:
实体类被声明为分部类,因此可以创建扩展功能,在重新生成数据模型时它不会被覆盖。
示例:
protectedvoidPage_Load(objectsender,EventArgse)
{
NorthwindEntitiesdb=newNorthwindEntities();
varresult=frompindb.Products
wherep.Discontinued==false
selectnew
{
ID=p.ProductID,
Name=p.ProductName
};
GridView1.DataSource=result;
GridView1.DataBind();
}
ID=5的产品不符合条件被过滤掉了
实体关系
实体类包含导航属性。通过导航属性可以在数据模型间移动而不需要考虑外键关系。看下面的示例:protectedvoidPage_Load(objectsender,EventArgse)
{
NorthwindEntitiesdb=newNorthwindEntities();
varresult=fromcindb.Customers
leto=fromqinc.Orders
whereq.Employee.LastName!="King"
selectq
wherec.City=="London"&&o.Count()>5
selectnew
{
Name=c.CompanyName,
Contact=c.ContactName,
OrderCount=o.Count()
};
GridView1.DataSource=result;
GridView1.DataBind();
}
这个查询使用Orders导航属性查询每个与Customer关联的所有Orders。我们使用Order实体类型的Employee导航属性检查下了订单的员工的姓并过滤掉了姓等于“King”的数据。
使用导航属性,不必为每个实体类创建单独的查询就可以在数据模型间导航。
1.一对多关系
一对多关系的导航属性通过强类型的EntityCollection来处理。针对某个关系选择合适记录的问题你不需要关心,它已经由外键关系处理了。因此选择某个用户的订单时,仅仅得到了那些CustomerID值和Customer的CustomerID属性值相同的Order实例。
使用SelectMany扩展方法进行LINQtoEntities查询,可以直接把EntityCollection类作为查询结果,它会在结果集合里包含所有匹配的结果。
示例如下:
protectedvoidPage_Load(objectsender,EventArgse)
{
NorthwindEntitiesdb=newNorthwindEntities();
IEnumerable<Order>orders=db.Customers
//定位LAZYK客户
.Where(c=>c.CustomerID=="LAZYK")
//以上一个客户结果集为准,导航客户的Orders属性获得所有Order
.SelectMany(c=>c.Orders);
GridView1.DataSource=orders;
GridView1.DataBind();
}
也可以改写成隐式的LINQ表达式得到相同的结果:
varresult=fromoindb.Orders
whereo.CustomerID=="LAZYK"
selecto;
2.一对一关系
对于一对一关系,有两个导航属性。
TReference:它返回的结果是EntityReference<T>,其中,T是关联关系引用的实体类型。例如,Order实体类型有一个名为EmployeeReference的导航属性,它返回EntityReference<Employee>。
T:这个属性更有用。T是它引用的实体类型。例如,Order实体类型有一个名为Employee的方便的导航属性。
查询存储过程
在解决方案资源管理器双击NorthwindModel.edmx文件,打开数据模型图,按右键选择“从数据库更新”可导入存储过程。打开“实体数据模型浏览器”(在“视图”->“其他”菜单里可以找到),展开NorthwindModel.Store节点,打开存储过程目录,就会看到导入模型里的存储过程列表。选中存储过程,右键添加函数导入,导入界面还可以选择“创建新的复杂类型”,然后可以如下使用它:
protectedvoidPage_Load(objectsender,EventArgse)
{
NorthwindEntitiesdb=newNorthwindEntities();
IEnumerable<ESBC_Result>results
=fromcindb.ESBC(DateTime.Now.AddYears(-20),DateTime.Now)
selectc;
GridView1.DataSource=results;
GridView1.DataBind();
}
此时返回的类型的名称就是先前选择自定义复杂类型的名称。
LINQtoEntities查询揭秘
先前演示的LINQtoEntities的用法几乎和LINQtoObjects的用法完全一样。确实是这样(至少表面上是这样)。LINQ最棒的一件事就是它对各种数据源保持高度的一致性。如果你知道如何使用基本的LINQ查询,就可以用它来查询对象、数据库、XML等。缺点是这种相似性来自对很多复杂性的隐藏。如果不小心,就会给数据库产生很大的负载。你应该花时间好好检查为了服务你的LINQtoEntities查询,究竟生成了什么样的SQL查询。通过EntityFramework查看SQL查询并不容易,需要把LINQtoEntities查询的结果转换为System.Data.Objects.ObjectQuery的实例并调用
ToTraceString()方法才行。
示例:
protectedvoidPage_Load(objectsender,EventArgse)
{
NorthwindEntitiesdb=newNorthwindEntities();
varresult=fromcindb.Customers
leto=fromqinc.Orders
whereq.Employee.LastName!="King"
selectq
wherec.City=="London"&&o.Count()>5
selectnew
{
Name=c.CompanyName,
Contact=c.ContactName,
OrderCount=o.Count()
};
Label1.Text=(resultasSystem.Data.Objects.ObjectQuery).ToTraceString();
}
很多时候像这样打印SQL查询并不现实。如果使用的是非Express版本的SQLServer,可以使用SQLServerProfile工具。如果是Express版本的,那我们推荐使用Anjlab开发的开源免费的SQLProfile,它很不错。
1.迟到的过滤
一个导致不必要的数据库查询的常见原因是过滤查询中的数据太晚了,这是一个查询示例:
NorthwindEntitiesdb=newNorthwindEntities();
IEnumerable<NorthwindModel.Customer>custs
=fromcindb.Customers
wherec.Country=="UK"
selectc;
IEnumerable<NorthwindModel.Customer>results
=fromcincusts
wherec.City=="London"
selectc;
GridView1.DataSource=results;
GridView1.DataBind();
这里的问题是,第一个查询从数据库检索Country=UK的所有记录。第二个查询应用于第一个查询的结果,但是它使用的是LINQtoObjects,也就是说我们丢弃了从数据库请求的绝大部分数据(第一个查询就显得非常的浪费资源了)。
这个示例只会产生一条SQL查询,类似于于下面:
SELECT*FROMCustomersWHERECountry='UK'
解决方案是把过滤器融合到同一个查询里。
2.使用延迟和贪婪数据加载
为了让导航属性无缝地工作,LINQtoEntities使用了一项称作延迟加载的技术,只在需要的时候才从数据库加载数据。通过导航属性从某个实体类型转移到另一个实体类型时,第二个实体类型的实例仅在需要的时候才加载。
protectedvoidPage_Load(objectsender,EventArgse)
{
NorthwindEntitiesdb=newNorthwindEntities();
IEnumerable<NorthwindModel.Customer>custs
=fromcindb.Customers
wherec.Country=="UK"&&c.City=="London"
selectc;
List<string>names=newList<string>();
foreach(NorthwindModel.Customercincusts)
{
if(c.Orders.Count>2)
{
names.Add(c.CompanyName);
}
}
GridView1.DataSource=names;
GridView1.DataBind();
}
这个查询中,我们过滤出一组Customers,然后对其结果进行迭代,并导航到相关的Order实例,最终,我们得到了位于英国伦敦且订单多余两笔的公司名称。
由于延迟加载,Orders表的数据只在需要时加载,也就是说为了在循环中得到每个客户关联的订单,我们都生成了一条SQL查询。这产生了太多的查询。对于这个简单的示例,我们可以把所有这一切整合到一个LINQ查询里。
但其实我们要演示的是贪婪加载功能。它可以在查询中加载其他表的关联数据。示例如下:
protectedvoidPage_Load(objectsender,EventArgse)
{
NorthwindEntitiesdb=newNorthwindEntities();
IEnumerable<NorthwindModel.Customer>custs
=fromcindb.Customers.Include("Orders")
wherec.Country=="UK"&&c.City=="London"
selectc;
List<string>names=newList<string>();
foreach(NorthwindModel.Customercincusts)
{
if(c.Orders.Count>2)
{
names.Add(c.CompanyName);
}
}
GridView1.DataSource=names;
GridView1.DataBind();
}
使用Include()扩展方法包含关联的数据,它告诉LINQtoEntities引擎关联到我们查询的Customer的Order实例应该被加载,即便这个查询并没有直接关联到Orders表。最终,EntityFramework捕获了结果,也就是说当我们迭代Customer实例并检查关联的Orders时,它们都已经被加载了,不需要再生成额外的数据库查询。
3.使用显式加载
如果要完全控制加载的数据,可以使用显式加载。可以使用派生ObejectContext类禁用延迟加载,然后使用EntityCollection.Load()方法按需加载数据,可以通过IsLoaded方法检查所需的数据是否已经加载。
protectedvoidPage_Load(objectsender,EventArgse)
{
NorthwindEntitiesdb=newNorthwindEntities();
db.ContextOptions.LazyLoadingEnabled=false;
IEnumerable<NorthwindModel.Customer>custs
=fromcindb.Customers
wherec.Country=="UK"
selectc;
foreach(NorthwindModel.Customercincusts)
{
if(c.City=="London")
{
c.Orders.Load();
}
}
List<Order>orders=newList<Order>();
foreach(NorthwindModel.Customercincusts)
{
if(c.Orders.IsLoaded)
{
orders.Add(c.Orders.First());
}
}
GridView1.DataSource=orders;
GridView1.DataBind();
}
禁用了延迟加载,即导航属性要引用的数据不会被自动加载。
第一次迭代使用Load()显式加载那些符合条件的Orders数据,此时数据会从数据库加载到EntityFramework缓存里。
第二次迭代检查所有的Customers对象,用IsLoaded属性判断哪些Customers加载了Orders数据
First()方法可以把第一条Order添加到集合中
这个示例并不怎么自然,但它足以让你看出项目中实际使用显式加载所需的知识。
4.编译查询
LINQtoEntities另一个隐藏的功能是能够创建已编译的查询。已编译的查询是一个强类型的Func委托,它有一个用于查询的参数。编译查询时,执行翻译到SQL语句的动作,以后每次调用已编译的查询时都会对其重用。
这并不像使用存储过程那样高效,因为数据库还是要创建查询计划来执行SQL,但它确实避免了LINQtoEntities重复解析LINQ查询。
示例:
usingSystem.Data.Objects;
publicpartialclassChapter13_DerivedObjectContext:System.Web.UI.Page
{
//封装一个具有两个参数并返回TResult参数指定的类型值的方法。
Func<NorthwindEntities,string,IQueryable<NorthwindModel.Customer>>MyCompiledQuery;
NorthwindEntitiesdb;
protectedvoidPage_Load(objectsender,EventArgse)
{
//CompiledQuery表示一个缓存的LINQtoEntities查询
//Compile()创建一个表示已编译的LINQtoEntities查询的新委托
MyCompiledQuery=CompiledQuery.Compile<NorthwindEntities,string,
IQueryable<NorthwindModel.Customer>>((context,city)=>
fromcincontext.Customers
wherec.City==city
selectc);
db=newNorthwindEntities();
GridView1.DataSource=MyCompiledQuery(db,"London");
GridView1.DataBind();
}
} 原文:http://www.cnblogs.com/SkySoot/archive/2012/08/22/2651054.html
相关文章推荐
- c++ 遍历目录下文件
- python,pickle,serialize
- 新家来到,谢谢
- 小强的HTML5移动开发之路(12)——从一个多媒体标签说起
- 小强的HTML5移动开发之路(12)——从一个多媒体标签说起
- Ajax前后台传值小demo
- linux中的thread_info
- C++学习之路—多态性与虚函数(二)纯虚函数与抽象类
- 素数个数的位数
- Python学习第五天
- 丢一套源码过各种保护
- 深度探索C++对象模型:6.执行期语意学
- 两仪剑法
- 本地通知和推送通知简介
- 信号量、互斥体和自旋锁
- writing paper 需要注意的
- 不完全遍历Csrss进程中的句柄表
- SharePoint 入门书籍推荐
- UML初步认识
- SharePoint 入门书籍推荐