亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Entity Framework管理并發(fā)

 更新時(shí)間:2022年03月05日 15:26:34   作者:.NET開發(fā)菜鳥  
這篇文章介紹了Entity Framework管理實(shí)現(xiàn)并發(fā)的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

理解并發(fā)

并發(fā)管理解決的是允許多個(gè)實(shí)體同時(shí)更新,實(shí)際上這意味著允許多個(gè)用戶同時(shí)在相同的數(shù)據(jù)上執(zhí)行多個(gè)數(shù)據(jù)庫(kù)操作。并發(fā)是在一個(gè)數(shù)據(jù)庫(kù)上管理多個(gè)操作的一種方式,同時(shí)遵守了數(shù)據(jù)庫(kù)操作的ACID屬性(原子性、一致性、隔離性和持久性)。

想象一下下面幾種可能發(fā)生并發(fā)的場(chǎng)景:

1、用戶甲和乙都嘗試修改相同的實(shí)體。

2、用戶甲和乙都嘗試刪除相同的實(shí)體。

3、用戶甲正在嘗試修改一個(gè)實(shí)體時(shí),用戶乙已經(jīng)刪除了該實(shí)體。

4、用戶甲已經(jīng)請(qǐng)求讀取一個(gè)實(shí)體,用戶乙讀完該實(shí)體之后更新了它。

這些場(chǎng)景可能會(huì)潛在地產(chǎn)生錯(cuò)誤的數(shù)據(jù),試想,成百上千的用戶同時(shí)嘗試操作一個(gè)相同的實(shí)體,這種并發(fā)問題將會(huì)對(duì)系統(tǒng)帶來更大的影響。

在處理與并發(fā)相關(guān)的問題時(shí),一般有以下兩種方法:

1、樂觀并發(fā):無論何時(shí)從數(shù)據(jù)庫(kù)請(qǐng)求數(shù)據(jù),數(shù)據(jù)都會(huì)被讀取并保存到應(yīng)用內(nèi)存中。數(shù)據(jù)庫(kù)級(jí)別沒有放置任何顯示鎖。數(shù)據(jù)操作會(huì)按照數(shù)據(jù)層接收到的順序執(zhí)行。

2、悲觀并發(fā):無論何時(shí)從數(shù)據(jù)庫(kù)請(qǐng)求數(shù)據(jù),數(shù)據(jù)都會(huì)被讀取,然后該數(shù)據(jù)上就會(huì)加鎖,因此沒有人能訪問該數(shù)據(jù)。這會(huì)降低并發(fā)相關(guān)問題的機(jī)率,缺點(diǎn)是加鎖是一個(gè)昂貴的操作,會(huì)降低整個(gè)應(yīng)用程序的性能。

一、理解樂觀并發(fā)

前面提到,在樂觀并發(fā)中,無論何時(shí)從數(shù)據(jù)庫(kù)請(qǐng)求數(shù)據(jù),數(shù)據(jù)都會(huì)被讀取并保存到應(yīng)用內(nèi)存中。數(shù)據(jù)庫(kù)級(jí)別沒有放置任何顯式鎖。因?yàn)檫@種方法沒有添加顯式鎖,所以比悲觀并發(fā)更具擴(kuò)展性和靈活性。使用樂觀并發(fā),重點(diǎn)是如果發(fā)生了任何沖突,應(yīng)用程序要親自處理它們。最重要的是:使用樂觀并發(fā)控制時(shí),在應(yīng)用中要有一個(gè)沖突解決策略,要讓應(yīng)用程序的用戶知道他們的修改是否因?yàn)闆_突的緣故沒有持久化。樂觀并發(fā)本質(zhì)上是允許沖突發(fā)生,然后以一種適當(dāng)?shù)姆绞浇鉀Q該沖突。

下面是處理沖突的策略例子。

1、忽略沖突/強(qiáng)制更新

