<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ScottPony</title>
    <description>The latest articles on DEV Community by ScottPony (@scottpony).</description>
    <link>https://dev.to/scottpony</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F665037%2F12896069-3242-4c71-8582-b6c4ff3b55aa.jpeg</url>
      <title>DEV Community: ScottPony</title>
      <link>https://dev.to/scottpony</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/scottpony"/>
    <language>en</language>
    <item>
      <title>為什麼資料庫顯示亂碼？深入探討 Latin1 與 Windows-1252 的編碼衝突</title>
      <dc:creator>ScottPony</dc:creator>
      <pubDate>Tue, 19 Nov 2024 14:17:17 +0000</pubDate>
      <link>https://dev.to/scottpony/wei-shi-mo-zi-liao-ku-xian-shi-luan-ma-shen-ru-tan-tao-latin1-yu-windows-1252-de-bian-ma-chong-tu-4jkd</link>
      <guid>https://dev.to/scottpony/wei-shi-mo-zi-liao-ku-xian-shi-luan-ma-shen-ru-tan-tao-latin1-yu-windows-1252-de-bian-ma-chong-tu-4jkd</guid>
      <description>&lt;p&gt;在日常開發中，處理文字編碼問題是不可避免的挑戰。特別是在多語言環境下，編碼不一致可能導致程式無法正確解析資料，進而影響系統的穩定性。本文將分享一個實際案例，深入探討為何資料庫會顯示亂碼，以及如何解決 Latin1 與 Windows-1252 編碼衝突的問題。&lt;/p&gt;

&lt;h2&gt;
  
  
  問題背景
&lt;/h2&gt;

&lt;p&gt;在一個專案中，我們從客戶端的資料庫查詢工具中，發現某筆記錄的欄位內容顯示為亂碼。這個欄位的資料類型為 OID（帶有 0x 前綴的十六進位字串），同時也是另一張資料表的鍵值。&lt;/p&gt;

&lt;p&gt;當我們在程式中嘗試讀取這筆資料並解析為位元組陣列時，卻拋出了無法解析的錯誤。經過在 IDE 中的除錯，我們確認程式讀取到的確實是亂碼，導致自訂的 toByteArray() 方法無法正常運作。&lt;/p&gt;

&lt;p&gt;至此，我們判斷這是由於編碼問題導致程式無法繼續執行。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60onfwv14t9qfldlb62e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60onfwv14t9qfldlb62e.png" alt="Garbled text in the database" width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  問題調查過程
&lt;/h2&gt;

&lt;p&gt;為了找出問題的根源，我們展開了以下調查：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;線上轉碼測試：使用線上轉碼工具（如 &lt;a href="https://www.rapidtables.com/convert/number/ascii-to-hex.html" rel="noopener noreferrer"&gt;Text轉Hex&lt;/a&gt;），將亂碼貼上並嘗試不同的編碼格式，觀察轉換後的十六進位字串是否與資料庫查詢工具所得到的結果一致。&lt;/li&gt;
&lt;li&gt;確認資料庫編碼：&lt;a href="https://dev.to/scottpony/sql-server-key-values-are-compared-case-insensitively-in-efcore-8-im0"&gt;得知資料庫中的欄位編碼設定為 "Latin1"（即 ISO-8859-1）&lt;/a&gt;。在轉碼工具中選擇 "ISO-8859-1" 編碼，成功將亂碼轉換為與資料庫一致的十六進位字串。&lt;/li&gt;
&lt;li&gt;程式內部測試：在程式中使用 .NET 提供的 Encoding.Latin1.GetBytes("myString") 方法，嘗試將亂碼轉換為位元組陣列。雖然這次沒有拋出例外，但轉換後的位元組陣列與預期結果不符。&lt;/li&gt;
&lt;li&gt;分析差異：深入比較發現，轉換結果中只有第一個字元不同。該字元為歐元符號 (€)，而在 &lt;a href="https://zh.wikipedia.org/zh-tw/ISO/IEC_8859-1" rel="noopener noreferrer"&gt;ISO-8859-1&lt;/a&gt; 編碼中，編碼範圍 0x80 至 0x9F 並未定義。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  找出問題根源
&lt;/h2&gt;

