2024年3月24日 星期日

DbServices SDK開發 — 3 (unit test)

上一篇講解了測試專案的架構,本篇開始逐一講解測試函數的內容,進而理解SDK是如何被使用。

DCL

這邊所說的DCL與一般定義不同,非CRUD類的都會放在這兒。

取回所有資料表


利用GetAllTableNames()來取回目前資料庫中所有資料表(含view,不要含view則參數設false)。
[TestMethod()]
public virtual void GetAllTableNamesTest()
{
string[]? allTableName = _db.GetAllTableNames();
Assert.IsNotNull(allTableName);
Assert.IsTrue(allTableName.Length > 0);
Console.WriteLine($"All Table Name: {string.Join("\r\n", allTableName)}");
}


測試結果:


設定資料表

這個目的是告訴DbService 現在要針對那個資料表進行處理。其實,所有的函數都可以接受資料表名稱參數,只是設定後,就可以不用輸入。

使用SetCurrentTableName(string tableName)可透過字串設定目標資料表。GetCurrentTableName()可以確認目前是否有設定好目標資料表。
[TestMethod()]
public virtual void SetCurrentTableNameTest()
{
_db.SetCurrentTableName("PersonTable");
Assert.AreEqual("PersonTable", _db.GetCurrentTableName());
Console.WriteLine($"Current Table: {_db.GetCurrentTableName()}");
}

測試結果:


使用SetCurrentTable<T>()則可在已知Modle下,設定目標資料表。
[TestMethod()]
public virtual void SetCurrentTableTest()
{
_db.SetCurrentTable<PersonTable>();
Assert.AreEqual("PersonTable", _db.GetCurrentTableName());
Console.WriteLine($"Current Table: {_db.GetCurrentTableName()}");
}

測試結果:


取回資料表欄位定義


使用GetFieldsByTableName(string tableName)可以取回此資料表的所有資訊。回傳型態是IEnumerable<FieldBaseModel>。FieldBaseModel的結構後續再討論。
[TestMethod()]
public virtual void GetFieldsByTableNameTest()
{

var result = _db.GetFieldsByTableName("PersonTable");
Assert.IsNotNull(result);
Assert.IsTrue(result.Any());

var nameList = from item in result
select item.FieldName;
Console.WriteLine($"Fields:\r\n{string.Join("\r\n", nameList)}");
}


測試結果:

DbServices SDK開發 — 2 (Unit Test)

SDK的使用可分成DB First與Model First兩種方式。DB First如上篇所展示,資料庫已有資料表的情形下,透過SDK來存取資料庫。Model First則是先定義好Model後產生資料表,接著運用泛型機制對資料庫進行存取。本篇從Unit Test角度來看這些SDK要如何使用。
測試總管

本系列文章不是基礎教學文件,基本東西就不說明了。下圖是Visual Studio的測試總管畫面。總計18單元測試項目(可能還會增加),是針對SDK所提供的函數進行測試。

測試專案


雖然現階段測試僅針對SQLite(TesterForSqlite.cs),但實際上所提供的服務函數各家資料庫都是一樣的(DataBaseServiceTests.cs),主要差別是在SQL產生器的部分。Models目錄就是定義好的模型。


模型定義


所有資料表類別,都需要繼承TableDefBaseModel類別。只要單純宣告此類別所擁有的屬性即可。PersonTable類別為例。 
public class PersonTable : TableDefBaseModel
{
public string? Name { get; set; }
public int Age { get; set; }

}
TableDefBasedModel類別由資料表類別來繼承,提供幾個重要的共通欄位。
public abstract class TableDefBaseModel
{
protected TableDefBaseModel(string? userName = null)
{
CreatedDate ??= DateTime.UtcNow;
LastModifyDate = DateTime.UtcNow;
CreatorName = userName;

}

[Key]
[Required]
public long Id { get; set; }
[Required]
public DateTime? CreatedDate { get; set; }
public DateTime? LastModifyDate { get; set; }
public string? CreatorName { get; set; }
}

為了後續各項服務能方便使用,補了一個Extension Function。至於用途如何,在講解測試函數時便可知。public static class TableDefBaseModelExtension
public static class TableDefBaseModelExtension
{
public static IEnumerable<KeyValuePair<string, object?>> GetKeyValuePairs(this TableDefBaseModel source, bool withKey = false)
{
var properties = source.GetType().GetProperties();
List<KeyValuePair<string, object?>> target = [];
foreach (var p in properties)
{
if (p.Name == "Id" && !withKey) continue;
target.Add(new(p.Name, p.GetValue(source)));
}
return target;
}
}

測試類別

[TestClass]
public class TesterForSqlite: DataBaseServiceTests
{
private const string connString = @"Data Source=C:\temp\Test.db;";
public TesterForSqlite() : base(MainService.UseSQLite(connString))
{
}
}
針對不同的資料庫提供者,建立不同測試類別。測試類別都繼承DataBaseServiceTests,因為所需要測試的函數幾乎一樣,這邊只需要定義資料庫連線字串,以及告知父類別這個測試類別是真的那個資料庫提供者。

測試父類別

此類別是主要實做區。依據SQL的功能,將測試項目分成DCL、DDL、DQL與DML各大項目。

結語

避免文章篇幅過大,先介紹於此。下一篇則針對測試函數內容逐一說明。

2024年3月18日 星期一

DbServices SDK開發 — 1 (Web API)

在某次的系統整合案中,資料提供端什麼也不想做,只願意開放資料庫存取功能。受限於專案時程,最終方案就是在資料庫外架設一個Web API服務。只要設定好資料庫連線字串,透過這個服務,即可得知資料庫的資料表,然後進行CRUD作業。

這個Web API的背後,其實是一個資料庫存取服務器(DbServices,也就是本系列重點),主要採用DotNet 8、C#與Dapper 技術。由於Dapper需要自行撰寫SQL,也就演變成開發SQL產生器。由於各家資料庫廠商支援的SQL有共通標準,也會有私有指令,所以在支援服務層級架構上,就得稍做設計。 目前支援MS-SQL與SQLite,Oracle與MySQL有提供,但沒有仔細測試。至於其他的資料庫,只要補上自己特殊的語法即可。

DbServices 需求情境是企業內部使用,所以並沒有建置授權認證機制。本系列文章,僅是開發心得分享,並不足以當作學習教材,程式仍有許多改善空間。

使用情境

Web API是應用DbServices的其中一種,這種情境下前端就比較不受限。另一種情境是直接使用SDK,各項支援功能就會比較複雜多樣。這邊先以Web API來簡易說明。測試環境是使用SQLite。

在設定好資料庫連線字串後,執行專案即可得到Swagger介面。


取得資料表清單

首先使用GET /meta服務,來取得此資料庫所有資料表。SDK可設定是否需要包含View。


取得資料表內容

使用GET /{resourceName}/fieldSet服務,輸入資料表名稱,就可以得知此資料表的所有資訊。

圖中為例,PersonTable總共有六個欄位,詳情參照FieldDefine。有3筆記錄。


取回欄位可能值

有時候可能需要先知道某資料表的某欄位可能包含哪些值,例如要處理列舉型態。則可使用GET /{resourceName}/valueSet這個服務。輸入資料表與欄位名稱即可。


取回資料表所有紀錄

使用GET /{resourceName}服務,在沒有打查詢條件時,則會取回所有紀錄。

DbService SDK並沒有提供複雜的Order By等機制,基本上概念是配合前端Data Grid來使用。這方面的元件選擇性多元化。


依條件取回記錄

同樣使用GET /{resourceName}服務,可以再加上查詢條件。SDK還有提供其他機制,例如補上opertor,多個Key-Value,直接補上where子句內容。


取回特定紀錄

使用GET /{resourceName}/{id}則可利用id取回特定記錄。


新增記錄

使用POST /{resourceName}服務,輸入資料表名稱以及在Body補上JSON內容,即可新增一筆資料。


驗證看看。確實name=ZZ這筆已經存進去了。


更新紀錄

使用PUT /{resourceName}/{id}可以用來更新每特定記錄。