這種策略是讓所有的用戶更改相同的數(shù)據(jù)集,然后所有的修改都會(huì)經(jīng)過數(shù)據(jù)庫(kù),這就意味著數(shù)據(jù)庫(kù)會(huì)顯示最后一次更新的值。這種策略會(huì)導(dǎo)致潛在的數(shù)據(jù)丟失,因?yàn)樵S多用戶的更改數(shù)據(jù)都丟失了,只有最后一個(gè)用戶的更改是可見的。

2、部分更新

在這種情況中,我們也允許所有的更改,但是不會(huì)更新完整的行,只有特定用戶擁有的列更新了。這就意味著,如果兩個(gè)用戶更新相同的記錄但卻不同的列,那么這兩個(gè)更新都會(huì)成功,而且來自這兩個(gè)用戶的更改都是可見的。

3、警告/詢問用戶

當(dāng)一個(gè)用戶嘗試更新一個(gè)記錄時(shí),但是該記錄自從他讀取之后已經(jīng)被其他用戶更改了,這時(shí)應(yīng)用程序就會(huì)警告該用戶該數(shù)據(jù)已經(jīng)被其他用戶更改了,然后詢問他是否仍然要重寫該數(shù)據(jù)還是首先檢查已經(jīng)更新的數(shù)據(jù)。

4、拒絕更改

當(dāng)一個(gè)用戶嘗試更新一個(gè)記錄時(shí),但是該記錄自從他讀取之后已經(jīng)被其他用戶更改了,此時(shí)告訴該用戶不允許更新該數(shù)據(jù),因?yàn)閿?shù)據(jù)已經(jīng)被其他用戶更新了。

二、理解悲觀并發(fā)

悲觀并發(fā)正好和樂觀并發(fā)相反,悲觀并發(fā)的目標(biāo)是永遠(yuǎn)不讓任何沖突發(fā)生。這是通過在使用記錄之前就在記錄上放置顯式鎖實(shí)現(xiàn)的。數(shù)據(jù)庫(kù)記錄上可以得到兩種類型的鎖:

只讀鎖

更新鎖。

當(dāng)把只讀鎖放到記錄上時(shí),應(yīng)用程序只能讀取該記錄。如果應(yīng)用程序要更新該記錄,它必須要獲取到該記錄上的更新鎖。如果記錄上加了只讀鎖,那么該記錄仍然能夠被想要只讀鎖的請(qǐng)求使用。然而,如果需要更新鎖,該請(qǐng)求必須等到所有的只讀鎖釋放。同樣,如果記錄上加了更新鎖,那么其他的請(qǐng)求不能再在這個(gè)記錄上加鎖,該請(qǐng)求必須等到已存在的更新鎖釋放才能加鎖。

從前面的描述中,似乎悲觀并發(fā)能解決所有跟并發(fā)相關(guān)的問題,因?yàn)槲覀儾槐卦趹?yīng)用中處理這些問題。然而,事實(shí)上并不是這樣的。在使用悲觀并發(fā)管理之前,我們需要記住,使用悲觀并發(fā)有很多問題和開銷。下面是使用悲觀并發(fā)面臨的一些問題:

應(yīng)用程序必須管理每個(gè)操作正在獲取的所有鎖。

加鎖機(jī)制的內(nèi)存需求會(huì)降低應(yīng)用性能。

多個(gè)請(qǐng)求互相等待需要的鎖,會(huì)增加死鎖的可能性。由于這些原因,EF不直接支持悲觀并發(fā)。如果想使用悲觀并發(fā)的話,我們可以自定義數(shù)據(jù)庫(kù)訪問代碼。此外,當(dāng)使用悲觀并發(fā)時(shí),LINQ to Entities不會(huì)正確工作。

三、使用EF實(shí)現(xiàn)樂觀并發(fā)

使用EF實(shí)現(xiàn)樂觀并發(fā)有很多方法,接下來我們就看一下這些方法。

