ASP.NET Core 中的规约模式(Specification Pattern )——增强泛型仓储模式

原文链接:https://codewithmukesh.com/blog/specification-pattern-in-aspnet-core/

在本文中,我们将讨论在 ASP.NET Core 应用程序中实现规约模式以及它如何增强现有的泛型仓储模式。我们将从头开始构建具有泛型仓储模式、Entity Framework Core的 ASP.NET Core WebAPI,并最终实现规约模式模式。您可以在此处找到此实现的完整源代码[1]。让我们开始吧。

理解规约模式:为什么?

让我们通过一个简单的示例来了解使用规约模式的必要性。下面是Developer类的代码片段,它具有Name、Email、Experience等所需的属性。

public class Developer
{public int Id { get; set; }public string Name { get; set; }public string Email { get; set; }public int YearsOfExperience {get;set;}public decimal EstimatedIncome {get;set;}public int Followers { get; set; }
}

现在,我们可能会有一个服务层,它通过像Entity Framework Core这样的抽象从DB返回数据集。这是它的样子。

public class DeveloperService : IDeveloperService
{private readonly ApplicationDbContext _context;public DeveloperService(ApplicationDbContext context){_context = context;}public async Task<IEnumerable<Developer>> GetDeveloperCount(){// return a count of all developers in the database}
}

虽然您将获得所有开发人员的数量,但更实际和合乎逻辑的要求是使用某种过滤器获得开发人员的数量,同意吗?例如,获取估计收入为 100,000 美元或以上的开发人员的数量,或具有 5 年或以上经验的开发人员的数量。可能性是无限的。

但是,这最终会让您拥有大量的服务层函数,例如 GetDeveloperCountWithSalariesGreaterThan(decimal minSalary)、GetDeveloperCountWithExperienceMoreThan(int minExp) 等等。需求越多,您最终拥有的功能数量就越多。如果您需要薪水高于 x 且经验高于 y 年的开发人员数量怎么办?  这是另一个可能导致额外方法的挑战。

您可能会争辩说您可以将这些过滤器直接应用于Entity Framework Core实体,例如

await _context.Developers.Where(a=>a.Salary > 10000 && a.Experience > 6).ToListAsync()

但是,不,这与您需要的干净的应用程序代码库相去甚远。这种方法最终会很快破坏应用程序的可伸缩性,相信我,这根本无法维护。小提示,您的应用程序中始终需要一个位于应用程序和数据库之间的服务层,并全权负责处理业务逻辑。

这是您的应用程序需要使用规约模式的地方。注意,泛型仓储模式有一些限制,这些限制是通过使用规约模式解决的。我们将建立一个项目,然后使用规约。

我们将建造什么

为了演示 ASP.NET Core 中的规约模式,我们将构建一个具有2个端点的简单Web API应用程序:

  • 返回特定的开发人员详细信息

  • 返回开发人员列表

但是,我们将添加泛型仓储模式和工作单元的组合,使这个实现更加合乎逻辑和实用。我们将在这里专门识别和实现规约模式的用例。这几乎是您使用 ASP.NET Core 5.0 构建完整应用程序时所需的一切。让我们开始吧。

PS,你可以在这里找到这个实现的完整源代码。

设置项目

首先,让我们打开 Visual Studio 2019+ 并创建一个新的解决方案和一个 WebAPI 项目。请注意,我们也将在此实现中遵循六边形架构,以保持解决方案的良好组织。

添加API项目后,让我们再向此解决方案添加2个类库项目。我们称之为Data和Core。

  • Data是与数据库和上下文相关的所有实现所在的地方。

