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即可。

    如果我的文章对你有帮助,就点一下推荐吧.(*^__^*)
  • 相关阅读:
    数据库连接池
    Apache- DBUtils框架学习
    权限表的设计
    Java的I/O对文件的操作
    Java下载文件
    Java连接MySQL数据库
    C#用log4net记录日志
    C#多线程和线程池
    C#利用反射动态调用DLL并返回结果,和获取程序集的信息
    CephRGW 在多个RGW负载均衡场景下,RGW 大文件并发分片上传功能验证
  • 原文地址:https://www.cnblogs.com/Alex80/p/5496770.html
Copyright © 2011-2022 走看看