在实际项目开发中,我们可能会碰到各种各样的项目环境,有些项目需要一个大而全的整体框架来支撑开发,有些中小项目这需要一些简单便捷的系统框架灵活开发。目前大型一点的框架,可以采用ABP或者ABP VNext的框架,两者整体思路和基础设计类似,不过ABP侧重于一个独立完整的项目框架,开发的时候统一整合处理;而ABP VNext则是以微服务架构为基础,各个模块独立开发,既可以整合在一个项目中,也可以以微服务进行单独发布,并统一通过网关处理进行交流。不管ABP或者ABP VNext框架,都集合了.NET CORE领域众多技术为一体,并且基础类设计上,错综复杂,关系较多,因此开发学习有一定的门槛,中小型项目应用起来有一定的费劲之处。本系列随笔介绍底层利用SqlSugar来做ORM数据访问模块,设计一个简单便捷一点的框架,本篇从基础开始介绍一些框架内容,参照一些ABP/ABP VNext中的一些类库处理,来承载类似条件分页信息,查询条件处理等处理细节。

1、基于SqlSugar开发框架的架构设计

主要的设计模块场景如下所示。

为了避免像ABP VNext框架那样分散几十个项目,我们尽可能聚合内容放在一个项目里面。

1)其中一些常用的类库,以及SqlSugar框架的基类放在框架公用模块里面。

2)Winform开发相关的基础界面以及通用组件内容,放在基础Winform界面库BaseUIDx项目中。

3)基础核心数据模块SugarProjectCore,主要就是开发业务所需的数据处理和业务逻辑的项目,为了方便,我们区分Interface、Modal、Service三个目录来放置不同的内容,其中Modal是SqlSugar的映射实体,Interface是定义访问接口,Service是提供具体的数据操作实现。其中Service里面一些框架基类和接口定义,统一也放在公用类库里面。

4)Winform应用模块,主要就是针对业务开发的WInform界面应用,而WInform开发为了方便,也会将一些基础组件和基类放在了BaseUIDx的Winform专用的界面库里面。

5)WebAPI项目采用基于.net Core6的项目开发,通过调用SugarProjectCore实现相关控制器API的发布,并整合Swagger发布接口,供其他前端界面应用进行调用。

6)纯前端通过API进行调用Web API的接口,纯前端模块可以包含Vue3&Element项目,以及基于EelectronJS应用,发布跨平台的基于浏览器的应用界面,以及其他App或者小程序整合Web API进行业务数据的处理或者展示需要。

如后端开发,我们可以在VS2022中进行管理,管理开发Winform项目、Web API项目等。

Winform界面,我们可以采用基于.net Framework开发或者.net core6进行开发均可,因为我们的SugarProjectCore项目是采用.net Standard模式开发,兼容两者。这里以权限模块来进行演示整合使用。

而纯前端的项目,我们可以基于VSCode或者 HBuilderX等工具进行项目的管理开发工作。

2、框架基础类的定义和处理

在开发一个易于使用的框架的时候,主要目的就是减少代码开发,并尽可能通过基类和泛型约束的方式,提高接口的通用性,并通过结合代码生成工具的方式,来提高标准项目的开发效率。

那么我们这里基于SqlSugar的ORM处理,来实现常规数据的增删改查等常规操作的时候,我们是如何进行这些接口的封装处理的呢。

例如,我们对于一个简单的客户信息表,如下所示。

那么它生成的SqlSugar实体类如下所示。

    /// <summary>
/// 客户信息
/// 继承自Entity,拥有Id主键属性
/// </summary>
[SugarTable("T_Customer")]
public class CustomerInfo : Entity<string>
{
/// <summary>
/// 默认构造函数(需要初始化属性的在此处理)
/// </summary>
public CustomerInfo()
{
this.CreateTime = System.DateTime.Now;
} #region Property Members /// <summary>
/// 姓名
/// </summary>
public virtual string Name { get; set; } /// <summary>
/// 年龄
/// </summary>
public virtual int Age { get; set; } /// <summary>
/// 创建人
/// </summary>
public virtual string Creator { get; set; } /// <summary>
/// 创建时间
/// </summary>
public virtual DateTime CreateTime { get; set; } #endregion
}

其中 Entity<string> 是我们根据需要定义一个基类实体对象,主要就是定义一个Id的属性来处理,毕竟对于一般表对象的处理,SqlSugar需要Id的主键定义(非中间表处理)。

    [Serializable]
public abstract class Entity<TPrimaryKey> : IEntity<TPrimaryKey>
{
/// <summary>
/// 实体类唯一主键
/// </summary>
[SqlSugar.SugarColumn(IsPrimaryKey = true, ColumnDescription = "主键")]
public virtual TPrimaryKey Id { get; set; }
}

而IEntity<T>定义了一个接口

    public interface IEntity<TPrimaryKey>
{
/// <summary>
/// 实体类唯一主键
/// </summary>
TPrimaryKey Id { get; set; }
}

以上就是实体类的处理,我们一般为了查询信息,往往通过一些条件传入进行处理,那么我们就需要定义一个通用的分页查询对象,供我们精准进行条件的处理。

生成一个以***PageDto的对象类,如下所示。

    /// <summary>
/// 用于根据条件分页查询,DTO对象
/// </summary>
public class CustomerPagedDto : PagedAndSortedInputDto, IPagedAndSortedResultRequest
{
/// <summary>
/// 默认构造函数
/// </summary>
public CustomerPagedDto() : base() { } /// <summary>
/// 参数化构造函数
/// </summary>
/// <param name="skipCount">跳过的数量</param>
/// <param name="resultCount">最大结果集数量</param>
public CustomerPagedDto(int skipCount, int resultCount) : base(skipCount, resultCount)
{
} /// <summary>
/// 使用分页信息进行初始化SkipCount 和 MaxResultCount
/// </summary>
/// <param name="pagerInfo">分页信息</param>
public CustomerPagedDto(PagerInfo pagerInfo) : base(pagerInfo)
{
} #region Property Members /// <summary>
/// 不包含的对象的ID,用于在查询的时候排除对应记录
/// </summary>
public virtual string ExcludeId { get; set; } /// <summary>
/// 姓名
/// </summary>
public virtual string Name { get; set; } /// <summary>
/// 年龄-开始
/// </summary>
public virtual int? AgeStart { get; set; }
/// <summary>
/// 年龄-结束
/// </summary>
public virtual int? AgeEnd { get; set; } /// <summary>
/// 创建时间-开始
/// </summary>
public DateTime? CreateTimeStart { get; set; }
/// <summary>
/// 创建时间-结束
/// </summary>
public DateTime? CreateTimeEnd { get; set; } #endregion
}

其中PagedAndSortedInputDto, IPagedAndSortedResultRequest都是参考来自于ABP/ABP VNext的处理方式,这样我们可以便于数据访问基类的查询处理操作。

接着我们定义一个基类MyCrudService,并传递如相关的泛型约束,如下所示

    /// <summary>
/// 基于SqlSugar的数据库访问操作的基类对象
/// </summary>
/// <typeparam name="TEntity">定义映射的实体类</typeparam>
/// <typeparam name="TKey">主键的类型,如int,string等</typeparam>
/// <typeparam name="TGetListInput">或者分页信息的条件对象</typeparam>
public abstract class MyCrudService<TEntity, TKey, TGetListInput> :
IMyCrudService<TEntity, TKey, TGetListInput>
where TEntity : class, IEntity<TKey>, new()
where TGetListInput : IPagedAndSortedResultRequest

我们先忽略基类接口的相关实现细节,我们看看对于这个MyCrudService和 IMyCrudService 我们应该如何使用的。

首先我们定义一个应用层的接口ICustomerService如下所示。

    /// <summary>
/// 客户信息服务接口
/// </summary>
public interface ICustomerService : IMyCrudService<CustomerInfo, string, CustomerPagedDto>, ITransientDependency
{ }

然后实现在CustomerService中实现它的接口。

    /// <summary>
/// 应用层服务接口实现
/// </summary>
public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService

这样我们对于特定Customer的接口在ICustomer中定义,标准接口直接调用基类即可。

基类MyCrudService提供重要的两个接口,让子类进行重写,以便于进行准确的条件处理和排序处理,如下代码所示。

    /// <summary>
/// 基于SqlSugar的数据库访问操作的基类对象
/// </summary>
/// <typeparam name="TEntity">定义映射的实体类</typeparam>
/// <typeparam name="TKey">主键的类型,如int,string等</typeparam>
/// <typeparam name="TGetListInput">或者分页信息的条件对象</typeparam>
public abstract class MyCrudService<TEntity, TKey, TGetListInput> :
IMyCrudService<TEntity, TKey, TGetListInput>
where TEntity : class, IEntity<TKey>, new()
where TGetListInput : IPagedAndSortedResultRequest
{
/// <summary>
/// 留给子类实现过滤条件的处理
/// </summary>
/// <returns></returns>
protected virtual ISugarQueryable<TEntity> CreateFilteredQueryAsync(TGetListInput input)
{
return EntityDb.AsQueryable();
}
/// <summary>
/// 默认排序,通过ID进行排序
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
protected virtual ISugarQueryable<TEntity> ApplyDefaultSorting(ISugarQueryable<TEntity> query)
{
if (typeof(TEntity).IsAssignableTo<IEntity<TKey>>())
{
return query.OrderBy(e => e.Id);
}
else
{
return query.OrderBy("Id");
}
}
}

对于Customer特定的业务对象来说,我们需要实现具体的条件查询细节和排序条件,毕竟我们父类没有约束确定实体类有哪些属性的情况下,这些就交给子类做最合适了。

    /// <summary>
/// 应用层服务接口实现
/// </summary>
public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService
{
/// <summary>
/// 自定义条件处理
/// </summary>
/// <param name="input">查询条件Dto</param>
/// <returns></returns>
protected override ISugarQueryable<CustomerInfo> CreateFilteredQueryAsync(CustomerPagedDto input)
{
var query = base.CreateFilteredQueryAsync(input); query = query
.WhereIF(!input.ExcludeId.IsNullOrWhiteSpace(), t => t.Id != input.ExcludeId) //不包含排除ID
.WhereIF(!input.Name.IsNullOrWhiteSpace(), t => t.Name.Contains(input.Name)) //如需要精确匹配则用Equals
//年龄区间查询
.WhereIF(input.AgeStart.HasValue, s => s.Age >= input.AgeStart.Value)
.WhereIF(input.AgeEnd.HasValue, s => s.Age <= input.AgeEnd.Value) //创建日期区间查询
.WhereIF(input.CreateTimeStart.HasValue, s => s.CreateTime >= input.CreateTimeStart.Value)
.WhereIF(input.CreateTimeEnd.HasValue, s => s.CreateTime <= input.CreateTimeEnd.Value)
; return query;
} /// <summary>
/// 自定义排序处理
/// </summary>
/// <param name="query">可查询LINQ</param>
/// <returns></returns>
protected override ISugarQueryable<CustomerInfo> ApplyDefaultSorting(ISugarQueryable<CustomerInfo> query)
{
return query.OrderBy(t => t.CreateTime, OrderByType.Desc); //先按第一个字段排序,然后再按第二字段排序
//return base.ApplySorting(query, input).OrderBy(s=>s.Customer_ID).OrderBy(s => s.Seq);
}
}

通过 CreateFilteredQueryAsync 的精确条件处理,我们就可以明确实体类的查询条件处理,因此对于CustomerPagedDto来说,就是可以有客户端传入,服务后端的基类进行处理了。

如基类的分页条件查询函数GetListAsync就是根据这个来处理的,它的实现代码如下所示。

        /// <summary>