  • Core是我们将添加接口和域实体的地方。

这就是现阶段解决方案的样子。

添加所需的模型

如前所述,在Core项目中,创建一个名为Entities的新文件夹并向其中添加2个类,即DeveloperAddress

public class Address
{public int Id { get; set; }public string City { get; set; }public string Street { get; set; }
}
public class Developer
{public int Id { get; set; }public string Name { get; set; }public string Email { get; set; }public int YearsOfExperience { get; set; }public decimal EstimatedIncome { get; set; }public Address Address { get; set; }
}

添加 DBContext 、Migrations和必需的包

现在,让我们将所需的NuGet包安装到相应的项目中。

打开包管理器控制台并从下拉列表中将Data项目设置为默认项目。 运行以下命令以安装所需的软件包。

Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools

接下来,将API项目设置为默认项目,并运行以下命令。

Install-Package Microsoft.EntityFrameworkCore.Design

在设置应用程序上下文类之前,让我们添加连接字符串。为此,从API项目打开 appsettings.json并添加以下内容。

请注意,我们目前正在使用SQLServer Local DB进行此演示。

"ConnectionStrings": {"DefaultConnection": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=specification-pattern-demo;Integrated Security=True;MultipleActiveResultSets=True"
},

完成后,让我们创建所需的上下文类,以帮助我们访问数据库。为此,在数据项目下,添加一个新类并将其命名为ApplicationDbContext。

public class ApplicationDbContext : DbContext
{public ApplicationDbContext(DbContextOptions options) : base(options){}public DbSet<Developer> Developers { get; set; }public DbSet<Address> Addresses { get; set; }
}

在这里,您可以看到我们提到了要包含在 Application Db Context 中的 Developer 和 Address 类。

接下来,我们需要将此上下文添加到我们的ASP.NET Core应用程序的服务容器并配置连接详细信息。在API工程中打开Startup.cs,在ConfigureServices方法下添加如下内容。

services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

最后,我们准备添加迁移并更新数据库。再次打开包管理器控制台并将Data项目设置为默认项目。运行以下命令:

add-migration initial
update-database

这是演示相同内容的屏幕截图。请注意,您可能会收到有关上述小数属性精度的警告。我们暂时可以忽略它。

完成后,我们的数据库现在应该准备好了所需的表和相应的字段。出于演示目的,我使用 Visual Studio 2019 IDE 的 SQL Server 对象资源管理器工具将一些示例数据直接添加到数据库中。

实现泛型仓储模式

由于我们的需求是返回开发人员的结果集,所以我们创建一个泛型仓储模式,以便它可以使用 ApplicationDbContext 从数据库中查询数据。使用泛型仓储模式的重要性在于,此代码也可以重用于多个其他实体。

例如,我们稍后添加一个名为 Product 的新实体,您不一定需要添加用于从数据库访问 Product 数据的新类,但您可以在大多数用例中使用现有的泛型仓储库实现。请注意,我们将在本文后面的部分讨论和解决泛型仓储库模式的一些限制。

在 Core 项目下,添加一个新文件夹并将其命名为 Interfaces。在这里,添加一个新接口IGenericRepository

public interface IGenericRepository<T> where T: class
{Task<T> GetByIdAsync(int id);Task<List<T>> GetAllAsync();
}

创建泛型仓储实现

现在,让我们实现上面创建的接口。由于我们遵循六边形/洋葱架构,我们将不得不在应用程序核心之外添加实现。这意味着,所有与数据相关的实现都将添加到数据项目中。

在这里,添加一个新类 GenericRepository

public class GenericRepository<T> : IGenericRepository<T> where T : class
{protected readonly ApplicationDbContext _context;public GenericRepository(ApplicationDbContext context){_context = context;}public async Task<List<T>> GetAllAsync(){return await _context.Set<T>().ToListAsync();}public async Task<T> GetByIdAsync(int id){return await _context.Set<T>().FindAsync();}
}

可以看到我们正在将 ApplicationDbContext 的实例注入到这个仓储实现的构造函数中。此实例进一步用于从数据库读取数据。

最后在API工程的Startup.cs中添加如下内容,将IGenericRepository接口注册到应用的服务容器中。

services.AddScoped(typeof(IGenericRepository<>), (typeof(GenericRepository<>)));

泛型仓储模式的问题:反模式?

一些开发人员认为泛型仓储是一种反模式。如果使用不当,是的,任何模式都会弄乱您的代码。对泛型仓储的主要抱怨是单个方法可能会将整个数据库访问代码暴露给用户。这也可能意味着需要针对每种需求组合使用多种方法(如本文开头所述)。例如,看下面的接口声明:

List<T> FindAsync(Expression<Func<T, bool>> query);

此方法可以作为泛型仓储模式的一部分来解决我们遇到的问题。但是由于该方法过于笼统,泛型仓储不可能知道我们传递给它的表达式。另一个想法可能是从 IGenericRepository 接口中删除此方法并在新接口中使用它,例如,从 IGenericRepository 派生的 IDeveloperRepository。这可能会奏效,但考虑到未来实体的添加和需求的变化,这种变化不是一个明智的选择。

想象一下有 20-30 个新实体并且必须创建大量新仓储?不是个好主意,是吗?考虑在 IDevloperRepository 及其实现中具有多种方法,例如 GetDevelopersWithSalariesGreaterThan(decimal salary)和 GetDevelopersWithExperienceLessThan(int years),不简洁,是吗?

如果有更简洁的方法来解决这个需求呢?这正是规约模式派上用场的地方。

在 ASP.NET Core 中使用规约模式增强仓储模式

规约模式乍一看可能会觉得很复杂。我也感觉到了。但是,一旦您添加了某些基类和评估器,您所要做的就是创建规约类,根据您的要求,这些类通常为 2 到 10 行。让我们开始使用 ASP.NET Core 中的规约模式。

在 Core 项目下,添加一个新文件夹并将其命名为 Specifications。这是所有与规约相关的接口都要去的地方。

创建一个新接口并将其命名为 ISpecification.cs

public interface ISpecification<T>
{Expression<Func<T, bool>> Criteria { get; }List<Expression<Func<T, object>>> Includes { get; }Expression<Func<T, object>> OrderBy { get; }Expression<Func<T, object>> OrderByDescending { get; }
}

这只是一个最小的实现。让我解释每个声明的方法定义。

