概要
在項目開發中,根據用戶的需求,一般來是,我們的查詢表達式是固定的,新的查詢需求都要通過代碼的修改來實現。而對於不確定的查詢條件,固定查詢表達式的方式顯然是行不通的。
針對固定查詢表達式存在的問題,我們提出基於表達式目錄樹的解決方案,該解決方案能幫助我們實時自動構建任何需要的查詢表達式,以應用對各種複雜的查詢場景。
通過定義IQueryable的擴展方法,來實現查詢表達式的實時構建,並且以保證構建的查詢表達式同IQueryable已支持的EF查詢或內存查詢的兼容性,完整代碼已經上傳CSDN,需要的讀者可以免費下載,如果下載失敗請留言告訴我郵箱。
設計及關鍵代碼實現
實現目標
基於既定需求,我們的實現目標如下:
- 支持的查詢字段類型包括數字,字符串,日期,枚舉和布爾:
- 數字類型查詢支持大於,小於,等於,大於等於,小於等於的比較操作
- 字符串類型查詢支持等於,包含,以指定字符開頭或結尾的比較操作
- 日期類型查詢支持大於,小於,等於,大於等於,小於等於的比較操作
- 枚舉和布爾類型查詢支持等於的比較操作
- 支持多個條件按照And或Or關係的組合查詢,And爲默認組合方式
- 支持數字,字符串,日期及和枚舉類型的否定查詢
設計實現
IQueryable的擴展方法定於如下:
- public static IQueryable WhereEnhance (this IQueryable source, List conditions)
- public static IQueryable WhereEnhance (this IQueryable source, ConditionTreeNode root)
針對不同複雜度的查詢,我們定義了兩個WhereEnhance的方法,兩個方法接收的參數類型不同。
- 對於方法1,將查詢條件通過一個List傳入。每個查詢條件記錄和下一個查詢條件的邏輯關係And或者Or。因爲每個節點只能記錄和下一個節點的邏輯關係,對於複雜的嵌套邏輯關係無法支持,所以該方法只適用於查詢條件的邏輯關係比較簡單的查詢情景, WhereCondition類的定義請參見附錄。
- 對於方法2,將查詢條件通過一個二叉樹的根節點傳入。二叉樹的非葉節點保存查詢條件之間的邏輯關係And或者Or。葉節點保存具體的查詢條件。這樣,複雜的嵌套邏輯關係完全可以通過二叉樹來表示,所以該方法適用於查詢條件的邏輯關係比較複雜的情景。ConditionTreeNode 定義詳見附錄。
關鍵代碼解釋
WhereEnhance方法(接收List參數)
本文通過定義一個IQueryable的擴展方法WhereEnhance來實現上述查詢表達式構建流程,該方法既可以用於內存數據的查詢,也可以和EF配合在一起查詢數據庫中的數據。具體代碼如下:
public static IQueryable<T> WhereEnhance<T> (this IQueryable<T> source, List<WhereCondition> conditions) {
ParameterExpression parameter = Expression.Parameter(typeof(T),"s");
Expression conditionsExpression = CreateExpressions<T>(conditions, parameter);
LambdaExpression whereCondition = Expression.Lambda<Func<T,bool>>(conditionsExpression,new ParameterExpression[]{
parameter});
Console.WriteLine("Lambda Expression: " + whereCondition);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] {
source.ElementType },
source.Expression,
whereCondition);
return source.Provider.CreateQuery<T>(whereCallExpression) as IQueryable<T>;
}
private static Expression CreateExpressions<T>(List<WhereCondition> conditions, ParameterExpression parameter){
Expression conditionsExpression = Expression.And(Expression.Constant(true),Expression.Constant(true));
ConditionRelation relation = ConditionRelation.And;
for(var i = 0 ; i< conditions.Count; ++i){
MemberExpression property = Expression.Property(parameter, conditions[i].PropertyName);
PropertyType propertyType = CheckFieldType(property);
Expression whereCondition;
if (propertyType == PropertyType.String){
whereCondition = buildStringWhere<T>(conditions[i], parameter);
}else if (propertyType == PropertyType.Number
|| propertyType == PropertyType.DateTime
|| propertyType == PropertyType.Enum
|| propertyType == PropertyType.Bool){
whereCondition = buildNumberOrDateTimeWhere<T>(conditions[i], parameter);
}else{
throw new ArgumentException($"We cannot support the property type {property.Type} in {typeof(T)}. ");
}
if (i==0){
conditionsExpression = whereCondition;
relation = conditions[i].NextRelation;
}else{
conditionsExpression = Combine<T>(
conditionsExpression,
whereCondition,
relation
);
relation = conditions[i].NextRelation;
}
}
return conditionsExpression;
}
- 通過在該方法中定義泛型參數,實現在不同的WepAPI中,所有業務相關類型的查詢支持。在實際應用中,建議增加泛型約束。
- 構建Lambda表達式左側的變量s,類型爲T。
- 調用方法CreateExpressions創建查詢表達式,其中傳入的查詢條件排列順序,即爲這些條件生成對應查詢表達式後,表達式進行And或Or組合時候的順序。
- 遍歷所有查詢條件,生成具體的查詢表達式。
- 調用CheckFieldType方法,檢查屬性類型,該方法請見附錄。
- 字符串類型的屬性,所有的比較全部通過字符串類的實例方法實現,所以定義函數buildStringWhere來實現字符串查詢表達式的構建。
- 數字,日期,枚舉都支持通過操作符 < > 或=的比較,布爾類型也支持=操作符。因此將這些類型查詢表達式構建分爲一類,通過buildNumberOrDateTimeWhere方法構建查詢表達式。
- 將生成的查詢表達式通過Combine方法組合在一起。Combine方法支持And和Or兩種組合方式,默認支持And方式。Combine方法定義詳見附錄。
- 將構建好的表達式和Lambda表達式組裝到一起,構建出Lambda表達式。
- 調用Where方法,執行Lambda表達式。
WhereEnhance方法(接收二叉樹根節點參數)
public static IQueryable<T> WhereEnhance<T> (this IQueryable<T> source, ConditionTreeNode root) {
ParameterExpression parameter = Expression.Parameter(typeof(T),"s");
Expression conditionsExpression = CreateExpressions<T>(root, parameter);
LambdaExpression whereCondition = Expression.Lambda<Func<T,bool>>(conditionsExpression,new ParameterExpression[]{
parameter});
Console.WriteLine("Lambda Expression: " + whereCondition);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] {
source.ElementType },
source.Expression,
whereCondition
);
return source.Provider.CreateQuery<T>(whereCallExpression) as IQueryable<T>;
}
private static Expression CreateExpressions<T>(ConditionTreeNode node, ParameterExpression parameter){
if (node.Relation != ConditionRelation.None){
return Combine<T>(
CreateExpressions<T>(node.LeftNode, parameter),
CreateExpressions<T>(node.RightNode, parameter),
node.Relation
);
}
else{
MemberExpression property = Expression.Property(parameter, node.NodeValue.PropertyName);
PropertyType propertyType = CheckFieldType(property);
Expression whereCondition;
if (propertyType == PropertyType.String){
whereCondition = buildStringWhere<T>(node.NodeValue, parameter);
}else if (propertyType == PropertyType.Number || propertyType == PropertyType.DateTime || propertyType == PropertyType.Enum){
whereCondition = buildNumberOrDateTimeWhere<T>(node.NodeValue, parameter);
}else{
throw new ArgumentException($"We cannot support the property type {property.Type} in {typeof(T)}. ");
}
return whereCondition;
}
}
- 通過在該方法中定義泛型參數,實現在不同的WepAPI中,所有業務相關類型的查詢支持。在實際應用中,建議增加泛型約束。
- 構建Lambda表達式左側的變量s,類型爲T。
- 調用方法CreateExpressions創建查詢表達式,傳入的查詢條件是一棵二叉樹根的節點。
- 如果當前節點是非葉節點:
- 遞歸構建當前節點的左子樹節點。
- 遞歸構建當前節點的右子樹節點。
- 將生成的查詢表達式通過Combine方法組合在一起。Combine方法支持And和Or兩種組合方式,默認支持And方式。Combine方法定義詳見附錄。
- 返回構建好的查詢表達式。
- 如果當前節點是葉節點:
- 調用CheckFieldType方法,檢查屬性類型,該方法請見附錄。
- 字符串類型的屬性,所有的比較全部通過字符串類的實例方法實現,所以定義函數buildStringWhere來實現字符串查詢表達式的構建。
- 數字,日期,枚舉都支持通過操作符 < > 或=的比較,布爾類型也支持=操作符。因此將這些類型查詢表達式構建分爲一類,通過buildNumberOrDateTimeWhere方法構建查詢表達式。
- 返回構建好的查詢表達式。
- 如果當前節點是非葉節點:
- 將構建好的表達式和Lambda表達式組裝到一起,構建出Lambda表達式。
- 調用Where方法,執行Lambda表達式。
buildStringWhere方法
buildStringWhere方法用於構建字符串類型的查詢表達式。構建目標:
s => s.Property.Method(keyword),其中:
- s的類型是T,在運行時會傳入具體的類型。
- Property是類中的某一個屬性,類型爲字符串類型。
- Method是具體字符串比較方法,支持Equals,Contains,StartsWith和EndsWith,4個字符串比較方法。
- keyword是查詢關鍵字。
private static Expression buildStringWhere<T>(WhereCondition condition, ParameterExpression parameter){
Dictionary<string,string> methodsMap = new Dictionary<string,string>(){
{
EQ, "Equals"},
{
"contain", "Contains"},
{
"beginwith", "StartsWith"},
{
"endwith", "EndsWith"},
};
MemberExpression property = Expression.Property(parameter, condition.PropertyName);
ConstantExpression rightValue = GetConstantExpression(condition.PropertyValue, property);
ConstantExpression optionValue = Expression.Constant(StringComparison.CurrentCultureIgnoreCase);
Expression methodCall = Expression.Call(
property,
typeof(string).GetMethod(methodsMap[condition.Action],
new Type[]{
typeof(string),
//typeof(StringComparison)
}), // method
new Expression[] // Work method's parameter
{
rightValue,
// optionValue
}
);
if (condition.Reversed){
methodCall = Expression.Not(methodCall);
}
return methodCall;
}
- 根據查詢條件查詢條件中的關鍵字,調用GetConstantNumberExpression方法,構建關鍵字的常量表達式。關鍵字類型與對應的類中屬性類型一致。該方法請見附錄。
- 根據查詢條件,調用具體的字符串比較方法。對於忽略大小寫的查詢,只能支持內存查詢。如果需要在EFCore的查詢中使用,需要在數據庫中進行配置,因此暫時註釋掉了對應代碼。
- 如果是否定比較,將原有查詢表達式,傳化爲否定查詢表達式。
- 返回構建好的字符串查詢表達式。
buildNumberOrDateTimeWhere方法
buildNumberOrDateTimeWhere方法用於構建數字,枚舉和日期類型的查詢表達式。構建目標:
s=> s.Property [>|<|=|<=|>=] keywork 其中:
- s的類型是T,在運行時會傳入具體的類型。
- Property是類中的某一個屬性,類型數字,枚舉或日期類型。其中數字類型支持decimal,int, long,short,byte類型。
- 比較操作符包含大於,小於,等於,小於等於,大於等於中的一個。
- keyword是查詢關鍵字。
private static Expression buildNumberOrDateTimeWhere<T>(WhereCondition condition, ParameterExpression parameter){
MemberExpression propert = Expression.Property(parameter, condition.PropertyName);
ConstantExpression rightValue = GetConstantExpression(condition.PropertyValue, property);
Expression whereCondition;
switch(condition.Action){
case GT:
whereCondition = Expression.GreaterThan(property, rightValue);
break;
case GE:
whereCondition = Expression.GreaterThanOrEqual(property, rightValue);
break;
case LT:
whereCondition = Expression.LessThan(property, rightValue);
break;
case LE:
whereCondition = Expression.LessThanOrEqual(property, rightValue);
break;
default:
whereCondition = Expression.Equal(property, rightValue);
break;
}
if (condition.Reversed){
whereCondition = Expression.Not(whereCondition);
}
return whereCondition;
}
- 根據查詢條件查詢條件中的關鍵字,調用GetConstantExpression方法,構建關鍵字的常量表達式。關鍵字類型與對應的類中屬性類型一致。該方法請見附錄。
- 根據查詢條件,選擇具體的比較操作符。
- 如果是否定比較,將原有查詢表達式傳化爲否定查詢表達式。
- 返回構建好的查詢表達式。
驗證及測試
本次開發的WhereEnhance方法,作爲IQueryable的擴展方法,可以支持內存查詢和配合EFCore,進行數據庫的查詢。本文以查詢銀行分行信息作爲查詢數據,進行查詢。
由於內存數據查詢和數據庫查詢使用的數據和測試結果完全相同,故本文只提供數據庫查詢的測試用例和結果。如果需要內存查詢的相關數據,請參看完整代碼。測試數據詳見附錄。
Branch類支持EFCore的完整代碼
[Table ("t_branch")]
public class Branch{
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity), Column(Order=0)]
public int Id {
get;set;}
[Required, Column(Order=1)]
public string BranchName {
get;set;}
[Required, Column(Order=2)]
public string BranchManager {
get;set;}
[Required, Column(Order=3)]
public int BranchCode {
get;set;}
[Column (TypeName = "datetime", Order=4), Required]
public DateTime BuildDate {
get;set;}
[Required, Column(Order=5)]
public BranchStatus Status {
get;set;} = BranchStatus.Open;
[Required, Column(Order=6)]
public bool HasATM {
get;set;} = true;
[Timestamp, Column(Order=7)]
public byte[] RowVersion {
get; set; }
}
數據庫建表語句和初始化數據請參看附錄。
測試用例
查詢分行代碼小於4的分行信息
using(var context = new ExpressionTreeContext()){
WhereCondition condition = new WhereCondition(){
PropertyName = "BranchCode",
PropertyValue = "4",
Action = ActionType.lt.ToString(),
Reversed = false
};
List<WhereCondition> conditions = new List<WhereCondition>(){
condition,
};
var branches = await context.Branches
.WhereEnhance(conditions)
.AsNoTracking ()
.ToListAsync ();
foreach(var branch in branches){
System.Console.WriteLine(branch.BranchName);
}
}
執行結果:
查詢分行名稱以“分行2”結尾的分行信息
using(var context = new ExpressionTreeContext()){
WhereCondition condition = new WhereCondition(){
PropertyName = "BranchName",
PropertyValue = "分行2",
Action = ActionType.endwith.ToString(),
Reversed = false
};
List<WhereCondition> conditions = new List<WhereCondition>(){
condition,
};
var branches = await context.Branches
.WhereEnhance(conditions)
.AsNoTracking ()
.ToListAsync ();
foreach(var branch in branches){
System.Console.WriteLine(branch.BranchName);
}
}
執行結果:
查詢成立日期晚於2016-10-04的分行信息
using(var context = new ExpressionTreeContext()){
WhereCondition condition = new WhereCondition(){
PropertyName = "BuildDate",
PropertyValue = "2016-10-04",
Action = ActionType.gt.ToString(),
Reversed = false
};
List<WhereCondition> conditions = new List<WhereCondition>(){
condition,
};
var branches = await context.Branches
.WhereEnhance(conditions)
.AsNoTracking ()
.ToListAsync ();
foreach(var branch in branches){
System.Console.WriteLine(branch.BranchName);
}
}
執行結果:
查詢狀態爲Open的分行信息
using(var context = new ExpressionTreeContext()){
WhereCondition condition = new WhereCondition(){
PropertyName = "Status",
PropertyValue = "1",
Action = ActionType.eq.ToString(),
Reversed = false
};
List<WhereCondition> conditions = new List<WhereCondition>(){
condition,
};
var branches = await context.Branches
.WhereEnhance(conditions)
.AsNoTracking ()
.ToListAsync ();
foreach(var branch in branches){
System.Console.WriteLine(branch.BranchName);
}
}
執行結果:
查詢沒有ATM機的分行信息
using(var context = new ExpressionTreeContext()){
WhereCondition condition = new WhereCondition(){
PropertyName = "HasATM",
PropertyValue = "false",
Action = ActionType.eq.ToString(),
Reversed = false
};
List<WhereCondition> conditions = new List<WhereCondition>(){
condition,
};
var branches = await context.Branches
.WhereEnhance(conditions)
.AsNoTracking ()
.ToListAsync ();
foreach(var branch in branches){
System.Console.WriteLine(branch.BranchName);
}
}
執行結果:
查詢類型不是Open的分行信息
using(var context = new ExpressionTreeContext()){
WhereCondition condition = new WhereCondition(){
PropertyName = "Status",
PropertyValue = "2",
Action = ActionType.eq.ToString(),
Reversed = true
};
List<WhereCondition> conditions = new List<WhereCondition>(){
condition,
};
var branches = await context.Branches
.WhereEnhance(conditions)
.AsNoTracking ()
.ToListAsync ();
foreach(var branch in branches){
System.Console.WriteLine(branch.BranchName);
}
}
執行結果:
查詢成立日期不晚於2016-10-04的分行信息
using(var context = new ExpressionTreeContext()){
WhereCondition condition = new WhereCondition(){
PropertyName = "BuildDate",
PropertyValue = "2016-10-04",
Action = ActionType.gt.ToString(),
Reversed = true
};
List<WhereCondition> conditions = new List<WhereCondition>(){
condition,
};
var branches = await context.Branches
.WhereEnhance(conditions)
.AsNoTracking ()
.ToListAsync ();
foreach(var branch in branches){
System.Console.WriteLine(branch.BranchName);
}
}
執行結果:
查詢成立日期在2016-10-02到2016-10-06之間,不包含2016-10-02的分行信息
using(var context = new ExpressionTreeContext()){
WhereCondition conditionStartDate = new WhereCondition(){
PropertyName = "BuildDate",
PropertyValue = "2016-10-02",
Action = ActionType.gt.ToString(),
Reversed = false
};
WhereCondition conditionEndDate = new WhereCondition(){
PropertyName = "BuildDate",
PropertyValue = "2016-10-06",
Action = ActionType.le.ToString(),
Reversed = false
};
List<WhereCondition> conditions = new List<WhereCondition>(){
conditionStartDate,
conditionEndDate,
};
var branches = await context.Branches
.WhereEnhance(conditions)
.AsNoTracking ()
.ToListAsync ();
foreach(var branch in branches){
System.Console.WriteLine(branch.BranchName);
}
}
執行結果:
查詢成立日期在2016-10-02到2016-10-07之間,不包含2016-10-02並且分行類型不是Open的分行信息
using(var context = new ExpressionTreeContext()){
WhereCondition condition = new WhereCondition(){
PropertyName = "Status",
PropertyValue = "1",
Action = ActionType.eq.ToString(),
Reversed = true
};
WhereCondition conditionStartDate = new WhereCondition(){
PropertyName = "BuildDate",
PropertyValue = "2016-10-02",
Action = ActionType.gt.ToString(),
Reversed = false
};
WhereCondition conditionEndDate = new WhereCondition(){
PropertyName = "BuildDate",
PropertyValue = "2016-10-07",
Action = ActionType.le.ToString(),
Reversed = false
};
List<WhereCondition> conditions = new List<WhereCondition>(){
conditionStartDate,
conditionEndDate,
condition,
};
var branches = await context.Branches
.WhereEnhance(conditions)
.AsNoTracking ()
.ToListAsync ();
foreach(var branch in branches){
System.Console.WriteLine(branch.BranchName);
}
}
執行結果:
查詢成立日期早於2016-10-02或者晚於2016-10-09,並且分行經理是Tom的分行信息
using(var context = new ExpressionTreeContext()){
WhereCondition condition = new WhereCondition(){
PropertyName = "BranchManager",
PropertyValue = "Tom",
Action = ActionType.eq.ToString(),
Reversed = false
};
WhereCondition conditionStartDate = new WhereCondition(){
PropertyName = "BuildDate",
PropertyValue = "2016-10-02",
Action = ActionType.lt.ToString(),
NextRelation = ConditionRelation.Or,
Reversed = false
};
WhereCondition conditionEndDate = new WhereCondition(){
PropertyName = "BuildDate",
PropertyValue = "2016-10-04",
Action = ActionType.gt.ToString(),
Reversed = false
};
List<WhereCondition> conditions = new List<WhereCondition>(){
conditionStartDate,
conditionEndDate,
condition,
};
var branches = await context.Branches
.WhereEnhance(conditions)
.AsNoTracking ()
.ToListAsync ();
foreach(var branch in branches){
System.Console.WriteLine(branch.BranchName);
}
}
執行結果:
如果我們將執行順序改爲如下:
List<WhereCondition> conditions = new List<WhereCondition>(){
condition,
conditionStartDate,
conditionEndDate,
};
執行結果如下:
根據測試結果可以看到,如果改變條件的順序,無論時生成的查詢表達式,還是EFCore生成的SQL都不是我們預期的。由於當前版本不支持括號,請將Or操作儘量放到查詢條件的最前面。
複雜條件查詢
查詢條件的Lambda表達式構建目標:
t => (([t].[BranchCode] > 1) AND ([t].[BranchCode] < 3)) OR ((([t].[BranchCode] < 9) AND ([t].[BranchCode] > 5))
AND ([t].[BranchManager] = N’Tom’))
using(var context = new ExpressionTreeContext()){
ConditionTreeNode root = new ConditionTreeNode();
root.Relation = ConditionRelation.Or;
ConditionTreeNode p11 = new ConditionTreeNode();
p11.Relation = ConditionRelation.And;
root.LeftNode = p11;
ConditionTreeNode p12 = new ConditionTreeNode();
p12.Relation = ConditionRelation.And;
root.RightNode = p12;
ConditionTreeNode p21 = new ConditionTreeNode();
p11.LeftNode = p21;
p21.NodeValue = new WhereCondition(){
PropertyName = "BranchCode",
Action = "gt",
PropertyValue = "1"
};
ConditionTreeNode p22 = new ConditionTreeNode();
p11.RightNode = p22;
p22.NodeValue = new WhereCondition(){
PropertyName = "BranchCode",
Action = "lt",
PropertyValue = "3"
};
ConditionTreeNode p23 = new ConditionTreeNode();
p23.Relation = ConditionRelation.And;
ConditionTreeNode p24 = new ConditionTreeNode();
p24.NodeValue = new WhereCondition(){
PropertyName = "BranchManager",
Action = "eq",
PropertyValue = "Tom",
Reversed = false
};
p12.LeftNode = p23;
p12.RightNode = p24;
ConditionTreeNode p31 = new ConditionTreeNode();
p31.NodeValue = new WhereCondition(){
PropertyName = "BranchCode",
Action = "lt",
PropertyValue = "9"
};
ConditionTreeNode p32 = new ConditionTreeNode();
p32.NodeValue = new WhereCondition(){
PropertyName = "BranchCode",
Action = "gt",
PropertyValue = "5"
};
p23.LeftNode = p31;
p23.RightNode = p32;
string json = JsonConvert.SerializeObject(root);
root = JsonConvert.DeserializeObject<ConditionTreeNode>(json);
var branches = await context.Branches
.WhereEnhance(root)
.AsNoTracking ()
.ToListAsync ();
foreach(var branch in branches){
System.Console.WriteLine(branch.BranchName);
}
}
執行結果如下:
表達式目錄樹系列全部文章
- C#表達式目錄樹系列之1 – 表達式目錄樹基本概念
- C#表達式目錄樹系列之2 – 常見的表達式目錄樹的實例
- C#表達式目錄樹系列之3 – 爲EF查詢實現動態OrderBy
- C#表達式目錄樹系列之4 – 解決C#泛型約束與無法創建帶參數的泛型實例的矛盾
- C#表達式目錄樹系列之5 – 根據URL動態創建查詢表達式
附錄
查詢條件相關的類
public class WhereCondition{
public string PropertyName {
get;set;}
public string Action {
get;set;}
public bool Reversed {
get;set;} = false;
public string PropertyValue {
get;set;}
public ConditionRelation NextRelation {
get;set;} = ConditionRelation.And;
}
public enum ConditionRelation{
And= 1,
Or,
None
}
public class ConditionTreeNode{
public ConditionTreeNode LeftNode {
get;set;}
public ConditionTreeNode RightNode {
get;set;}
public ConditionRelation Relation {
get;set;} = ConditionRelation.None;
public WhereCondition NodeValue {
get;set;}
}
WhereCondition類具體內容包括:
- 查詢字段名稱
- 查詢條件,例如eq,gt等,具體請參見代碼。
- 查詢關鍵字
- Reversed字段表示是否是否定查詢,即不等於,不大於,不包含等,默認是false
- ConditionRelation是當前查詢條件和下一個條件的關係,默認是And,可以設置爲Or
ConditionTreeNode類具體內容包括:
- 二叉樹的左右節點
- 如果是非葉節點,則Relation記錄其左右節點的邏輯關係And或者Or。如果是葉節點,Relation爲None。
- 如果是非葉節點,NodeValue爲空。如果是葉節點,NodeValue是WhereCondition類的對象。
分行類代碼
public class Branch {
public string BranchName {
get;set;}
public int BranchCode {
get;set;}
public DateTime BuildDate {
get;set;}
public BranchStatus Status {
get;set;} = BranchStatus.Open;
public string BranchManager {
get;set;}
}
public enum BranchType {
Open = 1,
Closed,
Maintance
}
判斷屬性類型的代碼
public enum PropertyType {
String = 1,
Number,
DateTime,
Enum,
Bool,
Unsupported
}
private static PropertyType CheckFieldType(MemberExpression member){
string typeName = member.Type.ToString();
string baseTypeName = member.Type.BaseType.ToString();
string[] supportedNumbertypes = new string []{
DECIMAL, BYTE, SHORT , INT, LONG};
if (baseTypeName == ENUM){
return PropertyType.Enum;
}
if (typeName == STRING){
return PropertyType.String;
}else if (supportedNumbertypes.Contains(typeName)){
return PropertyType.Number;
}else if (typeName == DATETIME){
return PropertyType.DateTime;
} else if (typeName == BOOL){
return PropertyType.Bool;
}
return PropertyType.Unsupported;
}
創建常量表達式的代碼
private static ConstantExpression GetConstantExpression(string val, MemberExpression member){
ConstantExpression constExpression;
string typeName = member.Type.ToString();
string baseTypeName = member.Type.BaseType.ToString();
switch(typeName){
case STRING:
constExpression = Expression.Constant(val, member.Type);
break;
case DECIMAL:
constExpression = Expression.Constant(Convert.ToDecimal(val), member.Type);
break;
case SHORT:
constExpression = Expression.Constant(Convert.ToInt16(val), member.Type);
break;
case INT:
constExpression = Expression.Constant(Convert.ToInt32(val), member.Type);
break;
case LONG:
constExpression = Expression.Constant(Convert.ToInt64(val), member.Type);
break;
case DATETIME:
constExpression = Expression.Constant(DateTime.Parse(val), member.Type);
break;
case BYTE:
constExpression = Expression.Constant(Convert.ToByte(val), member.Type);
break;
case BOOL:
constExpression = Expression.Constant(Convert.ToBoolean(val), member.Type);
break;
default:
if (baseTypeName == ENUM){
int data = Int32.Parse(val.ToString());
String name = Enum.GetName(member.Type, data);
constExpression = Expression.Constant(Enum.Parse(member.Type, name, false), member.Type);
}else{
constExpression = Expression.Constant(Convert.ToInt64(val), member.Type);
}
break;
}
return constExpression;
}
}
注意枚舉類型需要通過基類來判斷。
查詢表達式組合代碼
private static Expression Combine<T>
(
Expression first,
Expression second,
ConditionRelation relation = ConditionRelation.And
)
{
if ( relation == ConditionRelation.And){
return Expression.AndAlso(first, second);
}else{
return Expression.OrElse(first, second);
}
}
測試數據
數據庫建表語句:
USE [Bank]
GO
/****** Object: Table [dbo].[t_branch] Script Date: 2021/2/1 16:38:49 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[t_branch](
[Id] [int] IDENTITY(1,1) NOT NULL,
[BranchName] [nvarchar](max) NOT NULL,
[BranchCode] [int] NOT NULL,
[BuildDate] [datetime] NOT NULL,
[RowVersion] [timestamp] NULL,
[BranchManager] [nvarchar](max) NOT NULL,
[HasATM] [bit] NOT NULL,
[Status] [int] NOT NULL,
CONSTRAINT [PK_t_branch] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[t_branch] ADD DEFAULT (N'') FOR [BranchManager]
GO
ALTER TABLE [dbo].[t_branch] ADD DEFAULT (CONVERT([bit],(0))) FOR [HasATM]
GO
ALTER TABLE [dbo].[t_branch] ADD DEFAULT ((0)) FOR [Status]
GO
數據初始化語句:
insert into [dbo].[t_branch] ([BranchName], [BranchCode], [BuildDate],[BranchManager], [HasATM], [Status] ) values
(N'天津分行1',1, '2016-10-01', N'Tom',1,1),
(N'天津分行2',2, '2016-10-02', N'Tom',1,1),
(N'天津分行3',3, '2016-10-03', N'Tom',1,1),
(N'天津分行4',4, '2016-10-04', N'Tom',1,1),
(N'天津分行5',5, '2016-10-05', N'Tom',1,1),
(N'天津分行6',6, '2016-10-06', N'Tom',0,1),
(N'天津分行7',7, '2016-10-07', N'Jack',1,2),
(N'天津分行8',8, '2016-10-08', N'Jack',1,2),
(N'天津分行9',9, '2016-10-09', N'Mary',1,3)
展望
本次開發的WhereEnhance方法,讓我們可以藉助表達式目錄樹相關的類和方法,根據需要動態構建查詢表達式,便於業務系統的擴展。但還存在一些不足,未來可以加強的功能包括:
- 當前對於查詢條件的組合順序上,僅支持按照標號,順序組合,未來可以支持通過增加括號,按照括號的優先級進行組合。
- 現在的查詢表達式的構建支持數字,字符串,枚舉和日期4種常用數據類型,未來會支持更多類型,例如各種類型對應的可空類型等。