Sy.ExpressionBuilder是一套依赖于表达式树上的集成的查询组件。设计的初衷没别的,就为了少写代码,让查询业务可以变得更加模式化。可以从nuget 获取到该组件。
来到查询,查询实体需要继承 QueryPageModel或者 QueryModel,从名字也基本可以看出来,一个用于分页,一个无分页,你可以根据自己需求选用哪个方式,如下我选了带分页的方式。
public partial class AllManagerDto:QueryPageModel
这样这个查询实体就拥有了我们这个插件的大多数功能。
属性名称约束
为了方便处理各种属性类型,我做了一些属性名称的约定。
时间范围查询 =>属性名称 以 Start,End 结尾 ,生成条件为 >= 和<=。
数字范围查询 =>属性名称 以 Min,Max 结尾 ,生成条件为 >= 和<=。
字符串查询 => 名字需要和表字段一致,生成条件为 Contains。
编号查询必须是以Id结尾,不然如果编号为字符串的话,查询方式会以Contains形式查询。
例如:
/// <summary> /// 租户编号 /// </summary> public virtual int? TenantIdMin { get; set; } /// <summary> /// 租户编号 ///</summary> public virtual int? TenantIdMax { get; set; } /// <summary> /// 创建时间 /// </summary> public virtual DateTime? CreateTimeStart { get; set; } /// <summary> /// 创建时间 ///</summary> public DateTime? CreateTimeEnd { get; set; } /// <summary> /// 创建人编号 /// </summary> public virtual string? CreateUserId { get; set; }View Code
特性约束
应用ConditionAttribute 特性,参数为 (字段名称,条件,查询方式) 目前我定义了常用的范围的约束。范围类型枚举
/// <summary> /// 高级搜索条件 /// </summary> [Description("高级搜索条件")] public enum EnumCondition { /// <summary> /// 包含 /// </summary> [Description("包含")] Contains = 0, /// <summary> /// 等于 /// </summary> [Description("等于")] Equal = 1, /// <summary> /// 大于等于 /// </summary> [Description("大于等于")] GtEqual = 2, /// <summary> /// 大于 /// </summary> [Description("大于")] Gt = 3, /// <summary> /// 小于等于 /// </summary> [Description("小于等于")] LtEqual = 4, /// <summary> /// 小于 /// </summary> [Description("小于")] Lt = 5, /// <summary> /// 不等于 /// </summary> [Description("不等于")] NotEqual = 6, /// <summary> /// SQL(In函数) /// </summary> [Description("SQL In")] In = 7, /// <summary> /// 在什么之间 /// </summary> [Description("在什么之间")] Between = 8, /// <summary> /// 不包含 /// </summary> [Description("不包含")] NotContain = 9, /// <summary> /// 从尾部匹配 /// </summary> [Description("从尾部匹配")] EndsWith = 10, /// <summary> /// 从头部匹配 /// </summary> [Description("从头部匹配")] StartsWith = 11, /// <summary> /// 不在范围内 /// </summary> [Description("不在范围内")] NotIn = 12, /// <summary> /// 空的 /// </summary> [Description("空的")] IsEmpty = 13, /// <summary> /// 不为空的 /// </summary> [Description("不为空的")] IsNotEmpty = 14, /// <summary> /// 非Null的 /// </summary> [Description("非Null的")] IsNotNull = 15, /// <summary> /// Null的 /// </summary> [Description("Null的")] IsNull = 16, /// <summary> /// IsNullOrWhiteSpace /// </summary> [Description("IsNullOrWhiteSpace")] IsNullOrWhiteSpace = 17, /// <summary> /// IsNotNullNorWhiteSpace /// </summary> [Description("IsNotNullNorWhiteSpace")] IsNotNullNorWhiteSpace = 18, /// <summary> /// /// </summary> [Description("枚举")] HasFlag = 19, }View Code
当然我们怎么会少得了查询方式(且和或)的约束,这就放上来
/// <summary> /// 当前条件所属类型 /// </summary> [Description("当前条件所属类型")] public enum EnumConditionType { /// <summary> /// 并 /// </summary> [Description("并")] And = 0, /// <summary> /// 或 /// </summary> [Description("或")] Or = 1 }View Code
这样就构成了我们的查询约束,一般情况下,当前表字段查询的话我们只要属性名和表字段名一直即可,例如查询用户表的下UserName,如下即可
/// <summary> /// 用户名称 /// </summary> public string? UserName { get; set; }
如果有那种不想暴露字段在外部的,这时我们的特性才会说显示出用户,例如我还是要查询UserName,但是暴露给前端的名称确是Uname,因为特性中的属性名优先级会高于查询模型中的名称,那我们可如下处理
[Condition("UserName", EnumCondition.Contains,EnumConditionType.And)] public string?Uname { get; set; }
又或者我们的某个字段需要包含一个集合的情况,我们可以如下实现(传过来的是用逗号分隔如:1,2,5,6)
[Condition("UserId",EnumCondition.In,EnumConditionType.And)] public string?UserIds{ get; set; }
导航属性单个查询,例如我在用户表,要根据角色名称查询,我们只要如下定义即可(导航属性名【角色表】+“.”+角色表下的角色名称,注意这个英文的 .,这个才是精髓)
[Condition($"{nameof(Role)}.{nameof(Role.RoleName)}", EnumCondition.Contains,EnumConditionType.And)]
public string? RoleName { get; set; }
导航属性集合查询,例如我在角色表,要查询有分配用户名字叫老王的所有角色,我们只要如下定义即可(导航属性名+“[”+角色表下的角色名称+"]",注意这个英文的 [], [] 表示这个是个集合)
[Condition("Users[UserName]", EnumCondition.Contains,EnumConditionType.And)]
public string? UserName{ get; set; }
特别说明,该组件还支持位移枚举的查询,使用也超级简单,和一般属性几乎无差,查询模型中如下定义即可。
/// <summary> /// 性别 /// </summary>
public EnumGender? Gender { get; set; }
不参与查询特性
如果我们有个别参数是作用于别的用途,不直接参数查询,或者目前该插件处理不了的,我们可以通过该特性排除,如下使用
/// <summary> /// 不参与查询 /// </summary> [NotQuery] public string TreeId { get; set; }
时间范围特性(2022年3月21号加)
当限制用户查询时间范围的时候用,方式如下(特性只用加一个,EnumTimeType 有年月日 时 多种类型可以选择)
/// <summary> /// 创建时间 开始 /// </summary> [TimeSpan(4,EnumTimeType.Day)] public DateTime? CreateTimeStart { get; set; } /// <summary> /// 创建时间 结束 /// </summary> public DateTime? CreateTimeEnd { get; set; }
额外扩展
对于排序,有些情况要做到用户点击表头,然后由后端进行排序后返回,这里我也预留了空间,如下(input 为继承了:QueryPageModel 的模型),第一个参数表示 要排序的字段名,第二个参数true 表示倒序,false 表示正序。
input.AddOrderByItem(nameof(News.Id),true);
我还加了一个相对显得鸡肋的默认排序(强迫症喜欢有个默认的),如果前端有传排序过来的话,这个是无效的,使用方式如下:
input.DefaultOrderBy(nameof(News.CreateTime)
当我们了解了以上约定,我们定义一个相对完整的查询模型,如下
/// <summary> /// 查询参数实体 /// </summary> public class AllManagerDto : QueryPageModel { /// <summary> /// 创建时间 开始(时间必须以Start结尾) /// </summary> public DateTime? CreateTimeStart { get; set; } /// <summary> /// 创建时间 结束(结束时间必须以End结尾) /// </summary> public DateTime? CreateTimeEnd { get; set; } /// <summary> /// 角色编号 /// </summary> [Condition("Role.Id", EnumCondition.In)] public string RoleId { get; set; } /// <summary> /// 角色名称 /// </summary> [Condition("Role.RoleName", EnumCondition.Contains)] public string RoleName { get; set; } /// <summary> /// 系统名称 /// </summary> [Condition("Sys[SysName]", EnumCondition.Contains)] public string SysName { get; set; } /// <summary> /// 名称 /// </summary> public string UserName { get; set; } /// <summary> /// 性别 /// </summary> public EnumGender? Gender { get; set; } [NotMapped] public string TreeId { get; set; } /// <summary> /// 年龄 开始(必须以Min结尾) /// </summary> public int? AgeMin { get; set; } /// <summary> /// 年龄 结尾(必须以Max结尾) /// </summary> public int? AgeMax{ get; set; } }View Code
现在我们就可以进行查询了,如下(留意这句,input.ToExpression<Manager>() 主要是这句把查询参数转换成表达式了)
/// <summary> /// 查询 /// </summary> /// <param name="input"></param> /// <returns></returns> public static List<Manager> GetAll(AllManagerDto input) { var list = GetList(); input.RoleId = "1"; input.Tel = "18888888888"; input.UserName = "张三"; input.Gender = EnumGender.Man; input.CreateTimeStart = DateTime.Parse("2021-9-22"); var query = input.ToExpression<Manager>(); return list.AsQueryable().Where(query).ToList(); }
放个以前的效果图
后续有扩展,会在这里加.....
好了,到这你又可以去撸代码了。