zoukankan      html  css  js  c++  java
  • Entity Framework 并发处理

    什么是并发?

    并发分悲观并发和乐观并发。

    悲观并发:比如有两个用户A,B,同时登录系统修改一个文档,如果A先进入修改,则系统会把该文档锁住,B就没办法打开了,只有等A修改完,完全退出的时候B才能进入修改。

    乐观并发:同上面的例子,A,B两个用户同时登录,如果A先进入修改紧跟着B也进入了。A修改文档的同时B也在修改。如果在A保存之后B再保存他的修改,此时系统检测到数据库中文档记录与B刚进入时不一致,B保存时会抛出异常,修改失败。

    EF中如何控制并发?

    Entity Framework不支持悲观并发,只支持乐观并发。

    如果要对某一个表做并发处理,就在该表中加一条Timestamp类型的字段。注意,一张表中只能有一个Timestamp的字段。

    Data Annotations中用Timestamp来标识设置并发控制字段,标识为Timestamp的字段必需为byte[]类型。

    public class Person
        {
            public int PersonId { get; set; }
            public int SocialSecurityNumber { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            [Timestamp]
            public byte[] RowVersion { get; set; }
        }

    Fluent API用IsRowVersion方法

    modelBuilder.Entity<Person>().Property(p => p.RowVersion).IsRowVersion();

    我们看到生成的数据库中,RowVersion是timestamp类型。

    下面我们写一段代码来测试一下:

           static void Main(string[] args)
            {
                var person = new Person
                {
                    FirstName = "Rowan",
                    LastName = "Miller",
                    SocialSecurityNumber = 12345678
                };
                //新增一条记录,保存到数据库中
                using (var con = new BreakAwayContext())
                {
                    con.People.Add(person);
                    con.SaveChanges();
                }
    
                var firContext = new BreakAwayContext();
                //取第一条记录,并修改一个字段:这里是修改了FirstName
                //先不保存
                var p1 = firContext.People.FirstOrDefault();
                p1.FirstName = "Steven";
    
                //再创建一个Context,同样取第一条记录,修改LastName字段并保存
                using (var secContext = new BreakAwayContext())
                {
                    var p2 = secContext.People.FirstOrDefault();
                    p2.LastName = "Francis";
                    secContext.SaveChanges();
                }
                try
                {
                    firContext.SaveChanges();
                    Console.WriteLine(" 保存成功");
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    Console.WriteLine(ex.Entries.First().Entity.GetType().Name + " 保存失败");
                }
                Console.Read();
            }

    上面我们实例化了三个DbContext,第一个增加一条记录到数据库中,第二个修改刚增加的记录但不保存,然后第三个Context也取刚新增的记录并保存,最后再保存第二个Context,结果保存失败。

    可以看到我们的并发控制取到了作用。

    分析EF生成的SQL语句:

    exec sp_executesql N'update [dbo].[People]
    set [LastName] = @0
    where (([PersonId] = @1) and ([RowVersion] = @2))
    select [RowVersion]
    from [dbo].[People]
    where @@ROWCOUNT > 0 and [PersonId] = @1',N'@0 nvarchar(max) ,@1 int,@2 binary(8)',@0=N'Francis',@1=1,@2=0x00000000000007D1

    可以看到,它在取对应记录的时候把RowVersion也作为筛选条件。上面例子中的secContext保存的时候,数据库中的RowVersion字段的值就变了,所以firContext保存的时候用原来的RowVersion取值,自然就取不到相应的记录而报错。

    如果我们只是要对某个字段作并发控制呢?别着急,EF也有办法。

    Data Annotations中用ConcurrencyCheck来标识

     public class Person
        {
            public int PersonId { get; set; }
            [ConcurrencyCheck]
            public int SocialSecurityNumber { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public byte[] RowVersion { get; set; }
        }

    Fluent API用IsConcurrencyToken方法

    modelBuilder.Entity<Person>().Property(p => p.SocialSecurityNumber).IsConcurrencyToken();

    上面的实体中,我们将SocialSecurityNumber(社会保险号)标识为开放式并发,也写一个类似的代码测试一下:

     static void Main(string[] args)
            {
                var person = new Person
                {
                    FirstName = "Rowan",
                    LastName = "Miller",
                    SocialSecurityNumber = 12345678
                };
                //新增一条记录,保存到数据库中
                using (var con = new BreakAwayContext())
                {
                    con.People.Add(person);
                    con.SaveChanges();
                }
    
                var firContext = new BreakAwayContext();
                //取第一条记录,并修改SocialSecurityNumber字段
                //先不保存
                var p1 = firContext.People.FirstOrDefault();
                p1.SocialSecurityNumber = 123;
    
                //再创建一个Context,同样取第一条记录,
                //修改SocialSecurityNumber字段并保存
                using (var secContext = new BreakAwayContext())
                {
                    var p2 = secContext.People.FirstOrDefault();
                    p2.SocialSecurityNumber = 456;
                    secContext.SaveChanges();
                }
                try
                {
                    firContext.SaveChanges();
                    Console.WriteLine(" 保存成功");
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    Console.WriteLine(ex.Entries.First().Entity.GetType().Name + " 保存失败");
                }
                Console.Read();
            }

    运行结果同样是保存失败,说明我们的并发控制起作用了。

    分析一下EF执行的SQL:

    exec sp_executesql N'update [dbo].[People]
    set [SocialSecurityNumber] = @0
    where (([PersonId] = @1) and ([SocialSecurityNumber] = @2))
    ',N'@0 int,@1 int,@2 int',@0=123,@1=1,@2=12345678

    可以看到,EF将我们要并发控制的列SocialSecurityNumber也作为一个筛选条件,这样firContext保存的时候也会因为的数据库中SocialSecurityNumber值变了,取不到对应的记录而更新失败。

     补充一下:如果是EDMX如何将字段设置为Concurrency。很简单,在对应的字段上右键-属性。在打开的属性窗口中有一个并发模式,你将它选择为Fixed即可。

  • 相关阅读:
    HLG 1522 子序列的和【队列的应用】
    POJ 3273 Monthly Expense【二分】
    HDU 4004 The Frog's Games 【二分】
    POJ 2001 Shortest Prefixes【第一棵字典树】
    POJ 2823 Sliding Window【单调对列经典题目】
    HDU 1969 Pie 【二分】
    POJ 3125 Printer Queue【暴力模拟】
    POJ 3250 Bad Hair Day【单调栈】
    字典树【模板】
    验证码 Code
  • 原文地址:https://www.cnblogs.com/Gyoung/p/2866649.html
Copyright © 2011-2022 走看看