/// 根据条件获取列表
/// </summary>
/// <param name="input">分页查询条件</param>
/// <returns></returns>
public virtual async Task<PagedResultDto<TEntity>> GetListAsync(TGetListInput input)
{
var query = CreateFilteredQueryAsync(input);
var totalCount = await query.CountAsync(); query = ApplySorting(query, input);
query = ApplyPaging(query, input); var list = await query.ToListAsync(); return new PagedResultDto<TEntity>(
totalCount,
list
);
}

而其中 ApplySorting 就是根据条件决定是否选择子类实现的默认排序进行处理的。

        /// <summary>
/// 记录排序处理
/// </summary>
/// <returns></returns>
protected virtual ISugarQueryable<TEntity> ApplySorting(ISugarQueryable<TEntity> query, TGetListInput input)
{
//Try to sort query if available
if (input is ISortedResultRequest sortInput)
{
if (!sortInput.Sorting.IsNullOrWhiteSpace())
{
return query.OrderBy(sortInput.Sorting);
}
} //IQueryable.Task requires sorting, so we should sort if Take will be used.
if (input is ILimitedResultRequest)
{
return ApplyDefaultSorting(query);
} //No sorting
return query;
}

对于获取单一对象,我们一般提供一个ID主键获取即可。

        /// <summary>
/// 根据ID获取单一对象
/// </summary>
/// <param name="id">主键ID</param>
/// <returns></returns>
public virtual async Task<TEntity> GetAsync(TKey id)
{
return await EntityDb.GetByIdAsync(id);
}

也可以根据用户的Express条件进行处理,在基类我们定义很多这样的Express条件处理,便于子类进行条件处理的调用。如对于删除,可以指定ID,也可以指定条件删除。

        /// <summary>
/// 删除指定ID的对象
/// </summary>
/// <param name="id">记录ID</param>
/// <returns></returns>
public virtual async Task<bool> DeleteAsync(TKey id)
{
return await EntityDb.DeleteByIdAsync(id);
}
/// <summary>
/// 根据指定条件,删除集合
/// </summary>
/// <param name="input">表达式条件</param>
/// <returns></returns>
public virtual async Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> input)
{
var result = await EntityDb.DeleteAsync(input);
return result;
}

如判断是否存在也是一样处理

        /// <summary>
/// 判断是否存在指定条件的记录
/// </summary>
/// <param name="id">ID 主键</param>
/// <returns></returns>
public virtual async Task<bool> IsExistAsync(TKey id)
{
var info = await EntityDb.GetByIdAsync(id);
var result = (info != null);
return result;
} /// <summary>
/// 判断是否存在指定条件的记录
/// </summary>
/// <param name="input">表达式条件</param>
/// <returns></returns>
public virtual async Task<bool> IsExistAsync(Expression<Func<TEntity, bool>> input)
{
var result = await EntityDb.IsAnyAsync(input);
return result;
}

关于Web API的处理,我在随笔《基于SqlSugar的数据库访问处理的封装,在.net6框架的Web API上开发应用》中也有介绍,主要就是先弄好.net6的开发环境,然后在进行相关的项目开发即可。

根据项目的需要,我们定义了一些控制器的基类,用于实现不同的功能。

其中ControllerBase是.net core Web API中的标准控制器基类,我们由此派生一个LoginController用于登录授权,而BaseApiController则处理常规接口用户身份信息,而BusinessController则是对标准的增删改查等基础接口进行的封装,我们实际开发的时候,只需要开发编写类似CustomerController基类即可。

BaseApiController没有什么好介绍的,就是封装一下获取用户的身份信息。

可以通过下面代码获取接口用户的Id

        /// <summary>
/// 当前用户身份ID
/// </summary>
protected virtual string? CurrentUserId => HttpContext.User.FindFirst(JwtClaimTypes.Id)?.Value;

而BusinessController控制器则是继承这个BaseApiController即可。通过泛型约束传入相关的对象信息。

    /// <summary>
