樂觀併發的解決方案有以下三種: 1) 可以跟蹤用戶已修改的屬性,並僅更新資料庫中相應的列。 2) 可讓後提交的用戶更改覆蓋之前用戶提交的更改。 3) 可以阻止在資料庫中更新後一用戶提交的更改。 ...
學習ASP.NET Core Razor 編程系列目錄
學習ASP.NET Core Razor 編程系列二——添加一個實體
學習ASP.NET Core Razor 編程系列三——創建數據表及創建項目基本頁面
學習ASP.NET Core Razor 編程系列四——Asp.Net Core Razor列表模板頁面
學習ASP.NET Core Razor 編程系列五——Asp.Net Core Razor新建模板頁面
學習ASP.NET Core Razor 編程系列六——資料庫初始化
學習ASP.NET Core Razor 編程系列七——修改列表頁面
學習ASP.NET Core Razor 編程系列八——併發處理
學習ASP.NET Core Razor 編程系列九——增加查詢功能
學習ASP.NET Core Razor 編程系列十——添加新欄位
學習ASP.NET Core Razor 編程系列十一——把新欄位更新到資料庫
學習ASP.NET Core Razor 編程系列十二——在頁面中增加校驗
學習ASP.NET Core Razor 編程系列十三——文件上傳功能(一)
學習ASP.NET Core Razor 編程系列十四——文件上傳功能(二)
學習ASP.NET Core Razor 編程系列十五——文件上傳功能(三)
學習ASP.NET Core Razor 編程系列十六——排序
學習ASP.NET Core Razor 編程系列十七——分組
在文章(學習ASP.NET Core Razor 編程系列八——併發處理)中對於併發錯誤,我們只是簡單粗暴的進行了異常捕獲,然後拋出了異常。在本文中我們來看兩個解決併發的方法。
樂觀併發的解決方案有以下三種:
1) 可以跟蹤用戶已修改的屬性,並僅更新資料庫中相應的列。
在這種情況下,數據不會丟失。 兩個用戶更新了不同的欄位內容(例如:書名與出版社)。下次有人瀏覽書籍信息時,將看到書名和出版社兩個人的更改。 這種更新方法可以減少導致數據丟失的衝突數。這種方法需要維持重要狀態,以便跟蹤所有資料庫值與當前值,增加了應用複雜,可能會影響應用性能。通常不適用於 Web 應用。
2) 可讓後提交的用戶更改覆蓋之前用戶提交的更改。
這種方法稱為“客戶端優先”或“最後一個優先”方案。 (客戶端的所有值優先於數據存儲的值。)如果不對併發處理進行任何編碼,則自動執行“客戶端優先”。
3) 可以阻止在資料庫中更新後一用戶提交的更改。
這種方法,需要顯示錯誤信息,顯示當前數據和資料庫中的數據,允許用戶重新修改,並保存。這稱為“存儲優先”方案。 (數據存儲值優先於客戶端提交的值。)
一、客戶端優化
接下去我們來看看“客戶端優先”方案。 此方法確保後一用戶的提交為準,覆蓋資料庫中的數據。
樂觀併發允許發生併發衝突,併在併發衝突發生時作出正確反應。 例如,管理員訪問用書籍信息編輯頁面,將“Publishing”欄位值修改為“清華大學出版社”。
1.首先,我們使用Visual Studio 2017打開Books\Edit.cshmtl.cs文件,看一下OnPostAsync()方法,代碼如下。如下圖。
public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return Page(); } _context.Attach(Book).State = EntityState.Modified; try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!_context.Book.Any(e => e.ID == Book.ID)) { return NotFound(); } else { throw; } } return RedirectToPage("./Index"); }
2.在Visual Studio 2017中按F5運行應用程式。在瀏覽器中瀏覽書籍信息,併在書籍列表頁面中選擇一條書籍信息。我們假設有兩個用戶要對此條書籍信息進行編輯。首先是管理員,對此條書籍信息修改了“Publishing”的信息。如下圖。
3.在管理員單擊“Save”按鈕之前,Test用戶訪問了相同頁面,並將“出版日期”修改為了“2018-01-08”。如下圖。
4.Test用戶先單擊“保存”,併在瀏覽器的書籍信息列表頁面中看到了他修改的出版日期數據保存到了資料庫。如下圖。
5.此時,管理員單擊“編輯”頁面上的“保存”,但頁面的上的“出版日期”還是“2018-01-13”,按照“客戶端優化”規則會把Test用戶的修改覆蓋掉。如下圖。
二、存儲優先
接下去我們來看看“存儲優先”方案。 此方法可確保用戶在未收到警報時不會覆蓋任何更改。
首先我們來瞭解三組值:
- “當前值”是應用程式嘗試寫入資料庫的值。
- “原始值”是在進行任何編輯之前最初從資料庫中檢索的值。
- “資料庫值”是當前存儲在資料庫中的值。
處理併發衝突的常規方法是:
1)在 SaveChanges
期間捕獲 DbUpdateConcurrencyException
。
2)使用 DbUpdateConcurrencyException.Entries
為受影響的實體準備一組新更改。
3)刷新併發令牌的原始值以反映資料庫中的當前值。
4)重試該過程,直到不發生任何衝突。
下麵的示例,使用時間戳作為行級版本號。
1. 在Visual Studio 2017的“解決方案資源管理器”中使用滑鼠左鍵雙擊打開 Models /Book.cs文件, 對User實體添加跟蹤屬性RowVersion,併在其上添加Timestamp特性。代碼如下:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; namespace RazorMvcBooks.Models { public class Book { public int ID { get; set; } [Required] [StringLength(50, MinimumLength = 2)] public string Name { get; set; } [Display(Name = "出版日期")] [DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } [Range(1,200)] [DataType(DataType.Currency)] public decimal Price { get; set; } public string Author { get; set; } [ Required] public string Publishing { get; set; } [Timestamp] public byte[] RowVersion { get; set; } } }
2.在Visual Studio 2017中選擇“菜單>Nuget包管理器>程式包管理器控制台”,然後在打開的程式包管理器控制台依次執行以下命令
Add-Migration
RowVer
Update-Database
3.在SQL Server Management Studio中查看Book表。如下圖。
4.在Visual Studio 2017的“解決方案資源管理器”中使用滑鼠左鍵雙擊打開 Pages/Books/Edit.cshtml.cs文件,對OnPostAsync方法進行修改。Entity Framework Core 使用包含原始 RowVersion 值的 WHERE 子句生成 SQL UPDATE 命令。如果沒有行受到 UPDATE 命令影響(沒有行具有原始 RowVersion 值),將引發 DbUpdateConcurrencyException 異常。代碼如下:
public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return Page(); } var updBook = _context.Book.AsNoTracking().Where(u => u.ID == Book.ID).First(); // 如果為null,則當前用戶信息已經被 刪除 if (updBook == null) { return HandDeleteBook(); } _context.Attach(Book).State = EntityState.Modified; if (await TryUpdateModelAsync<Book>( Book, "Book", s => s.Name, s =>s.Publishing, s => s.ReleaseDate, s => s.Price)) { try { await _context.SaveChangesAsync(); return RedirectToPage("./Index"); } catch (DbUpdateConcurrencyException ex) { var exceptionEntry = ex.Entries.Single(); var clientValues = (Book)exceptionEntry.Entity; var databaseEntry = exceptionEntry.GetDatabaseValues(); if (databaseEntry == null) { ModelState.AddModelError(string.Empty, "保存失敗!.當前用戶信息已經被刪除"); return Page(); } var dbValues = (Book)databaseEntry.ToObject(); setDbErrorMessage(dbValues, clientValues, _context); //用資料庫中的 RowVersion 值設置為當前實體對象客戶端界面中的RowVersion值。 用戶下次單擊“保存”時,將僅捕獲最後一次顯示編輯頁後發生的併發錯誤。 Book.RowVersion = (byte[])dbValues.RowVersion; //ModelState 具有舊的 RowVersion 值,因此需使用 ModelState.Remove 語句。 在 Razor 頁面中,
//當兩者都存在時,欄位的 ModelState 值優於模型屬性值。 ModelState.Remove("Book.RowVersion"); } } return Page(); } private PageResult HandDeleteBook() { Book deletedDepartment = new Book(); ModelState.AddModelError(string.Empty, "保存失敗!.當前書籍信息已經被刪除!"); return Page(); }
6.在Edit.cshtml.cs文件,添加setDbErrorMessage方法。為每列添加自定義錯誤消息,當這些列中的資料庫值與客戶端界面上的值不同時,給出相應的錯誤信息。代碼如下:
private void setDbErrorMessage(Book dbValues, Book clientValues, BookContext context) { if (dbValues.Name != clientValues.Name) { ModelState.AddModelError("Book.Name", $"資料庫值: {dbValues.Name}"); } if (dbValues.Publishing != clientValues.Publishing) { ModelState.AddModelError("Book.Publishing", $"資料庫值: {dbValues.Publishing}"); } if (dbValues.ReleaseDate != clientValues.ReleaseDate) { ModelState.AddModelError("Book.ReleaseDate", $"資料庫值: {dbValues.ReleaseDate}"); } if (dbValues.Price != clientValues.Price) { ModelState.AddModelError("Book.Price", $"資料庫值: {dbValues.Price}"); } ModelState.AddModelError(string.Empty,"您嘗試編輯的書籍信息記錄被另一個用戶修改了。編輯操作被取消," + "資料庫中的當前值已經顯示。如果仍想編輯此記錄,請單擊“保存”按鈕。"); }
7.在Visual Studio 2017的“解決方案資源管理器”中使用滑鼠左鍵雙擊打開 Pages/Books/Edit.cshtml文件, <form method="post">標簽下麵添加添加隱藏的行版本。必須添加 RowVersion,以便回發綁定值。
<input type="hidden" asp-for="Book.RowVersion" />
8.在Visual Studio 2017中按F5運行應用程式。使用兩個瀏覽器打開同一條書籍信息記錄進行編輯,此時兩個瀏覽器顯示的書籍信息是一樣的。瀏覽器1中的書籍信息界面。在修改了“Publishing”的數據由“清華大學出版社”修改為“機械工業出版社”,然後點擊“Save”按鈕。如下圖。
9.在瀏覽器中單擊“保存”之後,瀏覽器會自動跳轉到書籍信息列表頁面中看到了所修改的“Publishing”數據保存到了資料庫。如下圖。
10.在第二個瀏覽器中,修改“出版日期”的值,由“2018-01-13”改為“2018-01-08”。如下圖。
11.然後使用單擊“ Save”按鈕。此時由於客戶端界面上的信息與資料庫中的值不一樣,所以會出現錯誤提示信息。如下圖。
12. 把“Publishing”修改為“機械工業出版社”,再次單擊“保存”,將第二個瀏覽器中輸入的值保存到資料庫。 瀏覽器自動跳轉到書籍信息列表,可以看到保存的值。如下圖。
13.當然如果你不做任何修改,再次點擊保存,也會把當前頁面上的數據保存到資料庫中。如下圖。