在Asp.Net Core中集成ABP Dapper
在实际的项目中,除了集成ABP框架的EntityFrameworkCore以外,在有些特定的场景下不可避免地会使用一些SQL查询语句,一方面是由于现在的EntityFrameworkCore2.X有些问题没有解决,另外一方面是基于性能方面的考虑,在了解本篇内容之前,首先还是来看看官方文档来给出的说明。
按照官方的介绍整体可以分为下面的步骤:1 安装依赖包。2 添加DependsOn属性标签。3 Entity to Table Mapping。 4 Usage 通过上面的4个步骤我们就能够正常在Asp.Net Core项目中使用ABP Dapper了,下面我们就具体的过程来做进一步的说明。
一 安装包依赖
这个不做过多的解释,通过Nuget 包管理器或者通过程序包管理控制台来添加Abp.Dapper的引用,在我们实际的项目中整个类库的结构如下图所示,包含Dapper和EntityFrameworkCore两种方案。

图一 项目结构
二 添加DependsOn属性标签
后面我们就需要在我们当前类库项目中唯一的SalesDataModule中来做一些初始化和添加DependsOn标签的操作了。
[DependsOn(typeof(AbpZeroCoreEntityFrameworkCoreModule))]
[DependsOn(typeof(AbpDapperModule))]
public class SalesDataModule : AbpModule {
public override void Initialize() {
IocManager.RegisterAssemblyByConvention(typeof(SalesDataModule).GetAssembly());
DapperExtensions.DapperExtensions.SetMappingAssemblies(new List<Assembly> { typeof(SalesDataModule).GetAssembly() });
}
}
这里我们应该了解为什么要添加依赖关系?这个我们当前的SalesDataModule会依赖于AbpDapperModule和AbpZeroCoreEntityFrameworkCoreModule,确立了这样的依赖关系后,在ABP框架中就会将当前Module所依赖的其它Module放到List<AbpModule>的前面,这样通过这样对的层层依赖关系进行拓扑排序就能够保证被依赖的AbpModule一定先进行初始化操作,这样就能够避免引用关系的错误,从而导致代码逻辑的错误,具体说来:如果A 依赖于B,B依赖于C,那么这三个模块之间的排序为C B A,这样在整个Module系统初始化的时候,会先执行Module C的PreIntialize()、Initialize()、PostInitialize()方法,我们看看ABP中的源码。
public virtual void StartModules()
{
var sortedModules = _modules.GetSortedModuleListByDependency();
sortedModules.ForEach(module => module.Instance.PreInitialize());
sortedModules.ForEach(module => module.Instance.Initialize());
sortedModules.ForEach(module => module.Instance.PostInitialize());
}
这个里面sortedModules就是通过这种依赖关系进行拓扑排序的,然后依次这行每个模块中的这几个方法进行一些初始化的操作。
三 Entity to Table Mapping
这个按照官方的解释就是在建立Entity和数据库实体之间的关系,这个类似于在Domain层实体上面添加【Table】标签(个人理解),在这个里面我们还能添加一些其它特性,比如Ignore掉一些导航属性等等......
public sealed class VehicleOrderPlanMapper : ClassMapper<VehicleOrderPlan> {
public VehicleOrderPlanMapper() {
Table("VehicleOrderPlan");
Map(x => x.Branch).Ignore();
AutoMap();
}
}
四 应用
这个部分就结合具体的项目来谈一谈一些细节方面的东西,我们先来看看具体的代码。
public class VehicleOrderPlanDapperRepository : DcsDapperRepositoryBase<VehicleOrderPlan>, IVehicleOrderPlanDapperRepository {
public VehicleOrderPlanDapperRepository(IActiveTransactionProvider activeTransactionProvider) : base(activeTransactionProvider) {
}
public IEnumerable<WeeklyOrderPlanSummary> GetWeeklyOrderPlanSummary(int? yearOfPlan, int? weekOfPlan, string provinceName,
[CanBeNull]VehicleOrderPlanType[] planType, string marketName, PageRequest page) {
var sqlParam = new StringBuilder()
.AppendIf(yearOfPlan.HasValue, $" AND p.YearOfPlan = :{nameof(yearOfPlan)}")
.AppendIf(weekOfPlan.HasValue, $" AND p.WeekOfPlan = :{nameof(weekOfPlan)}")
.AppendIf(!provinceName.IsNullOrWhiteSpace(), $@" AND EXISTS ( SELECT 1
FROM Company C WHERE c.Id = p.DealerId AND c.Status <> {(int)MasterDataStatus.作废}
AND c.ProvinceName like '%' || :{nameof(provinceName)} || '%')");
var planTypes = new[] {
VehicleOrderPlanType.周度计划,
VehicleOrderPlanType.小品种计划,
VehicleOrderPlanType.移库计划
};
if (planType != null && planType.Length > 0)
planTypes = planTypes.Intersect(planType).ToArray();
var departmentParam = string.Empty;
if (!marketName.IsNullOrWhiteSpace())
departmentParam = $" AND (m.Name LIKE '%' || :{nameof(marketName)} || '%')";
var sql = $@"
SELECT p.YearOfPlan, p.WeekOfPlan, TRUNC(p.CreateTime) AS CreateTime, TRUNC(p.StartTime) AS StartTime, TRUNC(p.EndTime) AS EndTime,
pd.ProductCode, pd.ProductName, pd.ProductType, pd.ProductCategoryCode AS VehicleModelCode, pd.ProductCategoryName AS VehicleModelName,
Sum(pd.PlannedQuantity) AS PlannedQuantity, Sum(pd.FirstPlannedQuantity) AS FirstPlannedQuantity,
Sum(pd.QuantityOfAssessment) AS QuantityOfAssessment, Sum(pd.ConfirmedQuantity) AS ConfirmedQuantity
FROM VehicleOrderPlan p
CROSS JOIN VehicleOrderPlanDetail pd
WHERE (p.Status <> {(int)VehicleOrderPlanStatus.作废} AND p.Type in {planTypes.ToSqlInParam()} {sqlParam} AND EXISTS (
SELECT 1
FROM DealerMarketDptRelation dm
WHERE (((dm.BranchId = p.BranchId) AND (dm.DealerId = p.DealerId)) AND (dm.Status = {(int)BaseDataStatus.有效})) AND EXISTS (
SELECT 1
FROM MarketingDepartment m
WHERE ((m.BranchCode = {SunlightConsts.DEFAULT_BRANCH_QRSALESLTD}) AND (m.Status = {(int)BaseDataStatus.有效})) {departmentParam}
AND (m.Id = dm.MarketId)))) AND (p.Id = pd.VehicleOrderPlanId)
GROUP BY p.YearOfPlan, p.WeekOfPlan, TRUNC(p.CreateTime), TRUNC(p.StartTime), TRUNC(p.EndTime), pd.ProductCode,
pd.ProductName, pd.ProductType, pd.ProductCategoryCode, pd.ProductCategoryName";
return QueryPaged<WeeklyOrderPlanSummary>(sql, page, new {
yearOfPlan,
weekOfPlan,
provinceName,
marketName
});
}
}
这段代码主要是通过具体传入的参数计划年、计划周、省份......等参数到数据库中查询相关的记录,这里我们先看看基类DcsDapperRepositoryBase<VehicleOrderPlan>里面做了些什么?
public class DcsDapperRepositoryBase<TEntity> : DapperEfRepositoryBase<DcsDbContext, TEntity>
where TEntity : class, IEntity<int> {
public DcsDapperRepositoryBase(IActiveTransactionProvider activeTransactionProvider) : base(activeTransactionProvider) {
} /// <summary>
/// 以分页的形式查询数据
/// </summary>
/// <typeparam name="TValueObject"></typeparam>
/// <param name="sql"></param>
/// <param name="pageRequest"></param>
/// <param name="parameters">参数的匿名对象</param>
/// <returns></returns>
protected IEnumerable<TValueObject> QueryPaged<TValueObject>(string sql, PageRequest pageRequest, object parameters = null)
where TValueObject : ValueObjectBase {
var orderCondition = (string.IsNullOrWhiteSpace(pageRequest.Ordering) ? string.Empty : "ORDER BY " + pageRequest.Ordering);
orderCondition.SqlInjectionInspect();
var pagedSql = $@"WITH ""_data"" AS ({sql}),
""_count"" AS (SELECT COUNT(0) AS OverallCount FROM ""_data"")
SELECT *
FROM (SELECT A.*, ROWNUM AS ""RowNum""
FROM (SELECT * FROM ""_data""
{orderCondition}) A
WHERE ROWNUM <= {pageRequest.PageSize * (pageRequest.PageIndex + 1)}) B,
""_count""
WHERE ""RowNum"" > {pageRequest.PageSize * pageRequest.PageIndex}";
return Query<TValueObject>(pagedSql, parameters);
}
}
在这个基类中我们继承了ABP中的基类DapperEfRepositoryBase<DcsDbContext, TEntity>,这个泛型基类第一个参数就是我们项目中的具体DbContext,第二个参数就是我们具体定义的实体,这个实体是主键为Int的自增长类型。这里面由于查询的数据非常多,所以我们这里实际上返回的是分页的第一页的结果集,这里还有一个重要的知识就是,为了防止sql注入,这里sql中的参数都采用参数的匿名对象,而不是直接进行拼接,这个是防止SQL注入的时候最常见的方式。通过这个具体的例子你应该知道怎样在ABP Dapper中使用匿名参数对象来防止SQL注入,另外通过这段SQL你还知道在Oracle数据库中如何对查询到的结果进行分页处理。
在处理完这些后,我们再来看看当前VehicleOrderPlanRepository继承的接口是在哪里进行定义的?具体的领域层又该如何进行调用?
public interface IVehicleOrderPlanDapperRepository : IDapperRepository<VehicleOrderPlan> {
IEnumerable<WeeklyOrderPlanSummary> GetWeeklyOrderPlanSummary(int? yearOfPlan, int? weekOfPlan, string provinceName,
[CanBeNull] VehicleOrderPlanType[] planType, string marketName, PageRequest page);
}
这个接口非常简单,但是这个接口究竟应该在哪里进行定义呢?我们按照DDD思想,首先想到的就是在领域层进行定义,不然领域层其它业务该在哪里调用这个方法呢?那么这个可以从哪里找到答案呢?