/// 本控制器基类专门为访问数据业务对象而设的基类
/// </summary>
/// <typeparam name="TEntity">定义映射的实体类</typeparam>
/// <typeparam name="TKey">主键的类型,如int,string等</typeparam>
/// <typeparam name="TGetListInput">或者分页信息的条件对象</typeparam>
[Route("[controller]")]
[Authorize] //需要授权登录访问
public class BusinessController<TEntity, TKey, TGetListInput> : BaseApiController
where TEntity : class, IEntity<TKey>, new()
where TGetListInput : IPagedAndSortedResultRequest
{
/// <summary>
/// 通用基础操作接口
/// </summary>
protected IMyCrudService<TEntity, TKey, TGetListInput> _service { get; set; } /// <summary>
/// 构造函数,初始化基础接口
/// </summary>
/// <param name="service">通用基础操作接口</param>
public BusinessController(IMyCrudService<TEntity, TKey, TGetListInput> service)
{
this._service = service;
} ....

这个基类接收一个符合基类接口定义的对象作为基类增删删改查等处理方法的接口对象。在具体的CustomerController中的定义处理如下所示。

    /// <summary>
/// 客户信息的控制器对象
/// </summary>
public class CustomerController : BusinessController<CustomerInfo, string, CustomerPagedDto>
{
private ICustomerService _customerService; /// <summary>
/// 构造函数,并注入基础接口对象
/// </summary>
/// <param name="customerService"></param>
public CustomerController(ICustomerService customerService) :base(customerService)
{
this._customerService = customerService;
}
}

这样就可以实现基础的相关操作了。如果需要特殊的接口实现,那么定义方法实现即可。

类似字典项目中的控制器处理代码如下所示。定义好HTTP方法,路由信息等即可。

        /// <summary>
/// 根据字典类型ID获取所有该类型的字典列表集合(Key为名称,Value为值)
/// </summary>
/// <param name="dictTypeId">字典类型ID</param>
/// <returns></returns>
[HttpGet]
[Route("by-typeid/{dictTypeId}")]
public async Task<Dictionary<string, string>> GetDictByTypeID(string dictTypeId)
{
return await _dictDataService.GetDictByTypeID(dictTypeId);
} /// <summary>
/// 根据字典类型名称获取所有该类型的字典列表集合(Key为名称,Value为值)
/// </summary>
/// <param name="dictTypeName">字典类型名称</param>
/// <returns></returns>
[HttpGet]
[Route("by-typename/{dictTypeName}")]
public async Task<Dictionary<string, string>> GetDictByDictType(string dictTypeName)
{
return await _dictDataService.GetDictByDictType(dictTypeName);
}

基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用的更多相关文章

  1. 基于MVC4+EasyUI的Web开发框架形成之旅(5)--框架总体界面介绍

    在前面介绍了一些关于最新基于MVC4+EasyUI的Web开发框架文章,虽然Web开发框架的相关技术文章会随着技术的探讨一直写下去,不过这个系列的文章,到这里做一个总结,展示一下整体基于MVC4+Ea ...

  2. 基于NopCommerce的开发框架——缓存、网站设置、系统日志、用户操作日志

    最近忙于学车,抽时间将Nop的一些公用模块添加进来,反应的一些小问题也做了修复.另外有园友指出Nop内存消耗大,作为一个开源电商项目,性能方面不是该团队首要考虑的,开发容易,稳定,代码结构清晰简洁也是 ...

  3. 基于Vue的Quasar Framework 介绍 这个框架UI组件很全面

    基于Vue的Quasar Framework 介绍 这个框架UI组件很全面 基于Vue的Quasar Framework 中文网http://www.quasarchs.com/ quasarfram ...

  4. [转]基于C#的开源GIS项目介绍之SharpMap篇

    我是一个刚毕业的GIS本科毕业生,目前在杭州从事GIS软件应用开发.在项目开发中总感觉自己的编程水平还不够,于是想找些开源GIS小项目来研究研究,借以提高自己的编程能力和项目开发能力.在网上搜了一下“ ...

  5. 基于QTP的自己主动化測试框架介绍

    继前面用了七章介绍了基于QTP的自己主动化測试框架,以下再用几个视频再补充一下.        视频一:基本框架特点介绍说明 .框架的特点从正反两面进行了分析以及主要思想      http://v. ...

  6. 基于StringUtils工具类的常用方法介绍(必看篇)

    前言:工作中看到项目组里的大牛写代码大量的用到了StringUtils工具类来做字符串的操作,便学习整理了一下,方便查阅. isEmpty(String str) 是否为空,空格字符为false is ...

  7. Winform开发框架之插件化应用框架实现

    支持插件化应用的开发框架能给程序带来无穷的生命力,也是目前很多系统.程序追求的重要方向之一,插件化的模块,在遵循一定的接口标准的基础上,可以实现快速集成,也就是所谓的热插拔操作,可以无限对已经开发好系 ...

  8. [译]基于Vue.js的10个最佳UI框架,用于构建移动应用程序

    原文查看10 Best Vue.js based UI Frameworks for Building Mobile Apps 如果您期待使用Vue.js构建移动应用程序,那么您可以选择许多可用的UI ...

  9. ABP VNext框架基础知识介绍(1)--框架基础类继承关系

    在我较早的时候,就开始研究和介绍ABP框架,ABP框架相对一些其他的框架,它整合了很多.net core的新技术和相关应用场景,虽然最早开始ABP框架是基于.net framework,后来也全部转向 ...

  10. Hibernate 系列 01 - 框架技术 (介绍Hibernate框架的发展由来)

    引导目录: Hibernate 系列教程 目录 本篇导航: 为什么学习框架技术 框架的概念 主流框架的介绍 1.为什么学习框架技术 如何制作一份看上去具有专业水准的PPT文档呢?一个简单的方法就是使用 ...

随机推荐

  1. C语言 &#183; 送分啦

    问题描述 这题想得分吗?想,请输出"yes":不想,请输出"no". 输出格式 输出包括一行,为"yes"或"no". ...

  2. 演示get、post请求如何算sn,算得sn如何使用

    import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.UnsupportedEncoding ...

  3. JavaWeb基础: XML基础知识

    简介 XML:可扩展标记语言,W3C指定的用于描述结构化数据的语言,XML在实际开发中,经常用作软件的配置文件,以描述程序模块之间的依赖和组合关系. XML约束:XML常常用于软件配置管理,对于软件框 ...

  4. HDU 5592 ZYB&#39;s Premutation(树状数组+二分)

    题意:给一个排列的每个前缀区间的逆序对数,让还原 原序列. 思路:考虑逆序对的意思,对于k = f[i] - f[i -1],就表示在第i个位置前面有k个比当前位置大的数,那么也就是:除了i后面的数字 ...

  5. 使用AngularJS开发下一代Web应用

    原版的:https://github.com/edagarli/AngularJSWeb 来源书:https://github.com/shyamseshadri/angularjs-book 版权声 ...

  6. 平均得分 【杭州电-HDOJ-2023】 附加题+详细说明

    /* 平均得分 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Su ...

  7. angular 格式化日期

    参考:http://blog.csdn.net/zk437092645/article/details/37882191 html: <input type="text" d ...

  8. 如何使用bootstrap框架

    Bootstrap是前端工程师比较常用的框架.插件,根据它的定义,Bootstrap就是用于前端开发的一个模板,就是别人做好了我们直接可以搬过来直接使用或者根据自己需要略加修改设计自己的页面效果的成品 ...

  9. Pytorch快速入门及在线体验

    本文搭配了Pytorch在线环境,可以直接在线体验. Pytorch是Facebook 的 AI 研究团队发布了一个基于 Python的科学计算包,旨在服务两类场合: 1.替代numpy发挥GPU潜能 ...

  10. 第十二周PSP