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

    如果我的文章对你有帮助,就点一下推荐吧.(*^__^*)
  • 相关阅读:
    共享纸巾更换主板代码分析 共享纸巾主板更换后的对接代码
    Python Django Ajax 传递列表数据
    Python Django migrate 报错解决办法
    Python 创建字典的多种方式
    Python 两个list合并成一个字典
    Python 正则 re.sub替换
    python Django Ajax基础
    Python Django 获取表单数据的三种方式
    python Django html 模板循环条件
    Python Django ORM 字段类型、参数、外键操作
  • 原文地址:https://www.cnblogs.com/Alex80/p/6421948.html
Copyright © 2011-2022 走看看