星期一, 6月 26, 2023

[SQL] Trigger - insert 觸發

該 論壇問題 討論 insert、update、delete 時 Trigger 觸發情況 (單筆、多筆),對於 insert 觸發情況沒有很肯定,驗證並筆記

建立測試環境
USE TempDB
GO

DROP TABLE IF EXISTS tblTrigger
DROP TABLE IF EXISTS tblLog
DROP TRIGGER IF EXISTS triInsert

CREATE TABLE tblTrigger (ID int identity(1,1) Primary key , InsertTime datetime)
CREATE TABLE tblLog (ID int , InsertTime datetime)
GO

CREATE TRIGGER triInsert
   ON tblTrigger
   AFTER INSERT -- 限定 insert 時觸發
AS 
    BEGIN

        INSERT INTO tblLog (ID , InsertTime)
        SELECT ID , GETDATE()
        FROM inserted

    END
GO
驗證方式

透過 SQL Profile 來觀察 Trigger 觸發情況,請參考該篇筆記開啟 - [SQL] SQL Profile 側錄 Trigger,下述測試案例圖片,紅框代表批次 ( batch )、綠框代表 Trigger 觸發

測試案例 1 - 基礎 insert

測試語法
DECLARE @now datetime = getdate()
INSERT INTO tblTrigger VALUES(@now)
INSERT INTO tblTrigger VALUES(@now)
INSERT INTO tblTrigger VALUES(@now)
測試結果:一個 batch 內 Trigger 被觸發三次,每次觸發都 insert 一筆資料

[SQL] Trigger - insert 觸發-1

測試案例 2 - GO 拆分批次

測試語法
DECLARE @now datetime = getdate()
INSERT INTO tblTrigger (InsertTime) VALUES(@now)
GO

DECLARE @now datetime = getdate()
INSERT INTO tblTrigger VALUES(@now)
INSERT INTO tblTrigger VALUES(@now)
GO
測試結果:GO 會拆分 batch,所以第一個 batch 內會觸發 Trigger 一次,第二個 batch 內會觸發 Trigger 兩次,每次觸發都 insert 一筆資料

[SQL] Trigger - insert 觸發-2

測試案例 3 - GO 重覆執行

測試語法
DECLARE @now datetime = getdate()
INSERT INTO tblTrigger (InsertTime) VALUES(@now)
GO 3
測試結果:GO 數字會重覆執行 insert,所以是三個 batch 內各含一次觸發 Trigger,每次觸發都 insert 一筆資料

[SQL] Trigger - insert 觸發-3

測試案例 4 - 資料表值建構函式
ˇ
測試語法
DECLARE @now datetime = getdate()
INSERT INTO tblTrigger (InsertTime) VALUES
	(@now) , (@now) , (@now)
GO
測試結果:一個 batch 內觸發一次 Trigger,該觸發會 insert 多筆資料

[SQL] Trigger - insert 觸發-4

測試案例 5 - insert select 

測試語法
DECLARE @now datetime = getdate()
INSERT INTO tblTrigger (InsertTime) 
SELECT @now
FROM tblTrigger
WHERE ID BETWEEN 22 AND 25
測試結果:一個 batch 內觸發一次 Trigger,該觸發會 insert 多筆資料

[SQL] Trigger - insert 觸發-5

星期六, 6月 24, 2023

[SQL] SQL Profile 側錄 Trigger

要使用 SQL Profile 側錄 Trigger 執行的 TSQL 語法,要特別開啟
  • SP:StmtStarting
  • SP:StmtCompleted
這兩個事件,開啟步驟如下

[SQL] SQL Profile 側錄 Trigger-1

實際開啟側錄,在下圖中就可以看見 Trigger 中特殊 table - inserted 

[SQL] SQL Profile 側錄 Trigger-2

星期三, 6月 21, 2023

[SQL] 累計加總應用 - 金額拆帳

在論壇上看見的問題 - SQL計算金額問題,題意如下圖

[SQL] 累計加總應用 - 金額拆帳

個人解法如下,論壇上有大神提供解法,相較下大神是比較優雅點,但核心精神都是透過 SUM() OVER 來進行專案金額累計後,再來進行數學運算
SELECT
  T3.* ,
  專案金額 - 已收金額 AS 未收款
FROM
  (
    SELECT
      T2.* ,
      CASE	
        WHEN 專案累積金額 <= 已收總金額 THEN 專案金額
        WHEN 專案累積金額 > 已收總金額 AND 已收總金額 - 前一筆累計金額 > 0 THEN 已收總金額 - 前一筆累計金額
        ELSE 0
      END AS 已收金額
    FROM
      (
        SELECT
          T.* ,
          LAG(專案累積金額 , 1 , 0) OVER (PARTITION BY 結帳單 ORDER BY 專案) AS 前一筆累計金額
        FROM	
          (
            SELECT 
              * ,
              SUM(專案金額) OVER (PARTITION BY 結帳單 ORDER BY 專案 ROWS UNBOUNDED PRECEDING) AS 專案累積金額
            FROM TestTable
          ) AS T
      ) AS T2
  ) AS T3
