一、使用事务
ADO.NET 1.0和ADO.NET 1.1使用Transaction类来支持事务。ADO.NET引入了TransactionScope。TransactionScope扩大了能够加入到事务中的内容范围。这个功能使得事务能够跨越多种技术。
最简单的TransactionScope用法是在using块中创建对象,然后编写组成事务的代码(比如对SQL Server的调用等,这里,LINQ to SQL代码将隐式调用SQL Server)。最后,在using语句块退出之前,调用TransactionScope.Complete。如果出现了未处理的异常,using块将在Commit调用之前退出,该事务将被回滚。为了使用TransactionScope,务必确保引用了System.Transaction程序集。
【代码】
using System.Data.Linq;
using System.Linq;
using System.Transactions;
namespace 事务
{
class Program
{
static void Main(string[] args)
{
NewDataContext context = new NewDataContext();
Table<Customer> customers = context.GetTable<Customer>();
Table<Order> orders = context.GetTable<Order>();
//使代码块成为事务性代码。此类不能被继承
using (TransactionScope scope=new TransactionScope())
{
var removeCustomers = from c in customers
where c.CustomerID == "1001"
select c;
var removeOrders = from o in orders
where o.CustomerID == "1001"
select o;
if (removeOrders.Any())
{
orders.DeleteAllOnSubmit(removeOrders);
context.SubmitChanges();
}
customers.DeleteAllOnSubmit(removeCustomers);
context.SubmitChanges();
//在using块退出之前执行以下语句,指示范围中的所有操作都已完成,如若出现异常,则事务回滚
scope.Complete();
}
}
}
}
using System;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data.Linq.Mapping;
namespace 事务
{
[Table(Name ="tb_Order")]
public class Order
{
[Column(IsPrimaryKey =true)]
public string OrderID { get; set; }
[Column()]
public string CustomerID { get; set; }
[Column()]
public string EmployeeID { get; set; }
[Column(CanBeNull =true)]
public DateTime? OrderDate { get; set; }
[Column(CanBeNull =true)]
public DateTime? RequiredDate { get; set; }
public override string ToString()
{
var builder = new StringBuilder();
PropertyInfo[] props = this.GetType().GetProperties();
Array.ForEach(props.ToArray(), prop =>
{
builder.AppendFormat("\t{0}:{1}", prop.Name,
prop.GetValue(this, null) == null ? "<Empty>\n" :
prop.GetValue(this, null).ToString() + "\n");
});
return builder.ToString();
}
}
}
using System;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using 将EntitySet类添加为属性;
namespace 事务
{
[Table(Name ="tb_Customer")]
public class Customer
{
[Column(IsPrimaryKey =true)]
public string CustomerID { get; set; }
[Column()]
public string CompanyName { get; set; }
[Column()]
public string ContactName { get; set; }
[Column()]
public string ContactTitle { get; set; }
[Column()]
public string Address { get; set; }
[Column()]
public string City { get; set; }
[Column()]
public string Region { get; set; }
[Column()]
public string PostalCode { get; set; }
[Column()]
public string Country { get; set; }
[Column()]
public string Phone { get; set; }
[Column()]
public string Fax { get; set; }
private EntitySet<Order> orders;
[Association(Storage ="orders",OtherKey ="CustomerID")]
public EntitySet<Order> Orders
{
get { return orders; }
set { orders.Assign(value); }
}
/// <summary>
/// 转储实体
/// </summary>
/// <returns></returns>
public override string ToString()
{
StringBuilder builder = new StringBuilder();
PropertyInfo[] props = this.GetType().GetProperties();
Array.ForEach(props.ToArray(), prop =>
{
object obj = prop.GetValue(this, null);
if (obj is IList)
(obj as IList).AppendEntitySet(builder);
else
{
builder.AppendFormat("{0}:{1}", prop.Name, prop.GetValue(this,null) == null ? "<empty>\n" :
prop.GetValue(this, null).ToString() + "\n");
}
});
return builder.ToString();
}
}
}
using System.Collections;
using System.Text;
namespace 将EntitySet类添加为属性
{
public static class ExtendList
{
public static void AppendEntitySet(this IList entity, StringBuilder builder)
{
foreach (var obj in entity)
{
builder.AppendFormat("{0}\n", obj.ToString());
}
}
}
}
using System;
using System.Data.Linq;
namespace 事务
{
public class NewDataContext:DataContext
{
private static readonly string connectionString = "Data Source=.;Initial Catalog=db_Customer;Integrated Security=true";
public NewDataContext():base(connectionString)
{
//this.Log = Console.Out;
}
}
}
二、理解冲突解决
冲突随处可见。在软件方面,当多个用户同时修改时就会引发数据库冲突。用户可以是人,也可以是子程序。首先需要熟悉一些术语。并发是指同时更新;并发冲突是指导致冲突的同步更新;并发控制是指用于解决并发问题的技术;乐观并发允许可能发生冲突是指的并发修改;悲观并发使用锁来避免存在冲突的并发修改;冲突解决是解决冲突的处理过程。
显然,悲观记录锁机制可以轻松阻止并发,其问题在于可扩展性不好。记录锁很昂贵,它是一种有限的资源。在拥有大量用户的Internet领域,锁住记录将导致可扩展性变得很差。
因此,只好使用乐观并发。这种方法允许同时发生的修改操作,当冲突发生时再去解决它。LINQ支持乐观并发。
LINQ是通过异常机制来支持并发的。LINQ支持映射,而这些映射可以通过ColumnAttribute的UpdateCheck参数来标记哪些列需要在更新时进行并发冲突检测。当两个用户以冲突的方式修改相同数据时,LINQ将会抛出一个异常,然后由程序员来指出要如何解决这些冲突数据。解决冲突的常用办法是,将两个版本的数据都提供给用户,然后由用户来决定要保留哪一个。
2.1 为SubmitChanges指明冲突处理模式
SubmitChanges方法有一个ConflictMode参数。ConflictMode是一个枚举,可以是FailOnFirstConflict(当两个值出现了冲突时,更新将立即停止)或ContinueOnConflict(所有冲突将会被累积,当整个更新过程结束时才返回全部的冲突,更新操作也同时停止)。
其实,无论是否指定了ConflictMode枚举,发生并发冲突时都会抛出一个ChangeConflictException。
【举例】
using System;
using System.Linq;
using System.Data.Linq;
using System.ComponentModel;
using 将EntitySet类添加为属性;
namespace 理解冲突解决
{
class Program
{
static void Main(string[] args)
{
//在单独的线程上执行操作
BackgroundWorker worker1 = new BackgroundWorker();
worker1.DoWork += new DoWorkEventHandler(worker1_DoWork);
BackgroundWorker worker2 = new BackgroundWorker();
worker2.DoWork += new DoWorkEventHandler(worker1_DoWork);
worker1.RunWorkerAsync("朱啸是傻逼");
worker2.RunWorkerAsync("朱啸是大胖");
Console.ReadLine();
}
static void worker1_DoWork(object sender,DoWorkEventArgs e)
{
NewDataContext northwind = new NewDataContext();
var one = northwind.GetCustomers.Single(c => c.CustomerID == "1002");
//Speedy Express
//获取表示异步操作参数的值
one.CompanyName = (string)e.Argument;
System.Threading.Thread.Sleep(2500);
northwind.SubmitChanges(ConflictMode.FailOnFirstConflict);
}
}
}
2.2 捕获并解决并发冲突
单用户应用程序或那些几乎不会出现多个用户同时更新相同记录的应用程序可以使用悲观并发。只需要简单地认为“最后更新记录的用户才拥有正确的信息”即可。如果多个用户都要更新记录,那么你就需要通过捕获ChangeConflictException来处理冲突。注意,有些列在更新时可能无需冲突检测。
接下来需要逐步修改上述代码。首先将忽略某些实体字段/数据库列上的并发检测,然后再检测冲突并解决这些冲突。
2.2.1 忽略某些列上的冲突检测
2.2.2 获取冲突的信息
2.2.3 确定与冲突相关的实体和表
2.2.4 比较成员冲突的各个状态
2.2.5 将解决过的冲突写入数据库