• 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的值和主键作为条件来更新数据行,从而实现乐观并发控制。

  • 相关阅读:
    【转】HTTP 状态码详解
    程序员自我提升之粗粮与细粮
    配置Eclipse支持MacBook Pro Retina屏幕的办法(解决Retina屏幕下eclipse字体变虚的问题)
    IE6下lineheight失效:当文字与图片img在同一行中显示时lineheight失效的解决办法
    2013年第二周的总结
    为什么这么多的云存储产品却没有一个能够统一管理它们的应用出现
    微软的云笔记:OneNote+SkyDrive
    完美的外出上网解决方案随身随地享用你的专有WIFI网络(3G无线路由器+sim卡卡托+3G资费卡)
    [转]如何才能更加有效率
    Perl Note
  • 原文地址:https://www.cnblogs.com/jlzhou/p/2960759.html
Copyright © 2020-2023  润新知