LINQ体验(10)——LINQ to SQL语句之开放式并发控制和事务

快有1个月没有上网了,从今天开始继续这个系列。告诉大家一个好消息:微软于2月1日发布了Visual Studio Team System 2008 Team Suite简体中文版,您可以在这里下载Visual Studio Team System 2008 Team Suite简体中文版90 天试用版。今天简单的学习下开放式并发控制和事务的内容,具体详细的内容现在可以参看MSDN了。
Simultaneous Changes开放式并发控制

下表介绍 LINQ to SQL 文档中涉及开放式并发的术语:
术语 说明
并发 两个或更多用户同时尝试更新同一数据库行的情形。
并发冲突 两个或更多用户同时尝试向一行的一列或多列提交冲突值的情形。
并发控制 用于解决并发冲突的技术。
开放式并发控制 先调查其他事务是否已更改了行中的值,再允许提交更改的技术。相比之下,保守式并发控制则是通过锁定记录来避免发生并发冲突。之所以称作开放式控制,是因为它将一个事务干扰另一事务视为不太可能发生。
冲突解决 通过重新查询数据库刷新出现冲突的项,然后协调差异的过程。刷新对象时,LINQ to SQL 更改跟踪器会保留以下数据:
最初从数据库获取并用于更新检查的值 通过后续查询获得的新数据库值。
LINQ to SQL 随后会确定相应对象是否发生冲突(即它的一个或多个成员值是否已发生更改)。如果此对象发生冲突,LINQ to SQL 下一步会确定它的哪些成员发生冲突。LINQ to SQL 发现的任何成员冲突都会添加到冲突列表中。

在 LINQ to SQL 对象模型中,当以下两个条件都得到满足时,就会发生“开放式并发冲突”:客户端尝试向数据库提交更改;数据库中的一个或多个更新检查值自客户端上次读取它们以来已得到更新。 此冲突的解决过程包括查明对象的哪些成员发生冲突,然后决定您希望如何进行处理。
Optimistic Concurrency

说明:这个例子中在你读取数据之前,另外一个用户已经修改并提交更新了这个数据,所以不会出现冲突。

//我们打开一个新的连接来模拟另外一个用户 
NorthwindDataContext otherUser_db = new NorthwindDataContext(); 
var otherUser_product = 
otherUser_db.Products.First(p => p.ProductID == 1); 
otherUser_product.UnitPrice = 999.99M; 
otherUser_db.SubmitChanges(); 
//我们当前连接 
var product = db.Products.First(p => p.ProductID == 1); 
product.UnitPrice = 777.77M; 
try 
{ 
db.SubmitChanges();//当前连接执行成功 
} 
catch (ChangeConflictException) 
{ 
}



说明:我们读取数据之后,另外一个用户获取并提交更新了这个数据,这时,我们更新这个数据时,引起了一个并发冲突。系统发生回滚,允许你找回最近更新的数据,让你决定继续你的更新。

//当前用户 
var product = db.Products.First(p => p.ProductID == 1); 
//我们打开一个新的连接来模拟另外一个用户 
NorthwindDataContext otherUser_db = new NorthwindDataContext() ; 
var otherUser_product = 
otherUser_db.Products.First(p => p.ProductID == 1); 
otherUser_product.UnitPrice = 999.99M; 
otherUser_db.SubmitChanges(); 
//当前用户修改 
product.UnitPrice = 777.77M; 
try 
{ 
db.SubmitChanges(); 
} 
catch (ChangeConflictException) 
{ 
//发生异常! 
}



Transactions事务

LINQ to SQL 支持三种事务模型,分别是:

* 显式本地事务:调用 SubmitChanges 时,如果 Transaction 属性设置为事务,则在同一事务的上下文中执行 SubmitChanges 调用。成功执行事务后,要由您来提交或回滚事务。与事务对应的连接必须与用于构造 DataContext 的连接匹配。如果使用其他连接,则会引发异常。
* 显式可分发事务:可以在当前 Transaction 的作用域中调用 LINQ to SQL API(包括但不限于 SubmitChanges)。LINQ to SQL 检测到调用是在事务的作用域内,因而不会创建新的事务。在这种情况下, <token>vbtecdlinq </token> 还会避免关闭连接。您可以在此类事务的上下文中执行查询和 SubmitChanges 操作。
* 隐式事务:当您调用 SubmitChanges 时,LINQ to SQL 会检查此调用是否在 Transaction 的作用域内或者 Transaction 属性是否设置为由用户启动的本地事务。如果这两个事务它均未找到,则 LINQ to SQL 启动本地事务,并使用此事务执行所生成的 SQL 命令。当所有 SQL 命令均已成功执行完毕时,LINQ to SQL 提交本地事务并返回。

1.Implicit(隐式)

说明:这个例子在执行SubmitChanges()操作时,隐式地使用了事务。因为在更新2种产品的库存数量时,第二个产品库存数量为负数了。这导致了更新产品全部失败了,系统回滚到这个操作的初始状态。

try 
{ 
Product prod1 = db.Products.First(p => p.ProductID == 4); 
Product prod2 = db.Products.First(p => p.ProductID == 5); 
prod1.UnitsInStock -= 3; 
prod2.UnitsInStock -= 5;//错误:库存数量的单位不能是负数 
//要么全部成功要么全部失败 
db.SubmitChanges(); 
} 
catch (System.Data.SqlClient.SqlException e) 
{ 
//执行异常处理 
} 

2.Explicit(显式) 

说明:这个例子使用事务封闭数据提交。 

using (TransactionScope ts = new TransactionScope()) 
{ 
try 
{ 
Product prod1 = db.Products.First(p => p.ProductID == 4); 
Product prod2 = db.Products.First(p => p.ProductID == 5); 
prod1.UnitsInStock -= 3; 
prod2.UnitsInStock -= 5;//错误:库存数量的单位不能是负数 
db.SubmitChanges(); 
} 
catch (System.Data.SqlClient.SqlException e) 
{ 
//执行异常处理 
} 
}

 

 

 

 

以Northwind为示例数据库,DLINQ(LINQ to SQL)之事务处理和并发处理


示例
TransactionAndConcurrency.aspx

<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="TransactionAndConcurrency.aspx.cs"
    Inherits="LINQ_DLINQ_TransactionAndConcurrency" Title="事务处理和并发处理" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
事务处理和并发处理详见:TransactionAndConcurrency.aspx.cs
</asp:Content>

TransactionAndConcurrency.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml.Linq;
using DAL;
using System.Transactions;
public partial class LINQ_DLINQ_TransactionAndConcurrency : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// 事务处理(DataContext.SubmitChanges()会做默认的事务处理)
TransactionDemo1();
// 事务处理(用DataContext.Connection.BeginTransaction()的方式做事务处理)
TransactionDemo2();
// 事务处理
TransactionDemo3();
// 并发处理
Concurrency();
}
/**////
/// 事务处理(DataContext.SubmitChanges()会做默认的事务处理)
///
private void TransactionDemo1()
{
try
{
NorthwindDataContext ctx = new NorthwindDataContext();
ctx.Categories.InsertOnSubmit(new Categories { CategoryName = "test", Description = "test" });
ctx.Categories.InsertOnSubmit(new Categories { CategoryID = 1 });
ctx.SubmitChanges();
}
catch
{ }
}
/**////
/// 事务处理(用DataContext.Connection.BeginTransaction()的方式做事务处理)
///
private void TransactionDemo2()
{
int? categoryId = null;
NorthwindDataContext ctx = new NorthwindDataContext();
if (ctx.Connection.State != ConnectionState.Open)
{
ctx.Connection.Open();
}
System.Data.Common.DbTransaction tran = ctx.Connection.BeginTransaction();
try
{
ctx.AddCategory("test", "test", ref categoryId);
ctx.DeleteCategory(1);
tran.Commit();
}
catch
{
tran.Rollback();
}
}
/**////
/// 事务处理(用System.Transactions.TransactionScope做事务处理)
///
private void TransactionDemo3()
{
int? categoryId = null;
using (TransactionScope tc = new TransactionScope())
{
try
{
NorthwindDataContext ctx = new NorthwindDataContext();
ctx.AddCategory("test", "test", ref categoryId);
ctx.DeleteCategory(1);
tc.Complete();
}
catch
{ }
}
}
/**////
/// 并发处理
///
private void Concurrency()
{
// 修改字段的UpdateCheck为Never,可在对象关系设计器(Object Relational Designer)中修改
// [Column(Storage="_UnitPrice", DbType="Money")]
// [Column(Storage="_UnitPrice", DbType="Money", UpdateCheck=UpdateCheck.Never)]

NorthwindDataContext ctx = new NorthwindDataContext();
var products = ctx.Products;
foreach (var p in products)
{
p.UnitPrice ++;
}
try
{
// 表示即使发生冲突也要继续
ctx.SubmitChanges(System.Data.Linq.ConflictMode.ContinueOnConflict);
}
catch (System.Data.Linq.ChangeConflictException e)
{
foreach (System.Data.Linq.ObjectChangeConflict occ in ctx.ChangeConflicts)
{
Products p = (Products)occ.Object;
// 以当前客户端中的值为准
occ.Resolve(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
// 以当前数据库中的值为准
occ.Resolve(System.Data.Linq.RefreshMode.KeepCurrentValues);
// 如果数据库中的值没有发生变化,则以当前客户端中的值为准。否则,则以当前数据库中的值为准
occ.Resolve(System.Data.Linq.RefreshMode.KeepChanges);
foreach (System.Data.Linq.MemberChangeConflict mcc in occ.MemberConflicts)
{
// 当前客户端中的值
string currentValue = mcc.CurrentValue.ToString();
// 原来数据库中的值
string originalValue = mcc.OriginalValue.ToString();
// 当前数据库中的值
string databaseValue = mcc.DatabaseValue.ToString();
// 以当前客户端中的值为准
mcc.Resolve(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
// 以当前数据库中的值为准
mcc.Resolve(System.Data.Linq.RefreshMode.KeepCurrentValues);
// 如果数据库中的值没有发生变化,则以当前客户端中的值为准。否则,则以当前数据库中的值为准
mcc.Resolve(System.Data.Linq.RefreshMode.KeepChanges);
}
}
}
// 表示只要发生冲突就不再继续
ctx.SubmitChanges(System.Data.Linq.ConflictMode.FailOnFirstConflict);
// ctx.SubmitChanges();
}
}