LINQ
在本地查詢IEnumerbale
主要是用委托來作為傳參,而解析型查詢IQueryable
則用Expression
來作為傳參:
public static IEnumerable<T> Where<T>(this IEnumerable<T> enumable, Func<T, bool> func)
public static IQueryable<T> Where<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> func)
一、Expression是什么
1、如何定義
Expression<Func<TSource, bool>>
就是表達式目錄樹Expression
不能帶有大括號,只能有一行代碼
2、和委托的區別
-
在委托外面包裹一層
Expression<>
就是表達式目錄樹 -
表達式目錄樹可以通過
Compile()
轉換成一個委托
3、Expression本質
- 表達式目錄樹是一個類的封裝,描述了一個結構,有身體部分和參數部分
- 身體部分分為左邊和右邊,內部描述了左邊和右邊之間的關系,可以不斷的往下拆分,類似於二叉樹
- 表達式目錄樹展開后的每一個節點也是一個表達式目錄樹
Expression<Func<People, bool>> expression = p => p.Id == 10;
Func<People, bool> func = expression.Compile();
bool bResult = func.Invoke(new People()
{
Id = 10,
Name = "張三"
});
二、Expression與Expression Tree
首先我們來寫下一些代碼:
Expression<Func<int, int>> expression = (num) => num + 5;
Console.WriteLine($"NodeType:{expression.NodeType}");
Console.WriteLine($"Body:{expression.Body}");
Console.WriteLine($"Body Type: {expression.Body.GetType()}");
Console.WriteLine($"Body NodeType: {expression.Body.NodeType}");
輸出如下:
NodeType:Lambda
Body:(num + 5)
Body Type: System.Linq.Expressions.SimpleBinaryExpression
Body NodeType: Add
我們將expression
轉為LambdaExpression
看看都有啥:
if (expression.NodeType == ExpressionType.Lambda)
{
var lambda = (LambdaExpression)expression;
var parameter = lambda.Parameters.Single();
Console.WriteLine($"parameter.Name:{parameter.Name}");
Console.WriteLine($"parameter.Type:{parameter.Type}");
Console.WriteLine($"parameter.ReturnType:{lambda.ReturnType}");
}
輸出如下:
parameter.Name:num
parameter.Type:System.Int32
parameter.ReturnType:System.Int32
由於我們知道expression.Body
是BinaryExpression
,那么我們就將其轉為它,然后我們繼續看下去:
if (expression.Body.NodeType == ExpressionType.Add)
{
var binaryExpreesion = (BinaryExpression)expression.Body;
Console.WriteLine($"Left Type:{binaryExpreesion.Left.GetType()}");
Console.WriteLine($"Left NodeType:{binaryExpreesion.Left.NodeType}");
Console.WriteLine($"Right Type:{binaryExpreesion.Right.GetType()}");
Console.WriteLine($"Right NodeType:{binaryExpreesion.Right.NodeType}");
if (binaryExpreesion.Left is ParameterExpression parameterExpreesion)
{
Console.WriteLine($"parameterExpreesion.Name:{parameterExpreesion.Name}");
Console.WriteLine($"parameterExpreesion.Type:{parameterExpreesion.Type}");
}
if (binaryExpreesion.Right is ConstantExpression constantExpreesion)
{
Console.WriteLine($"constantExpreesion.Value:{constantExpreesion.Value}" );
}
}
輸出如下:
Left Type:System.Linq.Expressions.PrimitiveParameterExpression`1[System.Int32]
Left NodeType:Parameter
Right Type:System.Linq.Expressions.ConstantExpression
Right NodeType:Constant
parameterExpreesion.Name:num
parameterExpreesion.Type:System.Int32
constantExpreesion.Value:5
最后我們將表達式樹轉為委托:
var @delegate = expression.Compile();
Console.WriteLine(@delegate?.Invoke(2));
輸出:
7 //2+5
實際上,通過Expression<Func<int, int>> expression = (num) => num + 5;
,賦值后的expression
變成了一個表達式樹,它的結構是這樣的:
而有意思的是二元表達式樹BinaryExpression
是一個二叉樹,而LambdaExpression
則是一個支持參數的表達式,能夠通過其Parameters
屬性知道傳入的參數的類型和數量,通過ReturnType
知道返回值是什么類型
而我們再看看整個關於Expression
的繼承關系鏈:
因此,我們也可以顯式的通過各自Expreesion
的實現子類來創建跟lambda
表達式一樣的結果:
var parameterExpreesion1 = Expression.Parameter(typeof(int), "num");
BinaryExpression binaryExpression1 = Expression.MakeBinary(ExpressionType.Add, parameterExpreesion1, Expression.Constant(5));
Expression<Func<int, int>> expression1 = Expression.Lambda<Func<int, int>>(binaryExpression1, parameterExpreesion1);
if (expression1.Body.NodeType == ExpressionType.Add)
{
var binaryExpreesion1 = (BinaryExpression)expression1.Body;
Console.WriteLine($"Left Type:{binaryExpreesion1.Left.GetType()}");
Console.WriteLine($"Left NodeType:{binaryExpreesion1.Left.NodeType}");
Console.WriteLine($"Right Type:{binaryExpreesion1.Right.GetType()}");
Console.WriteLine($"Right NodeType:{binaryExpreesion1.Right.NodeType}");
if (binaryExpreesion1.Left is ParameterExpression parameterExpreesion2)
{
Console.WriteLine($"parameterExpreesion.Name:{parameterExpreesion2.Name}");
Console.WriteLine($"parameterExpreesion.Type:{parameterExpreesion2.Type}");
}
if (binaryExpreesion1.Right is ConstantExpression constantExpreesion1)
{
Console.WriteLine($"constantExpreesion.Value:{constantExpreesion1.Value}");
}
var @delegate1 = expression1.Compile();
Console.WriteLine(@delegate1(2));
輸出結果:
Left Type:System.Linq.Expressions.PrimitiveParameterExpression`1[System.Int32]
Left NodeType:Parameter
Right Type:System.Linq.Expressions.ConstantExpression
Right NodeType:Constant
parameterExpreesion.Name:num
parameterExpreesion.Type:System.Int32
constantExpreesion.Value:5
result:7
我們則發現,結果是一模一樣的,但是費勁了很多,因此用lamda
構建表達式樹是一個非常愉快的語法糖,讓你能夠愉快的在使用表達式和表達式樹
三、Expression動態拼裝
1、最基礎版本
Expression<Func<int>> expression = () => 123 + 234;
//常量表達式
ConstantExpression expression1 = Expression.Constant(123);
ConstantExpression expression2 = Expression.Constant(234);
//二元表達式
BinaryExpression binaryExpression = Expression.Add(expression1, expression2);
Expression<Func<int>> expressionReslut = Expression.Lambda<Func<int>>(binaryExpression);
Func<int> func = expressionReslut.Compile();
int iResult = func.Invoke();
2、帶參數版本
Expression<Func<int, int>> expression1 = m => m + 1;
Func<int, int> func = expression1.Compile();
int iResult = func.Invoke(5);
//參數表達式
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
//常量表達式
ConstantExpression constant = Expression.Constant(1, typeof(int));
//二元表達式
BinaryExpression addExpression = Expression.Add(parameterExpression, constant);
Expression<Func<int, int>> expression = Expression.Lambda<Func<int, int>>(addExpression, new ParameterExpression[1]
{
parameterExpression
});
Func<int, int> func1 = expression.Compile();
int iResult1 = func1.Invoke(5);
3、帶有多個參數
Expression<Func<int, int, int>> expression = (m, n) => m * n + 2;
Func<int, int, int> func = expression.Compile();
int iResult = func.Invoke(10, 20);
//參數表達式
ParameterExpression parameterExpressionM = Expression.Parameter(typeof(int), "m");
ParameterExpression parameterExpressionN = Expression.Parameter(typeof(int), "n");
//二元表達式
BinaryExpression multiply = Expression.Multiply(parameterExpressionM, parameterExpressionN);
//常量表達式
ConstantExpression constantExpression = Expression.Constant(2);
//二元表達式
BinaryExpression plus = Expression.Add(multiply, constantExpression);
Expression<Func<int, int, int>> expression1 = Expression.Lambda<Func<int, int, int>>(plus, new ParameterExpression[2]
{
parameterExpressionM,
parameterExpressionN
});
Func<int, int, int> func1 = expression1.Compile();
int iResult1 = func1.Invoke(10, 20);
4、對象字段值比較
類似於這種比較復雜的,建議大家可以反編譯看看
Expression<Func<People, bool>> predicate = c => c.Id == 10;
Func<People, bool> func = predicate.Compile();
bool bResult = func.Invoke(new People()
{
Id = 10
});
//參數表達式
ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "c");
//反射獲取屬性
FieldInfo fieldId = typeof(People).GetField("Id");
//通過parameterExpression來獲取調用Id
MemberExpression idExp = Expression.Field(parameterExpression, fieldId);
//常量表達式
ConstantExpression constant10 = Expression.Constant(10, typeof(int));
//二元表達式
BinaryExpression expressionExp = Expression.Equal(idExp, constant10);
Expression<Func<People, bool>> predicate1 = Expression.Lambda<Func<People, bool>>(expressionExp, new ParameterExpression[1]
{
parameterExpression
});
Func<People, bool> func1 = predicate1.Compile();
bool bResult1 = func1.Invoke(new People()
{
Id = 10
});
5、多條件
如果遇到很長的表達式目錄樹,拼裝建議從右往左拼裝
Expression<Func<People, bool>> predicate = c => c.Id.ToString() == "10" && c.Name.Equals("張三") && c.Age > 35;
Func<People, bool> func = predicate.Compile();
bool bResult = func.Invoke(new People()
{
Id = 10,
Name = "張三",
Age = 36
});
ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "c");
//c.Age > 35
ConstantExpression constant35 = Expression.Constant(35);
PropertyInfo propAge = typeof(People).GetProperty("Age");
MemberExpression ageExp = Expression.Property(parameterExpression, propAge);
BinaryExpression cagExp = Expression.GreaterThan(ageExp, constant35);
//c.Name.Equals("張三")
ConstantExpression constantrichard = Expression.Constant("張三");
PropertyInfo propName = typeof(People).GetProperty("Name");
MemberExpression nameExp = Expression.Property(parameterExpression, propName);
MethodInfo equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
MethodCallExpression NameExp = Expression.Call(nameExp, equals, constantrichard);
//c.Id.ToString() == "10"
ConstantExpression constantExpression10 = Expression.Constant("10", typeof(string));
FieldInfo fieldId = typeof(People).GetField("Id");
var idExp = Expression.Field(parameterExpression, fieldId);
MethodInfo toString = typeof(int).GetMethod("ToString", new Type[0]);
var toStringExp = Expression.Call(idExp, toString, Array.Empty<Expression>());
var EqualExp = Expression.Equal(toStringExp, constantExpression10);
//c.Id.ToString() == "10"&& c.Name.Equals("張三")&& c.Age > 35
var plus = Expression.AndAlso(EqualExp, NameExp);
var exp = Expression.AndAlso(plus, cagExp);
Expression<Func<People, bool>> predicate1 = Expression.Lambda<Func<People, bool>>(exp, new ParameterExpression[1]
{
parameterExpression
});
Func<People, bool> func1 = predicate1.Compile();
bool bResult1 = func1.Invoke(new People()
{
Id = 10,
Name = "張三",
Age = 36
});
四、Expression應用之Mapper映射
需求:需要把People
字段值映射到PeopleCopy
字段
1、硬編碼
性能好,不靈活;不能共用
PeopleCopy peopleCopy0 = new PeopleCopy()
{
Id = people.Id,
Name = people.Name,
Age = people.Age
};
2、反射
靈活,但是性能不好
using System;
namespace MyExpression.MappingExtend
{
public class ReflectionMapper
{
/// <summary>
/// 反射
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
/// <param name="tIn"></param>
/// <returns></returns>
public static TOut Trans<TIn, TOut>(TIn tIn)
{
TOut tOut = Activator.CreateInstance<TOut>();
foreach (var itemOut in tOut.GetType().GetProperties())
{
var propIn = tIn.GetType().GetProperty(itemOut.Name);
itemOut.SetValue(tOut, propIn.GetValue(tIn));
}
foreach (var itemOut in tOut.GetType().GetFields())
{
var fieldIn = tIn.GetType().GetField(itemOut.Name);
itemOut.SetValue(tOut, fieldIn.GetValue(tIn));
}
return tOut;
}
}
}
調用
PeopleCopy peopleCopy1 = ReflectionMapper.Trans<People, PeopleCopy>(people);
3、序列化
靈活,但是性能不好
using Newtonsoft.Json;
namespace MyExpression.MappingExtend
{
public class SerializeMapper
{
/// <summary>
/// 序列化
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
public static TOut Trans<TIn, TOut>(TIn tIn)
{
string strJson = JsonConvert.SerializeObject(tIn);
return JsonConvert.DeserializeObject<TOut>(strJson);
}
}
}
調用
PeopleCopy peopleCopy2 = SerializeMapper.Trans<People, PeopleCopy>(people);
4、Expression動態拼接+普通緩存
- 把
People
變成PeopleCopy
的過程封裝在一個委托中,這個委托通過表達式目錄樹Compile
出來,過程動態拼裝適應不同的類型 - 第一次生成的時候,保存一個委托在緩存中,如果第二次來,委托就可以直接從緩存中獲取到,直接運行委托,效率高
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace MyExpression.MappingExtend
{
public class ExpressionMapper
{
/// <summary>
/// 字典緩存,保存的是委托,委托內部是轉換的動作
/// </summary>
private static Dictionary<string, object> _Dic = new Dictionary<string, object>();
/// <summary>
/// Expression動態拼接+普通緩存
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
/// <param name="tIn"></param>
/// <returns></returns>
public static TOut Trans<TIn, TOut>(TIn tIn)
{
string key = $"funckey_{typeof(TIn).FullName}_{typeof(TOut).FullName}";
if (!_Dic.ContainsKey(key))
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
foreach (var item in typeof(TOut).GetProperties())
{
MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
foreach (var item in typeof(TOut).GetFields())
{
MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
{
parameterExpression
});
Func<TIn, TOut> func = lambda.Compile();//拼裝是一次性的
_Dic[key] = func;
}
return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn);
}
}
}
調用
PeopleCopy peopleCopy3 = ExpressionMapper.Trans<People, PeopleCopy>(people);
5、Expression動態拼接+泛型緩存
泛型緩存,就是為每一組類型的組合,生成一個副本,性能最高
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace MyExpression.MappingExtend
{
/// <summary>
/// Expression動態拼接+泛型緩存
/// </summary>
/// <typeparam name="TIn"></typeparam>
/// <typeparam name="TOut"></typeparam>
public class ExpressionGenericMapper<TIn, TOut>//Mapper`2
{
private static Func<TIn, TOut> _FUNC = null;
static ExpressionGenericMapper()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
foreach (var item in typeof(TOut).GetProperties())
{
MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
foreach (var item in typeof(TOut).GetFields())
{
MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
{
parameterExpression
});
_FUNC = lambda.Compile();//拼裝是一次性的
}
public static TOut Trans(TIn t)
{
return _FUNC(t);
}
}
}
調用
PeopleCopy peopleCopy4 = ExpressionGenericMapper<People, PeopleCopy>.Trans(people);
6、性能比較
Expression
動態拼接+泛型緩存性能高,而且靈活
long common = 0;
long generic = 0;
long cache = 0;
long reflection = 0;
long serialize = 0;
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = new PeopleCopy()
{
Id = people.Id,
Name = people.Name,
Age = people.Age
};
}
watch.Stop();
common = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = ReflectionMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
reflection = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = SerializeMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
serialize = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = ExpressionMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
cache = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = ExpressionGenericMapper<People, PeopleCopy>.Trans(people);
}
watch.Stop();
generic = watch.ElapsedMilliseconds;
}
Console.WriteLine($"common = { common} ms");
Console.WriteLine($"reflection = { reflection} ms");
Console.WriteLine($"serialize = { serialize} ms");
Console.WriteLine($"cache = { cache} ms");
Console.WriteLine($"generic = { generic} ms");
運行結果
common = 32 ms
reflection = 1026 ms
serialize = 2510 ms
cache = 236 ms
generic = 31 ms
五、ExpressionVisitor解析Expression
1、Expression解析
Expression
是通過訪問者模式進行解析的,官方提供了ExpressionVisitor
抽象類ExpressionVisitor
的Visit
方法是解析表達式目錄樹的一個入口,Visit
方法判斷Expression
是一個什么表達式目錄樹,走不同的細分方法進行進一步解析ExpressionVisitor
的VisitBinary
方法是對二元表達式的解析,所有復雜的表達式都會拆解成二元表達式進行解析
2、Expression修改
自定義一個OperationsVisitor
,繼承自ExpressionVisitor
,復寫父類的VisitBinary
方法,修改Expression
的解析
OperationsVisitor定義
using System.Linq.Expressions;
namespace MyExpression
{
/// <summary>
/// 自定義Visitor
/// </summary>
public class OperationsVisitor : ExpressionVisitor
{
/// <summary>
/// 覆寫父類方法;//二元表達式的訪問
/// 把表達式目錄樹中相加改成相減,相乘改成相除
/// </summary>
/// <param name="b"></param>
/// <returns></returns>
protected override Expression VisitBinary(BinaryExpression b)
{
if (b.NodeType == ExpressionType.Add)//相加
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);
return Expression.Subtract(left, right);//相減
}
else if (b.NodeType==ExpressionType.Multiply) //相乘
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);
return Expression.Divide(left, right); //相除
}
return base.VisitBinary(b);
}
}
}
Expression解析轉換
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
Console.WriteLine(exp.ToString());
OperationsVisitor visitor = new OperationsVisitor();
Expression expNew = visitor.Visit(exp);
Console.WriteLine(expNew.ToString());
運行結果
(m, n) => ((m * n) + 2)
(m, n) => ((m / n) - 2)
3、封裝多條件連接擴展方法
擴展方法實現
/// <summary>
/// 合並表達式 And Or Not擴展方法
/// </summary>
public static class ExpressionExtend
{
/// <summary>
/// 合並表達式 expr1 AND expr2
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expr1"></param>
/// <param name="expr2"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
if (expr1 == null || expr2 == null)
{
throw new Exception("null不能處理");
}
ParameterExpression newParameter = Expression.Parameter(typeof(T), "x");
NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
Expression left = visitor.Visit(expr1.Body);
Expression right = visitor.Visit(expr2.Body);
BinaryExpression body = Expression.And(left, right);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
/// <summary>
/// 合並表達式 expr1 or expr2
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expr1"></param>
/// <param name="expr2"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
if (expr1 == null || expr2 == null)
{
throw new Exception("null不能處理");
}
ParameterExpression newParameter = Expression.Parameter(typeof(T), "x");
NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
Expression left = visitor.Visit(expr1.Body);
Expression right = visitor.Visit(expr2.Body);
BinaryExpression body = Expression.Or(left, right);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
/// <summary>
/// 表達式取非
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expr"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
{
if (expr == null)
{
throw new Exception("null不能處理");
}
ParameterExpression newParameter = expr.Parameters[0];
UnaryExpression body = Expression.Not(expr.Body);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
}
自定義Visitor
internal class NewExpressionVisitor : ExpressionVisitor
{
public ParameterExpression _NewParameter { get; private set; }
public NewExpressionVisitor(ParameterExpression param)
{
this._NewParameter = param;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return this._NewParameter;
}
}
數據過濾方法定義
/// <summary>
/// 篩選數據執行
/// </summary>
/// <param name="func"></param>
private static void Do(Expression<Func<People, bool>> func)
{
List<People> people = new List<People>()
{
new People(){Id=4,Name="123",Age=4},
new People(){Id=5,Name="234",Age=5},
new People(){Id=6,Name="345",Age=6},
};
List<People> peopleList = people.Where(func.Compile()).ToList();
}
Expression拼接
Expression<Func<People, bool>> lambda1 = x => x.Age > 5;
Expression<Func<People, bool>> lambda2 = x => x.Id > 5;
Expression<Func<People, bool>> lambda3 = lambda1.And(lambda2);//且
Expression<Func<People, bool>> lambda4 = lambda1.Or(lambda2);//或
Expression<Func<People, bool>> lambda5 = lambda1.Not();//非
Do(lambda3);
Do(lambda4);
Do(lambda5);
六、ExpressionVisitor應用之ToSql
需求:實現ORM
框架Expression
映射成SQL
自定義一個ConditionBuilderVisitor
繼承自ExpressionVisitor
,復寫父類的方法,Expression
解析過程中實現SQL
的拼接
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace MyExpression
{
public class ConditionBuilderVisitor : ExpressionVisitor
{
private Stack<string> _StringStack = new Stack<string>();
/// <summary>
/// 返回拼裝好的sql條件表達式
/// </summary>
/// <returns></returns>
public string Condition()
{
string condition = string.Concat(this._StringStack.ToArray());
this._StringStack.Clear();
return condition;
}
/// <summary>
/// 如果是二元表達式
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitBinary(BinaryExpression node)
{
if (node == null) throw new ArgumentNullException("BinaryExpression");
this._StringStack.Push(")");
base.Visit(node.Right);//解析右邊
this._StringStack.Push(" " + ToSqlOperator(node.NodeType) + " ");
base.Visit(node.Left);//解析左邊
this._StringStack.Push("(");
return node;
}
/// <summary>
/// 解析屬性
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitMember(MemberExpression node)
{
if (node == null) throw new ArgumentNullException("MemberExpression");
if (node.Expression is ConstantExpression)
{
var value1 = this.InvokeValue(node);
var value2 = this.ReflectionValue(node);
this._StringStack.Push("'" + value2 + "'");
}
else
{
this._StringStack.Push(" [" + node.Member.Name + "] ");
}
return node;
}
private string ToSqlOperator(ExpressionType type)
{
switch (type)
{
case (ExpressionType.AndAlso):
case (ExpressionType.And):
return "AND";
case (ExpressionType.OrElse):
case (ExpressionType.Or):
return "OR";
case (ExpressionType.Not):
return "NOT";
case (ExpressionType.NotEqual):
return "<>";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case (ExpressionType.Equal):
return "=";
default:
throw new Exception("不支持該方法");
}
}
private object InvokeValue(MemberExpression member)
{
var objExp = Expression.Convert(member, typeof(object));//struct需要
return Expression.Lambda<Func<object>>(objExp).Compile().Invoke();
}
private object ReflectionValue(MemberExpression member)
{
var obj = (member.Expression as ConstantExpression).Value;
return (member.Member as FieldInfo).GetValue(obj);
}
/// <summary>
/// 常量表達式
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitConstant(ConstantExpression node)
{
if (node == null) throw new ArgumentNullException("ConstantExpression");
this._StringStack.Push("" + node.Value + "");
return node;
}
/// <summary>
/// 方法表達式
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
protected override Expression VisitMethodCall(MethodCallExpression m)
{
if (m == null) throw new ArgumentNullException("MethodCallExpression");
string format;
switch (m.Method.Name)
{
case "StartsWith":
format = "({0} LIKE '{1}%')";
break;
case "Contains":
format = "({0} LIKE '%{1}%')";
break;
case "EndsWith":
format = "({0} LIKE '%{1}')";
break;
default:
throw new NotSupportedException(m.NodeType + " is not supported!");
}
this.Visit(m.Object);
this.Visit(m.Arguments[0]);
string right = this._StringStack.Pop();
string left = this._StringStack.Pop();
this._StringStack.Push(String.Format(format, left, right));
return m;
}
}
}
ConstantSqlString泛型緩存緩存生成的sql
using System;
using System.Linq;
namespace MyExpression
{
public class ConstantSqlString<T>
{
/// <summary>
/// 泛型緩存,一個類型一個緩存
/// </summary>
private static string FindSql = null;
/// <summary>
/// 獲取查詢sql
/// </summary>
static ConstantSqlString()
{
Type type = typeof(T);
FindSql = $"Select {string.Join(',', type.GetProperties().Select(c => $"[{c.Name}]").ToList())} from {type.Name}";
}
/// <summary>
/// 獲取查詢sql+條件篩選
/// </summary>
/// <param name="exp"></param>
/// <returns></returns>
public static string GetQuerySql(string exp)
{
return $"{FindSql} where {exp}";
}
}
}
普通多條件
Expression<Func<People, bool>> lambda = x => x.Age > 5
&& x.Id > 5
&& x.Name.StartsWith("1") // like '1%'
&& x.Name.EndsWith("1") // like '%1'
&& x.Name.Contains("1");// like '%1%'
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
string sql = ConstantSqlString<People>.GetQuerySql(vistor.Condition());
Console.WriteLine(sql);
外部參數變量
string name = "AAA";
Expression<Func<People, bool>> lambda = x => x.Age > 5 && x.Name == name || x.Id > 5;
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
string sql = ConstantSqlString<People>.GetQuerySql(vistor.Condition());
Console.WriteLine(sql);
內部常量多條件
Expression<Func<People, bool>> lambda = x => x.Age > 5 || (x.Name == "A" && x.Id > 5);
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
string sql = ConstantSqlString<People>.GetQuerySql(vistor.Condition());
Console.WriteLine(sql);
運行結果
Select [Age],[Name] from People where ((((( [Age] > 5) AND ( [Id] > 5)) AND ( [Name] LIKE '1%')) AND ( [Name] LIKE '%1')) AND ( [Name] LIKE '%1%'))
Select [Age],[Name] from People where ((( [Age] > 5) AND ( [Name] = 'AAA')) OR ( [Id] > 5))
Select [Age],[Name] from People where (( [Age] > 5) OR (( [Name] = A) AND ( [Id] > 5)))
七、多條件拼接示例
1、動態拼接lambda表達式
/// <summary>
/// Lambda表達式拼接擴展類
/// </summary>
public static class Utility
{
/// <summary>
/// Lambda表達式拼接
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="first"></param>
/// <param name="second"></param>
/// <param name="merge"></param>
/// <returns></returns>
public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
{
// 構建參數映射(從第二個參數到第一個參數)
var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);
// 用第一個lambda表達式中的參數替換第二個lambda表達式中的參數
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// 將lambda表達式體的組合應用於第一個表達式中的參數
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
/// <summary>
/// and擴展
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.And);
}
/// <summary>
/// or擴展
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.Or);
}
}
/// <summary>
///
/// </summary>
public class ParameterRebinder : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> map;
public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
/// <summary>
///
/// </summary>
/// <param name="map"></param>
/// <param name="exp"></param>
/// <returns></returns>
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
/// <summary>
///
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
protected override Expression VisitParameter(ParameterExpression p)
{
ParameterExpression replacement;
if (map.TryGetValue(p, out replacement))
{
p = replacement;
}
return base.VisitParameter(p);
}
}
//常見用途,比如我們數據層封裝了如下方法:
/// <summary>
/// 獲取一個實體
/// </summary>
/// <param name="expWhere">查詢條件</param>
/// <returns></returns>
public virtual T Find(Expression<Func<T, bool>> expWhere) {
T entity = null;
using (IDbContext MsSqlDB = CreateSqlContext()) {
IQuery<T> q = MsSqlDB.Query<T>();
entity = q.Where(expWhere).FirstOrDefault();
}
return entity;
}
//上層調用時候可以動態拼接lambda表達式:
Expression<Func<Books, bool>> where = c => true;
if (bookID == "-1")
{
where = where.And(a => a.Id == "001");
}
if (bookName == "-1")
{
where = where.Or(a => a.Name == "Test");
}
new BaseLocalBll<Books>().Find(where);
2、使用Expression進行查詢拼接
public static class DynamicLinqExpressions
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.Or(expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.And(expr1.Body, invokedExpr), expr1.Parameters);
}
}
//使用方法
protected void btnSearch_Click(object sender, EventArgs e)
{
using(LinqDBDataContext db = new LinqDBDataContext())
{
var list = db.StuInfo;
var where = DynamicLinqExpressions.True<StuInfo>();
if(txtName.Text.Trim().Length!=0)
{
where = where.And(p => p.StuName.Contains(txtName.Text.Trim()));
}
if(txtAge.Text.Trim().Length!=0)
{
where = where.And(p => p.StuAge == Convert.ToInt32(txtAge.Text.Trim()));
}
var result = list.Where(where.Compile()).ToList();
this.repStuInfo.DataSource = result;
this.repStuInfo.DataBind();
}
}
3、簡單的多條件拼接方法
var list = new List<Expression<Func<Student, bool>>>();
list.Add(o => true);
if (stuID != 0) list.Add(o => o.ID == stuID);
if (sex != "") list.Add(o => o.Sex == sex);
if (age != null) list.Add(o => o.Age > age);
Expression<Func<Student, bool>> expWhere = null;
foreach (var expression in list)
{
expWhere = expWhere.And(expression);
}
Students.Where(expWhere.Compile()).ToList();