1、新建控制臺(tái)項(xiàng)目,項(xiàng)目名:EFConcurrencyApp,新聞實(shí)體類定義如下:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFConcurrencyApp.Model
{
    public class News
    {
        public int Id { get; set; }
        [MaxLength(100)]
        public string Title { get; set; }
        [MaxLength(30)]
        public string Author { get; set; }
        public string Content { get; set; }
        public DateTime CreateTime { get; set; }
        public decimal Amount { get; set; }

    }
}

2、使用數(shù)據(jù)遷移的方式生成數(shù)據(jù)庫(kù),并填充種子數(shù)據(jù)。

namespace EFConcurrencyApp.Migrations
{
    using EFConcurrencyApp.Model;
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;

    internal sealed class Configuration : DbMigrationsConfiguration<EFConcurrencyApp.EF.EFDbContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(EFConcurrencyApp.EF.EFDbContext context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method
            //  to avoid creating duplicate seed data.

            context.News.AddOrUpdate(
                 new Model.News()
                 {
                     Title = "美國(guó)大城市房?jī)r(jià)太貴 年輕人靠“眾籌”買房",
                     Author = "佚名",
                     Content = "美國(guó)大城市房?jī)r(jià)太貴 年輕人靠“眾籌”買房",
                     CreateTime = DateTime.Now,
                     Amount = 0,
                 },
                 new Model.News()
                 {
                     Title = "血腥撲殺流浪狗太殘忍?那提高成本就是必須的代價(jià)",
                     Author = "佚名",
                     Content = "血腥撲殺流浪狗太殘忍?那提高成本就是必須的代價(jià)",
                     CreateTime = DateTime.Now,
                     Amount = 0,
                 },
                 new Model.News()
                 {
                     Title = "iPhone 8或9月6日發(fā)布 售價(jià)或1100美元起",
                     Author = "網(wǎng)絡(luò)",
                     Content = "iPhone 8或9月6日發(fā)布 售價(jià)或1100美元起",
                     CreateTime = DateTime.Now,
                     Amount = 0,
                 }
                 );
        }
    }
}

3、數(shù)據(jù)庫(kù)上下文定義如下

using EFConcurrencyApp.Model;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFConcurrencyApp.EF
{
    public class EFDbContext:DbContext
    {
        public EFDbContext()
            : base("name=AppConnection")
        {

        }

        public DbSet<News> News { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // 設(shè)置表名和主鍵
            modelBuilder.Entity<News>().ToTable("News").HasKey(p => p.Id);
            base.OnModelCreating(modelBuilder);
        }
    }
}

4、實(shí)現(xiàn)EF的默認(rèn)并發(fā)

先看一下EF默認(rèn)是如何處理并發(fā)的,現(xiàn)在假設(shè)我們的應(yīng)用程序要更新一個(gè)News的Amount值,那么我們首先需要實(shí)現(xiàn)這兩個(gè)函數(shù)FindNews()和UpdateNews(),前者用于獲取指定的News,后者用于更新指定News。

Program類里面定義的兩個(gè)方法如下:

static News FindNews(int id)
{
      using (var db = new EFDbContext())
      {
            return db.News.Find(id);
      }
}
static void UpdateNews(News news)
{
      using (var db = new EFDbContext())
      {
          db.Entry(news).State = EntityState.Modified;
          db.SaveChanges();
       }
}

下面我們實(shí)現(xiàn)這樣一個(gè)場(chǎng)景:有兩個(gè)用戶甲和乙都讀取了同一個(gè)News實(shí)體,然后這兩個(gè)用戶都嘗試更新這個(gè)實(shí)體的不同字段,比如甲更新Title字段,乙更新Author字段,代碼如下:

//1.用戶甲獲取id=1的新聞
var news1 = FindNews(1);
//2.用戶乙獲取id=1的新聞
var news2 = FindNews(1);
//3.用戶甲更新這個(gè)實(shí)體的新聞標(biāo)題
news1.Title = news1.Title + "(更新)";
UpdateNews(news1);
//4.用戶乙更新這個(gè)實(shí)體的Amount
news2.Amount = 10m;
UpdateNews(news2);

