星期六, 4月 23, 2022

[WebApi Core] Filters - Response Headers 資訊

Filters in ASP.NET Core 內發現 ActionFilter 應用,可以把資訊打在 Header 內回傳,順道了解那些資訊可以透過 ActionExecutingContext 抓出來 

建立 ResponseHeaderAttribute

查資料時發現一個小應用,透過 Stopwatch 來偵測 Action 執行時間,一併紀錄下來
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;

namespace FilterSample
{
    public class ResponseHeaderAttribute : ActionFilterAttribute
    {
        private readonly string _name;
        private readonly string _value;
        private readonly Stopwatch _sw = new Stopwatch();

        public ResponseHeaderAttribute(string name, string value)
        {
            this._name = name;
            this._value = value;
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            _sw.Start();
        }

        public override void OnActionExecuted(ActionExecutedContext context)
        {
            string HttpMethod = context.HttpContext.Request.Method;
            string ControllerName = ((ControllerActionDescriptor)context.ActionDescriptor).ControllerName;
            string ActionName = ((ControllerActionDescriptor)context.ActionDescriptor).ActionName;

            _sw.Stop();
            TimeSpan ts = _sw.Elapsed;
            _sw.Reset();

            context.HttpContext.Response.Headers.Add(nameof(HttpMethod), HttpMethod);
            context.HttpContext.Response.Headers.Add(nameof(ControllerName), ControllerName);
            context.HttpContext.Response.Headers.Add(nameof(ActionName), ActionName);
            context.HttpContext.Response.Headers.Add("RunTime", ts.ToString());
            context.HttpContext.Response.Headers.Add(_name, _value);            
        }
    }
}

套用 ResponseHeadersAttribute

使用 NET 6 WebAPI Core 專案預設 Controller - WeatherForecast,有些 Code 就刪除不顯示啦
using Microsoft.AspNetCore.Mvc;

namespace FilterSample.Controllers
{
    [ApiController]
    [Route("[controller]")]
    [ResponseHeader("ControllerLevel", "Controller")] // 使用 ResponseHeaderAttribute
    public class WeatherForecastController : ControllerBase
    {
        [HttpGet(Name = "GetWeatherForecast")]
        public IEnumerable<WeatherForecast> Get()
        {

        }
    }
}

實際測試

透過 Postman 來進行測試,Header 資訊如下圖

[WebApi Core] Filters - Response Headers 資訊

星期五, 4月 22, 2022

[SQL] 更新最後結案的 100 筆資料

幫同事弄公司內部測試環境,發現範例內工單資料竟然都已經全部結案,直接下語法更新最後結案的 100 筆工單資料。

以前對於 CTE 更新應用,只用過 [SQL] 刪除重覆資料,第一次在 CTE 內使用 TOP + Order By 並更新 Table 資料,紀錄一下
; 
WITH CTE AS
(
	SELECT TOP 100 *
	FROM 工單資料
	ORDER BY 結案時間 DESC
)
UPDATE CTE 
SET 結案時間 = null

星期二, 4月 19, 2022

[SQL] 可更新的 Function

同事分享時提到 iTVF 可以更新資料表資料,MSTVF 不行,當場愣了一下,腦海裡對於 Function 使用,都是應用在 select 相關,壓根沒有想要透過 function 來更新資料,在 User-Defined Functions 內 Valid statements in a function 章節有提到 
UPDATE, INSERT, and DELETE statements modifying table variables that are local to the function. 
利用 AdventureWorks2019 來驗證

建立 iTVF
use AdventureWorks2019
go

CREATE FUNCTION udfGetSalesOrderHeader(@SalesOrderID int)
RETURNS TABLE
AS
	RETURN
	(
		SELECT 
			SOH.SalesOrderID ,
			SOH.OrderDate ,
			SOH.ShipDate ,
			SOH.SalesOrderNumber ,
			SOH.SubTotal ,
			SOH.CustomerID ,
			P.LastName , 
			P.MiddleName , 
			P.FirstName
		FROM [Sales].[SalesOrderHeader] AS SOH
			JOIN [Sales].[Customer] AS C ON SOH.CustomerID = C.CustomerID
			LEFT JOIN Person.Person AS P ON C.PersonID = P.BusinessEntityID
		WHERE SOH.SalesOrderID = @SalesOrderID
	) 
GO
實際測試
-- 資料原日期為 2011-08-08 
SELECT * FROM udfGetSalesOrderHeader(44132)

UPDATE udfGetSalesOrderHeader(44132)
SET ShipDate = DateFromParts(2022,4,18)

-- 更新後日期為 2022-04-18
SELECT * FROM udfGetSalesOrderHeader(44132)

星期日, 4月 17, 2022

[C#] 跨執行序更新控件

公司內透過 Quartz 來進行排程,該排程有更新控件並顯示相關資料需求,參考官方文章 - How to make thread-safe calls to controls (Windows Forms .NET),透過 Invoke 搭配 delegate 來處理,避免發生更新 UI Thread 上控件,會拋出 Exception

官方文件 Code 整理,就不直接操作 thread 囉
using System;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ThreadSafeSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Task.Run(() => 
            {
                WriteTextSafe("This text was set safely.");
            });
        }

        public void WriteTextSafe(string text)
        {
            if (textBox1.InvokeRequired)
            {
                textBox1.Invoke(new Action(() => WriteTextSafe(text)));
            }
            else
            {
                textBox1.Text = text;
            }
        }
    }
}

星期一, 4月 11, 2022

[SQL] 移除叢集索引

和同事討論時對方提到,叢集索引無法用語法刪除,一定要透過 SSMS GUI 來刪除才行,對於這結論有點意外,因為調整 Table Schema 時一直都是用語法刪除叢集索引,了解同事資料來源出處後才意識到,是指被 Primary Key 或 Unique 條件約束限制的索引,無法透過 drop index 語法直接移除,而是必須使過 alter table drop constraint

建立 tblTable 來進行測試
use tempdb
go

DROP TABLE IF EXISTS tblDemo

CREATE TABLE tblDemo 
(
	PKCol int Primary Key ,
	UniqueCol int UNIQUE(UniqueCol)
)
從 SSMS 上觀察 tblTable 的索引鍵和索引資訊

   [SQL] 移除叢集索引-1

利用 drop index 來刪除叢集索引
DROP INDEX [PK__tblDemo__B178A281BD0D9E71] ON tblDemo 
明確的 DROP INDEX 不允許用於索引 'tblDemo.PK__tblDemo__B178A28183FAA431'。它正由 PRIMARY KEY 條件約束強制執行。

利用 drop index 來刪除 unique 非叢集索引
DROP INDEX UQ__tblDemo__3617A0E74BD6978D ON tblDemo
明確的 DROP INDEX 不允許用於索引 'tblDemo.UQ__tblDemo__3617A0E750A99D25'。它正由 UNIQUE KEY 條件約束強制執行。

直接在 SSMS 上刪除叢集索引並透過 SQL Profile 側錄執行語法

[SQL] 移除叢集索引-2

官方文件上範例

星期五, 4月 08, 2022

[SQL] Inline Function

在 SQL Server 2019 (相容性層級 150) 推出 inline function,可以避免 [SQL] 避免在 Select 中使用 Scalar Function 該情況,把之前筆記範例放在 SQL Server 2019 上跑並觀察改善之處

相關語法

Scalar Function
CREATE FUNCTION getTotalPrice(@PurNO char(11)) 
RETURNS money
BEGIN
 
  DECLARE @TotalPrice as money 

  SELECT
    @TotalPrice = SUM(ROUND(PurQty * Price , 0))
  FROM PurchDetail
  WHERE PurNO = @PurNO

  RETURN @TotalPrice
END
GO
TSQL 測試語法
SELECT 
  PurNO , 
  dbo.getTotalPrice(PurNO) AS TotalPrice
FROM Purch
WHERE PurDate BETWEEN '20180101' AND '20180630'

執行計畫觀察

在 SQL Server 2019 前版本,執行計畫會只有 Purch Table 而已

[SQL] Inline Function-4

在 SQL Server 2019 上的 inline function 可以看見 PurchDetail Table 出現啦

[SQL] Inline Function-2 

從 Profiler 觀察 function 重覆執行情況

SQL Server 2019 前重覆執行情況

[SQL] Inline Function-5

SQL Server 2019 inline function 只會執行一次 

  [SQL] Inline Function-1

inlineable 查詢

inline function 有很多限制,該限制可以參考官方文件,多到嚇人的限制條件,但建立的 function 是否可以 inlineable,可以透過 sys.sql_modules 來查詢
SELECT
	[object_id] ,
	[definition] ,
	is_inlineable
FROM sys.sql_modules 
WHERE object_id = object_id('getTotalPrice')

[SQL] Inline Function-3

另外文件上寫的很清楚,即使是 inlinable,也不表示就會是 inline function。SQL Server 會根據每個查詢來決定是否為 inline function,可能會因為 function 內多達數千行,而不使用 inline 功能的

星期三, 4月 06, 2022

[C#] Quartz.NET - 傳遞自訂 class

[C#] Quartz.NET 筆記中,有紀錄傳遞參數方式,該篇筆記是要透過 JobDataMap 把自訂 class 傳進 Job 去

自訂 class
namespace QuartzSample
{
    public class CustomClass
    {
        public int ID { get; set; }
        public string Description { get; set; }

        public override string ToString()
        {
            return $"編號-{ID} ; 描述:{Description}";
        }
    }
}
Job 內透過 JobDetail 和 JobDataMap 把自訂 class 抓出來
using Quartz;
using System;
using System.Threading.Tasks;

namespace QuartzSample
{
    public class PassCustomClassJob : IJob
    {
        public Task Execute(IJobExecutionContext context)
        {
            var customClass = context.JobDetail.JobDataMap[nameof(CustomClass)] as CustomClass;

            return Task.Run(() => {
                Console.WriteLine(customClass.ToString());
            });
        }
    }
}

測試 Code
namespace QuartzSample
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            StdSchedulerFactory factory = new StdSchedulerFactory();
            IScheduler scheduler = await factory.GetScheduler();
            await scheduler.Start();
            
            // 透過 JobDataMap 把自訂 class 傳進 Job 內
            JobDataMap jobDataMap = new JobDataMap();
            jobDataMap.Add(nameof(CustomClass), new CustomClass() { ID = 1 , Description = "傳遞自訂 class"});

            IJobDetail job = JobBuilder.Create<PassCustomClassJob>()
                .UsingJobData(jobDataMap) // 把自訂 class 傳進 Job 內
                .Build();

            ITrigger trigger = TriggerBuilder.Create()
                .StartNow()
                .WithSimpleSchedule(x => x
                    .WithIntervalInSeconds(1) 
                    .RepeatForever())
                .Build();

            await scheduler.ScheduleJob(job, trigger);
            await Task.Delay(TimeSpan.FromSeconds(10));
        }
    }
}
執行結果
最後,沒想到寫這該篇筆記時,已經推出 3.4.0 版本了,Orz 

星期五, 4月 01, 2022

[SQL] STRING_SPLIT 執行計畫

在網路上看到有人在 Store Procedure 內傳入逗號分隔資料,並搭配 string_split 來搜尋資料,在練習發現執行計畫內都會有 [排序 (相異排序)] 操作子出現,等於 QO 會自動移除重覆資料,透過 AdventureWorks2019 Perosn.Person Table 來筆記

 [排序 (相異排序)] 操作子圖示和說明
The Distinct Sort logical operator scans the input, removing duplicates and sorting by the columns specified in the DISTINCT ORDER BY:() predicate of the Argument column. Distinct Sort is a logical operator.
測試語法,從執行計畫中就可以發現 [排序 (相異排序)] 存在,然後原本 4 筆資料就變成 3 筆啦
SELECT 
	[BusinessEntityID] ,
	ModifiedDate
FROM Person.Person
WHERE ModifiedDate IN 
	(
		-- 20060623 為重覆資料
		SELECT CAST(Value as datetime)
		FROM STRING_SPLIT('20060623,20060623,20070119,20071104' , ',')
	)
研究 string_split 文件並測試,是沒有移除重覆資料功能,單純就是把資料切分出來而已,資料還是 4 筆
SELECT Value FROM STRING_SPLIT('20060623,20060623,20070119,20071104' , ',')
IN 語法,從執行計畫 Operator 內發現,雖然沒有 [排序 (相異排序)],但只有 20060623,20070119,20071104 這三個條件出現而已
SELECT 
	[BusinessEntityID] ,
	ModifiedDate
FROM Person.Person
WHERE ModifiedDate IN ('20060623','20060623','20070119','20071104')
測試起來是 IN + string_split 就會自動出現 [排序 (相異排序)] 來移除重覆資料