您好,登錄后才能下訂單哦!
對于并發(fā)問題這個話題相信大家并不陌生,當(dāng)數(shù)據(jù)量比較大時這個時候我們就需要考慮并發(fā),對于并發(fā)涉及到的內(nèi)容也比較多,在EF Core中我們將并發(fā)分為幾個小節(jié)來陳述,讓大家看起來也不太累,也容易接受,我們由淺入深。首先我們看下給出的Blog實(shí)體類。
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; } }
對于在VS2015中依賴注入倉儲我們就不再敘述,比較簡單,我們看下控制器中的兩個方法,一個是渲染數(shù)據(jù),一個是更新數(shù)據(jù)的方法,如下:
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); } }
視圖渲染數(shù)據(jù)如下:
@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>
最終在頁面上渲染的數(shù)據(jù)如下:
接下來我們演示下如何引起并發(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ā)問題呢?在這里我們有兩種方式,我們一一來陳述。
既然要講并發(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,如下:
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); } }
當(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è)置行版本的情況。
當(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ù)往下看。
我們通過三種設(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上下文來操作。
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(); } } }
上述則是通用解決并發(fā)異常的辦法,我們只是注意上述表明的TODO邏輯,我們需要得到并發(fā)的屬性,然后再來更新其值即可,我們對于Name會產(chǎn)生并發(fā),所以遍歷實(shí)體屬性時獲取到Name,然后更新其值即可,簡單粗暴,完勝。我們看如下演示。
上述我們將Name修改為efcoreefcore,在SaveChanges前修改數(shù)據(jù)庫中的Name,接著再來進(jìn)行SaveChanges時,此時肯定會走并發(fā)異常,我們在并發(fā)異常中進(jìn)行處理,最終我們能夠很清楚的看到最終數(shù)據(jù)庫中的Name更新為efcoreefcore,我們在最后重試一次在一定程度上可以保證能夠解決并發(fā)。
免責(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)容。