資料訪問模式:資料併發控制(Data Concurrency Control)

libingql發表於2014-07-29

  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; }
    }
}
View Code

  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; }
    }
}
View Code

  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);
    }
}
View Code

  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;
            }
        }
    }
}
View Code

相關文章