.NET 幫助

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

發佈 2024年4月3日
分享:

CQRS 介紹

CQRS 代表指令查詢責任分離。這是一種專注於將數據讀取與寫入分離的模式。這種區分對於多種原因來說至關重要。首先,它允許對每個操作進行更靈活的優化,提高應用程式的性能和可擴展性。當你分離指令 (寫入) 和查詢 (讀取),你可以獨立地優化它們。

例如,一個複雜的應用程式可能需要快速的讀取操作,但可以容忍較慢的寫入操作。通過應用 CQRS 模式,開發人員可以為讀取和寫入操作使用不同的資料模型,將資料訪問層進行分離,以適應每個操作的具體需求。在本文中,我們將探討 CQRS 模式的概念和 IronPDF 圖書館

核心概念和組件

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

  • 命令:這些負責更新數據。命令包含複雜的業務邏輯,並且可以通過操作而不返回任何信息來改變數據存儲中的數據狀態。命令承擔唯一的寫數據任務,直接影響應用程式的狀態而不產生任何輸出。例如,添加新用戶或更新現有產品詳情是由命令執行的動作。
  • 查詢:查詢由查詢處理器管理,檢索數據或數據傳輸對象而不改變系統的狀態。它們是你對數據提出的問題。例如,獲取用戶的配置檔或列出庫存中所有可用產品是查詢。查詢返回數據,但保證不修改數據或其狀態。

在 .NET 應用中實施 CQRS 的熱門工具之一是 MediatR,一個中介者模式庫。它有助於減少應用程序組件之間的耦合,使它們間接通信。MediatR 透過在命令/查詢與其處理器之間進行仲裁來促進命令和查詢的處理。

使用 ASP.NET Core 的實際實現

在 ASP.NET Core 中實現 CQRS 模式涉及設置您的項目以分離命令和查詢,使用像 MediatR 這樣的庫在它們之間進行調解。以下是一個簡化的概述,介紹如何在您的 ASP.NET Core 應用程式中設置 CQRS。

第一步:設置您的 ASP.NET 應用程序

  1. 啟動 Visual Studio 並選擇創建一個新項目。

  2. 搜尋並選擇「ASP.NET Core Web 應用程序」項目類型。然後點擊下一步。

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

  1. 為您的項目命名並設置位置。點擊創建。

  2. 選擇 "Web Application" (模型-視圖-控制器)適用於 ASP.NET Core 的範本。確保您正在目標對應到符合您需求的 .NET Core 版本。點擊建立。

步驟2

接下來,您需要為 CQRS 組織您的專案。您可以通過添加文件夾來分離命令、查詢與它們將使用的通用介面來完成此操作。在解決方案資源管理器中,右鍵單擊您的專案,選擇「新增」,然後選擇「新文件夾」。創建三個文件夾:「Commands」、「Queries」和「Interfaces」。

在「Interfaces」文件夾中,為您的命令和查詢添加介面。對於命令,您可能擁有一個介面 ICommandHandler,其中包含一個方法 Handle,該方法接受命令並執行操作。對於查詢,您可以擁有一個介面 IQueryHandler,其中包含一個方法 Handle,該方法接受查詢並返回數據。

CQRS 模式 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
VB   C#

在「Commands」資料夾中,新增一個包含任務細節屬性的類別 AddItemCommand。同時新增一個實作 ICommandHandler 並包含將任務添加到資料庫邏輯的類別 AddItemCommandHandler

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

以下是一個簡單的範例,你的 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
VB   C#

以及 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
VB   C#

您的 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
VB   C#

在您的 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
VB   C#

若要將所有內容連接在一起,特別是如果您使用依賴注入 (數字輸入) 在 ASP.NET Core 中,您需要在 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)()
VB   C#

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

IronPDF:C# PDF 庫

CQRS 模式 C# (對開發者的工作原理): 圖 3 - IronPDF 網頁

IronPDF 是一個用於C#程式語言開發者的工具,允許他們在應用程式中直接創建、閱讀和編輯PDF文件。這個庫使用方便,簡化了PDF功能的整合,比如生成PDF報告、發票或 HTML表單 程式碼。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
VB   C#

代碼範例

現在,讓我們探討如何在遵循命令查詢責任分離 (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
VB   C#

在此範例中,GeneratePdfReportCommand 代表 CQRS 模式中的一個命令。它包含一個方法 GenerateReportAsync,該方法接受 reportContent 作為 HTML 字串,以及 outputPath 作為 PDF 報告的保存路徑。使用 IronPDF 的 HtmlToPdf 類來將 HTML 內容轉換為 PDF 格式,然後保存到指定路徑。這個配置說明了如何將 PDF 生成功能整合到應用程式的架構中,特別是在需要明確劃分責任的情況下,如 CQRS 所倡導的。

CQRS 模式 C#(如何運作給開發者):圖 4 - 輸出的 PDF

結論

 CQRS 模式 C#(它如何為開發人員工作):圖 5 - IronPDF 授權頁面

總結,指令查詢責任分離 (Command Query Responsibility Segregation) (命令查詢責任分離) 模式提供了一種結構化的方法來分離應用程式中讀寫資料的職責。這種分離不僅能使架構更加清晰,還能提升系統的靈活性、可擴展性和性能。通過遵循上述步驟,您可以在ASP.NET Core應用程式中實現CQRS,並使用如MediatR這樣的工具來簡化指令、查詢及其處理器之間的通信。

將IronPDF整合到基於CQRS的應用程式中,可以進一步擴展其功能,使您能夠輕鬆地創建、操作和存儲PDF文檔。無論您是在生成報告、發票,還是任何形式的文檔,IronPDF的豐富功能和簡單語法,使其成為您開發工具包中的強大工具。IronPDF提供 免費試用,讓您在承諾之前有機會探索其功能。若要繼續使用,許可證價格從 $749 起,提供多種選項以配合您的項目需求。

< 上一頁
在 C# 中(對開發人員的運作方式)
下一個 >
C# 單元測試(開發者如何運作)

準備開始了嗎? 版本: 2024.10 剛剛發布

免費 NuGet 下載 總下載次數: 10,993,239 查看許可證 >