65.9K
CodeProject 正在变化。 阅读更多。
Home

LINQ FAQ: 第 3 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.41/5 (13投票s)

2009年7月28日

CPOL

10分钟阅读

viewsIcon

47764

downloadIcon

1749

并发处理、编译查询、CRUD 实现以及使用 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 查询,这四个步骤减少到两个步骤。

图:查询计划绕过了许多步骤

编写编译后的 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>

如需进一步阅读,请观看以下面试准备视频和分步视频系列。

© . All rights reserved.