LINQ FAQ: 第 3 部分






4.41/5 (13投票s)
并发处理、编译查询、CRUD 实现以及使用 XML 文件配置映射简单的 .NET 类。
目录
- 引言和目标
- LINQ FAQ 第一、二部分链接
- 如何在 LINQ 中处理并发?
- LINQ 提供哪些其他功能来精细调整字段级别的并发?
- 当发生并发冲突时,LINQ 提供哪些类型的错误报告选项?
- 什么是编译查询?
- 编写编译后的 LINQ 查询涉及哪些不同的步骤?
- 你能解释 LINQ 的内存提交和物理提交吗?
- 你能展示一个使用 LINQ 的简单 CRUD 示例吗?
- 如何使用 XML 文件将 LINQ 属性映射到简单的 .NET 类?
- 如何使用 XML 文件将存储过程映射到 .NET 类?
引言和目标
这是我的 LINQ FAQ 系列中的第三部分。在本系列中,我们将介绍与并发处理、编译查询、CRUD 实现以及使用 XML 文件配置映射简单 .NET 类相关的 LINQ FAQ。我喜欢以 FAQ 格式写文章,唯一目的是它们言简意赅,让你通过阅读更少的内容了解更多信息。
这是我送给所有 .NET 朋友的小礼物,一本完整的 400 页 FAQ 电子书,涵盖了 Azure、WCF、WWF、Silverlight、WPF、SharePoint 等各种 .NET 技术:http://www.questpond.com/SampleDotNetInterviewQuestionBook.zip。
LINQ FAQ 第一、二部分链接
- LINQ FAQ 新手入门第一部分:这是 LINQ FAQ 系列的第一部分,首先介绍 LINQ 的确切含义,然后讨论不同的 LINQ 查询格式,如 group by、order by、按条件搜索等。如果你是 LINQ 新手,必读:LINQNewbie.aspx。
- LINQ FAQ 第二部分:在本 FAQ 中,我们将看到 LINQ to SQL 的基本示例,如何使用 LINQ 定义一对一和一对多关系,如何优化 LINQ 查询,执行 LINQ 存储过程,最后我们将看到一个使用 LINQ to SQL 的简单 CRUD 示例:LINQFAQPart2.aspx。
如何在 LINQ 中处理并发?
LINQ 提供了三种方式来处理并发冲突。要处理并发冲突,我们需要将 LINQ to SQL 代码包装在 `try` 块中,并捕获 `ChangeConflictException`。然后,我们可以遍历 `ChangeConflicts` 集合来指定如何解决冲突。
catch (ChangeConflictException ex)
{
foreach (ObjectChangeConflict objchangeconf in objContext.ChangeConflicts)
{
objchangeconf.Resolve(RefreshMode.OverwriteCurrentValues);
}
}
LINQ 提供了三种处理并发冲突的方法
- `KeepCurrentValues`:当指定此选项并发生并发冲突时,LINQ 会保留 LINQ 实体对象的当前值,而不会将数据库中的新值推送到 LINQ 对象中。
- `OverwriteCurrentValues`:当指定此选项时,当前的 LINQ 对象数据将被数据库值覆盖。
- `KeepChanges`:这是最奇怪的选项,但在某些情况下可能很有用。当我们讨论类时,类可以有很多属性。已更改的属性将保持不变,但未更改的属性将从数据库中获取并替换。
我们需要使用 `RefreshMode` 来指定需要哪些选项,如下面的代码片段所示。
LINQ 提供哪些其他功能来精细调整字段级别的并发?
LINQ 并发系统提供的最佳选项之一是字段级别的并发行为控制。有三个选项我们可以使用 `UpdateCheck` 属性来指定
- `Never`:在检查并发冲突时,不使用此字段。
- `Always`:此选项指定始终使用此字段来检查并发冲突。
- `WhenChanged`:仅当成员值已更改时,才使用此字段来检测并发冲突。
下面的代码片段展示了如何使用 `UpdateCheck` 属性来控制上面指定的属性/字段级别的并发选项。
[Column(DbType = "nvarchar(50)",UpdateCheck=UpdateCheck.Never)]
public string CustomerCode
{
set
{
_CustomerCode = value;
}
get
{
return _CustomerCode;
}
}
当发生并发冲突时,LINQ 提供哪些类型的错误报告选项?
LINQ 并发系统允许你指定如何报告冲突。LINQ 系统有两种报告冲突的方式
- `ContinueOnConflict`:此选项告诉 LINQ 引擎即使存在冲突也要继续,并在过程结束时返回所有冲突。
- `FailOnFirstConflict`:此选项表示一旦发生第一个冲突就停止,并在那个时候返回所有冲突。换句话说,LINQ 引擎不会继续执行代码。
这两种选项都可以作为输入在 `SubmitChanges` 方法中使用 `ConflictMode` 枚举提供。下面是如何指定冲突模式的代码片段
objContext.SubmitChanges(ConflictMode.ContinueOnConflict);
什么是编译查询?
LINQ 提供了所谓的编译 LINQ 查询。在编译的 LINQ 查询中,计划被缓存到一个静态类中。我们都知道,静态类就是一个全局缓存。因此,LINQ 使用来自静态类对象的查询计划,而不是从头开始构建和准备查询计划。
总而言之,从 LINQ 查询构建到执行,需要执行四个步骤。通过使用编译的 LINQ 查询,这四个步骤减少到两个步骤。
编写编译后的 LINQ 查询涉及哪些不同的步骤?
第一件事是导入 `Data.Linq` 命名空间。
Import namespace
using System.Data.Linq;
编写编译查询的语法有点晦涩难懂。所以让我们将语法分解成小块,看看完整的语法是怎样的。要执行编译函数,我们需要编写函数到指针。这个函数必须是静态的,这样 LINQ 引擎才能使用存储在这些静态类对象中的查询计划。下面是我们定义函数的方式。它以 `public static` 开头,表示这个函数是静态的。然后我们使用 `Func` 关键字来定义输入参数和输出参数。下面是参数序列的定义方式
- 第一个参数应该是数据上下文。所以我们将数据类型定义为 `DataContext`。
- 后面跟着一个或多个输入参数;目前我们只有一个,即客户代码,所以我们将第二个参数数据类型定义为 `string`。
- 一旦我们完成了所有输入参数,我们就需要定义输出的数据类型。目前我们将输出数据类型定义为 `IQueryable`。
我们将这个委托函数命名为 `getCustomers`。
public static Func<DataContext, string, IQueryable<clsCustomerEntity>> getCustomers
我们需要使用 `DataContext` 对象和必要定义的输入参数,然后是 LINQ 查询,来调用静态类 `CompiledQuery` 的 `Compiled` 方法。对于下面的代码片段,为了最小化复杂性,我们没有指定 LINQ 查询
CompiledQuery.Compile((DataContext db, string strCustCode)=> Your LINQ Query );
将上面的两个代码片段组合起来,下面的代码片段就是完整的样子
public static Func<DataContext, string, IQueryable<clsCustomerEntity>>
getCustomers= CompiledQuery.Compile((DataContext db, string strCustCode)=> Your LINQ Query );
然后我们需要将这个静态函数包装在一个静态类中。我们采用了上面定义的函数,并将其包装在名为 `clsCompiledQuery` 的静态类中。
public static class clsCompiledQuery
{
public static Func<DataContext, string, IQueryable<clsCustomerEntity>>
getCustomers = CompiledQuery.Compile((DataContext db, string strCustCode)
=> from objCustomer in db.GetTable<clsCustomerEntity>()
where objCustomer.CustomerCode == strCustCode
select objCustomer);
}
使用编译后的查询非常简单;我们只需调用静态函数。目前这个函数返回的数据类型是 `IEnumerable`。所以我们需要定义一个 `IEnumerable` 客户实体,它将通过 `getCustomers` 委托函数进行填充。我们可以使用 `clsCustomerEntity` 类来循环遍历客户实体。
IQueryable<clsCustomerEntity> objCustomers =
clsCompiledQuery.getCustomers(objContext, txtCustomerCode.Text);
foreach (clsCustomerEntity objCustomer in objCustomers)
{
Response.Write(objCustomer.CustomerName + "<br>");
}
你能解释 LINQ 的内存提交和物理提交吗?
实体对象是 LINQ 技术的基础。因此,当任何数据被提交到数据库时,它都会经过 LINQ 对象。数据库操作通过 `DataContext` 类完成。如前所述,实体是 LINQ 的基础,所以所有数据首先被发送到这些实体,然后路由到实际的物理数据库。由于这种工作方式,数据库提交是一个两步过程,第一步是内存中的,然后是物理提交。为了进行内存操作,`DataContext` 提供了 `DeleteOnSubmit` 和 `InsertOnSubmit` 方法。当我们从 `DataContext` 类调用这些方法时,它们会在实体对象的内存中添加和更新数据。请注意,这些方法不会更改/向实际数据库添加新数据。一旦我们完成了内存操作,并希望将所有更新发送到数据库,我们就需要调用 `SubmitChanges()` 方法。此方法最终将数据提交到物理数据库。
所以,让我们考虑一个客户表(customerid、customercode 和 customername),看看如何进行内存提交和物理提交操作。
你能展示一个使用 LINQ 的简单 CRUD 示例吗?
步骤 1:创建实体客户类
作为第一步,我们创建客户类的实体,如下面的代码片段所示
[Table(Name = "Customer")]
public class clsCustomerEntity
{
private int _CustomerId;
private string _CustomerCode;
private string _CustomerName;
[Column(DbType = "nvarchar(50)")]
public string CustomerCode
{
set
{
_CustomerCode = value;
}
get
{
return _CustomerCode;
}
}
[Column(DbType = "nvarchar(50)")]
public string CustomerName
{
set
{
_CustomerName = value;
}
get
{
return _CustomerName;
}
}
[Column(DbType = "int", IsPrimaryKey = true,IsDbGenerated=true)]
public int CustomerId
{
set
{
_CustomerId = value;
}
get
{
return _CustomerId;
}
}
}
步骤 2:使用 LINQ 创建
创建数据上下文
第一件事是使用连接字符串创建一个 `DataContext` 对象。
DataContext objContext = new DataContext(strConnectionString);
设置插入数据
一旦使用 `DataContext` 对象创建了连接,下一步就是创建客户实体对象并将数据设置到对象属性。
clsCustomerEntity objCustomerData = new clsCustomerEntity();
objCustomerData.CustomerCode = txtCustomerCode.Text;
objCustomerData.CustomerName = txtCustomerName.Text;
执行内存更新
然后我们使用 `InsertOnSubmit` 方法在实体对象中执行内存更新。
objContext.GetTable<clsCustomerEntity>().InsertOnSubmit(objCustomerData);
执行最终的物理提交
最后,我们执行到实际数据库的物理提交。请注意,在调用 `SubmitChanges()` 之前,数据不会最终提交到数据库。
objContext.SubmitChanges();
最终的创建 LINQ 代码
下面是最终整合的 LINQ 代码
DataContext objContext = new DataContext(strConnectionString);
clsCustomerEntity objCustomerData = new clsCustomerEntity();
objCustomerData.CustomerCode = txtCustomerCode.Text;
objCustomerData.CustomerName = txtCustomerName.Text;
objContext.GetTable<clsCustomerEntity>().InsertOnSubmit(objCustomerData);
objContext.SubmitChanges();
步骤 3:使用 LINQ 更新
让我们来看下一个数据库操作,即更新。
创建数据上下文
像往常一样,我们首先需要使用连接字符串创建一个 `DataContext` 对象,正如在创建步骤中所讨论的那样。
DataContext objContext = new DataContext(strConnectionString);
选择我们要更新的客户 LINQ 对象
使用我们要更新的 LINQ 查询获取 LINQ 对象
var MyQuery = from objCustomer in objContext.GetTable<clsCustomerEntity>()
where objCustomer.CustomerId == Convert.ToInt16(txtCustomerId.Text)
select objCustomer;
最后设置新值并将数据更新到物理数据库
执行更新并调用 `SubmitChanges()` 进行最终更新。
clsCustomerEntity objCustomerData = (clsCustomerEntity)MyQuery.First<clsCustomerEntity>();
objCustomerData.CustomerCode = txtCustomerCode.Text;
objCustomerData.CustomerName = txtCustomerName.Text;
objContext.SubmitChanges();
LINQ 更新的最终代码
下面的代码展示了最终的 LINQ 更新查询的样子
DataContext objContext = new DataContext(strConnectionString);
var MyQuery = from objCustomer in objContext.GetTable<clsCustomerEntity>()
where objCustomer.CustomerId == Convert.ToInt16(txtCustomerId.Text)
select objCustomer;
clsCustomerEntity objCustomerData = (clsCustomerEntity)MyQuery.First<clsCustomerEntity>();
objCustomerData.CustomerCode = txtCustomerCode.Text;
objCustomerData.CustomerName = txtCustomerName.Text;
objContext.SubmitChanges();
步骤 4:使用 LINQ 删除
让我们来看下一个数据库操作,删除。
DeleteOnSubmit
我们将不详细介绍前面的步骤,如创建数据上下文和选择 LINQ 对象,这两者都在上一节中进行了说明。要从内存中删除对象,我们需要调用 `DeleteOnSubmit()`,要从最终数据库中删除,我们需要调用 `SubmitChanges()`。
objContext.GetTable<clsCustomerEntity>().DeleteOnSubmit(objCustomerData);
objContext.SubmitChanges();
步骤 5:自解释的 LINQ 选择和读取
现在,在最后一步,根据条件选择和读取 LINQ 对象。下面的代码片段展示了如何执行 LINQ 查询并将对象值设置到 ASP.NET UI。
DataContext objContext = new DataContext(strConnectionString);
var MyQuery = from objCustomer in objContext.GetTable<clsCustomerEntity>()
where objCustomer.CustomerId == Convert.ToInt16(txtCustomerId.Text)
select objCustomer;
clsCustomerEntity objCustomerData = (clsCustomerEntity)MyQuery.First<clsCustomerEntity>();
txtCustomerCode.Text = objCustomerData.CustomerCode;
txtCustomerName.Text = objCustomerData.CustomerName;
如何使用 XML 文件将 LINQ 属性映射到简单的 .NET 类?
LINQ 提供基于属性的 XML 映射。因此,你可以拥有纯 .NET 类,如下面所示的 `clsCustomer` 类,你可以在 XML 文件中定义 LINQ 映射。然后,LINQ 引擎可以从 XML 文件读取映射并将其应用于你的简单 .NET 类。
public class clsCustomer
{
private int _intCustomerId;
private string _strCustomerName;
private string _strCustomerCode;
public int CustomerId
{
set
{
_intCustomerId = value;
}
get
{
return _intCustomerId;
}
}
public string CustomerName
{
set
{
_strCustomerName = value;
}
get
{
return _strCustomerName;
}
}
public string CustomerCode
{
set
{
_strCustomerCode = value;
}
get
{
return _strCustomerCode;
}
}
}
然后,我们需要创建一个简单的 XML 文件,该文件定义了与类成员的映射。
<?xml version="1.0" encoding="utf-8"?>
<Database Name="TstServer" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">
<Table Name="dbo.Customer" Member="WebAppMappingXML.clsCustomer">
<Type Name="WebAppMappingXML.clsCustomer">
<Column Name="CustomerId" Member="CustomerId" />
<Column Name="CustomerName" Member="CustomerName" />
<Column Name="CustomerCode" Member="CustomerCode" />
</Type>
</Table>
</Database>
要将 XML 映射与简单的 .NET 类绑定,我们首先需要创建 `XMLMappingSource` 对象,如下面的代码片段所示。
XmlMappingSource xms = XmlMappingSource.FromUrl(physicalPath + "Mapping.xml");
我们需要将 `XMLMappingSource` 对象传递给下面的代码片段中所示的 `DataContext` 类。
DataContext objContext = new DataContext(strConn, xms);
最后,我们可以获取表并循环遍历实体对象。
var query = from customer in objContext.GetTable<clsCustomer>()
select customer;
foreach (var item in query)
{
Response.Write(item.CustomerCode + "<br>");
}
如何使用 XML 文件将存储过程映射到 .NET 类?
如果你的项目中有存储过程,你可以使用 `Function` XML 元素在 XML 文件中定义你的存储过程名称。客户端代码在绑定 `DataContext` 和 `XMLMappingsource` 对象时保持不变。
<?xml version="1.0" encoding="utf-8"?>
<Database Name="TstServer" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">
<Table Name="dbo.Customer" Member="WebAppMappingXML.clsCustomer">
<Type Name="WebAppMappingXML.clsCustomer">
<Column Name="CustomerId" Member="CustomerId" />
<Column Name="CustomerName" Member="CustomerName" />
<Column Name="CustomerCode" Member="CustomerCode" />
</Type>
</Table>
<Function Name="dbo.sp_getCustomerCode" Method="getCustomerByCode">
<Parameter Name="CustomerCode" Parameter="" />
<ElementType Name="clsCustomer" />
</Function>
</Database>
如需进一步阅读,请观看以下面试准备视频和分步视频系列。