1.数据并发控制(Data Concurrency Control)简介
数据并发控制(Data Concurrency Control)是用来处理在同一时刻对被持久化的业务对象进行多次修改的系统。当多个用户修改业务对象的状态并试图并发地将其持久化到数据库时,需要一种机制来确保一个用户不会对另一个并发用户的事务状态造成负面影响。
有两种形式的并发控制:乐观和悲观。乐观并发控制假设当多个用户对业务对象的状态同时进行修改时不会造成任何问题,也称为最晚修改生效(last change wins)。对于一些系统,这是合理的行为。但如果业务对象的状态需要与从数据库中取出的状态保持一致,就需要悲观并发控制。
悲观并发控制可以有多中风格,可以在检索出记录后锁定数据表,也可以保存业务对象原始内容的副本,然后再进行更新之前将该副本与数据存储中的版本进行比对。确保在这次事务期间没有对该记录进行修改。
2.数据并发控制的实现示例
常用的数据并发控制实现方式有两种:数据库实现及代码控制实现。悲观并发控制在数据库实现方面可以有加入数据库锁机制,代码控制实现方面可以增加一个保存版本号字段,用于版本之间的对比。使用版本号来检查在业务实体从数据库中检索出之后是否被修改。更新时,把业务实体的版本号与数据库中的版本号进行比对之后再提交修改。这样确保业务实体在被检索出后没有被修改。
使用版本号来实现悲观并发控制的方式,其中的版本号可以使用数据库中提供的数据类型timestamp或代码中控制管理版本。数据库timestamp类型字段在每一次update操作均会生成新值。
1>、timestamp版本控制
SQL Server中timestamp类型对应C#的byte[]类型,timestamp字段值为byte[8]。
2>、程序代码实现版本控制
代码结构:
EntityBase.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DataAccessPatterns.DataConcurrencyControl.Model { public abstract class EntityBase { public Guid Version { get; set; } } }
Person.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DataAccessPatterns.DataConcurrencyControl.Model { public class Person : EntityBase { public Guid ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } }
IPersonRepository.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using DataAccessPatterns.DataConcurrencyControl.Model; namespace DataAccessPatterns.DataConcurrencyControl.Repository { public interface IPersonRepository { void Add(Person person); void Save(Person person); Person FindBy(Guid id); } }
PersonRepository.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using DataAccessPatterns.DataConcurrencyControl.Model; namespace DataAccessPatterns.DataConcurrencyControl.Repository { public class PersonRepository : IPersonRepository { public void Add(Person person) { using (var context = new DataAccessPatternsContext()) { context.Persons.Add(person); context.SaveChanges(); } } public void Save(Person person) { // person.Version为获取出来的上一次版本,Version字段值保持获取出来时字段值。 string strSql = String.Format(@"UPDATE dbo.Person SET FirstName='{0}',LastName='{1}' WHERE ID='{3}' AND Version='{4}'", person.FirstName, person.LastName, person.ID, person.Version); using (var context = new DataAccessPatternsContext()) { int affectedRows = context.Database.ExecuteSqlCommand(strSql, null); if (affectedRows == 0) { throw new ApplicationException(@"No changes were made to Person ID (" + person.ID + "), this was due to another process updating the data."); } else { // person.Version赋予新值用于下一次版本对比 person.Version = Guid.NewGuid(); } } } public Person FindBy(Guid id) { using (var context = new DataAccessPatternsContext()) { return context.Persons.Find(id) as Person; } } } }