首页 > 数据库 >基于SqlSugar的开发框架循序渐进介绍(1)--框架基础类的设计和使用

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

时间:2022-09-18 09:44:39浏览次数:83  
标签:框架 -- 基类 virtual 处理 query input public SqlSugar

在实际项目开发中,我们可能会碰到各种各样的项目环境,有些项目需要一个大而全的整体框架来支撑开发,有些中小项目这需要一些简单便捷的系统框架灵活开发。目前大型一点的框架,可以采用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;
        }
 

https://weibo.com/a/hot/7627533502715906_1.html
https://weibo.com/a/hot/7627533505828866_1.html
https://weibo.com/a/hot/7627533508941825_1.html

标签:框架,--,基类,virtual,处理,query,input,public,SqlSugar
From: https://www.cnblogs.com/jwdlhad/p/16704225.html

相关文章

  • JAVA 调用方法(函数)实现打印矩形
    publicclasstest1{publicstaticvoidmain(String[]args){printRectangle(3,5);//调用printRectangle方法实现打印矩形printRectangle(2,4......
  • python常用魔术方法 (repr str call)
    repr和str的区别https://blog.csdn.net/LIFENG0402/article/details/121567390 classPeople:#类名Person后面加不加(Object)效果是一样的,都表示继承自Object类......
  • JAVA break和continue的区别和应用
    1.break语句:1)打印直角三角星型publicclasstest1{publicstaticvoidmain(String[]args){inti,j;for(i=1;i<=9;i++){//外层循环......
  • 大学个人职业规划
    1.自我介绍    大家好我是软工九班的张国伟,我的大专学校是湖南软件职业技术大学,在大专时就是学的软件工程专业,然后主学的语言是java,在进入大学的第一天就开始对......
  • 洛谷真题字典树
    P8306【模板】字典树1#include<bits/stdc++.h>2usingnamespacestd;3intt,n,q;4constintmaxn=3000005;5chars[maxn];6intson[maxn][80],cnt[ma......
  • C++学习笔记-day16
    1、模板......
  • 树链剖分
    树链剖分的主要支持以下操作:将树结点$x$到$y$的最短路径上所有结点加权查询树结点$x$到$y$的最短路径上所有结点的权值总和将以$x$为根的子树内所有结点加权查询以$x$......
  • static关键字
    1.staticstatic翻译为“静态”所有static关键字修饰的都是类相关的,类级别的。所有static修饰的,都是采用“类名.”的方式访问。static修饰的变量:静态变量static修饰的方法:......
  • 名为WSS_Content_xxx 的 SharePoint 数据库已存在。您必须为该新数据库提供其他名称
    英文报错:ASharePointdatabasenamedWSS_Content_xxxalreadyexists. Youmustsupplyanothernameforthenewdatabase. 使用命令创建内容数据库,中途报错了Ne......
  • 什么是 MAX OUT
    可以参考这篇文章:Maxout激活函数原理及实现-简书(jianshu.com)其中文章中的这张图片是精华. 另外我觉得还可以这样子理解:  也就是说,从左到右做线性层运算,然......