0%

[DotnetCore]ORM系列-SqlSugar:共同欄位新增、編輯技巧

前提情要

筆者的慣例,使用ORM框架時,會先找一下可以擴充的點,畢竟SelectInsertUpdate這種用到時,查看一下官方文件是可以馬上得到答案的,且用法都大同小異,擴充點好不好,適不適合自己的使用情境,對筆者來說選擇要不要使用這個ORM框架的主要考量原因之一,當然會看一下網路上大神們的文章,效能比較表,筆者會因為語法簡單好用,擴充點又符合自己的使用情境,犧牲一下效能,對筆者來說是ok的,因為比較效能的時間單位都是ns級的,基本上不要差太多,是沒什麼感覺的。這篇主要是講解SqlSugar套件,在操作共同欄位,像CreatedAtCreatedByUpdatedAtUpdatedBy這種,找到對應的擴充點,做統一處理。

內容

SqlSugar本身有提供AOP機制,透過相關事件,很方便地紀錄執行過程的Log,筆者在[DotnetCore]ORM系列-SqlSugar:AOP篇有詳細說明,SqlSugar另有提供DataExecuting事件,讓我們可以很方便地去修改其對應的Model值,以筆者實作過SqlSugarAOP機制,覺得也可以使用OnExecutingChangeSql事件中變更其parameter的值,但就沒這麼方便,SqlSugar5.0.3.5版本開始提供其DataExecuting事件,更方便地撰寫統一處裡的欄位值

資料過濾器

跟著筆者實際動手寫吧,先看一下結構語法

1
2
3
4
db.Aop.DataExecuting = (oldValue, entityInfo) =>
{
// 寫操作邏輯
};

撰寫程式前,需釐清一下其邏輯,以筆者的情境來說CreatedAtCreatedByUpdatedAtUpdatedBy這四個欄位,依照操作Type為InsertUpdate而設定欄位不一樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
db.Aop.DataExecuting = (oldValue, entityInfo) =>
{
// 寫操作邏輯
switch (entityInfo.OperationType)
{
case DataFilterType.InsertByObject:
switch (entityInfo.PropertyName)
{
case "CreatedAt":
entityInfo.SetValue(DateTime.Now);
break;
case "CreatedBy":
// 取得登入使用者
break;
case "UpdatedAt":
entityInfo.SetValue(DateTime.Now);
break;
case "UpdatedBy":
// 取得登入使用者
break;
}
break;
case DataFilterType.UpdateByObject:
switch (entityInfo.PropertyName)
{
case "UpdatedAt":
entityInfo.SetValue(DateTime.Now);
break;
case "UpdatedBy":
// 取得登入使用者
break;
}
break;
}
};

實際執行

建立資料表

為實作,必須將筆者使用的MySql範例資料庫:classicmodels,加一個含有這些共同欄位的資料表,就索性命名為demo資料表吧,結構如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
///<summary>
///
///</summary>
[SugarTable("demo")]
public partial class demo
{
public demo(){

}
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
[SugarColumn(IsPrimaryKey=true,IsIdentity=true)]
public long Id {get;set;}

/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public string Name {get;set;}

/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public DateTime CreatedAt {get;set;}

/// <summary>
/// Desc:
/// Default:
/// Nullable:True
/// </summary>
public string CreatedBy {get;set;}

/// <summary>
/// Desc:
/// Default:
/// Nullable:True
/// </summary>
public DateTime? UpdatedAt {get;set;}

/// <summary>
/// Desc:
/// Default:
/// Nullable:True
/// </summary>
public string UpdatedBy {get;set;}

}

安裝測試套件

筆者這邊為方便,安裝一套可以隨機產生名字的套件,以利demo

1
dotnet add package RandomNameGeneratorLibrary

語法就照套件github上的教學照刻即可

1
2
var _personGenerator = new PersonNameGenerator();
var name = _personGenerator.GenerateRandomFirstAndLastName();

