2026年2月26日 星期四

C# 14與.NET10新功能 - 2

 

這是《C# 14與.NET10新功能》系列文章的第二篇,我們將介紹.NET 10在ASP.NET Core網頁與資料存取的重點新功能以及如何利用GitHub Copilot應用程式現代化(GitHub Copilot app modernization)升級舊專案。

網頁與資料存取升級

.NET 10版不僅僅關注底層執行效能,也為開發者日常使用的應用程式框架帶來了許多更實用的更新。讓我們先來看看如何使用ASP.NET Core 10 Entity Framework Core 10 的新功能來打造一個現代化的安全應用程式。

ASP.NET Core 10:支援利用通行密鑰(Passkey)登入

ASP.NET Core 10支援利用通行密鑰 Passkey)登入網站。想像一下,若您正在開發一個網站管理系統。首先,您需要一個安全的方式來讓管理員登入,然後才可查詢所有使用者、產品等相關資料。ASP.NET Core 10現在可以使用通行密鑰(Passkey)實現無密碼登入,有時為了提升管理後台的安全性,您可以放棄傳統的密碼登入方式,改用通行密鑰(Passkey)這是一種以WebAuthn 標準為基礎的無密碼技術,使用者可以透過指紋、臉部辨識或 PIN 碼安全登入,既方便又極大地降低了帳號被盜的風險。

 

ASP.NET Core 10 透過 ASP.NET Core 的身分識別 APIASP.NET Core Identity API內建對 Passkey 的支援,讓您可以輕鬆地為網站或應用程式加入這項現代化的安全功能,而無需處理複雜的底層加密細節。

Entity Framework Core 10:使用原生 LEFT JOIN 簡化資料查詢

EF Core 10 終於引入了對 LEFT JOIN RIGHT JOIN 的原生支援。這是一個期待已久的功能,它讓處理這類關聯式資料的查詢變得極為直觀,程式碼也會變得更為簡潔、更易於維護。讓我們來透過一個使用Entity Framework Core 10Sqlite資料庫的主控台應用程式來說明。

 

使用Visual Studio 2026開發工具建立一個主控台專案(Console App),然後使用NuGet安裝「Microsoft.EntityFrameworkCore」與「Microsoft.EntityFrameworkCore.Sqlite」套件:

Install-Package Microsoft.EntityFrameworkCore

Install-Package Microsoft.EntityFrameworkCore.Sqlite


EF Core 10JOIN的語法非常接近 SQL,您可以寫得幾乎像 SQL,但仍然保持 LINQ 語意,在「Program.cs」檔案加入以下程式碼,查詢資料庫中所有產品(Products),並把其所屬的類(Categories)名稱一起帶出。若某些產品沒有對應的分類(NULL),依然要顯示產品資訊,這就是 LEFT JOIN

using System;

using Microsoft.EntityFrameworkCore;

 

// 定義Product實體

public class Product

{

    public int ProductId { get; set; }

    public string ProductName { get; set; } = string.Empty;

    public int? CategoryId { get; set; }

}

// 定義Category實體

public class Category

{

    public int CategoryId { get; set; }

    public string CategoryName { get; set; } = string.Empty;

}

 

// 定義 DbContext

public class NorthwindContext : DbContext

{

    // 定義 DbSet 屬性,代表Product資料表

    public DbSet<Product> Products => Set<Product>();

    // 定義 DbSet 屬性,代表Category資料表

    public DbSet<Category> Categories => Set<Category>();

 

    // 設定資料庫連線(此處使用 SQLite 檔案)

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

    {

        // 使用 EF Core SQLite 提供者,資料檔案為 northwind.db

        optionsBuilder.UseSqlite("Data Source=northwind.db");

    }

 

    protected override void OnModelCreating(ModelBuilder modelBuilder)

    {

        // 設定Product主鍵為ProductId

        modelBuilder.Entity<Product>().HasKey(p => p.ProductId);

        // 設定Category主鍵為CategoryId

        modelBuilder.Entity<Category>().HasKey(c => c.CategoryId);

    }

}

 

public class Program

{

    public static void Main()

    {

        // 使用 SQLite 範例:如有舊資料庫先刪除,然後建立並寫入範例資料

        using var ctx = new NorthwindContext();

 

        // 刪除舊資料庫並建立新資料表

        ctx.Database.EnsureDeleted();

        ctx.Database.EnsureCreated();

 

        // 寫入Category分類資料

        ctx.Categories.AddRange(

            new Category { CategoryId = 1, CategoryName = "Beverages" },

            new Category { CategoryId = 2, CategoryName = "Condiments" },

            new Category { CategoryId = 3, CategoryName = "Confections" }

        );

 

        // 寫入Product產品資料(部分 CategoryId null,用於示範 LEFT JOIN

        ctx.Products.AddRange(

            new Product { ProductId = 1, ProductName = "Chai", CategoryId = 1 },

            new Product { ProductId = 2, ProductName = "Chang", CategoryId = 1 },

            new Product { ProductId = 3, ProductName = "Aniseed Syrup", CategoryId = 2 },

            new Product { ProductId = 4, ProductName = "Chef Anton's Cajun Seasoning", CategoryId = 2 },

            new Product { ProductId = 5, ProductName = "Grandma's Boysenberry Spread", CategoryId = 2 },

            new Product { ProductId = 6, ProductName = "Uncategorized Sample Product", CategoryId = null }

        );

        ctx.SaveChanges();

 

        // 使用原生 LEFT JOINLINQ 查詢語法),EF Core 會轉譯為 SQL LEFT JOIN

        var query = (

            from p in ctx.Products

            join c in ctx.Categories on p.CategoryId equals c.CategoryId into gj

            from c in gj.DefaultIfEmpty() // LEFT JOIN:包含沒有對應類別的產品

            orderby p.ProductId

            select new

            {

                p.ProductId,

                p.ProductName,

                CategoryName = c != null ? c.CategoryName : null

            }

        );

 

        // 輸出產生的 SQL 查詢語句(SQLite 會產生 SQL

        try

        {

            Console.WriteLine(query.ToQueryString());

        }

        catch

        {

            // 若無法產生 SQL 字串則忽略

            Console.WriteLine("無法產生查詢的 SQL 字串。");

        }

 

 

        Console.WriteLine();

        Console.WriteLine("Product List :");

        // 執行查詢並輸出結果

        foreach (var row in query.ToList())

        {

            Console.WriteLine($"#{row.ProductId} {row.ProductName} | Category: {row.CategoryName ?? "(無分類)"}");

        }

    }

}


執行這個範例,LINQ 查詢的LEFT JOIN語法所產生的SQL如下:

SELECT "p"."ProductId", "p"."ProductName", "c"."CategoryName"

FROM "Products" AS "p"

LEFT JOIN "Categories" AS "c" ON "p"."CategoryId" = "c"."CategoryId"

ORDER BY "p"."ProductId"


這個範例執行的結果請參考下圖所示:

1Left Join範例

 

Right Join

LINQ 本身不支援 RIGHT JOIN功能,下面示範如何在 EF Core / LINQ中「模擬 RIGHT JOIN」語法。簡單的說,從想要保留全部列的那一邊開始做 LEFT JOIN(也就是把原本的左右資料表互換):

RIGHT JOIN = 保留右表所有列

因此在LINQ語法中等同於從右邊的資料表(此處為 Categories)出發,對左邊的資料表做 LEFT JOIN

EF Core 會把這種寫法轉譯成 LEFT JOIN(從 Categories Products),執行的結果將會與 SQL RIGHT JOIN 相同。

以下C#程式碼,改寫上例程式,模擬RIGHT JOIN,把查詢改成從「Categories」開始做 LEFT JOIN 到「Products」(等同於 Products RIGHT JOIN Categories):

using System;

using Microsoft.EntityFrameworkCore;

 

// 定義Product實體

public class Product

{

    public int ProductId { get; set; }

    public string ProductName { get; set; } = string.Empty;

    public int? CategoryId { get; set; }

}

// 定義Category實體

public class Category

{

    public int CategoryId { get; set; }

    public string CategoryName { get; set; } = string.Empty;

}

 

// 定義 DbContext

public class NorthwindContext : DbContext

{

    // 定義 DbSet 屬性,代表Product資料表

    public DbSet<Product> Products => Set<Product>();

    // 定義 DbSet 屬性,代表Category資料表

    public DbSet<Category> Categories => Set<Category>();

 

    // 設定資料庫連線(此處使用 SQLite 檔案)

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

    {

        // 使用 EF Core SQLite 提供者,資料檔案為 northwind.db

        optionsBuilder.UseSqlite("Data Source=northwind.db");

    }

 

    protected override void OnModelCreating(ModelBuilder modelBuilder)

    {

        // 設定Product主鍵為ProductId

        modelBuilder.Entity<Product>().HasKey(p => p.ProductId);

        // 設定Category主鍵為CategoryId

        modelBuilder.Entity<Category>().HasKey(c => c.CategoryId);

    }

}

 

public class Program

{

    public static void Main()

    {

        // 使用 SQLite 範例:如有舊資料庫先刪除,然後建立並寫入範例資料

        using var ctx = new NorthwindContext();

 

        // 刪除舊資料庫並建立新資料表

        ctx.Database.EnsureDeleted();

        ctx.Database.EnsureCreated();

 

        // 寫入Category分類資料

        ctx.Categories.AddRange(

            new Category { CategoryId = 1, CategoryName = "Beverages" },

            new Category { CategoryId = 2, CategoryName = "Condiments" },

            new Category { CategoryId = 3, CategoryName = "Confections" }

        );

 

        // 寫入Product產品資料(部分 CategoryId null,用於示範 JOIN

        ctx.Products.AddRange(

            new Product { ProductId = 1, ProductName = "Chai", CategoryId = 1 },

            new Product { ProductId = 2, ProductName = "Chang", CategoryId = 1 },

            new Product { ProductId = 3, ProductName = "Aniseed Syrup", CategoryId = 2 },

            new Product { ProductId = 4, ProductName = "Chef Anton's Cajun Seasoning", CategoryId = 2 },

            new Product { ProductId = 5, ProductName = "Grandma's Boysenberry Spread", CategoryId = 2 },

            new Product { ProductId = 6, ProductName = "Uncategorized Sample Product", CategoryId = null }

        );

        ctx.SaveChanges();

 

        // 模擬 RIGHT JOINLINQ 不支援 RIGHT JOIN):從 Categories 開始 LEFT JOIN Products

        var query = (

            from c in ctx.Categories

            join p in ctx.Products on c.CategoryId equals p.CategoryId into gj

            from p in gj.DefaultIfEmpty() // LEFT JOIN:包含沒有對應產品的類別(等同於 Products RIGHT JOIN Categories

            orderby c.CategoryId, p.ProductId

            select new

            {

                CategoryId = c.CategoryId,

                CategoryName = c.CategoryName,

                ProductId = p != null ? p.ProductId : (int?)null,

                ProductName = p != null ? p.ProductName : null

            }

        );

 

        // 輸出產生的 SQL 查詢語句(SQLite 會產生 SQL

        try

        {

            Console.WriteLine(query.ToQueryString());

        }

        catch

        {

            // 若無法產生 SQL 字串則忽略

            Console.WriteLine("無法產生查詢的 SQL 字串。");

        }

 

 

        Console.WriteLine();

        Console.WriteLine("Category with Products (模擬 RIGHT JOIN) :");

        // 執行查詢並輸出結果

        foreach (var row in query.ToList())

        {

            Console.WriteLine($"Category #{row.CategoryId} {row.CategoryName} | Product: #{row.ProductId?.ToString() ?? "(無產品)"} {row.ProductName ?? ""}");

        }

    }

}


這個範例執行的結果請參考下圖所示:

2Right Join範例

 

 

GitHub Copilot應用程式現代化(GitHub Copilot app modernization

應用程式現代化之所以重要,是因為使用較舊的框架可能會導致安全風險、影響效能,並使應用程式難以擴展或遷移到雲端。升級到新版本(例如 .NET 10版)可以帶來更好的執行時間效率、現代 API 和更佳的工具。

GitHub Copilot 應用程式現代化(GitHub Copilot app modernization)是一個內建於 Visual Studio 2026開發工具中的AI 工具。透過 AI 驅動的代理能力(agentic capabilities)。為開發人員提供自動化的支援,以快速、安全地將舊有的 .NET 應用程式升級到現代的、為雲端最佳化的 .NET 平台。

GitHub Copilot 應用程式現代化(GitHub Copilot app modernization的目標是將複雜的遷移過程從「可能需要數週」縮短到「只需幾個小時」,聽起來是不是令人驚豔?

 

GitHub Copilot 應用程式現代化工具的功能包括以下:

繁重工作自動化:引導您升級舊版應用程式到新版本,完成現代化的每一步,處理模糊不清的代碼問題,並自動執行許多繁瑣的升級步驟。

升級 .NET 框架:它支援將較舊的應用程式(例如 .NET Framework 4.8 專案)升級到最新的 .NET 版本(如 .NET 10)。它會產生升級計畫,詳細說明所有 NuGet 套件的更新,並將專案現代化到新的 SDK 風格。

 自動產生測試:GitHub Copilot 測試代理(Testing Agent)是這項現代化功能的一部分,專門為那些缺乏單元測試的舊應用程式自動產生測試程式碼。它能分析程式碼結構和專案設定,生成準確的測試程式作為程式碼可靠性的起點。

遷移到雲端的準備:無論您是升級到 .NET 10版還是準備遷移到雲端,此現代化工具都能提供協助。例如,它可以協助將應用程式從 Windows AD 驗證轉換為 Entra ID驗證。


使用範例

以一個.NET Framework 4.8版存取SQL Server資料庫的Windows Forms專案為例,在Visual Studio 2026開發工具開啟這個專案,然後從「方案總管」> 專案名稱上方按右鍵,選取「現代化」,請參考下圖所示:


3GitHub Copilot 應用程式現代化(GitHub Copilot app modernization


接下來會開啟GitHub Copilot Chat視窗,可以直接點選聊天介面中的「升級至較新版本的.NET」項目,然後按下下方的「傳送」按鈕,請參考下圖所示:

4:「升級至較新版本的.NET

 

聊天介面通常會在下方列出選項讓您選擇想繼續的動作,若沒有出現這些選項,可以直接在下方文字方塊輸入提示,要求升級到較新版本的.NET,請參考下圖所示:

5:接受升級設定並繼續

 

選擇聊天介面下方的「接受升級設定並繼續」項目,按下「送出 」按鈕,請參考下圖所示:

6:接受升級設定並繼續

 

接著稍候片刻,它就會開始建立升級計畫(Upgrade Plan),請參考下圖所示:

7:建立升級計畫(Upgrade Plan

 

然後開始進行升級,請參考下圖所示:

8:專案升級


根據專案的大小,升級所花費的時間也不同。

 

總結

本文章介紹 .NET 10 在「網頁應用程式安全性」與「資料存取層設計」上的重大升級,並結合 GitHub Copilot AI 現代化工具,說明如何讓開發者在效率與安全性之間取得平衡。整體來看,.NET 10 不僅是一個效能提升的版本,更是推動企業與開發者邁向雲端與無密碼時代的重要里程碑。

ASP.NET Core 10部分,全新支援的「通行密鑰(Passkey)登入」功能技術基於「WebAuthn」標準,讓使用者能透過指紋、臉部辨識或 PIN 碼進行無密碼登入,取代傳統帳密機制,大幅提升安全性與便利性。開發者可透過 ASP.NET Core Identity API 輕鬆整合 Passkey,無需手動處理複雜的金鑰與加密機制。這意味著網站與後台系統可更快速地導入企業級身分驗證,降低密碼洩漏風險,並提升使用者體驗。

其次,Entity Framework Core 10EF Core 10 的更新讓資料查詢更貼近真實 SQL 操作。它原生支援 LEFT JOIN RIGHT JOIN,開發者能使用簡潔的 LINQ 語法撰寫關聯查詢,輕鬆讀取包含空值(NULL)的資料列。我們以SQLite資料庫為範例展示如何透過 LINQ 撰寫 LEFT JOIN 取得所有產品及其分類,即使產品無分類仍能顯示,並進一步示範如何「模擬」 RIGHT JOIN,以保持語法一致性與可維護性。這項改進使 EF Core 更接近資料庫層的語意,並大幅減少手動撰寫 SQL 的需求。

最後,文章介紹 GitHub Copilot 應用程式現代化(App Modernization 工具。這是 Visual Studio 2026開發工具 內建的 AI 助理,透過智能代理(Agentic Capabilities)協助開發者自動化舊版應用程式升級流程。它能分析專案結構、生成升級計畫(Upgrade Plan)、更新 NuGet 套件、轉換至 SDK 風格專案,甚至能 自動產生測試程式 以補足缺乏單元測試的舊系統。此外,它也支援雲端遷移準備,例如將 Windows AD 驗證轉換為 Entra ID,幫助開發者快速導入現代化的雲端環境。這項工具能將傳統需要數週的升級流程縮短至數小時,讓開發團隊專注於功能創新而非重複性作業。

綜合而言,.NET 10EF Core 10 GitHub Copilot Modernization 形成一個完整的升級生態系:前端提升登入安全性,中層簡化資料查詢邏輯,後端則由 AI 自動協助重構與升級。這不僅展現出微軟推動開發自動化與安全現代化的決心,也讓開發者能更高效地打造可擴充、可維護、雲端導向的應用程式。


0 意見:

張貼留言