EF實體框架之CodeFirst七


前面的6篇博客基本把Code First學習的差不多了,今天這篇學習下code first中的並發控制和事務,基本也快學完了,頂多就差數據遷移。

在數據庫中也是有鎖和事務的概念,在C#中也是存在,當然code first也是必要要有的。對於什么是並發、什么是鎖和事務,它們的特性是什么這些概念性的就不一一列舉。因為這些要是發散的學習就涉及到好多的知識點。並發能聯想到多線程,多線程能聯想到同步異步,同步異步操作系統,等等。知識都是連貫着的。有了並發那就有不並發的情況,想讓不並發,那用什么呢?那就要用鎖。說起鎖又想起鎖的類型、粒度、死鎖等。什么悲觀鎖、樂觀鎖、粒度上又分單元格級、行級、表級等。總的來說想弄明白C#是很不容易的,擔負着打光棍的風險,把青春放在了敲代碼上。上面算是開場白一些不正經的,下面開始說正經的。

一、並發控制

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();
                    }
                }
            }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM