星期四, 12月 31, 2020

[C#] GraphicsPath.AddArc() Angle 參數

參考 GraphicsPath.AddArc 方法 內範例來了解 StartAngle 和 SweepAngle 這兩個參數的意義和使用

參數說明

類型說明
StartAngleThe starting angle of the arc, measured in degrees clockwise from the x-axis..
SweepAngleThe angle between startAngle and the end of the arc.

C# Code,稍微修正文章內範例
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace GraphicsSample
{
    public partial class FrmArcAngle : Form
    {
        public FrmArcAngle()
        {
            InitializeComponent();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            ArcAngle(e);
            base.OnPaint(e);
        }

        private void ArcAngle(PaintEventArgs e)
        {
            // 設定一個正方形
            Rectangle rect = new Rectangle(130, 20, 100, 100);

            // 透過 GraphicsPath 來畫橢圓弧形
            GraphicsPath myPath = new GraphicsPath();
            myPath.StartFigure();
            // X 軸角度為 0 並從 X 軸為 0 度開始畫一個 180 度橢圓弧形
            myPath.AddArc(rect, 0, 180);
            // 把起點和終點連起來
            myPath.CloseFigure();

            // 進行繪製
            e.Graphics.DrawRectangle(Pens.Black, rect);
            e.Graphics.DrawPath(new Pen(Color.Red, 5), myPath);
        }
    }
}
[C#] GraphicsPath.AddArc() Angle 參數

星期二, 12月 29, 2020

[C#] TreeView 圖示

根據 
的筆記

把 ImageList 所需三張圖片,放在 Resource 內來使用


using System;
using System.Drawing;
using System.Windows.Forms;
using TreeViewImage.Properties;

namespace TreeViewImage
{
    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
        }

        ImageList imageList4TreeView = new ImageList();

        private void Form1_Load(object sender, EventArgs e)
        {
            myTreeView.Font = new Font("微軟正黑體", 16F);

            // 建立搭配 TreeView 使用的 ImageList 圖庫
            imageList4TreeView.ImageSize = new Size(36, 36);
            imageList4TreeView.Images.Add(Resources.Community);
            imageList4TreeView.Images.Add(Resources.Man);
            imageList4TreeView.Images.Add(Resources.Woman);

            // 設定 TreeView 使用 ImageList
            myTreeView.ImageList = imageList4TreeView;

            DrawTreeView();
        }

        /// <summary>
        /// 在 TreeNode 內指定 ImageIndex 和 SelectedImageIndex 來顯示圖片
        /// </summary>
        private void DrawTreeView()
        {
            TreeNode root = new TreeNode() { Text = "組織圖" , ImageIndex = 0 };
            myTreeView.Nodes.Add(root);

            TreeNode DepartmentNode;
            TreeNode EmployeeNode;
            int index;
            for (int i = 1; i <= 2; i++)
            {
                DepartmentNode = new TreeNode() { Text = $"部門-{i}", ImageIndex = 0 , SelectedImageIndex = 0};
                root.Nodes.Add(DepartmentNode);

                for (int j = 1; j <= 4; j++)
                {
                    index = j % 2 == 0 ? 1 : 2;
                    EmployeeNode = new TreeNode() { Text = $"員工-{j}", ImageIndex = index , SelectedImageIndex = index };
                    DepartmentNode.Nodes.Add(EmployeeNode);
                }
            }

            myTreeView.ExpandAll();
        }
    }
}


SelectedImageIndex 說明
Gets or sets the image list index value of the image that is displayed when the tree node is in the selected state.
因為刻意變化每個 TreeNode 圖示,導致點選 TreeNode 後會根據 TreeView.SelectedImageIndex 來變化圖示,原是想說把 TreeView.SelectedImageIndex = -1 來取消,但沒有作用,最後是在建立 TreeNode 時會一併設定 TreeNode.SelectedImageIndex,避免受 TreeView.SelectedImageIndex 影響

ImageKey 和 ImageIndex 說明
ImageKey 和 ImageIndex 屬性互斥
  • 設定 ImageKey,ImageIndex 會自動設為 -1
  • 設定 ImageIndex,ImageKey 會自動設為空字串 ( "" ) 

星期日, 12月 27, 2020

[VFP] CTOD() 日期轉換

公司內發生的 bug,因為變成 SET DATE 環境日期設定,從 Taiwan 改為 YMD,導致 CTOD() 轉換日期異常,導致一連串錯誤,用段 Code 來記錄該情況
lcMonth = "10912"
lcYY = SUBSTR(lcMonth,1,3)
lcMM = SUBSTR(lcMonth,4,2)

SET DATE YMD
ldYMDDate = CTOD(lcYY + "/" + lcMM + "/01")
ldYMDYear = YEAR(ldYMDDate)

SET DATE TAIWAN
ldTAIWANDDate = CTOD(lcYY + "/" + lcMM + "/01")
ldTAIWANDYear = YEAR(ldTAIWANDDate)

TEXT TO lcMessage TEXTMERGE NOSHOW PRETEXT 2
	SET DATE YMD
	CTOD 轉日期:<<ldYMDDate>>
	取出西元年:<<ldYMDYear>>
	
	SET DATE TAIWAN
	CTOD 轉日期:<<ldTAIWANDDate>>
	取出西元年:<<ldTAIWANDYear>>
ENDTEXT 
MESSAGEBOX(lcMessage , 0 + 64 , "測試結果")
從下圖就可以發現,YMD 變成西元 109 年,Taiwan 則是西元 2020 年

[VFP] CTOD() 日期轉換-1

因為 CTOD() 會受 SET DATE 影響,所以建立一律改為用 DATE() 來轉日期,避免困惱

星期六, 12月 26, 2020

[C#] 繪製文字 - 對齊

根據 作法:對齊繪製的文字 的練習筆記
using System;
using System.Drawing;
using System.Windows.Forms;

namespace GraphicsSample
{
    public partial class FrmText : Form
    {

        public FrmText()
        {
            InitializeComponent();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            DrawStringAlignment(e);
            RenderTextCenter(e);
        }

        Font fontAlignment = new Font("微軟正黑體", 12, FontStyle.Bold);
        Pen penRect = Pens.Black;

        private void DrawStringAlignment(PaintEventArgs e)
        {
            string text = "Use StringFormat and Rectangle objects to center text in a rectangle.";
            Rectangle rect = new Rectangle(10, 10, 130, 140);

            // Alignment:水平對齊
            // LineAlignment:垂直對齊
            StringFormat stringFormat = new StringFormat();
            stringFormat.Alignment = StringAlignment.Center;
            stringFormat.LineAlignment = StringAlignment.Center;

            // 在矩形內繪製文字
            e.Graphics.DrawString(text, fontAlignment, Brushes.Blue, rect, stringFormat);
            e.Graphics.DrawRectangle(penRect, rect);
        }

        private void RenderTextCenter(PaintEventArgs e)
        {
            string text = "Use TextFormatFlags and Rectangle objects to center text in a rectangle.";
            Rectangle rect = new Rectangle(150, 10, 130, 140);

            // HorizontalCenter:水平置中
            // VerticalCenter:垂直置中
            // WordBreak:在字的結尾讓文字分行
            TextFormatFlags flags = TextFormatFlags.HorizontalCenter |
                TextFormatFlags.VerticalCenter |
                TextFormatFlags.WordBreak;

            // 在矩形內繪製文字
            TextRenderer.DrawText(e.Graphics, text, fontAlignment, rect, Color.Blue, flags);
            e.Graphics.DrawRectangle(penRect, rect);
        } 
    }
}

星期五, 12月 25, 2020

[C#] 繪製文字

繪製文字有兩種方法
  1. Graphics.DrawString:作法:在 Windows Form 上繪製文字
  2. TextRenderer.DrawText:作法:使用 GDI 繪製文字
該篇是上述兩篇筆記
using System;
using System.Drawing;
using System.Windows.Forms;

namespace GraphicsSample
{
    public partial class FrmText : Form
    {
        private Font font = new Font("微軟正黑體" , 30);

        public FrmText()
        {
            InitializeComponent();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            DrawString();
            RenderText(e);
        }
        public void DrawString()
        {
            using (Graphics g = this.CreateGraphics())
            using (SolidBrush b = new SolidBrush(Color.Black))
            {
                string drawString = "Graphics.DrawString:Sample Text";
                float x = 10.0F;
                float y = 10.0F;
                g.DrawString(drawString, font, b, x, y);
            }
        }

        private void RenderText(PaintEventArgs e)
        {
            // TextFormatFlags 列舉說明
            // Bottom:文字對齊底部
            // EndEllipsis:移除已修剪字行的結尾,並以省略符號取代
            TextFormatFlags flags = TextFormatFlags.Bottom | TextFormatFlags.EndEllipsis;
            TextRenderer.DrawText(e.Graphics, "TextRenderer.DrawText:Sample Text", font,
                new Rectangle(10, 40, 700, 100), SystemColors.ControlText, flags);
        }
    }
}
文章重點
The DrawText methods of the TextRenderer class are not supported for printing. When printing, always use the DrawString methods of the Graphics class.

星期四, 12月 24, 2020

[C#] 繪製實心矩形

根據 作法:在 Windows Form 上繪製實心矩形 的筆記
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

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

        private void DrawRectangle()
        {
            using (Graphics g = this.CreateGraphics())
            using (Pen p = new Pen(Color.Black))
            using (SolidBrush b = new SolidBrush(Color.LightBlue))
            {
                Rectangle rec = new Rectangle(20, 20, 300, 300);

                p.Width = 10;
                p.DashStyle = DashStyle.Solid;
                g.DrawRectangle(p, rec);

                g.FillRectangle(b, rec);
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            DrawRectangle();
        }
    }
}
 文章內注意事項
You should always call Dispose on any objects that consume system resources, such as Brush and Graphics objects. 
繪製過程假如沒有特別需求,EX:Pen 需要指定寬度 (Width)、線條樣式 (DashStyle) 等需求,.NET Framework 有提供 BrushesPens 靜態類別可以直接指定顏色

星期一, 12月 21, 2020

[C#] 利用 XSD 驗證 XML

根據 使用 XmlSchemaSet 進行 XML 架構 (XSD) 驗證如何使用 XSD 驗證 的測試筆記,該文章內容是以判斷 name、last-name 和 first-name 的 sequence 是否有效

XSD,文章內容有用到功能,從 XML Schema Tutorial 內抓出來整理

類型說明
sequenceThe sequence element specifies that the child elements must appear in a sequence.
Each child element can occur from 0 to any number of times.
minOccursThe minOccurs indicator specifies the minimum number of times an element can occur.
default value 1
maxOccursThe maxOccurs indicator specifies the maximum number of times an element can occur.
useAttributes are optional by default. To specify that the attribute is required, use the "use" attribute
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://www.contoso.com/books" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="bookstore">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="book">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="title" type="xs:string" />
              <xs:element name="author">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element minOccurs="0" name="name" type="xs:string" />
                    <xs:element minOccurs="0" name="last-name" type="xs:string" />
                    <xs:element minOccurs="0" name="first-name" type="xs:string" />
                  </xs:sequence>
                </xs:complexType>
              </xs:element>
              <xs:element name="price" type="xs:decimal" />
            </xs:sequence>
            <xs:attribute name="genre" type="xs:string" use="required" />
            <xs:attribute name="publicationdate" type="xs:date" use="required" />
            <xs:attribute name="ISBN" type="xs:string" use="required" />
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

星期六, 12月 19, 2020

[C#] csv 檔案轉換為 XML

根據官方該篇 - 如何從 CSV 檔案產生 XML (LINQ to XML) 的練習
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;

namespace XMLConvert
{
    class Program
    {
        static void Main(string[] args)
        {
            string csvFileFullName = @"D:\Demo\CSV2XML.csv";

            CSVFileCreate(csvFileFullName);

            List<string> source = CSVReader(csvFileFullName);

            XDocument xDoc = new XDocument
            (
                new XDeclaration("1.0", "utf-8", "yes"),
                new XComment("Linq2XML"),
                new XElement
                (
                    "Root",
                    source.Select(s => s.Split(','))
                        .Select
                        (e =>
                            new XElement("Customer", new XAttribute("CustomerID", e[0]),
                                new XElement("CompanyName", e[1]),
                                new XElement("ContactName", e[2]),
                                new XElement("ContactTitle", e[3]),
                                new XElement("Phone", e[4]),
                                new XElement("FullAddress",
                                    new XElement("Address", e[5]),
                                    new XElement("City", e[6]),
                                    new XElement("Region", e[7]),
                                    new XElement("PostalCode", e[8]),
                                    new XElement("Country", e[9]))))
                        )
            );

            xDoc.Save(@"D:\Demo\CSV2XML.xml");
        }

        private static void CSVFileCreate(string csvFileFullName)
        {
            string csvString = @"GREAL,Great Lakes Food Market,Howard Snyder,Marketing Manager,(503) 555-7555,2732 Baker Blvd.,Eugene,OR,97403,USA
HUNGC,Hungry Coyote Import Store,Yoshi Latimer,Sales Representative,(503) 555-6874,City Center Plaza 516 Main St.,Elgin,OR,97827,USA
LAZYK,Lazy K Kountry Store,John Steel,Marketing Manager,(509) 555-7969,12 Orchestra Terrace,Walla Walla,WA,99362,USA
LETSS,Let's Stop N Shop,Jaime Yorres,Owner,(415) 555-5938,87 Polk St. Suite 5,San Francisco,CA,94117,USA";

            File.WriteAllText(csvFileFullName, csvString);
        }

        private static List<string> CSVReader(string csvFileFullName)
        {
            return File.ReadAllLines(csvFileFullName).ToList();
        }
    }
}

結果截圖只顯示一筆資料而已

[C#] csv 檔案轉換為 XML

星期三, 12月 16, 2020

[VS] 重設視窗配置

最近使用 VS 時,發生原本釘在下方的 [錯誤清單]、[工作清單] 等視窗,每次開啟 VS 都會消失,今天莫名其妙的 [Team Explorer] 竟然叫不出來,最後都是透過 [重設視窗配置] 來解決該問題

  [VS] 重設視窗配置

星期一, 11月 23, 2020

[C#] 控件智能標籤

根據 Walkthrough: Adding Smart Tags to a Windows Forms Component 的筆記文

以 TextBox 為例,點選控件右上角的三角符號,跳出來的 TextBox 工作,就是所謂的智能標籤

[C#] 控件智能標籤-1

要建立智能標籤,要先引用 System.Design.dll 

[C#] 控件智能標籤-2 
 

星期六, 11月 21, 2020

[C#] Enum - FlagsAttribute

Enum FlagsAttribute 使用練習筆記

官方文章內容:FlagsAttribute 和 Enum 的指導方針
  • 將列舉常數定義為2的乘冪,也就是1、2、4、8 等等。 這表示結合的列舉常數中的個別旗標不會重迭
  • 請考慮建立常用旗標組合的列舉常數。 例如,如果您有一個用於包含列舉常數和之檔案 i/o 作業的列舉 Read = 1 Write = 2 ,請考慮建立列舉常數 ReadWrite = Read OR Write ,其中結合了 Read 和 Write 旗標。 此外,在某些情況下,用來結合旗標的位 OR 運算可能會被視為一種簡單的概念
  • 如果您定義負數作為旗標列舉常數,因為有許多旗標位置可能設定為1,這可能會讓您的程式碼變得令人困惑,並鼓勵程式碼錯誤,請務必謹慎使用
  • 測試旗標是否設定在數值中的方便方式,是在數值和旗標列舉常數之間執行位 AND 運算,將數值中的所有位設定為零,而不會對應至旗標,然後測試該運算的結果是否等於旗標列舉常數
  • 用作 None 值為零之旗標列舉常數的名稱。 您無法使用 None 位 and 運算中的列舉常數來測試旗標,因為結果一律為零。 不過,您可以在數值和列舉常數之間執行邏輯(而非位比較), None 以判斷是否已設定數值中的任何位。如果您建立一個值列舉,而不是旗標列舉,則建立列舉常數仍然是值得的 None 。 原因是,通用語言執行時間預設會將用於列舉的記憶體初始化為零。 因此,如果您未定義其值為零的常數,則在建立時,列舉會包含不合法的值。如果您的應用程式有明顯的預設情況,您的應用程式需要表示,請考慮使用其值為零的列舉常數來表示預設值。 如果沒有預設案例,請考慮使用值為零的列舉常數,表示不是任何其他列舉常數所表示的大小寫
  • 請勿只定義列舉值來鏡像列舉本身的狀態。 例如,請勿定義只標示列舉結尾的列舉常數。 如果您需要判斷列舉的最後一個值,請明確檢查該值。 此外,如果範圍內的所有值都有效,您可以針對第一個和最後一個列舉的常數執行範圍檢查
  • 請勿指定保留給未來使用的列舉常數
  • 當您定義採用列舉常數作為值的方法或屬性時,請考慮驗證值。 原因是,您可以將數值轉換成列舉型別,即使該數值未在列舉中定義
C# Code
using System;

namespace EnumFlags
{
    class Program
    {
        static void Main(string[] args)
        {
            Days days = Days.Saturday;
            Console.WriteLine($"初始值:{days}");

            days = days.Add(Days.Sunday);
            Console.WriteLine($"把星期日加入:{days}");

            days = days.Remove(Days.Saturday);
            Console.WriteLine($"把星期六移除:{days}");

            Console.WriteLine($"星期六日是否同時存在:{days.IsSatAndSunBoth()}");
            Console.WriteLine($"星期六日是否存在一日:{days.IsSatOrSunExists()}");
        }
    }

    // 沒有特別宣告的話,Enum 預設是 int
    [Flags]
    public enum Days
    {
        Monday = 1,
        Tuesday = 2 ,
        Wednesday = 4,
        Thursday = 8 ,
        Friday = 16,
        Saturday = 32,
        Sunday = 64,

        All = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
    };

    public static class ExtDays
    {
        public static Days Add(this Days source, Days flagAdd)
        {
            return source | flagAdd;
        }

        public static Days Remove(this Days source, Days flagRemove)
        {
            return source & ~flagRemove;
        }

        // 星期六日是否同時存在
        public static bool IsSatAndSunBoth(this Days value)
        {
            return value.HasFlag(Days.Saturday | Days.Sunday);
        }

        // 星期六日是否存在一日
        public static bool IsSatOrSunExists(this Days value)
        {
            return value.HasFlag(Days.Saturday) || value.HasFlag(Days.Sunday);
        }
    }
}
一開始練習時,因為是根據官方文章把 Enum 值,從 0 開始往後編,偏偏又用 [值為 0 的列舉] 來初始化,FlagAttribute 怎麼都加不上去,閱讀 [FlagsAttribute 指方針] 才發現到問題點
用作 None 值為零之旗標列舉常數的名稱。 您無法使用 None 位 and 運算中的列舉常數來測試旗標,因為結果一律為零。 不過,您可以在數值和列舉常數之間執行邏輯(而非位比較), None 以判斷是否已設定數值中的任何位。

如果您建立一個值列舉,而不是旗標列舉,則建立列舉常數仍然是值得的 None 。 原因是,通用語言執行時間預設會將用於列舉的記憶體初始化為零。 因此,如果您未定義其值為零的常數,則在建立時,列舉會包含不合法的值。

如果您的應用程式有明顯的預設情況,您的應用程式需要表示,請考慮使用其值為零的列舉常數來表示預設值。 如果沒有預設案例,請考慮使用值為零的列舉常數,表示不是任何其他列舉常數所表示的大小寫。

星期五, 11月 20, 2020

[C#] 刪除唯讀檔案

收到 Exception 訊息為 [System.UnauthorizedAccessException: 拒絕存取路徑],原以為是 File Server 上權限異常,經確認後發現,該圖檔莫名奇妙被設為 [唯讀],Orz
透過 C# 來刪除唯讀檔案,可以透過 FileInfo 或 FileAttribute 取消唯讀後再進行

FileInfo 取消唯讀
using System;
using System.IO;

namespace ReadOnlyFileDelete
{
    class Program
    {
        static void Main(string[] args)
        {
            string targetFileFullName = @"D:\Demo.txt";

            if (!File.Exists(targetFileFullName))
                throw new FileNotFoundException();

            // 利用 FileInfo
            FileInfo fi = new FileInfo(targetFileFullName);
            if (fi.IsReadOnly) fi.IsReadOnly = false;
            fi.Delete();
        }
    }
}
FileAttribute 取消唯讀
using System;
using System.IO;

namespace ReadOnlyFileDelete
{
    class Program
    {
        static void Main(string[] args)
        {
            string targetFileFullName = @"D:\Demo.txt";

            if (!File.Exists(targetFileFullName))
                throw new FileNotFoundException();

            FileAttributes attributes = File.GetAttributes(targetFileFullName);
            if (attributes.HasFlag(FileAttributes.ReadOnly))
            {
                attributes = attributes & ~FileAttributes.ReadOnly;
                File.SetAttributes(targetFileFullName, attributes);
            }            
            File.Delete(targetFileFullName);
        }
    }
}

星期二, 11月 17, 2020

[SQL] 隱含轉換與 Index Seek

一直都以為隱含轉換後,就不會有機會跑 Index Seek,只會是 Index Scan,在上一篇 [SQL] 探查剩餘 (Probe Residual) 內,因為朋友測試發現 Nested Loop 執行計畫,才注意到發生隱含轉換還是有機會跑 Index Seek 的

完整執行計畫,可以從黃框框發顯示 tblChar Clustered Index Seek

[SQL] 隱含轉換與 Index Seek-1 

看該 operator 明細就可以發現,搜尋述詞內的隱含轉換不是針對 char 欄位來進行,所以 Index Seek 還是有觸發
 
[SQL] 隱含轉換與 Index Seek-2

星期六, 11月 14, 2020

[SQL] 探查剩餘 (Probe Residual)

Turning 時發現執行計畫很詭異,因為清楚該 TSQL 商業邏輯,所以注意到 Hash Join 存在和其  [探查剩餘 (Probe Residual)] 資訊,後來發現是 JOIN ON 欄位資料型態不一致造成隱含轉換,再加上沒有 Index 造成該現象

模擬該情況,先建立 tblChar 和 tblNChar 兩個 Table
USE [AdventureWorks2017]
GO

CREATE TABLE [dbo].[tblChar]
(
	[NOs] [char](11) NOT NULL, -- 資料形態為 char
	CONSTRAINT [PK_tblChar] PRIMARY KEY CLUSTERED ([NOs] ASC)
)
GO

CREATE TABLE [dbo].[tblNChar]
(
	[ID] [int] NOT NULL,
	[Nos] [nchar](11) NULL, -- 資料形態為 nchar
	CONSTRAINT [PK_tblNChar] PRIMARY KEY CLUSTERED 
	(
		[ID] ASC
	)
) 
GO
針對兩個 Table 進行 JOIN,來呈現 Hash Join 內有 Probe Residual 情況
SELECT T1.*
FROM tblChar AS T1
	JOIN tblNChar AS T2 ON T1.NOs = T2.NOs
完整執行計畫 

[SQL] 探查剩餘 (Probe Residual)-1

重點的 Hash Join operator 和隱含轉換,只擷取重點來顯示

[SQL] 探查剩餘 (Probe Residual)-2

[SQL] 探查剩餘 (Probe Residual)-3 
解決方式就把資料形態改為一致 (char(11)) 並加上 Index 結案,謎之音:只存英數資料,為什麼要開成 unicdoe 欄位形態阿,>.<

以前對 Hash Join 認知,只存在 OLTP 系統內是要關注的重點,趁這次機會找些文章來更了解它

20201117 朋友根據文章內容試時,發現執行計畫不一樣,跑 Nested Loop,該點在自建的模擬環境內也會發生,可以透過連結提示來看到有 Hash Join 的執行計畫
SELECT T1.*
FROM tblChar AS T1
	INNER Hash JOIN tblNChar -- 連結提示,強迫 TSQL 跑 Hash Join
	AS T2 ON T1.NOs = T2.NOs

星期二, 11月 10, 2020

[C#] 設定檔 - Settings.settings

設定檔 - Settings.settings 可以用來儲存應用程式所需的參數,例如公司名稱、專案名稱、使用者、使用者喜好設定等,避免 Hard Code,增加應用彈性

在 Project Properties 內可以找到 Setting.settings 檔案
 
[C#] 應用程式設定檔 - Settings.settngs-1

點擊 Setting.settings 可以開啟 [設定] 頁面,分別輸入 Title、BGColor、LocationX、LocationY 和 BindingText 這 5 個設定值,各設定值用途
  • Title:Form.Text 使用
  • BGColor:使用者挑選顏色,並在 PictureBox 內顯示
  • LocationX 和 LocationY:紀錄 Form 最後的位置
  • BindingText:直接綁定 TextBox.Text Property

範圍有應用程式 (Application) 和使用者 (User) 兩種
  • 應用程式:唯讀,無法進行修改,設定值會存放在 App.Config 內的 <applicationSettings> 區段
  • 使用者:可讀寫,設定值會存放在 App.config 內的 <userSettings> 區段

  [C#] 應用程式設定檔 - Settings.settngs-2

設定完成後,可以在 app.Config 內看見相關設定
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="ConfigSample.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
        </sectionGroup>
        <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="ConfigSample.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
    <userSettings>
        <ConfigSample.Properties.Settings>
            <setting name="BGColor" serializeAs="String">
                <value />
            </setting>
            <setting name="LocationX" serializeAs="String">
                <value>0</value>
            </setting>
            <setting name="LocationY" serializeAs="String">
                <value>0</value>
            </setting>
            <setting name="BindingText" serializeAs="String">
                <value>控件綁定</value>
            </setting>
        </ConfigSample.Properties.Settings>
    </userSettings>
    <applicationSettings>
        <ConfigSample.Properties.Settings>
            <setting name="Title" serializeAs="String">
                <value>[C#] 應用程式設定檔 - Settings.settngs</value>
            </setting>
        </ConfigSample.Properties.Settings>
    </applicationSettings>
</configuration>

星期三, 11月 04, 2020

[C#] DockPanel Suite

DockPanel Suite 可以讓 WinForm 的 Form 有類似 VS 視窗,可以移動、隱藏和縮效果

透過 nuget 安裝 V3.0.6 後,可以在 VS 工具箱內看見綠框這三個選項 (DockPanel、VisualStudioToolStripExtender 和 VS2005Theme),另外 DockPanel Suite 還有其他 Theme 可以安裝,只安裝 VS2015 Theme (橘框)

[C#] DockPanel Suite-1

要 Dock 的 Form 必須繼承 DockContent (以本例來說是 frmDock Form)
using WeifenLuo.WinFormsUI.Docking;

namespace DockSample
{
    public partial class frmDock : DockContent
    {
        public frmDock()
        {
            InitializeComponent();
        }
    }
}
把 DockPanel 拉到 Form 內 (以本例來說,是 frmMain Form) 並進行下面設定
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;

namespace DockSample
{
    public partial class frmMain : Form
    {
        public frmMain()
        {
            InitializeComponent();

            // frmMain 必須開啟 IsMdiContainer
            this.IsMdiContainer = true;

            // 設定 DockPanel 置中
            dockPanel.Dock = DockStyle.Fill;

            // 不允許使用者移動 DockPanel 內視窗 (Form)
            dockPanel.AllowEndUserDocking = false;

            // 設定 DockPanel Theme
            dockPanel.Theme = new VS2015BlueTheme();

            // 根據 DockState 把 frmDock 釘在 frmMain.DockPanel 上
            frmDock frmDocument = new frmDock() { TabText = "Document" };
            frmDocument.Show(this.dockPanel, DockState.Document);

            frmDock frmDockLeft = new frmDock() { TabText = "DockLeft" }; ;
            frmDockLeft.Show(this.dockPanel, DockState.DockLeft);

            frmDock frmDockRight = new frmDock() { TabText = "DockRight" }; ;
            frmDockRight.Show(this.dockPanel, DockState.DockRight);

            frmDock frmDockBottom = new frmDock() { TabText = "DockBottom" }; ;
            frmDockBottom.Show(this.dockPanel, DockState.DockBottom);

            frmDock frmDockLeftAutoHide = new frmDock() { TabText = "DockLeftAutoHide" }; ;
            frmDockLeftAutoHide.Show(this.dockPanel, DockState.DockLeftAutoHide);

            frmDock frmFloat = new frmDock() { TabText = "Float" }; ;
            frmFloat.Show(this.dockPanel, DockState.Float);
        }
    }
}
執行效果

[C#] DockPanel Suite-2

星期五, 10月 30, 2020

[SQL] 透過擴充事件來捕捉 DeadLock

擴充事件預設啟用的 System_Health ,就包含 DeadLock 相關資訊

官方文章內容 - System_Health 所收集的資訊
  • The sql_text and session_id for any sessions that encounter an error that has a severity >= 20.
  • The sql_text and session_id for any sessions that encounter a memory-related error. The errors include 17803, 701, 802, 8645, 8651, 8657 and 8902.
  • A record of any non-yielding scheduler problems. These appear in the SQL Server error log as error 17883.
  • Any deadlocks that are detected, including the deadlock graph.
  • The callstack, sql_text, and session_id for any sessions that have waited on latches (or other interesting resources) for > 15 seconds
  • The callstack, sql_text, and session_id for any sessions that have waited on locks for > 30 seconds.
  • The callstack, sql_text, and session_id for any sessions that have waited for a long time for preemptive waits. The duration varies by wait type. A preemptive wait is where SQL Server is waiting for external API calls.
  • The callstack and session_id for CLR allocation and virtual allocation failures.
  • The ring buffer events for the memory broker, scheduler monitor, memory node OOM, security, and connectivity.
  • System component results from sp_server_diagnostics.
  • Instance health collected by scheduler_monitor_system_health_ring_buffer_recorded.
  • CLR Allocation failures.
  • Connectivity errors using connectivity_ring_buffer_recorded.
  • Security errors using security_error_ring_buffer_recorded.

透過之前筆記 - [SQL] 模擬死結產生 來產生死結,並在擴充事件內觀察 DeadLock,以下為 SSMS 操作步驟

[SQL] 透過擴充事件來捕捉 DeadLock-1

[SQL] 透過擴充事件來捕捉 DeadLock-2

利用 sys.fn_xe_file_target_read_file 來抓取 DeadLock 相關資訊
SELECT 
	-- DeadLock XML 內容
	CONVERT(xml,event_data).query('/event/data/value/deadlock') as DeadLockGraph ,
    
	-- 直接抓 DeadLock XML 內的 timestamp
	CONVERT(xml, event_data).value('(event[@name="xml_deadlock_report"]/@timestamp)[1]','datetime')  AS Execution_Time ,
    
	-- timestamp_utc 欄位資訊在 SQL Server 2017 才開始提供
	timestamp_utc
FROM sys.fn_xe_file_target_read_file('system_health*.xel', null, null, null)
WHERE [object_name] = 'xml_deadlock_report'
ORDER BY timestamp_utc DESC
[SQL] 透過擴充事件來捕捉 DeadLock-3

sys.fn_xe_file_target_read_file 抓出來的 DeadLock XML 檔案,另存為 xdl 後,再利用 SSMS 開啟,就可以看見 DeadLock Graph

[SQL] 透過擴充事件來捕捉 DeadLock-4

星期五, 10月 23, 2020

[C#] 以系統管理員身分執行此程式

要讓應用程式可以以系統管理員身分來執行,可以在 [應用程式清單檔案 (app.manifest)] 內設定requestedExecutionLevel 參數

先在 Project 內新增  應用程式清單檔案 (app.manifest)

[C#] 以系統管理員身分執行此程式-1

應用程式清單檔案 (app.manifest) 內就可以看到 requestedExecutionLevel 參數,預設為 asInvoker,變更為 requireAdministrator 就行,其實 comment 備註內都有說明,下面只擷取 requestedExecutionLevel 參數內容而已
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <!-- UAC 資訊清單選項
             如果要變更 Windows 使用者帳戶控制層級,請將 
             requestedExecutionLevel 節點以下列其中之一取代。

        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />

            指定 requestedExecutionLevel 項目會停用檔案及登錄虛擬化。
            如果您的應用程式需要針對回溯相容性進行這項虛擬化,請移除這個
            項目。
        -->
        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

設定完成後,執行檔案圖示右下角就會有個盾牌出現

[C#] 以系統管理員身分執行此程式-3

不透過應用程式清單檔案 (app.manifest) 來設定的話,也可以直接去執行檔內進行設定 

星期三, 10月 21, 2020

[C#] 更換執行檔參考 dll 位置

專案內會參考很多 dll 或套件,VS 建置預設 exe 和 dll 會放置在 debug 或 release 資料夾內,假如希望把相同功能 dll 整理在同資料夾,就需要在 App.Config 內明確指定 dll 引用位置

建立 DllLocation Solution 內 Core 和 ERP 兩個 Project 來模擬,build 至 release 資料夾後,建立 Lib 資料夾並把 Core.dll 搬移過去,如下圖

   [VS] 更換執行檔參考 dll 位置-1

整理過後執行 exe 檔案,會跳出下圖

[VS] 更換執行檔參考 dll 位置-2

參考該篇官方文章 - <probing> 項目 ,在 AppConfig 內加入設定 probing
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="Lib"/>
    </assemblyBinding>
  </runtime>

  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
  </startup>
</configuration>
這樣執行檔就可以正常引用 dll 囉

<configuration>  
   <runtime>  
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">  
         <probing privatePath="bin;bin2\subbin;bin3"/>  
      </assemblyBinding>  
   </runtime>  
</configuration>  
PrivatePath 屬性包含執行時間應搜尋元件的目錄。如果應用程式位於 C:\Program Files\MyApp,則執行時間會尋找在 C:\Program Files\MyApp\Bin、C:\Program Files\MyApp\Bin2\Subbin 和 C:\Program Files\MyApp\Bin3. 中指定程式碼基底的元件 PrivatePath 中指定的目錄必須是應用程式基底目錄的子目錄。

星期二, 10月 20, 2020

[C#] BackgroundWorker - Exception

使用 BackgroundWorker 時發現執行到一半就結束的情況,沒有 Exception 拋出,直覺是 Exception 被吃掉了,查之前筆記 - [C#] BackgroundWorker 範例4 發現,Exception 必須在 RunWorkerCompleted Event 內處理,之前筆記時沒有意識到這點,單獨在紀錄一下

官方文章說明 - BackgroundWorker.DoWork
If the operation raises an exception that your code does not handle, the BackgroundWorker catches the exception and passes it into the RunWorkerCompleted event handler, where it is exposed as the Error property of System.ComponentModel.RunWorkerCompletedEventArgs. If you are running under the Visual Studio debugger, the debugger will break at the point in the DoWork event handler where the unhandled exception was raised. If you have more than one BackgroundWorker, you should not reference any of them directly, as this would couple your DoWork event handler to a specific instance of BackgroundWorker. Instead, you should access your BackgroundWorker by casting the sender parameter in your DoWork event handler.
namespace BackgroundWorkerException
{
    public partial class Form1 : Form
    {
        private BackgroundWorker worker = new BackgroundWorker();

        public Form1()
        {
            InitializeComponent();
            worker.DoWork += Worker_DoWork;
            worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
        }

        private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // DoWork 內發生 Exception,會存放在 RunWorkerCompletedEventArgs.Error 內,
            // 所以 BackgroundWorker Exception 必須在 RunWorkerCompleted 內處理
            if (e.Error != null)
                MessageBox.Show(e.Error.Message);
        }

        private void Worker_DoWork(object sender, DoWorkEventArgs e)
        {
            throw new Exception("從 DoWork 內拋出 Exception");
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            worker.RunWorkerAsync();
        }
    }
}
[C#] BackgroundWorker - Exception

星期一, 10月 19, 2020

[C#] 利用 ZipFile 進行壓縮與解壓縮

根據官方文章 - 操作說明:壓縮與解壓縮檔案 了解 ZipFile Class 使用,練習重點在於
  • 壓縮
  • 解壓縮
  • 從 Zip 檔案內解壓縮特定格式檔案

在 .Net Framework 內必須把 System.IO.Compression 和 System.IO.Compression.ZipFile 加入參考

[C#] 利用 ZipFile 進行壓縮與解壓縮-1

C# Code 練習整理
using System;
using System.IO.Compression;
using System.IO;
using System.Linq;

namespace ZipFileSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // 相關路徑
            string mainPath = @"D:\ZipFileSample";
            string startPath = Path.Combine(mainPath, "Files");
            string zipPath = Path.Combine(mainPath, "result.zip");
            string extractAllPath = Path.Combine(mainPath, @"Extract\All");
            string extractImagePath = Path.Combine(mainPath, @"Extract\Image");

            // 針對資料夾進行壓縮
            if (File.Exists(zipPath)) File.Delete(zipPath);
            ZipFile.CreateFromDirectory(startPath, zipPath);

            // 確認路徑最後有 [\] 符號存在,避免 Path Traversal Attack
            if (!extractAllPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
                extractAllPath += Path.DirectorySeparatorChar;

            // 針對 Zip File 進行解壓縮
            if (Directory.Exists(extractAllPath)) Directory.Delete(extractAllPath ,true);
            ZipFile.ExtractToDirectory(zipPath, extractAllPath);

            // 針對 ZipFile 內的 bmp 檔案進行解壓縮,把範例內的 foreach 轉為 LINQ 練習
            using (ZipArchive archive = ZipFile.OpenRead(zipPath))
            {
                archive.Entries
                    .Where(entry => entry.FullName.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase))
                    .Select(entry => new
                    {
                        entry,
                        DestinationPath = Path.GetFullPath(Path.Combine(extractImagePath, entry.FullName))
                    })
                    .ToList()
                    .ForEach(entryVar => 
                    {
                    	// Ordinal match is safest, case-sensitive volumes can be mounted within volumes that are case-insensitive.
                        if (entryVar.DestinationPath.StartsWith(extractImagePath, StringComparison.Ordinal))
                        {
                            entryVar.entry.ExtractToFile(entryVar.DestinationPath , true);
                        }
                    });
            }
        }
    }
}

文章內 Path Traversal Attack 說明
當您在解壓縮檔案時,必須尋找惡意的檔案路徑,以免它逸出您解壓縮的目的目錄。 這是所謂的路徑周遊攻擊。 以下範例示範如何檢查惡意的檔案路徑,並提供安全的方式來解壓縮。

星期四, 10月 15, 2020

[VS] 使用 Configuration Transform 依組態切換 App.Config

在 Console 或 WinForm 中,可以使用 Configuration Transform 依組態切換 App.Config,來達到不同環境使用不同連線字串或參數設定來 run

先安裝 Configuration Transform 套件

[VS] 使用 Configuration Transform 依組態切換 App.Config-1 

安裝完成後,可以在 App.Config 上看見功能選項
 
     [VS] 使用 Configuration Transform 依組態切換 App.Config-2

安裝完成後會出現 App.Debug.Config 和 App.Release.Config 兩個設定檔案
 
  [VS] 使用 Configuration Transform 依組態切換 App.Config-3

星期二, 10月 13, 2020

[HyperV] vmcx 檔案刪除

偶而用 Hyper-V 來建立模擬環境,發現常放置 VM 的資料夾內,不知何故還存在已刪除 VM 的 vmcx 檔案

[Hyper-V]vmcx 刪除-1

透過該文章 - Hyper-V Old VM folders cannot be deleted 發現,先把 Hyper-V 服務停掉,就可以順利刪除檔案

[Hyper-V]vmcx 刪除-2

星期日, 10月 11, 2020

[SQL] 資料表值參數 (Table Value Parameters)

SQL Server 2008 功能 - 資料表值參數 (Table Value Parameters),簡稱 TVP,紀錄 MS SQL Table Type 建立和三種程式呼叫方式,基本上程式端都是透過對 Store Procedure 傳入 DataTable 來執行

官方文件內的資料表值參數的限制
  1. 無法將資料表值參數傳遞至 CLR 使用者定義函式。
  2. 資料表值參數索引只支援 UNIQUE 或 PRIMARY KEY 條件約束。 SQL Server 不會維護資料表值參數的統計資料。
  3. 資料表值參數在 Transact-SQL 程式碼中處於唯讀狀態。 您無法更新資料表值參數資料列中的資料行值,也無法插入或刪除資料列。 若要在資料表值參數中修改傳遞至預存程序或參數化陳述式的資料,您必須將資料插入至暫存資料表或資料表變數中。
  4. 無法使用 ALTER TABLE 陳述式來修改資料表值參數的設計
MS SQL Table Type 和 Store Procedure 設定方式
------ Step1:建立 Table Type
CREATE TYPE dbo.ProductCategoryTableType AS TABLE  
    ([Name] nvarchar(50)) 

------ Step2:建立 Store Procedure
-- 建立 Insert 相關
CREATE PROCEDURE uspProductCategories_Insert
    (@tvpProductCategories dbo.ProductCategoryTableType READONLY)  
AS
	BEGIN
		INSERT INTO [Production].[ProductCategory] 
			(
				[Name] , 
				rowguid  , 
				ModifiedDate
			)
		SELECT 
			[Name] , 
			NEWID()  , 
			GETDATE()
		FROM @tvpProductCategories ;
	END

-- 建立 Delete 相關
-- [ProductCategoryID] 才是 PK,方便記錄就用 Name 來進行 JOIN 
CREATE PROCEDURE uspProductCategories_Delete
    (@tvpProductCategories dbo.ProductCategoryTableType READONLY)  
AS
	BEGIN
		DELETE P
		FROM [Production].[ProductCategory] AS P
			JOIN @tvpProductCategories AS tvp ON P.[Name] = tvp.[Name]
	END
透過 Console 來記錄三種呼叫方式,方別為 ADO.NET、Dapper 和 EF
using System;
using System.Data;
using System.Data.SqlClient;
using Dapper;

namespace TVPDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            string connectionString = @"Data Source=.\SQL2017;Initial Catalog=AdventureWorks2017;Integrated Security=True;";
            DataTable dtProductCategory = GetDataTableProductCategory();
            // 透過變更 CRUDMode 來決定 insert 或 delete
            string crudModel = CRUDMode.Insert.ToString();

            string spName = $"uspProductCategories_{crudModel}";

            #region ADO.NET 呼叫 TVP
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                SqlCommand cmd = new SqlCommand(spName, conn);
                cmd.CommandType = CommandType.StoredProcedure;
                SqlParameter tvpParam = cmd.Parameters.AddWithValue("@tvpProductCategories", dtProductCategory);
                tvpParam.SqlDbType = SqlDbType.Structured;

                conn.Open();
                cmd.ExecuteNonQuery();
            }
            #endregion

            #region Dapper 呼叫 TVP
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                conn.Execute
                    (
                        spName,
                        // tvpProductCategories 為 SP 參數,不用前面的 @ 符號
                        // ProductCategoryTableType 為 Table Type
                        new { tvpProductCategories = dtProductCategory.AsTableValuedParameter("ProductCategoryTableType") },
                        commandType: CommandType.StoredProcedure
                    );
            }
            #endregion

            #region EF 呼叫 TVP
            using (AdventureWorksContext context = new AdventureWorksContext())
            {
                // @tvpProductCategories 為 SP 參數
                string cmdText = $"EXEC {spName} @tvpProductCategories";
                SqlParameter tvpParam = new SqlParameter("@tvpProductCategories" , SqlDbType.Structured);
                // ProductCategoryTableType 為 Table Type
                tvpParam.TypeName = "dbo.ProductCategoryTableType";
                tvpParam.Value = dtProductCategory;

                context.Database.ExecuteSqlCommand(cmdText, tvpParam);
            } 
            #endregion
        }

        enum CRUDMode
        {
            Insert,
            Delete
        }

        private static DataTable GetDataTableProductCategory()
        {
            DataTable dtProductCategory = new DataTable();
            dtProductCategory.Columns.Add("Name", typeof(string));
            dtProductCategory.Rows.Add("分類1");
            dtProductCategory.Rows.Add("分類2");
            dtProductCategory.Rows.Add("分類3");
            dtProductCategory.Rows.Add("分類4");
            dtProductCategory.Rows.Add("分類5");
            return dtProductCategory;
        }
    }
}

星期三, 9月 30, 2020

[Win] 遠端桌面

每次都會忘記如何開啟 Windows Server 遠端桌面,乾脆紀錄一下 

本機伺服器 => 遠端桌面,預設為停止  

[Win] 遠端桌面-1

啟用遠端桌面後,會跳到系統內容的遠端 Tag,開啟 [允許遠端連線到此電腦]

[Win] 遠端桌面-2

勾選  [允許遠端連線到此電腦] 後,會跳出該訊息視窗

[Win] 遠端桌面-3

在遠端 Tag 內,點選 [選取使用者] ,讓使用者具有遠端桌面權限

[Win] 遠端桌面-4

在防火牆進階設定內,也必須開啟 [Winodws 遠端桌面 - 使用者模式 (TCP-In)]

[Win] 遠端桌面-5

勾選 [已啟用] 

[Win] 遠端桌面-6

開啟遠端桌面並在防火牆上允許遠端桌面,這樣就算是完成設定啦

星期三, 9月 23, 2020

[SSMS] 匯入 CSV 檔案

MS SQL 匯入 Excel 檔案時,常常會需要搭配不同的版本的 OLE DB Drvier,甚至會牽扯到 Office 是 32bit 或 64bit 問題,常常搞到焦頭爛額,看線上影片時,才突然開腦洞,就轉成 csv 來處理不就好了,自己也常常寫程式把 ERP 資料匯出 csv 檔案,但在資料庫領域好像就有個莫名堅持,一定要處理 Excel 就是,Orz

SSMS v17.3 開始有 [匯入一般檔案] 可以使用,可以將 csv、txt 檔案匯入,支援以逗號分隔和固定寬度格式

[SSMS] 匯入 CSV 檔案-1

簡介

[SSMS] 匯入 CSV 檔案-2

選擇匯入檔案,假如該 Table 已經存在資料庫內,會出現提醒並無法往下執行

[SSMS] 匯入 CSV 檔案-3

預覽資料,會出現前 50 筆資料

[SSMS] 匯入 CSV 檔案-4

修改資料行,在此編輯欄位資料形態

[SSMS] 匯入 CSV 檔案-5

摘要

[SSMS] 匯入 CSV 檔案-6

結果

[SSMS] 匯入 CSV 檔案-7