溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

EntityFramework Core解決并發(fā)詳解

發(fā)布時間:2020-05-18 02:57:10 來源:網(wǎng)絡(luò) 閱讀:862 作者:胡壯壯 欄目:數(shù)據(jù)庫

話題(EntityFramework Core并發(fā))

對于并發(fā)問題這個話題相信大家并不陌生,當(dāng)數(shù)據(jù)量比較大時這個時候我們就需要考慮并發(fā),對于并發(fā)涉及到的內(nèi)容也比較多,在EF Core中我們將并發(fā)分為幾個小節(jié)來陳述,讓大家看起來也不太累,也容易接受,我們由淺入深。首先我們看下給出的Blog實(shí)體類。

 EntityFramework Core解決并發(fā)詳解

    public class Blog : IEntityBase
    {        public int Id { get; set; }        public string Name { get; set; }        public string Url { get; set; }        public ICollection<Post> Posts { get; set; }
    }

 EntityFramework Core解決并發(fā)詳解

對于在VS2015中依賴注入倉儲我們就不再敘述,比較簡單,我們看下控制器中的兩個方法,一個是渲染數(shù)據(jù),一個是更新數(shù)據(jù)的方法,如下:

 EntityFramework Core解決并發(fā)詳解

    public class HomeController : Controller
    {        private IBlogRepository _blogRepository;        public HomeController(IBlogRepository blogRepository)
        {
            _blogRepository = blogRepository;
        }        public IActionResult Index()
        {            var blog = _blogRepository.GetSingle(d => d.Id == 1);            return View(blog);
        }

        [HttpPost]        public IActionResult Index(Blog obj)
        {            try
            {
                _blogRepository.Update(obj);
                _blogRepository.Commit();
            }            catch (Exception ex)
            {
                ModelState.AddModelError("", ex.Message);
            }            return View(obj);
        }
    }

 EntityFramework Core解決并發(fā)詳解

視圖渲染數(shù)據(jù)如下:

 EntityFramework Core解決并發(fā)詳解

@using StudyEFCore.Model.Entities
@model Blog<html><head>
    <title></title></head><body>
    @using (Html.BeginForm("Index", "Home", FormMethod.Post))
    {        <table border="1" cellpadding="10">
            <tr>
                <td>博客ID :</td>
                <td>
                    @Html.TextBoxFor(m => m.Id,
       new { @readonly = "readonly" })            </td>
        </tr>
        <tr>
            <td>博客名稱 :</td>
            <td>@Html.TextBoxFor(m => m.Name)</td>
        </tr>
        <tr>
            <td>博客地址:</td>
            <td>@Html.TextBoxFor(m => m.Url)</td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="更新" />
            </td>
        </tr>
    </table>
    }
    @Html.ValidationSummary()</body></html>

 EntityFramework Core解決并發(fā)詳解

最終在頁面上渲染的數(shù)據(jù)如下:

 EntityFramework Core解決并發(fā)詳解

接下來我們演示下如何引起并發(fā)問題,如下:

 EntityFramework Core解決并發(fā)詳解

上述我們通過在視圖頁面更新值后然后在SaveChanges之前打斷點(diǎn),然后我們在數(shù)據(jù)庫中改變其值,再來SaveChanges此時會報異常,錯誤信息如下:

 
See http:

因?yàn)樵谖覀冺撁嫔细淖兤渲岛笪催M(jìn)行SaveChanges,但是此時我們修改了Name的值,接著再來SaveChanges,此時報上述錯誤也就是我們本節(jié)所說的并發(fā)問題。既然出現(xiàn)了這樣的問題,那么我們在EF Core中該如何解決出現(xiàn)的并發(fā)問題呢?在這里我們有兩種方式,我們一一來陳述。

EF Core并發(fā)解決方案一(并發(fā)Token)

既然要講并發(fā)Token,那么在此之前我們需要講講并發(fā)Token到底是怎樣工作的,當(dāng)我們對屬性標(biāo)識為并發(fā)Token,當(dāng)我們從數(shù)據(jù)庫中加載其值時,此時對應(yīng)的屬性的并發(fā)Token也就通過上下文而分配,當(dāng)對分配的并發(fā)Token屬性的相同的值進(jìn)行了更新或者刪除,此時會強(qiáng)制該屬性的并發(fā)Token去進(jìn)行檢測,它會去檢測影響的行數(shù)量,如果并發(fā)已經(jīng)匹配到了,然后一行將被更新到,如果該值在數(shù)據(jù)庫中已經(jīng)被更新,那么將沒有數(shù)據(jù)行會被更新。對于更新或者刪除通過在WHERE條件上包括并發(fā)Token。接下來我們對要更新的Name將其設(shè)置為并發(fā)Token,如下:

 EntityFramework Core解決并發(fā)詳解

    public class BlogMap : EntityMappingConfiguration<Blog>
    {        public override void Map(EntityTypeBuilder<Blog> b)
        {
            b.ToTable("Blog");
            b.HasKey(k => k.Id);            b.Property(p => p.Name).IsConcurrencyToken();          
            b.Property(p => p.Url);
            b.HasMany(p => p.Posts).WithOne(p => p.Blog).HasForeignKey(p => p.BlogId);
        }
    }

 EntityFramework Core解決并發(fā)詳解