上面的代碼嘗試模擬了一種并發(fā)問題?,F(xiàn)在,甲和乙兩個(gè)用戶都有相同的數(shù)據(jù)副本,然后嘗試更新相同的記錄。執(zhí)行代碼前,先看一下數(shù)據(jù)庫(kù)中的數(shù)據(jù):

為了測(cè)試,在執(zhí)行第四步時(shí)打一個(gè)斷點(diǎn):

在斷點(diǎn)之后的代碼執(zhí)行之前,去數(shù)據(jù)庫(kù)看一下數(shù)據(jù),可以看到用戶甲的更新已經(jīng)產(chǎn)生作用了:

繼續(xù)執(zhí)行代碼,在看一下數(shù)據(jù)庫(kù)中的數(shù)據(jù)發(fā)生了什么變化:

從上面的截圖可以看出,用戶乙的請(qǐng)求成功了,而用戶甲的更新丟失了。因此,從上面的代碼不難看出,如果我們使用EF更新整條數(shù)據(jù),那么最后一個(gè)請(qǐng)求總會(huì)獲得勝利,也就是說:最后一次請(qǐng)求的更新會(huì)覆蓋之前所有請(qǐng)求的更新。

四、設(shè)計(jì)處理字段級(jí)別并發(fā)的應(yīng)用

接下來,我們會(huì)看到如何編寫處理字段級(jí)別并發(fā)問題的應(yīng)用代碼。這是設(shè)計(jì)方式的應(yīng)用思想是:只有更新的字段才會(huì)在數(shù)據(jù)庫(kù)中進(jìn)行更改。這樣就保證了如果多個(gè)用戶正在更新不同的字段,所有的更改都可以持久化到數(shù)據(jù)庫(kù)。

實(shí)現(xiàn)這個(gè)的關(guān)鍵是讓該應(yīng)用識(shí)別用戶正在請(qǐng)求更新的所有列,然后為該用戶有選擇地更新那些字段。通過以下兩個(gè)方法來實(shí)現(xiàn):

取數(shù)據(jù)的方法:該方法會(huì)給我們一個(gè)原始模型的克隆,只有用戶請(qǐng)求的屬性會(huì)更新為新值。

更新的方法:它會(huì)檢查原始請(qǐng)求模型的哪個(gè)屬性值已經(jīng)發(fā)生更改,然后在數(shù)據(jù)庫(kù)中只更新那些值。

因此,首先需要?jiǎng)?chuàng)建一個(gè)簡(jiǎn)單的方法,該方法需要模型屬性的值,然后會(huì)返回一個(gè)新的模型,該模型除了用戶嘗試更新的屬性以外,其他的屬性值都和原來的模型屬性值相同。方法定義如下:

static News GetUpdatedNews(int id, string title, string author, decimal amount, string content, DateTime createTime)
{
     return new News
     {
           Id = id,
           Title = title,
           Amount = amount,
           Author = author,
           Content = content,
           CreateTime = createTime,
      };
}

下一步,需要更改更新的方法。該更新方法會(huì)實(shí)現(xiàn)下面更新數(shù)據(jù)的算法:

1、根據(jù)Id從數(shù)據(jù)庫(kù)中檢索最新的模型值。

2、檢查原始模型和要更新的模型來找出更改屬性的列表。

3、只更新步驟2中檢索到的模型發(fā)生變化的屬性。

4、保存更改。

更新方法定義如下:

static void UpdateNewsEnhanced(News originalNews, News newNews)
{
            using (var db = new EFDbContext())
            {
                //從數(shù)據(jù)庫(kù)中檢索最新的模型
                var news = db.News.Find(originalNews.Id);
                //接下來檢查用戶修改的每個(gè)屬性
                if (originalNews.Title != newNews.Title)
                {
                    //將新值更新到數(shù)據(jù)庫(kù)
                    news.Title = newNews.Title;
                }
                if (originalNews.Content != newNews.Content)
                {
                    //將新值更新到數(shù)據(jù)庫(kù)
                    news.Content = newNews.Content;
                }
                if (originalNews.CreateTime != newNews.CreateTime)
                {
                    //將新值更新到數(shù)據(jù)庫(kù)
                    news.CreateTime = newNews.CreateTime;
                }
                if (originalNews.Amount != newNews.Amount)
                {
                    //將新值更新到數(shù)據(jù)庫(kù)
                    news.Amount = newNews.Amount;
                }
                if (originalNews.Author != newNews.Author)
                {
                    //將新值更新到數(shù)據(jù)庫(kù)
                    news.Author = newNews.Author;
                }
                // 持久化到數(shù)據(jù)庫(kù)
                db.SaveChanges();
            }
}

運(yùn)行代碼前,先查看數(shù)據(jù)庫(kù)中的數(shù)據(jù):

然后執(zhí)行主程序代碼,在執(zhí)行第四步時(shí)打個(gè)斷點(diǎn):

再次查看數(shù)據(jù)庫(kù)的數(shù)據(jù),發(fā)現(xiàn)用戶甲的操作已經(jīng)執(zhí)行了:

繼續(xù)運(yùn)行程序,再次查看數(shù)據(jù)庫(kù)的數(shù)據(jù),發(fā)現(xiàn)用戶乙的操作也執(zhí)行了:

從上面的截圖看到,兩個(gè)用戶請(qǐng)求同一個(gè)實(shí)體的更新值都持久化到了數(shù)據(jù)庫(kù)中。因此,如果用戶更新不同的字段,該程序可以有效地處理并發(fā)更新了。但是如果多個(gè)用戶同時(shí)更新相同的字段,那么這種方法仍然顯示的是最后一次請(qǐng)求的值。雖然這種方式減少了一些并發(fā)相關(guān)的問題,但是這種方法意味著我們必須寫大量代碼來處理并發(fā)問題。后面我們會(huì)看到如何使用EF提供的機(jī)制來處理并發(fā)問題。

五、使用RowVersion實(shí)現(xiàn)并發(fā)

前面我們看到了EF默認(rèn)如何處理并發(fā)(最后一次請(qǐng)求的數(shù)據(jù)更新成功),然后看到如果多個(gè)用戶嘗試更新不同的字段時(shí),如何設(shè)計(jì)應(yīng)用處理這些問題。接下來,我們看一下當(dāng)多個(gè)用戶更新相同的字段時(shí),使用EF如何處理字段級(jí)更新。

EF讓我們指定字段級(jí)并發(fā),這樣如果一個(gè)用戶更新一個(gè)字段的同時(shí),該字段已經(jīng)被其他用戶更新過了,就會(huì)拋出一個(gè)并發(fā)相關(guān)的異常。使用這種方法,當(dāng)多個(gè)用戶嘗試更新相同的字段時(shí),我們就可以更有效地處理并發(fā)相關(guān)的問題。

如果我們?yōu)槎鄠€(gè)字段使用了特定字段的并發(fā),那么會(huì)降低應(yīng)用性能,因?yàn)樯傻腟QL會(huì)更大,更加有效的方式就是使用RowVersion機(jī)制。RowVersion機(jī)制使用了一種數(shù)據(jù)庫(kù)功能,每當(dāng)更新行的時(shí)候,就會(huì)創(chuàng)建一個(gè)新的行值。

給News實(shí)體類添加一個(gè)屬性:

[Timestamp]
public byte[] RowVersion { get; set; }

在數(shù)據(jù)庫(kù)上下文中配置屬性:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
      // 設(shè)置表名和主鍵
      modelBuilder.Entity<News>().ToTable("News").HasKey(p => p.Id);
      // 設(shè)置屬性
      modelBuilder.Entity<News>().Property(d => d.RowVersion).IsRowVersion();
      base.OnModelCreating(modelBuilder);
}

刪除原先的數(shù)據(jù)庫(kù),然后重新生成數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)模式變?yōu)椋?/p>