實作新增作業

  • 撰寫Action

筆者為示範成功更新Created、Updated相關欄位,使用Insert語法,筆者會另外寫一篇介紹SqlSugarInsertUpdate語法,目前就先將就看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// <summary>
/// 新增一筆demo資料
/// </summary>
/// <returns></returns>
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[HttpPost("insert")]
public IActionResult InsertDemo()
{
var _personGenerator = new PersonNameGenerator();
var name = _personGenerator.GenerateRandomFirstAndLastName();
_db.Insertable<Sugar.demo>(new Sugar.demo()
{
Name = name
}).ExecuteCommand();
return Ok(name);
}

程式碼前兩行就是用套件產生隨機取得的名字,將取得的名字當作demo.Name的值塞進demo資料表,以SqlSugar來說要執行Insert語法時,呼叫ExecuteCommand,其實另外還有不同的Execute方法,留給另一篇說明,最後回傳隨機產生的名字。

  • Postman測試

寫完Action相關語法後,跟著筆者使用Postman做一下測試吧

  • 資料庫結果

看一下資料庫中的結果

筆者這邊使用DBeaver的介面當作結果呈現一部份,可以看到Insert語法中筆者只塞了一個Name屬性值,CreatedAtUpdatedAt的值由AOP中的資料過濾器做掉了,有寫入成功。

實作更新作業

  • 撰寫Action

筆者為示範Updated相關欄位更新,實作一組Update作業

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// <summary>
/// 更新客戶名稱
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[HttpPost("update/{id}")]
public IActionResult UpdateDemo(long id)
{
var _personGenerator = new PersonNameGenerator();
var name = _personGenerator.GenerateRandomFirstAndLastName();
var demoModel = _db.Queryable<Sugar.demo>().First(x => x.Id == id);
demoModel.Name = name;
_db.Updateable(demoModel).ExecuteCommand();
return Ok(name);
}

大概邏輯就是先依照Id值找到對應的資料,將Name屬性值使用新的隨機產生名字取代,使用SqlSugarUpdate語法做更新,觀察一下UpdatedAt欄位是否被更新。首先再確認一下新增成功後的欄位值

Insert成功欄位值

欄位名稱
Id 3
Name Nga Prager
CreatedAt 2021-11-15 10:05:43
UpdatedAt 2021-11-15 10:05:43
  • Postman測試

上述新增成功的資料Id值為3,使用的Url為http://localhost:5000/api/Customer/update/3

  • 資料庫結果

檢視一下新增,更新欄位值對照表

Insert、Update成功欄位值

欄位名稱 新增值 更新值
Id 3 3
Name Nga Prager TonyYazdani
CreatedAt 2021-11-15 10:05:43 2021-11-15 10:05:43
UpdatedAt 2021-11-15 10:05:43 2021-11-15 10:16:39

有成功更新其UpdatedAt欄位值,表示筆者宣告的AOP資料過濾器是有效的。

SqlSugar的AOP 生命週期檢視

筆者這邊想要花點時間實驗一下,以[DotnetCore]ORM系列-SqlSugar:AOP篇的那些事件以及此主題中使用到的資料過濾器的執行先後順序,所謂知己知彼,百戰百勝,花點時間理解一下期執行順序,才不會撰寫的與執行的結果不符而造成錯誤的結果,筆者在DataExecuting事件的最上面加以下這行