驗證看看,確實id=4這筆資料已經被更新了。


刪除記錄


使用DELETE /{resourceName}/{id}刪除特定記錄。

結語

此處僅是簡單運用Web API功能,但已經足以滿足整合案中所需。Web API僅是資料服務,流程邏輯處理還是需要前端。

而這個Web API背後所用到DbService是用來簡化資料庫存取機制。只要告知是什麼資料庫,一換資料庫連線字串,立刻可以操作資料表。在實務上,確實會發生資料提供端得同時面對多種資料庫來源。

後續就慢慢來介紹DbService SDK的設計理念與應用。

2023年8月26日 星期六

[生成式AI] Google Bard與Bing問相同問題

題目:請推薦轉換JSON Schema 至 Protobuf的工具

Bing的回答



Google Bard的回答



討論:

1. G的內容,並不提供連結。B有。

2. G推薦需要自行網路上找,但可能會沒有這個東西。
3. B給的還算有意義,但實用性也不強。
4. 或許給的要求目標,整體環境還沒有很成熟。


2013年11月3日 星期日

Umbraco的Tag實作

現在網頁流行使用Tag,標籤。
在Umbraco中使用標籤的技術只要搞懂了就很簡單。一開始,倒是花了不少時間在測試。
幾個觀念得先記下來。
  1. Tags資料型態:Umbraco 有提供預設資料型態。但不建議直接使用,除非你整個網站都是用相同的標籤系統。例如說,部落格有自己的標籤,FAQ要有自己的標籤,那就得個別建自定資料型態。
  2. Tag group:延續前面,如果要有個別的標籤系統,那就分別為其定義群組名稱。注意,這個在程式時很重要。
在資料型態定義的地方完成上述工作後。接著當然是定義Document Type,把某個欄位使用這個資料型態。
如果是在backoffice的話,那就可以直接使用了。

如果是程式的話就得小心。其實也很簡單。
要呈現時,就將欄位物件轉成字串,再用spli(',')切開就得到一個一個的tag,接著就是看你要怎麼呈現了。

如果是要新增怎麼辦?
首先就是要搞清楚是用哪一個Tag group。
再來使用
Tag.AddTagsToNode(contentID, Tags, TagGroup)
他的觀念是把tag與content連結在一起。
如果你前台處理時,tags是用,結合的字串,就可以直接使用。

若是要更新的話,我目前作法是有點笨,先移除現在,再新增新的。
移除的話是
Tag.RemoveTagsFromNode(contentID, TagGroup)


當然,Unbraco的Package有一個Tag Manager可以安裝使用,協助你管理Tag。


2013年6月30日 星期日

實驗設計-11

上回提到 ,其實,還有一點小關卡需要再突破一下。

上面的式子講的是母體。
事實上我們進行實驗,隨機抽樣取得數據,我們怎能這麼大膽的說他代表各別水準母體的平均數呢?
所以,我們也只能透過現有收集到的數據資料進行推估。
這部份又是統計一個很重要應用,點估計、區間估計等。依據樣本資料你能多大膽地說母體就是多少。
可是,實驗設計強調的是處理誤差,用來評估你的實踐結果如何,可不可信。

回到主體。
我們對於各別水準也只能用現有的抽樣資料來進行評估。事實上,我們的數學式子只能這麼寫著。

是否還記得,透過誤差來進行相對性比較時,會發生抵消的問題。我們不是改用的平方差嗎?
我們稱之為變異數,是吧。

現在,只不過是改成有個水準下,各別進行了次的隨機抽樣數。用一個式子就把它表達出來。所以,不要開始被這些式子給嚇到了。

(3.1.2)
  

他的意思是說,當我對各水準母體變異數的估計值,其實就是等於各水準樣本變異數。

這別各水準樣本變異數的計算方式,他自己那個水準下,從第1個抽樣數據開始到第n 個,個別去做減去該水準之樣本平均數的平方,再累加起來。然後最後再除以n-1。n是代表我們總共抽了幾個,j是足標,代表目前是指到第幾個。