ORDER BY 結帳單, 專案

星期四, 6月 15, 2023

[C#] 提供資料行已經屬於 DataGridView 控制項

要把 DataGridViewColumn 塞進 DataGridView.Columns 內時收到錯誤訊息 - [提供資料行已經屬於 DataGridView 控制項],該錯誤直覺是 DataGridView.Columns 內假如該欄位已經存在,拋出該錯誤訊息合理,但遇上情況是該 DataGridView 和 DataGridViewColumn 是反覆 new 出來,要把 DataGridViewColumn 加入 DataGridView.Columns 時拋出該錯誤

錯誤訊息

[C#] 提供資料行已經屬於 DataGridView 控制項-1

從中斷點內可以發現到 DataGridView.Columns 內目前完全沒有存在任何欄位資訊

[C#] 提供資料行已經屬於 DataGridView 控制項-2

發現 Code 在建立 DataGridViewColumn 時,因為該欄位資訊也會應用在其他地方,所以把該欄位拉出來為 private property 暫存,該 DataGridViewColumn 變成重覆加入 DataGridView 內,範例 Code 如下
private DataGridViewTextBoxColumn _col { get; set; } = new DataGridViewTextBoxColumn() { HeaderText = "文字說明", DataPropertyName = "資料來源" };

private void button1_Click(object sender, EventArgs e)
{
        var dgv1 = new DataGridView();
        dgv1.Columns.Add(_col);

        // 該 DataGridViewTextBoxColumn 重覆加入 DataGridView 內
        var dgv2 = new DataGridView();
        dgv2.Columns.Add(_col);
}
從中斷點內確認 DataGridViewColumn.DataGridView property 在把 DataGridViewColumn 加入 DataGridView 時,就會自動補上該資訊,之後該 DataGridViewColumn 再拿去加入其他 DataGridView 就會拋出 Excpetion,假如透過 DataGridView.Columns.Remove() 或 DataGridView.Columns.Clear() 清除後,該 DataGridViewColumn.DataGridView 會變成 null 就可以再次加入其他 DataGridView

中斷點內觀察 DataGridViewColumn.DataGridView property  變化,原來 property 有變化會有顏色 Highligh 顯示,之前都沒有注意到這點

[C#] 提供資料行已經屬於 DataGridView 控制項-3

基本上就是一個 DataGridViewColumn 只能加入一個 DataGridView 的意思

星期一, 6月 12, 2023

[C#] 文化特性的日曆清單

實務上應用該文章 - [C#] 星期中文說明 內取得星期,在某次跑單元測試時,相關測試竟然全部 fail,研究後發現到原來是 Windows 預設日曆被變更掉

Win10 繁中 (zh-tw) 內有三種日曆可以選擇,預設為西曆 (中文),被我切換至西曆 (英文)

[C#] 文化特性的日曆清單-1

西曆 (中文) VS 西曆 (英文),剛好差異在星期文字顯示

[C#] 文化特性的日曆清單-2

C# CultureInfo zh-tw 有三種日曆清單可以使用,剛好對應 Windows 內可使用日曆,分別為
  • GregorianCalendar (Localized) 為預設值
  • TaiwanCalendar
  • GregorianCalendar (USEnglish)
可以透過 CultureInfo.OptionalCalendars 屬性來取得
using System;
using System.Globalization;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            CultureInfo ci = new CultureInfo("zh-tw");

            // 顯示 zh-tw 內的日曆
            Console.WriteLine("顯示 zh-tw 文化特性日曆清單");
            foreach (Calendar calendar in ci.OptionalCalendars)
            {
                if (calendar.GetType() == typeof(GregorianCalendar))
                {
                    GregorianCalendar gregorianCalendar = (GregorianCalendar)calendar;
                    Console.WriteLine($"     {calendar} ({gregorianCalendar.CalendarType})");
                }
                else
                {
                    Console.WriteLine($"     {calendar}");
                }
            }

            Console.WriteLine("");

            Console.WriteLine("各日曆星期表示");
            // 指定西曆 (中文)
            ci.DateTimeFormat.Calendar = new GregorianCalendar() { CalendarType = GregorianCalendarTypes.Localized };
            Console.WriteLine($"     西曆(中文):{ci.DateTimeFormat.GetDayName(DateTime.Today.DayOfWeek)}");

            // 指定中華民國曆
            ci.DateTimeFormat.Calendar = new TaiwanCalendar();
            Console.WriteLine($"     中華民國曆:{ci.DateTimeFormat.GetDayName(DateTime.Today.DayOfWeek)}");

            // 指定西曆 (英文)
            ci.DateTimeFormat.Calendar = new GregorianCalendar() { CalendarType = GregorianCalendarTypes.USEnglish };
            Console.WriteLine($"     西曆(英文):{ci.DateTimeFormat.GetDayName(DateTime.Today.DayOfWeek)}");
        }
    }
}

[C#] 文化特性的日曆清單-4

zh-tw 的 CultureInfo.DateTimeFormat.Calendar 只接受上述三種日曆,故意塞農民曆 TaiwanLunisolarCalendar 進去,會拋出 ArgmentOutOfRangeException

[C#] 文化特性的日曆清單-3

星期三, 6月 07, 2023

[Win] Microsoft Print to PDF

和同事溝通上有點落差,導致他刪除內建的 [Microsoft Print to PDF] Printer,查了一下要如何恢復,有兩種方式
  • 關閉並重新開啟
  • 新增印表機
關閉並重新開啟

控制台 => 程式和功能 => 開啟或關閉 Windows 功能

[Win10] Microsoft Print to PDF-1

取消 [Microsoft Print to PDF] 後,再勾選安裝

[Win10] Microsoft Print to PDF-2

新增印表機

新增印表機 => 勾選 [以手動設定新增本機印表機或網路印表機]

[Win10] Microsoft Print to PDF-3

勾選 [使用現有的連接埠] 並選擇 [File:(列印至檔案)]

[Win10] Microsoft Print to PDF-4

製造商 Microsoft 並選擇 Microsoft Print to PDF 就行,萬一印表機內沒有出現 Microsoft Print to PDF 的話,點擊 Windows Updates 更新印表機就會安裝進來

[Win10] Microsoft Print to PDF-5

星期四, 6月 01, 2023

[EF] edmx 無法產生 Model class

在 VS V17.6.2 上開啟 WinForm 搭配 .NET Framework4.8.1 並使用 EF 6.4.4 時,發現不論是專案內原有 edmx 還是新建立 edmx,竟然都無法產生 Model class

以下圖來說,正常情況下 edmx 內有 Address 存在,AdventureWorks2019.tt 檔案下應該要產生 Address class 才對

[EF] edmx 無法產生 Model class-1

AdventureWorks2019.Context.cs 檔案內有建立出 DbSet<Address>,但因為 Address class 沒有產生,會有紅色蚯蚓符號

[EF] edmx 無法產生 Model class-2

相關錯誤訊息

[EF] edmx 無法產生 Model class-3

錯誤		正在執行轉換: System.NullReferenceException: 並未將物件參考設定為物件的執行個體。
   於 Microsoft.VisualStudio.TextTemplating548217BD8377E326AEE514EAE722C9BA6586C4151F980CA7BACC70A5732FC55F588FBC8794326CD64388F9B4C50AD7729497DE610EBC8D6F64F48BB130F68063.GeneratedTextTransformation.DynamicTextTransformation.get_GenerationEnvironment()
   於 Microsoft.VisualStudio.TextTemplating548217BD8377E326AEE514EAE722C9BA6586C4151F980CA7BACC70A5732FC55F588FBC8794326CD64388F9B4C50AD7729497DE610EBC8D6F64F48BB130F68063.GeneratedTextTransformation.EntityFrameworkTemplateFileManager..ctor(Object textTransformation)
   於 Microsoft.VisualStudio.TextTemplating548217BD8377E326AEE514EAE722C9BA6586C4151F980CA7BACC70A5732FC55F588FBC8794326CD64388F9B4C50AD7729497DE610EBC8D6F64F48BB130F68063.GeneratedTextTransformation.EntityFrameworkTemplateFileManager.VsEntityFrameworkTemplateFileManager..ctor(Object textTemplating)
   於 Microsoft.VisualStudio.TextTemplating548217BD8377E326AEE514EAE722C9BA6586C4151F980CA7BACC70A5732FC55F588FBC8794326CD64388F9B4C50AD7729497DE610EBC8D6F64F48BB130F68063.GeneratedTextTransformation.EntityFrameworkTemplateFileManager.Create(Object textTransformation)
   於 Microsoft.VisualStudio.TextTemplating548217BD8377E326AEE514EAE722C9BA6586C4151F980CA7BACC70A5732FC55F588FBC8794326CD64388F9B4C50AD7729497DE610EBC8D6F64F48BB130F68063.GeneratedTextTransformation.TransformText()
在網路上找到該篇討論 - Create or recreate a Model does not works any more for winform .net framework solutions 確定問題點在哪,雖然有人提出該問題已經修正,詳見該 commit,但在我的VS IDE 環境內還是出現該問題

根據討論內容,直接修正 EF6.Utility.CS (C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\Extensions\Microsoft\Entity Framework Tools\Templates\Includes\EF6.Utility.CS)

修正後內容
private DynamicTextTransformation(object instance)
{
    _instance = instance;
    Type type = _instance.GetType();
    _write = type.GetMethod("Write", new Type[] { typeof(string) });
    _writeLine = type.GetMethod("WriteLine", new Type[] { typeof(string) });
    _generationEnvironment = type.GetProperty("GenerationEnvironment"); // 修正該行
    _host = type.GetProperty("Host");
    _errors = type.GetProperty("Errors");
}
修正內容為
// 把
_generationEnvironment = type.GetProperty("GenerationEnvironment", BindingFlags.Instance | BindingFlags.NonPublic);
// 修正為
_generationEnvironment = type.GetProperty("GenerationEnvironment");
重開 VS 後,edmx 就恢復正常啦