1
2
_logger.Info($@"[DataExecuting:OperateType]:{entityInfo.OperationType.ToString()};
[DataExecuting:PropertyName]:{entityInfo.PropertyName}");

筆者這邊使用Update作業當作觀察的依據,使用Postman重複打update url,觀看一下執行順序吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Select作業
2021-11-15 10:31:01.5895 INFO [OnExecutingChangeSql]SELECT `Id`,`Name`,`CreatedAt`,`CreatedBy`,`UpdatedAt`,`UpdatedBy` FROM `demo` WHERE ( `Id` = @Id0 ) ORDER BY NOW() LIMIT 0,1
2021-11-15 10:31:01.5936 INFO [OnExecutingChangeSql]@Id0:3
2021-11-15 10:31:01.5936 INFO [OnLogExecuting]Executing SQL: SELECT `Id`,`Name`,`CreatedAt`,`CreatedBy`,`UpdatedAt`,`UpdatedBy` FROM `demo` WHERE ( `Id` = @Id0 ) ORDER BY NOW() LIMIT 0,1
2021-11-15 10:31:01.6522 INFO [OnLogExecuted]Executed Time:00:00:00.0581930
// Update作業
2021-11-15 10:31:01.6553 INFO [DataExecuting:OperateType]:UpdateByObject;
[DataExecuting:PropertyName]:Id
2021-11-15 10:31:01.6555 INFO [DataExecuting:OperateType]:UpdateByObject;
[DataExecuting:PropertyName]:Name
2021-11-15 10:31:01.6555 INFO [DataExecuting:OperateType]:UpdateByObject;
[DataExecuting:PropertyName]:CreatedAt
2021-11-15 10:31:01.6555 INFO [DataExecuting:OperateType]:UpdateByObject;
[DataExecuting:PropertyName]:CreatedBy
2021-11-15 10:31:01.6555 INFO [DataExecuting:OperateType]:UpdateByObject;
[DataExecuting:PropertyName]:UpdatedAt
2021-11-15 10:31:01.6555 INFO [DataExecuting:OperateType]:UpdateByObject;
[DataExecuting:PropertyName]:UpdatedBy
2021-11-15 10:31:01.6588 INFO [OnExecutingChangeSql]UPDATE `demo` SET
`Name`=@Name,`CreatedAt`=@CreatedAt,`CreatedBy`=@CreatedBy,`UpdatedAt`=@UpdatedAt,`UpdatedBy`=@UpdatedBy WHERE `Id`=@Id
2021-11-15 10:31:01.6588 INFO [OnExecutingChangeSql]@Id:3
2021-11-15 10:31:01.6588 INFO [OnExecutingChangeSql]@Name:Sachiko Ellery
2021-11-15 10:31:01.6594 INFO [OnExecutingChangeSql]@CreatedAt:11/15/2021 10:05:43
2021-11-15 10:31:01.6594 INFO [OnExecutingChangeSql]@CreatedBy:
2021-11-15 10:31:01.6594 INFO [OnExecutingChangeSql]@UpdatedAt:11/15/2021 10:31:01
2021-11-15 10:31:01.6594 INFO [OnExecutingChangeSql]@UpdatedBy:
2021-11-15 10:31:01.6594 INFO [OnLogExecuting]Executing SQL: UPDATE `demo` SET
`Name`=@Name,`CreatedAt`=@CreatedAt,`CreatedBy`=@CreatedBy,`UpdatedAt`=@UpdatedAt,`UpdatedBy`=@UpdatedBy WHERE `Id`=@Id
2021-11-15 10:31:01.6631 INFO [OnLogExecuted]Executed Time:00:00:00.0033790

SelectUpdate作業綜合來看,可以整理出以下重點

  • Select作業時不會經過DataExecuting事件
  • Update作業的事件順序為
    • DataExecuting
    • OnExecutingChangeSql
    • OnLogExecuting
    • OnLogExecuted

以結果來看,算滿符合預期的結果,這樣大概就知道若要做什麼事情,找到對的事件做實作邏輯的宣告,才不會亂了套。

結論

筆者經由實驗,以編輯共同欄位時,透過DataExecuting事件是綽綽有餘的,除了筆者此主題中實驗的CreatedUpdated相關欄位之外,或許可能有其他應用,筆者若有新的應用也會再放上來。另,SqlSugar教學文件中提供Repository Pattern的教學,筆者再找時間利用Repository Pattern重做一次此篇主題,看看其差異。

參考