&lt;p&gt;了解到問題可能出在歐元符號的編碼上，我們進一步研究：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://zh.wikipedia.org/zh-tw/ISO/IEC_8859-15" rel="noopener noreferrer"&gt;ISO-8859-15&lt;/a&gt; 編碼：這是 ISO-8859-1 的擴充版本，定義了歐元符號。然而，歐元符號在 ISO-8859-15 中對應的十六進位值為 0xA4，與我們的資料並不匹配。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://zh.wikipedia.org/wiki/Windows-1252" rel="noopener noreferrer"&gt;Windows-1252&lt;/a&gt; 編碼：查閱相關資料得知，Windows-1252 編碼（又稱 CP1252）在 0x80 至 0x9F 範圍內定義了額外的可見字符，包括歐元符號。其中，歐元符號對應的十六進位值正是 0x80。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;這意味著，雖然資料庫宣稱使用 Latin1 編碼，但實際上存儲的資料包含了 Windows-1252 編碼的字符。&lt;/p&gt;

&lt;h2&gt;
  
  
  解決方案
&lt;/h2&gt;

&lt;p&gt;基於以上調查，我們決定在程式中採用 Windows-1252 編碼進行轉換。具體實現如下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;byte[] byteArray = Encoding.GetEncoding("CP1252").GetBytes("myString");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;使用 Encoding.GetEncoding("CP1252") 方法，我們成功將亂碼正確轉換為位元組陣列，並與資料庫中的十六進位字串一致。問題終於得到了解決。&lt;/p&gt;

&lt;h2&gt;
  
  
  總結
&lt;/h2&gt;

&lt;p&gt;這次的經驗提醒我們，在處理文字編碼時，不能僅依賴於表面設定的編碼格式。實際存儲的資料可能包含超出宣告範圍的字符，特別是在歷史悠久或多元化的系統中。&lt;/p&gt;

&lt;p&gt;為避免類似問題，建議：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;確認實際編碼：使用工具或程式檢查資料實際使用的編碼，而非僅依賴於設定。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;統一編碼標準：在系統中盡可能統一使用 UTF-8 或其他廣泛支持的編碼，減少編碼衝突的可能性。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;加強錯誤處理：在程式中加入編碼錯誤的處理機制，便於快速定位問題。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;透過深入的調查和分析，我們不僅解決了當前的問題，還為未來處理類似情況積累了寶貴的經驗。&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>database</category>
      <category>encode</category>
    </item>
    <item>
      <title>SQL Server key values are compared case-insensitively in EFCore 8</title>
      <dc:creator>ScottPony</dc:creator>
      <pubDate>Mon, 11 Nov 2024 14:09:07 +0000</pubDate>
      <link>https://dev.to/scottpony/sql-server-key-values-are-compared-case-insensitively-in-efcore-8-im0</link>
      <guid>https://dev.to/scottpony/sql-server-key-values-are-compared-case-insensitively-in-efcore-8-im0</guid>
      <description>&lt;p&gt;新版本在UAT階段採到這個坑，藉此機會紀錄一下。&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue
&lt;/h2&gt;

&lt;p&gt;後端API升級至.net 8，相關dependency也一併做升級來相容，因次這次也將EFCore升級到8。&lt;br&gt;
發現在升級的git branch上載入特定模組會回500 error。實際trace後發現是程式寫法會將EFCore query回的結果依照索引來產生dictionary，在產生dictionary時遇到duplicate key的excpetion。&lt;/p&gt;

&lt;h2&gt;
  
  
  分析
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;這裡假設table: test中有欄位A並為Primary key。其中有兩筆資料其A的value分別為"Scott"與"SCOTT"。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;以資料邏輯來說是絕對不會產生這種exception。EFCore做mapping使用的肯定是DB table內的Primary key。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;這段邏輯不在此次升級的改動範圍內。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;同樣的程式在Production沒有出現同樣錯誤，且使用master branch的code連UAT的DB發現並沒有出現相同錯誤。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;兩個branch最大差異就是dotNet版本。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;確認table所使用排序(Collation)類型為"Latin1_General_BIN"，是case-sensitive。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;查詢表格每個欄位的排序類型&lt;/em&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select COLUMN_NAME, COLLATION_NAME
  from INFORMATION_SCHMA.COLUMNS
where TABLE_NAME = 'YOUR_TABLE_NAME'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;在EFCore LINQ的Where()條件內直接指定排列(Collation)後，查詢結果回傳"兩筆"A欄位value皆為"SCOTT"的物件。代表指定&lt;strong&gt;有效&lt;/strong&gt;回傳兩筆(如預期大小寫不敏感視為相同)，但內容肯定在某個階段被改為大寫。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;CI: Case-Insensitive&lt;/em&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;context.YOUR_TYPE.Where(it =&amp;gt; EF.Functions.Collate(it.A, "Latin1_General_CI_AI") == "Scott")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;相反，在EFCore LINQ的Where()條件內直接指定排列(Collation)為大小寫敏感後，查詢結果回傳"一筆"A欄位value皆為"Scott"的物件。再次證明指定&lt;strong&gt;有效&lt;/strong&gt;回傳一筆(如預期大小寫敏感視為不同)且沒有被改為大寫。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;CI: Case-Sensitive&lt;/em&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;context.YOUR_TYPE.Where(it =&amp;gt; EF.Functions.Collate(it.A, "Latin1_General_CS_AI") == "Scott")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;綜合以上，已經可以將懷疑目標調整至dotNet與EFCore是否在新版中對字串大小寫的比較方面是否有改動了。&lt;/p&gt;

&lt;h2&gt;
  
  
  調查/蒐集資料
&lt;/h2&gt;

&lt;p&gt;直接上結果:&lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/breaking-changes#casekeys" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/breaking-changes#casekeys&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>efcore</category>
      <category>backenddevelopment</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Solving Potential Issues with Type Aliases Using Type Unions and Literal Types</title>
      <dc:creator>ScottPony</dc:creator>
      <pubDate>Fri, 07 Jun 2024 07:48:42 +0000</pubDate>
      <link>https://dev.to/scottpony/solving-potential-issues-with-type-aliases-using-type-unions-and-literal-types-2egn</link>
      <guid>https://dev.to/scottpony/solving-potential-issues-with-type-aliases-using-type-unions-and-literal-types-2egn</guid>
      <description>&lt;p&gt;類型聯合（Type Unions）和字面量類型（Literal Types）能有效解決類型別名（Alias Types）可能帶來的問題。類型聯合允許一個變量可以是多種類型之一，通過結合具體的類型定義，可以幫助區分和處理不同的類型，避免混淆。字面量類型通過將變量限制為具體的字面量值，增強了類型系統的嚴格性。結合使用類型聯合和字面量類型，能提供比單純的類型別名更強的類型安全性，減少代碼中的潛在錯誤。&lt;/p&gt;

&lt;h2&gt;
  
  
  問題
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Meters = number;
type Miles = number;

const landSpacecraft = (distance: Meters) {
  // ... do fancy math ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;以上程式碼定義了兩個Alias Type，但其實這兩個都是源於同樣的Type: number。這會造成甚麼潛在問題?如下:&lt;br&gt;
試想，當長度計算的要求接需要以Meter為單位，但當使用者意外傳進了型態為Miles的變數時，landSpacecraft是可以正常執行且在編譯時不會有任何提示。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const distanceInMiles: Miles = 1242;
landSpacecraft(distanceInMiles); // 傳入錯誤的單位類型，沒有報錯
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;當整個程式碼中不同單位混用的狀況是存在的，那這種情形就很容易發生，一般基本計算可能不會有太大影響，但如果程式用在高精密度的計算應用下，那單位不對可是會差之毫釐，失之千里!&lt;/p&gt;

&lt;h2&gt;
  
  
  如何避免
&lt;/h2&gt;

&lt;p&gt;這時，前面提到類型聯合（Type Unions）和字面量類型（Literal Types）就可以很方便地來避免這種狀況。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Meters = number;
type Miles = number;

type Distance = Meters | Miles;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Meters = {
  unit: 'meters';
  value: number;
};

type Miles = {
  unit: 'miles';
  value: number;
};

type Distance = Meters | Miles;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;首先，可以將兩個別名類別使用'|'宣告為聯合類別"Distance"。為了有效區別兩種單位，使用字面量類型（Literal Types）來對兩個別名類別增加屬性使其成為物件型態並使用這些屬性作為之後判斷是使用哪中單位的區別。&lt;/p&gt;

&lt;p&gt;接著，對landSpacecraft方法做改寫&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const landSpacecraft = (distance: Distance) =&amp;gt; {
  if (distance.unit === 'meters') {
    console.log(`Landing spacecraft with distance: ${distance.value} meters`);
    // 使用 meters 進行計算
  } else if (distance.unit === 'miles') {
    console.log(`Landing spacecraft with distance: ${distance.value / 1609.34} meters`);
    // 使用 miles 進行轉換為Meters的計算
  } else {
    console.error('Unknown distance unit');
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;原方法內此時就可以使用類別保護(type guards)來檢查'unit'屬性，針對不同單位來做相對應的處理。&lt;/p&gt;

&lt;p&gt;通過這種方式，我們可以確保在程式中不會混淆不同的單位類型，從而提高程式的安全性與可靠性。&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Mapped Object Types</title>
      <dc:creator>ScottPony</dc:creator>
      <pubDate>Thu, 16 May 2024 14:56:40 +0000</pubDate>
      <link>https://dev.to/scottpony/mapped-object-types-3o0p</link>
      <guid>https://dev.to/scottpony/mapped-object-types-3o0p</guid>
      <description>&lt;h2&gt;
  
  
  映射
&lt;/h2&gt;

&lt;p&gt;將一個集合中的每個元素與另一個元素關聯的動作。&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript中的映射類型
&lt;/h2&gt;

&lt;p&gt;允許你通過轉換現有類型的&lt;strong&gt;屬性&lt;/strong&gt;來創建新的類型。&lt;/p&gt;

&lt;h2&gt;
  
  
  範例
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type MoviesByGenre = {
  action: 'Die Hard';
  comedy: 'Groundhog Day';
  sciFi: 'Blade Runner';
  fantasy: 'The Lord of the Rings: The Fellowship of the Ring';
  drama: 'The Shawshank Redemption';
  horror: 'The Shining';
  romance: 'Titanic';
  animation: 'Toy Story';
  thriller: 'The Silence of the Lambs';
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//透過映射來定義新型別MovieInfoByGenre
type MovieInfoByGenre&amp;lt;T&amp;gt; = {
        //**in**專為映射所使用的運算子，告訴TypeScript，K為**in**運算子右邊集合內的單一元素
        //**keyof T**: T物件所有鍵(Property)的聯集
    [K in keyof T]: {
        name: string;
        year: number;
        director: string;
    };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Example = MovieInfoByGenre&amp;lt;MoviesByGenre&amp;gt;;

/*Example為: {
    action: {
        name: string;
        year: number;
        director: string;
    };
    comedy: {
        name: string;
        year: number;
        director: string;
    };
    sciFi: {
        name: string;
        year: number;
        director: string;
    };
    fantasy: {
        name: string;
        year: number;
        director: string;
    };
    drama: {
        name: string;
        year: number;
        director: string;
    };
    horror: {
        name: string;
        year: number;
        director: string;
    };
    romance: {
        name: string;
        year: number;
        director: string;
    };
    animation: {
        name: string;
        year: number;
        director: string;
    };
    thriller: {
        name: string;
        year: number;
        director: string;
    };
  }*/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>typescript</category>
      <category>typehero</category>
    </item>
  </channel>
</rss>