图二 ABP N层架构
有没有对这张图很熟悉,这个就是用于介绍ABP N层架构的示意图,红框标注的就是具体的结构中的接口定义和实现,这两者的定义和实现分别是处于不同的层中,一个属于Domain Layer中而具体的实现位于Infrastructure Layer中,一层定义接口,而另外一层则定义具体的实现,有了这个你是不是对整个ABP的架构有了更深入的理解呢?
最后,点击这里返回整个ABP系列的主目录。
在Asp.Net Core中集成ABP Dapper的更多相关文章
- 在 ASP.NET Core 中集成 Skywalking APM
前言 大家好,今天给大家介绍一下如何在 ASP.NET Core 项目中集成 Skywalking,Skywalking 是 Apache 基金会下面的一个开源 APM 项目,有些同学可能会 APM ...
- 如何简单的在 ASP.NET Core 中集成 JWT 认证?
前情提要:ASP.NET Core 使用 JWT 搭建分布式无状态身份验证系统 文章超长预警(1万字以上),不想看全部实现过程的同学可以直接跳转到末尾查看成果或者一键安装相关的 nuget 包 自上一 ...
- 在Asp.Net Core中集成Kafka
在我们的业务中,我们通常需要在自己的业务子系统之间相互发送消息,一端去发送消息另一端去消费当前消息,这就涉及到使用消息队列MQ的一些内容,消息队列成熟的框架有多种,这里你可以读这篇文章来了解这些MQ的 ...
- 在Asp.Net Core中集成Kafka(中)
在上一篇中我们主要介绍如何在Asp.Net Core中同步Kafka消息,通过上一篇的操作我们发现上面一篇中介绍的只能够进行简单的首发kafka消息并不能够消息重发.重复消费.乐观锁冲突等问题,这些问 ...
- ASP.NET Core中使用GraphQL - 第一章 Hello World
前言 你是否已经厌倦了REST风格的API? 让我们来聊一下GraphQL. GraphQL提供了一种声明式的方式从服务器拉取数据.你可以从GraphQL官网中了解到GraphQL的所有优点.在这一系 ...
- ASP.NET Core 简单集成签发 JWT (JSON Web Tokens)
什么是 JWT ? 从 https://jwt.io/ 可以了解到对 JWT 的描述:JSON Web Tokens are an open, industry standard RFC 7519 m ...
- Api网关Kong集成Consul做服务发现及在Asp.Net Core中的使用
写在前面 Api网关我们之前是用 .netcore写的 Ocelot的,使用后并没有完全达到我们的预期,花了些时间了解后觉得kong可能是个更合适的选择. 简单说下kong对比ocelot打动我的 ...
- asp.net core mvc 集成miniprofiler
原文:asp.net core mvc 集成miniprofiler asp.net core mvc 集成miniprofiler 一.环境介绍 二.监控asp.net 页面 三.监控执行的sql语 ...
- 如何在 ASP.NET Core 中发送邮件
前言 我们知道目前 .NET Core 还不支持 SMTP 协议,当我么在使用到发送邮件功能的时候,需要借助于一些第三方组件来达到目的,今天给大家介绍两款开源的邮件发送组件,它们分别是 MailKit ...
随机推荐
- 完美解决phpstudy安装后mysql无法启动(无需删除原数据库,无需更改任何配置,无需更改端口)直接共存
PHPstudy与原Mysql兼容解决 一.前言 今天学习php,当然是要先安装好运行环境了,phpstyudy是一个运行php的集成环境, 一键安装对新手很友好,与时作为一个新手,便跟着教程安装了p ...
- Fescar(Seata)-Springcloud流程分析-2阶段
上文我们分析了fescar的一阶段执行过程.在一阶段中,服务起始方发起全局事务并注册到TC.在调用协同服务时,协同服务的事务分支事务会先完成阶段一的事务提交或回滚,并生成事务回滚的undo_log日志 ...
- 将你的前端应用打包成docker镜像并部署到服务器?仅需一个脚本搞定
1.前言 前段时间,自己搞了个阿里云的服务器.想自己在上面折腾,但是不想因为自己瞎折腾而污染了现有的环境.毕竟,现在的阿里云已经没有免费的快照服务了.要想还原的话,最简单的办法就是重新装系统.而一旦重 ...
- ASP.NET Core - 从Program和Startup开始
Program 我们先看一下1.x和2.x的程序入口项的一个差异 1.x public class Program { public static void Main(string[] args) { ...
- 一套代码小程序&Web&Native运行的探索03——处理模板及属性
接上文:一套代码小程序&Web&Native运行的探索02 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/m ...
- Flutter 即学即用系列博客——03 在旧有项目引入 Flutter
前言 其实如果打算在实际项目中引入 Flutter,完全将旧有项目改造成纯 Flutter 项目的可能性比较小,更多的是在旧有项目引入 Flutter. 因此本篇我们就说一说如何在旧有项目引入 Flu ...
- python实现某目录下将多个文件夹内的文件复制到一个文件夹中
现实生活中,我们经常有这样的需求,如下图,有三个文件夹,文件夹1内含有1.txt文件 文件夹2中内含有2.txt文件,文件夹3中含有3.txt文件.我们有时候需要把1.txt, 2.txt, 3.tx ...
- MySQL 笔记整理(19) --为什么我只查一行的语句,也执行这么慢?
笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 19) --为什么我只查一行的语句,也执行这么慢? 需要说明一下,如果M ...
- java_异常
一.什么是异常? 1.当程序”运行后”,当jvm遇到一些无法处理的情况,例如:整数/0,这就表示jvm遇到一种”异常情况”. 通常jvm能够识别这些异常并在控制台打印异常信息,并结束程序 2.为了解决 ...
- java爬虫系列第五讲-如何使用代理防止爬虫被屏蔽?
本文内容 1.分析一下爬虫存在的问题及解决方案 2.webmagic中代理的使用 3.目前市面上一些比较好用的代理服务器 存在的问题 我们在使用爬虫过程中,大多都会遇到这样的问题:突然某一天爬虫爬不到 ...