  • Criteria - 您可以在此处添加基于实体的表达式。

  • Includes – 如果要包含外键表数据,可以使用此方法添加它。

  • OrderBy 和 OrderByDescending 是不言自明的。

接下来,在同一文件夹中,添加一个新类 BaseSpecifcation。这将是 ISpecification 接口的实现。

public class BaseSpecifcation<T> : ISpecification<T>
{public BaseSpecifcation(){}public BaseSpecifcation(Expression<Func<T, bool>> criteria){Criteria = criteria;}public Expression<Func<T, bool>> Criteria { get; }public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>();public Expression<Func<T, object>> OrderBy { get; private set; }public Expression<Func<T, object>> OrderByDescending { get; private set; }protected void AddInclude(Expression<Func<T, object>> includeExpression){Includes.Add(includeExpression);}protected void AddOrderBy(Expression<Func<T, object>> orderByExpression){OrderBy = orderByExpression;}protected void AddOrderByDescending(Expression<Func<T, object>> orderByDescExpression){OrderByDescending = orderByDescExpression;}
}

在这里,我们将添加3个基本方法和一个构造函数。

  • 将表达式添加到 Includes 属性

  • 将表达式添加到 OrderBy 属性

  • 将表达式添加到 OrderByDescending 属性

  • 您可以注意到我们还有一个接受条件的构造函数。Criteria 可以是 ( x=>x.Salary > 100 )  等。你明白了,是吗?

升级泛型仓储

首先,让我们在 IGenericRepository 接口中添加一个方法。

IEnumerable<T> FindWithSpecificationPattern(ISpecification<T> specification = null);

接下来,让我们在 GenericRepository 类中实现新方法。

public IEnumerable<T> FindWithSpecificationPattern(ISpecification<T> specification = null)
{return SpecificationEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), specification);
}

现在,设置所有这些背后的想法是创建可以返回特定结果集的单独规约类。这些新规约类中的每一个都将从 BaseSpecification 类继承。明白了吗?现在让我们创建这些规约类,以便它有意义 ????

因此,让我们得出 2 个要求/规约:

1.按薪水降序返回开发人员列表的规约。
2.另一个规约返回具有 N 或以上经验的开发人员列表及其地址。

在 Core 项目的同一个 Specification 文件夹下,添加我们的第一个规约类 DeveloperByIncomeSpecification

public class DeveloperByIncomeSpecification : BaseSpecifcation<Developer>
{public DeveloperByIncomeSpecification(){            AddOrderByDescending(x => x.EstimatedIncome);}
}

在这里,您可以看到我们从 BaseSpecification 类派生并在构造函数中使用 AddOrderByDescending 方法。理想情况下,此规约将返回一个按收入递减顺序排列的开发人员列表。

接下来,让我们添加另一个类,DeveloperWithAddressSpecification

public class DeveloperWithAddressSpecification : BaseSpecifcation<Developer>
{public DeveloperWithAddressSpecification(int years) : base(x=>x.EstimatedIncome > years){AddInclude(x => x.Address);}
}

因此,这里我们将查询表达式传递给 Specification Class 的基类,它是 BaseSpecification 的构造函数,然后将其添加到我们之前创建的 Criteria 属性中。其实很简单。

现在,随着我们的规约类准备就绪,让我们添加 api 端点。

在 API 项目下,在 Controllers 文件夹下添加一个新的 API Controller,并将其命名为 DevelopersController。

public class DevelopersController : ControllerBase
{public readonly IGenericRepository<Developer> _repository;public DevelopersController(IGenericRepository<Developer> repository){_repository = repository;}[HttpGet]public async Task<IActionResult> GetAll(){var developers = await _repository.GetAllAsync();return Ok(developers);}[HttpGet("{id}")]public async Task<IActionResult> GetById(int id){var developer = await _repository.GetByIdAsync(id);return Ok(developer);}[HttpGet("specify")]public async Task<IActionResult> Specify(){var specification = new DeveloperWithAddressSpecification(3);//var specification = new DeveloperByIncomeSpecification();var developers = _repository.FindWithSpecificationPattern(specification);return Ok(developers);}
}

第 3 – 7 行:将 IGenericRepository 注入到 Controller 的构造函数中。第 8 – 19 行:使用仓储实例返回所有开发人员和具有特定 Id 的开发人员的标准端点。

第 20 – 27 行:这是控制器最有趣的部分。这里的第 23 行和第 24 行是我们之前创建的 2 个规约类。这只是为了证明可以在控制器或使用 GenericRepository 的任何地方创建任何此类规约实例。我们将使用 DeveloperWithAddressSpecification(3) 进行演示。

现在让我们运行应用程序并检查指定端点的结果。

可以看到还返回了地址数据。现在,回到控制器,注释掉第 24 行,让我们暂时使用 DeveloperByIncomeSpecification。再次运行应用程序。

现在您可以注意到没有返回地址数据。为什么?很简单,因为我们使用了不同的规约,没有提到添加 Address 实体。相反,该规约按收入的递减顺序返回开发人员的集合。简单,但整洁对吗?这可能是 ASP.NET Core 应用程序中最酷的设计模式之一。

很奇怪,但这实际上是您可以理解规约模式是什么的时候???? 根据维基百科 - 在计算机编程中,规约模式是一种特定的软件设计模式,其中可以通过使用布尔逻辑将业务规则链接在一起来重新组合业务规则。该模式经常用于领域驱动设计的上下文中。

现在更有意义了,是吗?业务规则(我们要求返回具有一定经验水平或更高级别的开发人员)通过链接标准(这发生在 DeveloperWithAddressSpecification 类中)组合在一起,这是一个布尔逻辑。很简单,但是太强大了????

展望未来,这种模式的可能性是无穷无尽的,并且非常有助于扩展应用程序。这种模式也可能支持Data-Shaping和分页。非常强大的模式,学习曲线很小,是吗?这是这篇文章的总结。

总结

在本文中,我们介绍了 ASP.NET Core 应用程序中的规约模式,以及它如何通过占上风来增强泛型仓储模式。我们还构建了一个完整的 Web API 应用程序,该应用程序遵循洋葱架构以进行干净的代码管理。你也可以在我的 Github 上找到完整的源代码。有任何建议或问题吗?请随时将它们留在下面的评论部分。Thanks and Happy Coding!????

欢迎关注我的个人公众号”My IO“

参考资料

[1]

完整源代码: https://github.com/iammukeshm/specification-pattern-asp-net-core

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/300455.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

动画演示男性结扎手术 | 今日趣图

全世界只有3.14 % 的人关注了青少年数学之旅流鼻涕了怎么办&#xff1f;医学教育徐琦招聘程序员啦知识萌死大丧失&#xff1b;图熊本科技把下列句子补充完整图三好学生李宇幼年的大象喝水并不会使用鼻子而是趴在水里直接用嘴大喝一顿它们9个月之后才会懂得用鼻子喝水科普斯基请…

AspNetCoreMassTransit Courier实现分布式事务

在之前的一篇博文中&#xff0c;CAP框架可以方便我们实现非实时、异步场景下的最终一致性&#xff0c;而有些用例总是无法避免的需要在实时、同步场景下进行&#xff0c;可以借助Saga事务来解决这一困扰。在一些博文和仓库中也搜寻到了.Net下实现Saga模式的解决方案MassTransit…

设置列表字段为主键

转贴:Sample event handler to set a field as a pr imary key (enforce no duplicates) Got this as a request from a reader- how to prevent users from adding items with same titles as ones that already exist in the list. Codeusing System;using System.Collectio…

被学校辞退、拒绝FB后:语音识别大牛Povey确认兼职北京初创公司,称主业还选中国...

全世界只有3.14 % 的人关注了青少年数学之旅由于5月的学生抗议事件&#xff0c;语音识别领域著名学者、原约翰霍普金斯大学教授Daniel Povey被学校辞退。随后&#xff0c;Daniel Povey准备进入Facebook从事语音识别系统的开发&#xff0c;但是由于Facebook要对其进行长达6周的审…

dotnet中的counters说明(二)

上篇说了System.Runtime&#xff0c;它负责应用运行的环境资源的收集&#xff0c;这篇要继续说AspNetCore的Hosting,Http.Connections和Server.Kestrel三个计数器。同时&#xff0c;下面指标各项()里的项目是--counters 参数[]里的项&#xff0c;用逗号分隔多项指标。Microsoft…

有这些好习惯,可以让你悄悄变优秀

全世界只有3.14 %的人关注了青少年数学之旅这是一个普遍无趣的时代&#xff0c;很多人看似忙到起飞内在却空虚迷茫。今天我们为你诚意推荐几个公众号它们会成为你生活的一剂调味料&#xff0c;让你做一个学识丰富、灵魂有趣的人。快来关注&#xff0c;开启精彩的生活吧&#xf…

java设置access-allow_Java Web如何设置多个Access-Control-Allow-Origin

有没有办法让Access-Control-Allow-Origin header允许设置multiple cross-domains呢&#xff1f;如果设置response.addHeader("Access-Control-Allow-Origin","*");感觉这个接口太开放了&#xff0c;不太安全。 我想只设置自己指定的若干个域名或者端口可以…

微软 MS Learn 上线 Blazor 入门教程

微软官方学习网站 MS Learn 上线了 Blazor 入门教程模块&#xff0c;希望通过这个课程&#xff0c;让开发人员了解如何设置开发环境&#xff0c;以及如何使用 Blazor、Visual Studio Code 和 C# 生成你的首个 Web 应用。Build a web app with Blazor - Learndocs.microsoft.com…

Type Casting

Type Casting C : Documents : C Language Tutorial : Type Casting Search: userpass[register] javascript and cookies required C Language TutorialIntroduction?Instructions for useBasics of C?Structure of a program?Variables. Data Types.?Constants?Oper…

世界最牛实验室,堪称诺贝尔奖孵化器!到底是个怎样神奇的存在?!

▲ 点击查看随着诺贝尔各个奖项陆陆续续的公布&#xff0c;卡文迪许实验室&#xff0c;又开始重回大众视野。在这个世界最牛实验室之一的实验室里&#xff0c;仅仅过去了一百多年&#xff0c;就不断涌现出一批又一批世界一流的科学家&#xff1a;把电与磁进行有机统一的麦克斯…

开源的负载测试/压力测试工具 NBomber

负载测试和压力测试对于确保 web 应用的性能和可缩放性非常重要。尽管它们的某些测试是相同的&#xff0c;但目标不同。负载测试&#xff1a;测试应用是否可以在特定情况下处理指定的用户负载&#xff0c;同时仍满足响应目标。应用在正常状态下运行。压力测试&#xff1a;在极端…

男人都应该懂的一张图。。 | 今日趣图

全世界只有3.14 % 的人关注了青少年数学之旅美国人为了教民众如何辨别韩国人制作的韩国女性标准照左右军事成为一个有钱人的概率有多高&#xff1f;最新版男人都该懂的汽车品牌从属关系图twi:NOCO_1002肥胖和骨架没有必然联系800斤胖子的X射线照 科普君XueShu 雪树来猜一猜这是…

学做菜咯

以前在QQ空间发的贴&#xff0c;现在转到这边来&#xff0c;嘿嘿。青蛙达 - 07月28日- 14时28分今天中午做我个人最喜欢的菜之一《咖喱鸡饭》 早上到超市买了&#xff1a; 大蒜头、洋葱、南瓜、鸡翅膀、咖喱粉、胡椒粉、白糖、桂皮 回来发现原来把辣椒粉当成胡椒粉买回来了....…

Facebook 中国程序员之死

全世界只有3.14 % 的人关注了青少年数学之旅9 月 19 日&#xff0c;一位 Facebook 软件工程师从加州门洛帕克&#xff08;Menlo Park&#xff09;总部四楼纵身跳下&#xff0c;结束年轻的生命。Facebook 新闻发言人证实确有其事&#xff0c;并说公司将会联系员工家人。门洛帕克…

[导入]数据库物理模型设计的其他模式之继承模式

连载之7原创&#xff1a;胖子刘&#xff08;转载请注明作者和出处&#xff0c;谢谢&#xff09;数据库物理模型设计的其他模式除了上面提到的四种主要设计模式&#xff0c;还有一些其他模式&#xff0c;在某些项目中可能会用到&#xff0c;在这里先简单做个说明&#xff0c;暂不…

“我数学太烂,但高考136分!”刷完上万道题后,我找到2个月多考58分的捷径…...

全世界只有3.14 %的人关注了青少年数学之旅01难上天的高考试卷&#xff0c;我逆袭考到136分&#xff01;我叫刘辉&#xff0c;来自湖北省的某个县城&#xff0c;今年我数学考到了136分的好成绩&#xff0c;成功被一所985高校录取。↓我的高考成绩↓但回想一年之前&#xff0c;我…

[Forward] 因为火炬,所以迟到,工资照扣

今天跟往常一样,到香蜜湖等230 看到深南大道主道那边站了一名JC叔叔 在前面的主辅岔道口看到有交J叔叔...对面又大堵车...心想大事不妙..又要交通管制了......两hui期间因为交通管制让我第一个月上班就来了一次迟到 这时候是8点钟多一点开始管制.....很后悔没有上到最后一…

三分钟总览微软任务并行库TPL

点击上方蓝字进行关注有小伙伴问我每天忽悠的TPL是什么&#xff1f;☹️ 这次站位高一点&#xff0c;严肃讲一讲。引言俗话说&#xff0c;不想开飞机的程序员不是一名好爸爸&#xff1b;作为微软技术栈的老鸟&#xff0c;一直将代码整洁之道奉为经典&#xff0c; 优秀的程序员将…

第五章 MyEclipse配置hadoop开发环境

1.首先要下载相应的hadoop版本的插件&#xff0c;我这里就给2个例子&#xff1a; hadoop-1.2.1插件&#xff1a;http://download.csdn.net/download/hanyongan300/6238153 hadoop2.2.0插件&#xff1a;http://blog.csdn.net/twlkyao/article/details/17334693 上一章我也讲了怎…

这才是真正的,坐上来,自己动!| 今日趣图

全世界只有3.14 % 的人关注了青少年数学之旅你有见过加辣的奶茶吗&#xff1f;什么叫做科技改变生活其实你的猫一直都看不起你坐上来&#xff0c;自己动&#xff01;安全带使用体验当iPhone遇上数学在B站UP主的剪刀下诞生了各种神奇的CP组合其中最受欢迎的居然是伏地魔和林黛玉…