喔~又回到母體的問題。
整個實驗的誤差估計值,也就可以這樣子表示:
(3.1.4)


還是用資料來表示吧。延續使用上次盒鬚圖所用的資料。



樣本平均數的符號

樣本變異數的符號

    

再改個表達方式。也就是對各別水準母體的估計值。




整體變異數的估計值




另外還有個東西也要順便提一下,就是殘差。本篇一開始講的式子,稍微移動一下。


思考邏輯都一樣,這個是指母體。我們又只能透過樣本來進行估計。
(3.1.5)


這時候,回去看看(3.1.3)與(3.1.4),把它合併起來,並且用(3.1.5)取代,就可以得到下列式子。
(3.1.6)

這算是到目前為止最複雜的式子了。
下表就是加上殘差結果。


現在看到這一推數字,真的先不要太緊張或太在意。之前一直強調,我們是透過隨機抽樣的過程,以取得之樣本資料來推估我們無法一眼看透的母體。需要猜測的母體,可能隱含著某種函數特性,或許他真的是2x+1,只不過他不是老師出的題目,他會明明白白告訴你。而這些都是你遭遇的問題。因為你不是上帝,真的無法篤定說出他背後影藏的函數特性。
既然是猜測、是推估,就會衍生很多你說的對不對的問題,就會衍生出一堆來驗證你對不對的方法。
這些東西總得要有依據、要能計算。而樣本平均數、樣本變異數等,就是最基本的材料了。
由於是實驗的過程,你所關心的因素可能會產生相互影響,這又會觸發一些新的素材出來(目前這部份還沒討論)
後面數學式子會越來越多,越來越抽象。但也希望能一步一步抽絲剝繭。

實驗設計-10

實驗結果模型

數學與統計到底是不是同一件事情。當然不是。就像數學與物理到底是不是同一件事情,是一樣的道理。

數學式子是這樣子表達:
這個式子是說,有一個函數,當我們投入x會得到y。但是,現在這個函數,還沒有告訴你,所以,你不知道要怎麼計算。
如果跟你說
當x=1時,y等於多少?這時候,你應該會說選我選我,因為你知道答案。而且,隨便給你x多少,你都可以回答。
x     y
=====
-2   -3
-1   -1
 0    0
 1    3
 2    5
其實,他是由已知x來取得y。
其實,他就是一條斜率為2,向右平移1的一條直線。
其實,只要有兩點,我們就可以畫出一直線了。

回到統計的觀念。
因為他可以代表所有可能的情形,所以,我們可以稱就是母體。

問題就是這個函數(母體)長什麼樣子我們不知道。他是一個黑盒子。(數學的式子是白盒子)
    
而統計式子是這樣表達:
這個就是誤差。而就是我們要透過樣本資料去推估的母體。
所以,y才是我們的已知,而函數(母體)長什麼樣子我們都不知道,你的x根本英雄無用武之地。
這下子,y不是我們算來的,而是我們在隨機抽樣的過程中,藉由實驗、量測等方式取得之數據。
可能我們收集到的資料就會變成:
   y
====
 -3.1
 -0.9
  0.1
  3.0
  4.7

你可以想像,就好像有一個圖片,當你zoom in(放大時),每個點的距離就可以看得很清楚;當你zoom out(縮小時),每個點的距離就會變小,甚至看起來像一直線。
是得,這就是你可以忍受的誤差範圍。我們的式子
就可以改寫成 

如果,我把每個 都設成0時,不就回到原始數學式子了。可惜,這個 在統計來說是必要之惡。

再拉回簡單的問題。還好,我們的 這個黑盒子,目前不是一條直線。而是向靶紙一樣,只是一中心點而已。
也就是~~~平均數。

這時候我們就開始改用統計的習慣用法吧。假設進行了次隨機抽樣所得之數據,每個觀察數據就可以用下列公式表達。
   
也就是每一個觀察值,就是平均數再加上他自己的誤差。

那如果是單因子分析,假設有個水準(處理別)
各別水準進行了次的隨機抽樣數。 那所有的數據資料就可以這樣式子表達:



現在,看到這樣子的式子應該不會心慌慌吧。