.NET 幫助

CQRS 模式 C#(它如何對開發者起作用)

Kannaopat Udonpant
坎納帕特·烏頓潘
2024年4月3日
分享:

CQRS簡介

CQRS 代表命令查詢責任分離。 這是一個專注於將讀取數據與寫入數據分離的模式。 這一區分出於多個原因而顯得尤為重要。 首先,它允許對每個操作進行更靈活的優化,提高應用程式的性能和可擴展性。 當您將命令(寫入)與查詢(讀取)分開時,可以分別優化它們。

例如,一個複雜的應用程式可能需要快速的讀取操作,但可以容忍較慢的寫入操作。 通過應用 CQRS,開發者可以對讀取和寫入使用不同的數據模型,將數據訪問層分隔開來,以滿足每個操作的具體需求。 在本文中,我們將探討 CQRS 模式的概念以及適用於 .NET 開發人員的 IronPDF 函式庫

核心概念和元件

CQRS 的核心在於將命令操作和查詢操作分開,分別處理數據交互的不同方面。 了解這些組件對於有效地實施模式至關重要。

  • 指令:這些負責更新數據。 命令包含複雜的業務邏輯,能夠在不返回任何資訊的情況下通過操作來改變資料存儲庫中的資料狀態。 命令專門負責處理寫入數據任務,直接影響應用程式的狀態而不產生任何輸出。 例如,新增用戶或更新現有的產品詳情是由指令執行的操作。
  • 查詢:查詢由查詢處理程式管理,旨在檢索數據或數據傳輸對象,而不更改系統的狀態。 它們是您對數據提出的問題。 例如,查詢用戶的個人資料或列出庫存中所有可用產品都是查詢。 查詢返回數據,但確保它們不會修改數據或其狀態。

    在 .NET 應用程序中實現 CQRS 的熱門工具之一是 MediatR,一個中介者模式庫。 它有助於減少應用程式元件之間的耦合,使它們間接交流。 MediatR 通過在命令/查詢和其處理器之間進行調解,促進命令和查詢的處理。

使用 ASP.NET Core 的實際實施

在 ASP.NET Core 中實現 CQRS 模式需要設置項目以分離命令和查詢,並使用類似 MediatR 的庫來進行中介。 以下是如何在您的 ASP.NET Core 應用程式中設置 CQRS 的簡化概述。

步驟 1:設定您的 ASP.NET 應用程式

  1. 啟動 Visual Studio 並選擇建立一個新專案。

  2. 搜尋並選擇「ASP.NET Core Web 應用程式」專案類型。 點擊下一步。

    CQRS 模式 C#(如何為開發人員運作):圖 1 - 建立一個新的 ASP.NET 專案

  3. 為您的專案命名並設置其位置。 點擊建立。

  4. 選擇 ASP.NET Core 的「Web 應用程式(模型-視圖-控制器)」範本。 確保您選擇適合您需求的 .NET Core 版本。 點擊建立。

第2步

接下來,您將需要為 CQRS 組織您的專案。 您可以透過新增資料夾來分隔命令、查詢及其將使用的通用介面來實現這一點。 在解決方案總管中,右鍵點擊您的專案,選擇「新增」,然後選擇「新資料夾」。 創建三個資料夾:“Commands”、“Queries”和“Interfaces”。

在「Interfaces」資料夾中,為您的命令和查詢新增介面。 對於一個命令,你可能會有一個介面ICommandHandler,其中有一個方法Handle,這個方法接收命令並執行動作。 對於查詢,您可以有一個介面IQueryHandler,其包含一個方法Handle,該方法接收查詢並返回資料。

CQRS Pattern C#(對開發者的運作方式):圖 2 - 檔案組織範例

步驟 3

現在,讓我們添加一個命令和查詢來演示。 假設您的應用程式管理任務,並且您想添加任務(命令)和檢索任務(查詢)。

在「Interfaces」資料夾中,新增兩個介面:

//Define interfaces for your handlers:
public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}
public interface IQueryHandler<TQuery, TResult>
{
    TResult Handle(TQuery query);
}
//Define interfaces for your handlers:
public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}
public interface IQueryHandler<TQuery, TResult>
{
    TResult Handle(TQuery query);
}
'Define interfaces for your handlers:
Public Interface ICommandHandler(Of TCommand)
	Sub Handle(ByVal command As TCommand)
End Interface
Public Interface IQueryHandler(Of TQuery, TResult)
	Function Handle(ByVal query As TQuery) As TResult
End Interface
$vbLabelText   $csharpLabel

在「Commands」資料夾中,新增一個類別AddItemCommand,具有用於任務詳細資訊的屬性。 另外,新增一個類別 AddItemCommandHandler,實作 ICommandHandler,並包含將任務新增至資料庫的邏輯。

在「Queries」資料夾中,新增一個類別GetTasksQuery,該類別代表一個任務請求。 新增另一個類別 GetTasksQueryHandler,實作 IQueryHandler,並包含從資料庫獲取任務的邏輯。

舉例來說,您的 AddItemCommand 可能如下所示:

public class AddItemCommand
{
    public string Name { get; set; }
    public int Quantity { get; set; }
    // Constructor
    public AddItemCommand(string name, int quantity)
    {
        Name = name;
        Quantity = quantity;
    }
}
public class AddItemCommand
{
    public string Name { get; set; }
    public int Quantity { get; set; }
    // Constructor
    public AddItemCommand(string name, int quantity)
    {
        Name = name;
        Quantity = quantity;
    }
}
Public Class AddItemCommand
	Public Property Name() As String
	Public Property Quantity() As Integer
	' Constructor
	Public Sub New(ByVal name As String, ByVal quantity As Integer)
		Me.Name = name
		Me.Quantity = quantity
	End Sub
End Class
$vbLabelText   $csharpLabel

以及AddItemCommandHandler

public class AddItemCommandHandler : ICommandHandler<AddItemCommand>
{
    public void Handle(AddItemCommand command)
    {
        // Here, you'd add the item to your database, for example, to have employee data stored
        Console.WriteLine($"Adding item: {command.Name} with quantity {command.Quantity}");
        // Add database logic here
    }
}
public class AddItemCommandHandler : ICommandHandler<AddItemCommand>
{
    public void Handle(AddItemCommand command)
    {
        // Here, you'd add the item to your database, for example, to have employee data stored
        Console.WriteLine($"Adding item: {command.Name} with quantity {command.Quantity}");
        // Add database logic here
    }
}
Public Class AddItemCommandHandler
	Implements ICommandHandler(Of AddItemCommand)

	Public Sub Handle(ByVal command As AddItemCommand)
		' Here, you'd add the item to your database, for example, to have employee data stored
		Console.WriteLine($"Adding item: {command.Name} with quantity {command.Quantity}")
		' Add database logic here
	End Sub
End Class
$vbLabelText   $csharpLabel

如果您的GetItemsQuery不需要任何參數來獲取任務,它可能是空的,而GetItemsQueryHandler可能看起來像:

public class GetItemsQuery
{
    // This class might not need any properties, depending on your query
}
using CQRS_testing.Interfaces;
namespace CQRS_testing.Queries
{
    public class GetItemsQueryHandler : IQueryHandler<GetItemsQuery, IEnumerable<string>>
    {
        public IEnumerable<string> Handle(GetItemsQuery query)
        {
            // Here, you'd fetch items from your database
            return new List<string> { "Item1", "Item2" };
        }
    }
}
public class GetItemsQuery
{
    // This class might not need any properties, depending on your query
}
using CQRS_testing.Interfaces;
namespace CQRS_testing.Queries
{
    public class GetItemsQueryHandler : IQueryHandler<GetItemsQuery, IEnumerable<string>>
    {
        public IEnumerable<string> Handle(GetItemsQuery query)
        {
            // Here, you'd fetch items from your database
            return new List<string> { "Item1", "Item2" };
        }
    }
}
Imports CQRS_testing.Interfaces

Public Class GetItemsQuery
	' This class might not need any properties, depending on your query
End Class
Namespace CQRS_testing.Queries
	Public Class GetItemsQueryHandler
		Implements IQueryHandler(Of GetItemsQuery, IEnumerable(Of String))

		Public Function Handle(ByVal query As GetItemsQuery) As IEnumerable(Of String)
			' Here, you'd fetch items from your database
			Return New List(Of String) From {"Item1", "Item2"}
		End Function
	End Class
End Namespace
$vbLabelText   $csharpLabel

在您的 ASP.NET 控制器中,您將使用這些處理程序來處理命令和查詢。 為了增加任務,控制器操作會創建一個AddTaskCommand,從表單數據設置其屬性,然後將其傳遞給一個AddTaskCommandHandler實例來處理。 為了檢索任務,它會調用GetTasksQueryHandler來獲取數據並將其傳遞給視圖。

在控制器中進行連線

設定好您的命令和查詢後,現在可以在控制器中使用它們。 以下是在ItemsController類別中可能的做法:

public class ItemsController : Controller
{
    private readonly ICommandHandler<AddItemCommand> _addItemHandler;
    private readonly IQueryHandler<GetItemsQuery, IEnumerable<string>> _getItemsHandler;
    // Constructor injection is correctly utilized here
    public ItemsController(ICommandHandler<AddItemCommand> addItemHandler, IQueryHandler<GetItemsQuery, IEnumerable<string>> getItemsHandler)
    {
        _addItemHandler = addItemHandler;
        _getItemsHandler = getItemsHandler;
    }
    public IActionResult Index()
    {
        // Use the injected _getItemsHandler instead of creating a new instance
        var query = new GetItemsQuery();
        var items = _getItemsHandler.Handle(query);
        return View(items);
    }
    [HttpPost]
    public IActionResult Add(string name, int quantity)
    {
        // Use the injected _addItemHandler instead of creating a new instance
        var command = new AddItemCommand(name, quantity);
        _addItemHandler.Handle(command);
        return RedirectToAction("Index");
    }
}
public class ItemsController : Controller
{
    private readonly ICommandHandler<AddItemCommand> _addItemHandler;
    private readonly IQueryHandler<GetItemsQuery, IEnumerable<string>> _getItemsHandler;
    // Constructor injection is correctly utilized here
    public ItemsController(ICommandHandler<AddItemCommand> addItemHandler, IQueryHandler<GetItemsQuery, IEnumerable<string>> getItemsHandler)
    {
        _addItemHandler = addItemHandler;
        _getItemsHandler = getItemsHandler;
    }
    public IActionResult Index()
    {
        // Use the injected _getItemsHandler instead of creating a new instance
        var query = new GetItemsQuery();
        var items = _getItemsHandler.Handle(query);
        return View(items);
    }
    [HttpPost]
    public IActionResult Add(string name, int quantity)
    {
        // Use the injected _addItemHandler instead of creating a new instance
        var command = new AddItemCommand(name, quantity);
        _addItemHandler.Handle(command);
        return RedirectToAction("Index");
    }
}
Public Class ItemsController
	Inherits Controller

	Private ReadOnly _addItemHandler As ICommandHandler(Of AddItemCommand)
	Private ReadOnly _getItemsHandler As IQueryHandler(Of GetItemsQuery, IEnumerable(Of String))
	' Constructor injection is correctly utilized here
	Public Sub New(ByVal addItemHandler As ICommandHandler(Of AddItemCommand), ByVal getItemsHandler As IQueryHandler(Of GetItemsQuery, IEnumerable(Of String)))
		_addItemHandler = addItemHandler
		_getItemsHandler = getItemsHandler
	End Sub
	Public Function Index() As IActionResult
		' Use the injected _getItemsHandler instead of creating a new instance
		Dim query = New GetItemsQuery()
		Dim items = _getItemsHandler.Handle(query)
		Return View(items)
	End Function
	<HttpPost>
	Public Function Add(ByVal name As String, ByVal quantity As Integer) As IActionResult
		' Use the injected _addItemHandler instead of creating a new instance
		Dim command = New AddItemCommand(name, quantity)
		_addItemHandler.Handle(command)
		Return RedirectToAction("Index")
	End Function
End Class
$vbLabelText   $csharpLabel

為了將所有內容連接起來,尤其是如果您在 ASP.NET Core 中使用依賴注入 (DI),您需要在 Startup.cs 文件中將您的命令和查詢處理程序註冊到 DI 容器中。這樣,當需要時,ASP.NET 可以提供您的處理程序實例。

以下是註冊處理程式的一個非常基本的範例:

builder.Services.AddTransient<ICommandHandler<AddItemCommand>, AddItemCommandHandler>();
builder.Services.AddTransient<IQueryHandler<GetItemsQuery, IEnumerable<string>>, GetItemsQueryHandler>();
builder.Services.AddTransient<ICommandHandler<AddItemCommand>, AddItemCommandHandler>();
builder.Services.AddTransient<IQueryHandler<GetItemsQuery, IEnumerable<string>>, GetItemsQueryHandler>();
builder.Services.AddTransient(Of ICommandHandler(Of AddItemCommand), AddItemCommandHandler)()
builder.Services.AddTransient(Of IQueryHandler(Of GetItemsQuery, IEnumerable(Of String)), GetItemsQueryHandler)()
$vbLabelText   $csharpLabel

在實際應用CQRS時,寫操作和讀操作的数据模型之間的區別是基礎,確保架構支持多樣化和優化的數據處理方法。

IronPDF:C# PDF 庫

CQRS Pattern C# (對開發人員的工作原理):圖 3 - IronPDF 網頁

探索 IronPDF 以進行 PDF 管理 是一種供使用 C# 程式語言的開發者使用的工具,允許他們直接在應用程式中創建、讀取和編輯 PDF 文件。 這個函式庫使用者友好,使得整合 PDF 功能變得更加簡單,例如生成 PDF 報告、發票或從 HTML 建立 PDF 代碼。 IronPDF 支援多種功能,包括編輯 PDF 中的文字和圖像、設置文件安全性以及將網頁轉換為 PDF 格式。 它的多功能性和易用性使其成為開發人員在其專案中實現 PDF 操作的寶貴資源。

IronPDF 以其HTML 到 PDF 轉換能力而著稱,保持所有佈局和樣式完好無損。 它從網頁內容生成PDF,適合用於報告、發票和文件。 HTML 檔案、URL 及 HTML 字串都可以無縫地轉換成 PDF。

using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
Imports IronPdf

Friend Class Program
	Shared Sub Main(ByVal args() As String)
		Dim renderer = New ChromePdfRenderer()

		' 1. Convert HTML String to PDF
		Dim htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>"
		Dim pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent)
		pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf")

		' 2. Convert HTML File to PDF
		Dim htmlFilePath = "path_to_your_html_file.html" ' Specify the path to your HTML file
		Dim pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath)
		pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf")

		' 3. Convert URL to PDF
		Dim url = "http://ironpdf.com" ' Specify the URL
		Dim pdfFromUrl = renderer.RenderUrlAsPdf(url)
		pdfFromUrl.SaveAs("URLToPDF.pdf")
	End Sub
End Class
$vbLabelText   $csharpLabel

範例程式碼

現在,我們來探討如何在遵循命令查詢責任分離(CQRS)模式的 C# 應用程式中使用 IronPDF。 以下是一個簡化的範例,展示如何在 CQRS 設置中使用 IronPDF 生成 PDF 報告。 此範例是概念性的,聚焦於將生成 PDF 文件作為一個指令。

using IronPdf;
using System.Threading.Tasks;
namespace PdfGenerationApp.Commands
{
    public class GeneratePdfReportCommand
    {
        // Command handler that generates a PDF report
        public async Task GenerateReportAsync(string reportContent, string outputPath)
        {
            // Initialize the IronPDF HTML to PDF renderer
            var renderer = new ChromePdfRenderer();
            // Use IronPDF to generate a PDF from HTML content
            var pdf = await Task.Run(() => renderer.RenderHtmlAsPdf(reportContent));
            // Save the generated PDF to a specified path
            pdf.SaveAs(outputPath);
        }
    }
}
using IronPdf;
using System.Threading.Tasks;
namespace PdfGenerationApp.Commands
{
    public class GeneratePdfReportCommand
    {
        // Command handler that generates a PDF report
        public async Task GenerateReportAsync(string reportContent, string outputPath)
        {
            // Initialize the IronPDF HTML to PDF renderer
            var renderer = new ChromePdfRenderer();
            // Use IronPDF to generate a PDF from HTML content
            var pdf = await Task.Run(() => renderer.RenderHtmlAsPdf(reportContent));
            // Save the generated PDF to a specified path
            pdf.SaveAs(outputPath);
        }
    }
}
Imports IronPdf
Imports System.Threading.Tasks
Namespace PdfGenerationApp.Commands
	Public Class GeneratePdfReportCommand
		' Command handler that generates a PDF report
		Public Async Function GenerateReportAsync(ByVal reportContent As String, ByVal outputPath As String) As Task
			' Initialize the IronPDF HTML to PDF renderer
			Dim renderer = New ChromePdfRenderer()
			' Use IronPDF to generate a PDF from HTML content
			Dim pdf = Await Task.Run(Function() renderer.RenderHtmlAsPdf(reportContent))
			' Save the generated PDF to a specified path
			pdf.SaveAs(outputPath)
		End Function
	End Class
End Namespace
$vbLabelText   $csharpLabel

在此範例中,GeneratePdfReportCommand 代表 CQRS 模式中的一個命令。 它包含一個方法 GenerateReportAsync,該方法接受reportContent作為HTML字符串及outputPath作為PDF報告保存的位置。 IronPDF 的 HtmlToPdf 類別用來將 HTML 內容轉換為 PDF 格式,然後保存到指定路徑。 此設置說明了如何將 PDF 生成功能整合到應用程式的架構中,特別是在需要明確分離關注點的 CQRS 案例中。

CQRS Pattern C# (開發人員如何使用):圖 4 - 輸出 PDF

結論

CQRS 模式 C#(它如何為開發者運作):圖 5 - IronPDF 授權資訊

總結,命令查詢責任分離(CQRS)模式提供一種結構化的方法來分離應用程式中讀取和寫入資料的責任。 這種分離不僅澄清了架構,還增強了系統的靈活性、可擴展性和性能。 按照上述步驟操作,您可以在 ASP.NET Core 應用程式中實現 CQRS,使用像 MediatR 這樣的工具來簡化命令、查詢及其處理器之間的通信。

將 IronPDF 整合到基於 CQRS 的應用中,可以進一步擴展其功能,使您能夠輕鬆創建、操作和存儲 PDF 文件。 無論您是在生成報告、發票或任何形式的文件,IronPDF 的綜合功能和簡單明了的語法讓它成為您開發工具包中強大的工具。 IronPDF 提供免費試用,讓您在訂購前可先探索其功能。 若要繼續使用,授權費用從$749起,提供多種選項以配合您的專案需求。

Kannaopat Udonpant
坎納帕特·烏頓潘
軟體工程師
在成為軟體工程師之前,Kannapat 在日本北海道大學完成了環境資源博士學位。在攻讀學位期間,Kannapat 也成為了車輛機器人實驗室的成員,該實驗室隸屬於生物生產工程學系。2022 年,他利用自己的 C# 技能,加入了 Iron Software 的工程團隊,專注於 IronPDF 的開發。Kannapat 珍視這份工作,因為他可以直接向負責撰寫大部分 IronPDF 程式碼的開發人員學習。除了同儕學習外,Kannapat 還享受在 Iron Software 工作的社交方面。當他不在撰寫程式碼或文件時,Kannapat 通常會在 PS5 上玩遊戲或重看《最後生還者》。
< 上一頁
在 C# 中(對開發人員的運作方式)
下一個 >
C# 單元測試(開發者如何運作)