查看數(shù)據(jù),RowVersion列顯示的是二進(jìn)制數(shù)據(jù):

現(xiàn)在EF就會(huì)為并發(fā)控制追蹤RowVersion列值。接下來嘗試更新不同的列:

using (var context = new EFDbContext())
{
                var news = context.News.SingleOrDefault(p => p.Id == 1);
                Console.WriteLine(string.Format("標(biāo)題:{0} 打賞金額:{1} ", news.Title, news.Amount.ToString("C")));
                context.Database.ExecuteSqlCommand(@"update news set 
                        amount = 229.95 where Id = @p0", news.Id);
                news.Amount = 239.95M;
                Console.WriteLine(string.Format("標(biāo)題:{0} 打賞金額:{1} ", news.Title, news.Amount.ToString("C")));
                context.SaveChanges();
}

運(yùn)行程序,會(huì)拋出下面的異常:

從拋出的異常信息來看,很明顯是拋出了和并發(fā)相關(guān)的異常DbUpdateConcurrencyException,其他信息說明了自從實(shí)體加載以來,可能已經(jīng)被修改或刪除了。

無論何時(shí)一個(gè)用戶嘗試更新一條已經(jīng)被其他用戶更新的記錄,都會(huì)獲得異常DbUpdateConcurrencyException。

當(dāng)實(shí)現(xiàn)并發(fā)時(shí),我們總要編寫異常處理的代碼,給用戶展示一個(gè)更友好的描述信息。上面的代碼加上異常處理機(jī)制后修改如下:

using (var context = new EFDbContext())
{
      var news = context.News.SingleOrDefault(p => p.Id == 1);
      Console.WriteLine(string.Format("標(biāo)題:{0} 打賞金額:{1} ", news.Title, news.Amount.ToString("C")));
      context.Database.ExecuteSqlCommand(string.Format(@"update News set 
                        Amount = 229.95 where Id = {0}", news.Id));
      news.Amount = 239.95M;
      Console.WriteLine(string.Format("標(biāo)題:{0} 打賞金額:{1} ", news.Title, news.Amount.ToString("C")));

      try
      {
            context.SaveChanges();
      }
      catch (DbUpdateConcurrencyException ex)
      {
            Console.WriteLine(string.Format("并發(fā)異常:{0}", ex.Message));
      }
      catch (Exception ex)
      {
            Console.WriteLine(string.Format("普通異常:{0}", ex.Message));
      }
}

此時(shí),我們應(yīng)該使用當(dāng)前的數(shù)據(jù)庫(kù)值更新數(shù)據(jù),然后重新更改。作為開發(fā)者,如果我們想要協(xié)助用戶的話,我們可以使用EF的DbEntityEntry類獲取當(dāng)前的數(shù)據(jù)庫(kù)值。

using (var context = new EFDbContext())
{
      var news = context.News.SingleOrDefault(p => p.Id == 1);
      Console.WriteLine(string.Format("標(biāo)題:{0} 打賞金額:{1} ", news.Title, news.Amount.ToString("C")));
              
context.Database.ExecuteSqlCommand(string.Format(@"update News set 
       Amount = 229.95 where Id = {0}", news.Id));
       news.Amount = 239.95M;
       Console.WriteLine(string.Format("標(biāo)題:{0} 打賞金額:{1} ", news.Title, news.Amount.ToString("C")));       try
       {
         context.SaveChanges();
       }
       catch (DbUpdateConcurrencyException ex)
       {
          // 使用這段代碼會(huì)將Amount更新為239.95
          var postEntry = context.Entry(news);
          postEntry.OriginalValues.SetValues(postEntry.GetDatabaseValues());
          context.SaveChanges();
        }
        catch (Exception ex)
        {
           Console.WriteLine(string.Format("普通異常:{0}", ex.Message));
        }
}

示例代碼下載地址:點(diǎn)此下載

到此這篇關(guān)于Entity Framework管理并發(fā)的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論