MySQL 實現 EF Code First TimeStamp/RowVersion 並發控制


在將項目遷移到MySQL 5.6.10數據庫上時,遇到和遷移到PostgreSQL數據庫相同的一個問題,就是TimeStamp/RowVersion並發控制類型在非Microsoft SQL Server數據庫中的實現。

先上網搜索解決方案,找到Ak.Ini的博文http://www.cnblogs.com/akini/archive/2013/01/30/2882767.html,於是嘗試使用文中介紹的方法。

項目中有一個類要解決並發更新的問題,該類定義:

    public class Stock
    {
        public int Id { get; set; }

        [Required(ErrorMessageResourceName = "Generic_Required", ErrorMessageResourceType = typeof(ValidationMessage))]
        public Location Location { get; set; }
        
        [Required(ErrorMessageResourceName = "Generic_Required", ErrorMessageResourceType = typeof(ValidationMessage))]
        public Part Part { get; set; }

        public Batch Batch { get; set; }

        [Required(ErrorMessageResourceName = "Generic_Required", ErrorMessageResourceType = typeof(ValidationMessage))]
        public int Quantity { get; set; }

        [Required(ErrorMessageResourceName = "Generic_Required", ErrorMessageResourceType = typeof(ValidationMessage))]
        public int UpdatedBy { get; set; }

        [Required(ErrorMessageResourceName = "Generic_Required", ErrorMessageResourceType = typeof(ValidationMessage))]
        public DateTime UpdatedTime { get; set; }

        public DateTime RowVersion { get; set; }
    }

其中最后一個屬性是用作並發控制的,MySqlMigrationSqlGenerator不允許byte[]類型上標記TimeStamp/RowVersion,這里使用DateTime類型。

這是EF生成的Stocks表定義:

> DESC kit.Stocks

+ ---------- + --------- + --------- + -------- + ------------ + ---------- +
| Field      | Type      | Null      | Key      | Default      | Extra      |
+ ---------- + --------- + --------- + -------- + ------------ + ---------- +
| Id         | int(11)   | NO        | PRI      |              | auto_increment |
| Quantity   | int(11)   | NO        |          |              |            |
| UpdatedBy  | int(11)   | NO        |          |              |            |
| UpdatedTime | datetime  | NO        |          |              |            |
| RowVersion | datetime  | NO        |          | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| Location_Id | int(11)   | NO        | MUL      |              |            |
| Part_PartNo | varchar(50) | NO        | MUL      |              |            |
| Batch_BatchNo | varchar(50) | YES       | MUL      |              |            |
+ ---------- + --------- + --------- + -------- + ------------ + ---------- +
8 rows

然后在DbContext的構造器中加入下面修改DbModelBuilder的代碼:

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
            modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

            modelBuilder.Entity<Stock>().Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            modelBuilder.Entity<Stock>().Property(p => p.RowVersion).IsConcurrencyToken().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);      
        }  

上面代碼中前兩行是為了禁用EF級聯刪除的特性,可以參考我以前的博文:http://www.cnblogs.com/jlzhou/archive/2012/03/13/2394333.html

后兩行顯式聲明Id屬性為自增類型,其實EF默認會將Id屬性設置為自增類型,但是在本例中,如果不顯式聲明,EF在生成數據庫時會莫名其妙的將Id屬性當作一般類型處理,不知道是不是因為最后一行設置RowVersion屬性為Identity造成的。

我編寫了一個小程序,用於顯式控制EF根據類定義生成數據庫,並且在生成數據庫后,使用執行SQL語句的方式,修改數據庫對象的定義,比如加入DEFAULT值或者添加索引等約束。下面是代碼片段:

//DbContext構造器中的部分代碼,通過isDoInitialize參數來控制是否初始化數據庫。

        public BestDbContext(string databaseName, bool isDoInitialize = true) : base(databaseName) 
        {
            if (!isDoInitialize)
            {
                Database.SetInitializer<BestDbContext>(null);
            }
        }

//初始化數據庫
            Database.SetInitializer(new DropCreateDatabaseAlways<BestDbContext>());
            using (var db = new BestDbContext("name=" + databaseName))
            {
                try
                {
                    db.Database.Initialize(force: false);

                    MessageBox.Show("Database initialized!");
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Initialization Failed... " + ex.Message);
                }

                string sql;

                sql = @" ALTER TABLE `kit`.`Stocks` CHANGE COLUMN `RowVersion` `RowVersion` DATETIME NOT NULL 
                        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ; ";
                db.Database.ExecuteSqlCommand(sql);
            }

注意上面代碼最后的sql執行部分,這里加入對RowVersion數據庫服務器端的缺省值設置,自MySQL 5.6.5版本開始,DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP 選項也可以應用到Datetime類型的列。

最后驗證上述方法,使用 Entity Framework Profiler 試用版(http://hibernatingrhinos.com/products/EFProf),下載解壓縮后,在Project引用中加入對HibernatingRhinos.Profiler.Appender.dll的引用,然后在應用的啟動代碼部分Application_Start in web applications,Program.Main in windows / console applications or the App constructor for WPF applications),加入這一行代碼:HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();

啟動應用程序調試,並且啟動EFProf.exe監控程序,你就可以隨時看到EF動態生成的SQL命令了,很是方便,唯一的遺憾是這個工具是收費購買的,微軟又沒有提供非MSSQL的數據庫EF的SQL監控工具。

這是Stocks表在插入新記錄時,EF生成的SQL語句:

INSERT INTO `Stocks`
            (`Quantity`,
             `UpdatedBy`,
             `UpdatedTime`,
             `Location_Id`,
             `Part_PartNo`,
             `Batch_BatchNo`)
VALUES      ( 1,
              1,
              '2013-03-14T21:37:53' /* @gp1 */,
              1,
              'PART_A' /* @gp2 */,
              NULL);



SELECT `Id`,
       `RowVersion`
FROM   `Stocks`
WHERE  row_count() > 0
       AND `Id` = last_insert_id()

可以看出,保存新對象實例到數據庫時,EF會從數據庫取回RowVersion的值,而這個值是數據庫那邊生成的。

這是更新Stocks表時,EF生成的SQL語句:

UPDATE `Stocks`
SET    `Quantity` = 6,
       `UpdatedTime` = '2013-03-14T21:41:14' /* @gp1 */
WHERE  (`Id` = 1)
       AND (`RowVersion` = '2013-03-14T21:14:25' /* @gp2 */)

可以看出,在更新對象實例到數據庫時,EF會從使用先前從數據庫取回RowVersion的值和主鍵作為條件來更新數據行,從而實現樂觀並發控制。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM