Sy.ExpressionBuilder是一套依賴於表示式樹上的整合的查詢元件。設計的初衷沒別的,就為了少寫程式碼,讓查詢業務可以變得更加模式化。可以從nuget 獲取到該元件(目前支援 .net5 和.net6 版本)。
來到查詢,查詢實體需要繼承 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; }
特性約束
應用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, }
當然我們怎麼會少得了查詢方式(且和或)的約束,這就放上來
/// <summary> /// 當前條件所屬型別 /// </summary> [Description("當前條件所屬型別")] public enum EnumConditionType { /// <summary> /// 並 /// </summary> [Description("並")] And = 0, /// <summary> /// 或 /// </summary> [Description("或")] Or = 1 }
這樣就構成了我們的查詢約束,一般情況下,當前表欄位查詢的話我們只要屬性名和表欄位名一直即可,例如查詢使用者表的下UserName,如下即可
/// <summary> /// 使用者名稱稱 /// </summary> public string? UserName { get; set; }
如果有那種不想暴露欄位在外部的,這時我們的特性才會說顯示出使用者,例如我還是要查詢UserName,但是暴露給前端的名稱確是Uname,因為特性中的屬性名優先順序會高於查詢模型中的名稱,那我們可如下處理
[Condition("UserName", EnumCondition.Contains,EnumConditionType.And)] public string?Uname { get; set; }
又或者我們的某個欄位需要包含一個集合的情況,我們可以如下實現
[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; }
額外擴充套件
對於排序,有些情況要做到使用者點選表頭,然後由後端進行排序後返回,這裡我也預留了空間,如下(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; } }
現在我們就可以進行查詢了,如下(留意這句,input.ToQueryModel<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.ToQueryModel<Manager>(); return list.AsQueryable().Where(query).ToList(); }
放個以前的效果圖
後續有擴充套件,會在這裡加.....
好了,到這你又可以去擼程式碼了。