當(dāng)我們進(jìn)行如上設(shè)置后再來遷移更新模型,最終還是會拋出如下異常:

Database operation expected to affect  row(s) but actually affected  row(s). 
Data may have been modified or deleted since entities were loaded. 
See http:

接下來我們再來看看解決并發(fā)而設(shè)置行版本的情況。

EF Core并發(fā)解決方案二(行版本)

當(dāng)我們在插入或者更新時都會產(chǎn)生一個新的timestamp,這個屬性也會被當(dāng)做一個并發(fā)Token來對待,它會確保當(dāng)我們更新值時但是其值已經(jīng)被修改過時一定會如上所述拋出異常。那么怎么使用行版本呢,(我們只講Fluent API關(guān)于Data Annotations請自行查找資料)在實(shí)體中定義如下屬性:

 public byte[] RowVersion { get; set; }

接著對該屬性進(jìn)行如下配置。

b.Property(p => p.RowVersion).IsConcurrencyToken().ValueGeneratedOnAddOrUpdate();

當(dāng)我們再次進(jìn)行如上演示時肯定會拋出同樣的異常信息。 

上述兩種從本質(zhì)上都未能解決在EF Core中的并發(fā)問題只是做了基礎(chǔ)的鋪墊,那么我們到底該如何做才能解決并發(fā)問題呢,請繼續(xù)往下看。

解析EF Core并發(fā)沖突

我們通過三種設(shè)置來解析EF Core中的并發(fā)沖突,如下:

當(dāng)前值(Current values):試圖將當(dāng)前修改的值寫入到到數(shù)據(jù)庫。

原始值(Original values):在未做任何修改時的需要從數(shù)據(jù)庫中檢索到的值。

數(shù)據(jù)值(Database values):當(dāng)前保存在數(shù)據(jù)庫中的值。

由于并發(fā)會拋出異常,所以我們需要 在SaveChanges時在并發(fā)沖突所產(chǎn)生的異常中來進(jìn)行解決,并發(fā)異常呈現(xiàn)在 DbUpdateConcurrencyException 類中,我們只需要在此并發(fā)異常類解決即可。比如上述我們需要修改Name的值,我們做了基礎(chǔ)的鋪墊,設(shè)置了并發(fā)Token。但是還是會引發(fā)并發(fā)異常,未能解決問題,這個只是解決并發(fā)異常的前提,由于我們利用的倉儲來操作數(shù)據(jù),但是并發(fā)異常會利用到EF上下文,所以我們額外定義接口,直接通過上下文來操作,如下我們定義一個接口

    public interface IBlogRepository : IEntityBaseRepository<Blog>
    {        void UpdateBlog(Blog blog);
    }

解決并發(fā)異常通過EF上下文來操作。

 EntityFramework Core解決并發(fā)詳解

     public class BlogRepository : EntityBaseRepository<Blog>,
        IBlogRepository
    {        private EFCoreContext _efCoreContext;        public BlogRepository(EFCoreContext efCoreContext) : base(efCoreContext)
        {
            _efCoreContext = efCoreContext;
        }        public void UpdateBlog(Blog blog)
        {            try
            {
                _efCoreContext.Set<Blog>().Update(blog);
                _efCoreContext.SaveChanges();
            }            catch (DbUpdateConcurrencyException ex)
            {                foreach (var entry in ex.Entries)
                {                    if (entry.Entity is Blog)
                    {                        var databaseEntity = _efCoreContext.Set<Blog>().AsNoTracking().Single(p => p.Id == ((Blog)entry.Entity).Id);                        var databaseEntry = _efCoreContext.Entry(databaseEntity);                        foreach (var property in entry.Metadata.GetProperties())
                        {                            var proposedValue = entry.Property(property.Name).CurrentValue;                            var originalValue = entry.Property(property.Name).OriginalValue;                            var databaseValue = databaseEntry.Property(property.Name).CurrentValue;                            // TODO: Logic to decide which value should be written to database
                            var propertyName = property.Name;                            if (propertyName == "Name")
                            {                                // Update original values to
                                entry.Property(property.Name).OriginalValue = databaseEntry.Property(property.Name).CurrentValue;                                break;
                            }
                        }
                    }                    else
                    {                        throw new NotSupportedException("Don't know how to handle concurrency conflicts for " + entry.Metadata.Name);
                    }
                }                // Retry the save operation                _efCoreContext.SaveChanges();
            }
        }
    }

 EntityFramework Core解決并發(fā)詳解

上述則是通用解決并發(fā)異常的辦法,我們只是注意上述表明的TODO邏輯,我們需要得到并發(fā)的屬性,然后再來更新其值即可,我們對于Name會產(chǎn)生并發(fā),所以遍歷實(shí)體屬性時獲取到Name,然后更新其值即可,簡單粗暴,完勝。我們看如下演示。

 EntityFramework Core解決并發(fā)詳解

上述我們將Name修改為efcoreefcore,在SaveChanges前修改數(shù)據(jù)庫中的Name,接著再來進(jìn)行SaveChanges時,此時肯定會走并發(fā)異常,我們在并發(fā)異常中進(jìn)行處理,最終我們能夠很清楚的看到最終數(shù)據(jù)庫中的Name更新為efcoreefcore,我們在最后重試一次在一定程度上可以保證能夠解決并發(fā)。


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI