.NET 帮助

CQRS 模式 C#(开发人员的工作原理)

Chipego
奇佩戈-卡琳达
2024年四月3日
分享:

CQRS 简介

CQRS 代表命令查询职责分离。 这是一种侧重于将数据的读取与写入分离开来的模式。 这种区别至关重要,原因有以下几点。 首先,它可以更灵活地优化各项操作,提高应用程序的性能和可扩展性。 当您将命令(写操作)和查询(读操作)分开时,可以独立优化它们。

例如,一个复杂的应用程序可能需要快速的读取操作,但可以容忍较慢的写入操作。 通过应用 CQRS,开发人员可以为读取和写入使用不同的数据模型,隔离数据访问层,以满足每个操作的特定需求。 在本文中,我们将探讨CQRS模式的概念以及适用于.NET开发人员的IronPDF库

核心概念和组件

CQRS 的核心在于将命令和查询操作分开,各自处理数据交互的不同方面。 了解这些组件对于有效实施该模式至关重要。

  • 命令:这些负责更新数据。 命令体现了复杂的业务逻辑,可以通过不返回任何信息的方式改变数据存储中的数据状态。 命令专门负责处理写数据任务,直接影响应用程序的状态,而不产生任何输出。 例如,添加新用户或更新现有产品详细信息都是通过命令执行的操作。
  • 查询:查询由查询处理程序管理,用于获取数据或数据传输对象,而不会改变系统的状态。 它们是您对数据提出的问题。 例如,获取用户配置文件或列出库存中的所有可用产品都属于查询。 查询会返回数据,但要确保不会修改数据或其状态。

    MediatR 是在 .NET 应用程序中实施 CQRS 的常用工具之一,它是一个调解器模式库。 它有助于降低应用程序各组件之间的耦合度,使它们能够间接交流。 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 应用程序(Model-View-Controller)”模板。 确保您的目标是适合您要求的 .NET Core 版本。 单击创建。

第二步

接下来,您需要为 CQRS 组织您的项目。 为此,您可以添加文件夹,将命令、查询和常用界面分开。 在解决方案资源管理器中,右键单击您的项目,转到 "添加",再转到 "新建文件夹"。 创建三个文件夹:"命令"、"查询 "和 "界面"。

在 "接口 "文件夹中,为您的命令和查询添加接口。 对于一个命令,你可能有一个接口ICommandHandler,其中包含一个方法Handle,用于接收命令并执行操作。 对于查询,您可以拥有一个接口IQueryHandler,其中包含一个方法Handle,该方法接受查询并返回数据。

CQRS 模式 C#(它如何为开发人员工作):图 2 - 文件组织示例

步骤 3

现在,让我们添加一个命令和查询来进行演示。 假设您的应用程序管理任务,并且您想添加任务(命令)和检索任务(查询)。

在 "接口 "文件夹中添加两个接口:

//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类,并为任务详细信息设置属性。 另外,添加一个实现ICommandHandler的类AddItemCommandHandler,其中包含将任务添加到数据库的逻辑。

在“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 模式 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 字符串传入,以及 PDF 报告将被保存的 outputPath。 IronPDF 的HtmlToPdf类用于将 HTML 内容转换为 PDF 格式,然后保存到指定路径。 该设置说明了如何将 PDF 生成功能集成到应用程序的架构中,特别是在需要明确分离关注点的场景中,如 CQRS 所提倡的那样。

CQRS 模式 C#(开发人员如何使用):图 4 - 输出的 PDF

结论

CQRS 模式 C#(其工作原理对开发人员的影响):图 5 - IronPDF 许可证信息

总而言之,命令查询责任分离(CQRS)模式提供了一种结构化的方法,以在应用程序中分离读写数据的责任。 这种分离不仅可以明确架构,还可以增强系统的灵活性、可扩展性和性能。 按照上述步骤,您可以在 ASP.NET Core 应用程序中实施 CQRS,使用 MediatR 等工具简化命令、查询及其处理程序之间的通信。

将 IronPDF 集成到基于 CQRS 的应用程序中可进一步扩展其功能,使您能够毫不费力地创建、处理和存储 PDF 文档。 无论您是生成报告、发票还是任何形式的文档,IronPDF 的全面功能和简单语法都将使其成为您开发工具包中的强大工具。 IronPDF提供免费试用,让您有机会在承诺之前探索其功能。 为了持续使用,许可证从$749起,提供各种选项以满足您的项目需求。

Chipego
软件工程师
Chipego 拥有出色的倾听技巧,这帮助他理解客户问题并提供智能解决方案。他在 2023 年加入 Iron Software 团队,此前他获得了信息技术学士学位。IronPDF 和 IronOCR 是 Chipego 主要专注的两个产品,但他对所有产品的了解每天都在增长,因为他不断找到支持客户的新方法。他喜欢 Iron Software 的合作氛围,公司各地的团队成员贡献他们丰富的经验,以提供有效的创新解决方案。当 Chipego 离开办公桌时,你经常可以发现他在看书或踢足球。
< 前一页
在C#中(对开发人员的工作原理)
下一步 >
C# 单元测试(它如何为开发人员工作)