學習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 編程系列十六——排序

 學習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.當然如果你不做任何修改,再次點擊保存,也會把當前頁面上的數據保存到數據庫中。如下圖。

 

 


免責聲明!

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



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