• EF实体框架之CodeFirst七


    【本博客里有关于 EF实体框架之CodeFirs 系列的随笔都是摘自 作者:社会主义接班人 的博客 只作为学习笔记使用】

     作者:社会主义接班人
    出处:http://www.cnblogs.com/5ishare/

    一、并发控制

    1.锁分悲观锁和乐观锁。

    在code first中使用的是乐观锁。至于什么是悲观锁,大家应该都用过版本控制软件,VSS中有签入签出,当被迁出的时候其他人是修改不了的,这种就是可以理解为悲观锁。而在svn中当一个文件被迁出时,其他人还可以修改,之前commit的时候根据版本合并就OK,这种可以理解为乐观锁。

    2.锁的粒度

    code first在锁的粒度上也分为两种。一是行级一个单元格级及属性集。

    1).行级

    使用Timestamp来控制每行的并发。一张表中只能有一个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; }
            public decimal Money { get; set; }
            [Timestamp]
            public byte[] RowVersion { get; set; }
        }
    复制代码
    复制代码
                var person = new Person
                {
                    FirstName = "Cui",
                    LastName = "YanWei",
                    SocialSecurityNumber = 12345678
                };
                //新增一条记录,保存到数据库中
                using (var con = new EFCodeFirstDbContext())
                {
                    con.Persons.Add(person);
                    con.SaveChanges();
                }
    
                var firContext = new EFCodeFirstDbContext();
                //取第一条记录,并修改一个字段:这里是修改了FirstName
                //先不保存
                var p1 = firContext.Persons.FirstOrDefault();
                p1.FirstName = "CuiA";
    
                //再创建一个Context,同样取第一条记录,修改LastName字段并保存
                using (var secContext = new EFCodeFirstDbContext())
                {
                    var p2 = secContext.Persons.FirstOrDefault();
                    p2.LastName = "Ivan";
                    secContext.SaveChanges();
                }
                try
                {
                    firContext.SaveChanges();
                    Console.WriteLine(" 保存成功");
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    Console.WriteLine(ex.Entries.First().Entity.GetType().Name + " 保存失败");
                }
                Console.Read();
    复制代码

    在上面的代码中首先是新增一个Person对象,SaveChanges()保存到数据库。然后将Person对象获取出来改变FirstName,但此时并没有SaveChanges()保存到数据库.然后呢有将该对象取出来,修改LastName并SaveChanges()保存到数据库,此时再将上面的修改FirstName的保存到数据库,运行时会抛出异常。这是为什么呢?

    可以看下下面的三个截图:

     上面的3个截图,第一个sql是最先执行,然后是第二个,不过这里要注意下两个sql的binary字段,两个都是一样的,但是最后存在数据库的可是不一样,其实当第一个sql执行完时binary字段变成了也就是数据库现在存在的值,但是第二个sql执行时还是用的之前的值,所以就更新失败抛出异常。这就是Timestamp的机制。

    2).列级

     Data Annotations中用ConcurrencyCheck来标识控制列的并发。此时可以先把上面的Person类中的Timestamp注释掉。在SocialSecurityNumber属性上添加约定。

    复制代码
        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 decimal Money { get; set; }
            //[Timestamp]
            public byte[] RowVersion { get; set; }
        }
    复制代码

    我们将SocialSecurityNumber(社会保险号)标识为开放式并发。

    复制代码
                var person = new Person
                {
                    FirstName = "Cui",
                    LastName = "YanWei",
                    SocialSecurityNumber = 12345678
                };
                //新增一条记录,保存到数据库中
                using (var con = new EFCodeFirstDbContext())
                {
                    con.Persons.Add(person);
                    con.SaveChanges();
                }
                var firContext = new EFCodeFirstDbContext();
                //取第一条记录,并修改一个字段:这里是修改了SocialSecurityNumber=123
                //先不保存
                var p1 = firContext.Persons.FirstOrDefault();  
                p1.SocialSecurityNumber = 123;
                //再创建一个Context,同样取第一条记录,修改SocialSecurityNumber=456并保存
                using (var secContext = new EFCodeFirstDbContext())
                {
                    var p2 = secContext.Persons.FirstOrDefault();
    
                    p2.SocialSecurityNumber = 456;
                    secContext.SaveChanges();
                   
                }
                try
                {
                    firContext.SaveChanges();
                    Console.WriteLine(" 保存成功");
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    Console.WriteLine(ex.Entries.First().Entity.GetType().Name + " 保存失败");
                }
                Console.Read();
    复制代码

    下面还是3张截图.

    和Timestamp类似,3个截图的第一个sql是第二个更改操作的数据库上下文更改执行的sql,第二个是第一个更改操作的数据库上下文更改执行的sql,where语句中增加了SocialSecurityNumber,由于第一个sql执行之后SocialSecurityNumber已经改变,第二个sql还是使用旧的SocialSecurityNumber,所以更新失败。

    二、事务

     1.默认事务处理

    EF的默认行为是,无论何时执行任何涉及Create,Update或Delete的查询,都会默认创建事务。当DbContext类上的SaveChanges()方法被调用时,事务就会提交。

    2.分布式事务

    对于分布式事务可以想到TransactionScope,code first中分布式事务意味着多个DbContext,由于EF默认就是事务处理,所以只需关注分布式的事务,对于分布式事务的应用需要开启window服务,这个日后会具体总结。注意要引入类库:

    using System.Transactions;
                using (var ts = new TransactionScope(TransactionScopeOption.Required))
                {
                    //db数据库上下文操作
                    ts.Complete();
                }

    3.EF6管理事务

    从EF 6起,EF在DbContext对象上提供了Database.BeginTransaction()方法,当使用上下文类在事务中执行原生SQL命令时,这个方法特别有用。

    复制代码
                using (var db = new EFCodeFirstDbContext())
                {
                    using (var trans = db.Database.BeginTransaction())
                    {
                        try
                        {
                           
                            //执行sql
                            db.SaveChanges();
    
                            trans.Commit();
                        }
                        catch (Exception ex)
                        {
                            trans.Rollback();
                        }
                    }
                }

    复制代码
  • 相关阅读:
    Spring如何解决循环依赖
    AbstractQueuedSynchronizer之AQS
    Spring中各种扩展原理及容器创建原理
    SpringAOP和TX事务的源码流程
    Spring的IOC常用注解(含源码)
    采用lua脚本获取mysql、redis数据以及jwt的校验
    Redis常用数据类型及其存储结构(源码篇)
    Redis分布式锁
    雪花算法
    springboot2.2.6项目接入Nacos流程
  • 原文地址:https://www.cnblogs.com/NotEnough/p/6842760.html
Copyright © 2020-2023  润新知