概要
在项目开发中,根据用户的需求,一般来是,我们的查询表达式是固定的,新的查询需求都要通过代码的修改来实现。而对于不确定的查询条件,固定查询表达式的方式显然是行不通的。
针对固定查询表达式存在的问题,我们提出基于表达式目录树的解决方案,该解决方案能帮助我们实时自动构建任何需要的查询表达式,以应用对各种复杂的查询场景。
通过定义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);
}
}
执行结果如下:
展望
本次开发的WhereEnhance方法,让我们可以借助表达式目录树相关的类和方法,根据需要动态构建查询表达式,便于业务系统的扩展。个人推荐的使用方式如下:
对于方法1,可以将比较简单的查询条件放到GET请求的URI参数中,后端解析URI中的参数,生成具体的查询条件列表。这样可以有效避免代码因为很小的需求而进行修改。
对于方法2,可以将复杂的查询条件二叉树放到POST请求的Form中,后端在接收到参数后,可以直接进行查询。
表达式目录树系列全部文章
- C#表达式目录树系列之1 – 表达式目录树基本概念
- C#表达式目录树系列之2 – 常见的表达式目录树的实例
- C#表达式目录树系列之3 – 为EF查询实现动态OrderBy
- C#表达式目录树系列之4 – 解决C#泛型约束与无法创建带参数的泛型实例的矛盾
- C#表达式目录树系列之5 – 动态创建查询表达式
附录
查询条件相关的类
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)