我们一般在学习Linq查询时,查询条件都是写死的。但是我们在实际使用过程中肯定不能这样,而是需要动态创建Linq查询条件,这里我们就需要用到Linq.Expression,用Expression来创建查询条件。
DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
dt.Columns.Add("Name", typeof(string));
dt.Columns.Add("Height", typeof(float));
for(int i=0; i<1000;i++)
{
dt.Rows.Add(i + 1, "HSC" + (i + 1).ToString(), (i + 1) * 1.21);
}
我们这里用DataTable为数据源举例,因为网上linq to DataTable的动态查询示例实在太少了。请见下面代码:我们有一个3列的DataTable dt,我们需要筛选出Height<5.0的所有行。
如果写死查询条件的话非常简单,就下面一句话:
var query = dt.AsEnumerable().Where(s => s.Field<float>("Height") < 5.0).Select(s => s);
或者用函数委托,如下面的写法,但是这也是写死的查询。
Func<DataRow, bool> fun = r => r.Field<float>("Height") < 5.0;
var q = dt.AsEnumerable().Where(fun).Select(r => r);
或者用下面的写死的Expression也可以。
Expression<Func<DataRow, bool>> exp = s => s.Field<float>("Height")<5.0;
var q1 = dt.AsEnumerable().Where(exp.Compile()).Select(s => s);
但是我们需要的是动态创建查询条件,在网上搜索了2天,看了各种关于Expression和Expression tree的介绍,如果只是通过对象的属性来筛选的话还是比较简单的,用ParameterExpresssion表示DataRow, MemberExpression表示属性,ConstanExpress表示常量,最后在用一下Expression.LessThan()就可以了。但是我们如果把DataRow.Field当作属性来用(我一开始就是这么做的),那编译始终不过。Field是DataRow的一个方法,这里我们就要用反射来获取方法,再用MethodCallExpression表示DataRow.Field()方法,然后在获取对应列的数据DataRow.Field(“Height"),才可以拿来比较用。
但是网络上关于如何使用DataRow.Field()方法反射的Express实在是太少了,看了N多文章还是一知半解。就算有一些看似可以的方法,也没有写清楚如何在Linq里面调用。
这里我把我摸索出来的成功的方法写出来供大家参考,希望大家可以少走一些弯路。
private Func<DataRow, bool> GetLambdaExp()
{
ParameterExpression r = Expression.Parameter(typeof(DataRow), "r"); //DataRow参数:r
ConstantExpression expHeight = Expression.Constant("Height", typeof(string)); //"Height" 字符串常量
List<Expression> list = new List<Expression>();
list.Add(r); list.Add(expHeight); //参数列表:DataRow, "Height"
//获取Field方法反射 MethodInfo,!!!一定需要MakeGenericMethod!!!, 其中的float类型为r.Field<float>("Height")的返回值类型
MethodInfo mi = typeof(DataRowExtensions).GetMethod("Field", new Type[] { typeof(DataRow), typeof(string) }).MakeGenericMethod(typeof(float));
MethodCallExpression MC = Expression.Call(null, mi, list); //创建MethodCallExpression
ConstantExpression c5 = Expression.Constant((float)5.0, typeof(float));//创建float常量5.0
Expression con = Expression.LessThan(MC, c5); //Expression: r.Field<float>("Height")<5.0
var expLam = Expression.Lambda<Func<DataRow, bool>>(con, r);//Func<DataRow, bool> Expression
//var q = dt.AsEnumerable().Where(expLam.Compile()).Select(s => s);//如果需要直接调用
return expLam.Compile();
}
最后Linq调用我们创建好的动态查询Expression
var q = dt.AsEnumerable().Where(GetLambdaExp()).Select(r => r);
//用Expression动态创建和上面类似的查询
这里只是用MethodInfo和Expression实现了对于DataRow的Field的最基本的动态查询函数,各位自己再完善一下,加入各种组合查询条件。
-------------------------------------------------------------------- 分割线 ---------------------------------------------------------------
这里我又做了一个自定义的动态Linq查询,自定义的查询条件存储在Dictionary里面,然后根据Dictionary里面的条件创建动态Linq Expression查询。
private void bt_TestMapping_Click(object sender, EventArgs e)
{
Dictionary<string, string> FilterExpression = new Dictionary<string, string>();
FilterExpression.Add("`Height`>5.0", "AND");
FilterExpression.Add("`ID`<=10", "AND");
FilterExpression.Add("`Name`!='HSC100'", "AND");
FilterExpression.Add("`Age`>2", "AND");
FilterExpression.Add("`Weight`>1.20", "AND");
ParameterExpression r = Expression.Parameter(typeof(DataRow), "r"); //DataRow参数:r
Expression con = Expression.Constant(true); //All nested conditions
foreach (string key in FilterExpression.Keys)
{
string[] conArr = SplitConditionStr(key);
Type FieldType = dt.Columns[conArr[0]].DataType;
ConstantExpression expFieldName = Expression.Constant(conArr[0], typeof(string)); //"Height" 字符串常量
ConstantExpression expValue;// = Expression.Constant(conArr[2], FieldType);//创建float常量5.0 }
switch (FieldType.Name)
{
case "Single": { expValue = Expression.Constant(float.Parse(conArr[2]), FieldType); } break;
case "Double": { expValue = Expression.Constant(double.Parse(conArr[2]), FieldType); } break;
case "Int32": { expValue = Expression.Constant(int.Parse(conArr[2]), FieldType); } break;
case "Int64": { expValue = Expression.Constant(long.Parse(conArr[2]), FieldType); } break;
default: { expValue = Expression.Constant(conArr[2], FieldType); } break;
}
List<Expression> list = new List<Expression>();
list.Add(r); list.Add(expFieldName); //参数列表:DataRow, "Height"
MethodInfo mi = typeof(DataRowExtensions).GetMethod("Field", new Type[] { typeof(DataRow), typeof(string) }).MakeGenericMethod(FieldType);
MethodCallExpression MC = Expression.Call(null, mi, list); //创建MethodCallExpression
Expression SingleExpCon;
switch (conArr[1])
{
case ">=": { SingleExpCon = Expression.GreaterThanOrEqual(MC, expValue); } break;
case "<=": { SingleExpCon = Expression.LessThanOrEqual(MC, expValue); } break;
case "!=": { SingleExpCon = Expression.NotEqual(MC, expValue); } break;
case ">": { SingleExpCon = Expression.GreaterThan(MC, expValue); } break;
case "<": { SingleExpCon = Expression.LessThan(MC, expValue); } break;
case "=": { SingleExpCon = Expression.Equal(MC, expValue); } break;
default: { SingleExpCon = Expression.Equal(MC, expValue); } break;
}
switch (FilterExpression[key].ToUpper())
{
case "AND": { con = Expression.And(con, SingleExpCon); } break;
case "OR": { con = Expression.Or(con, SingleExpCon); }; break;
case "AND NOT": { con = Expression.And(con, Expression.Not(SingleExpCon)); }; break;
case "OR NOT": { con = Expression.Or(con, Expression.Not(SingleExpCon)); }; break;
default: con = Expression.Add(con, SingleExpCon); break;
}
}
//MessageBox.Show(con.ToString());
var expLam = Expression.Lambda<Func<DataRow, bool>>(con, r);//Func<DataRow, bool> Expression
var q = dt.AsEnumerable().Where(expLam.Compile()).Select(s => s);
foreach (var row in q)
{
dataGridView1.Rows[dt.Rows.IndexOf(row)].Selected = true;
}
}
string[] SplitConditionStr(string Condition)
{
string[] cons = new string[3];
if (Condition.Contains("!="))
{
cons = Regex.Split(Condition, @"!=");
return new string[] { cons[0].Replace("`", "").Replace("'", ""), @"!=", cons[1].Replace("`", "").Replace("'", "") };
}
else if (Condition.Contains(">="))
{
cons = Regex.Split(Condition, @">=");
return new string[] { cons[0].Replace("`", "").Replace("'", ""), @">=", cons[1].Replace("`", "").Replace("'", "") };
}
else if (Condition.Contains("<="))
{
cons = Regex.Split(Condition, @"<=");
return new string[] { cons[0].Replace("`", "").Replace("'", ""), @"<=", cons[1].Replace("`", "").Replace("'", "") };
}
else
{
char[] comparetors = new char[] { '=', '>', '<' };
cons = Condition.Split(comparetors);
if (Condition.Contains("="))
{
return new string[] { cons[0].Replace("`", "").Replace("'", ""), @"=", cons[1].Replace("`", "").Replace("'", "") };
}
else if (Condition.Contains(">"))
{
return new string[] { cons[0].Replace("`", "").Replace("'", ""), @">", cons[1].Replace("`", "").Replace("'", "") };
}
else//(Condition.Contains("<"))
{
return new string[] { cons[0].Replace("`", "").Replace("'", ""), @"<", cons[1].Replace("`", "").Replace("'", "") };
}
}
}