玩轉NET Expression
- 2022 年 4 月 4 日
- 筆記
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Fast.Framework.Models; namespace Fast.Framework.Extensions { /// <summary> /// 表達式擴展類 /// </summary> public static class ExpressionExtensions { /// <summary> /// 解析Sql /// </summary> /// <param name="expression">表達式</param> /// <param name="options">選項</param> /// <returns></returns> public static SqlInfo ResolveSql(this Expression expression, SqlExpressionOptions options) { var ex = new SqlExpressionVisitor(options); ex.Visit(expression); return ex.sqlInfo; } } /// <summary> /// 解析類型 /// </summary> public enum ResolveType { /// <summary> /// Sql字元串 /// </summary> SqlString = 0, /// <summary> /// 調用 /// </summary> Call = 1, /// <summary> /// 左相似 /// </summary> LeftLike = 2, /// <summary> /// 右相似 /// </summary> RightLike = 3, /// <summary> /// 相似 /// </summary> Like = 4 } /// <summary> /// 表達式映射 /// </summary> public static class ExpressionMapper { /// <summary> /// 表達式類型 /// </summary> public static readonly Dictionary<ExpressionType, string> expressionTypes; /// <summary> /// 方法調用 /// </summary> public static readonly Dictionary<string, Action<SqlInfo, MethodCallExpression, Func<Expression, Expression>, Action<ResolveType>>> methodCall; /// <summary> /// 轉換 /// </summary> public static readonly Action<MethodCallExpression, Func<Expression, Expression>, Action<ResolveType>> convert; /// <summary> /// 構造方法 /// </summary> static ExpressionMapper() { expressionTypes = new Dictionary<ExpressionType, string>() { {ExpressionType.Add,"+" }, {ExpressionType.Subtract,"-" }, {ExpressionType.Multiply,"*" }, {ExpressionType.Divide,"/" }, {ExpressionType.AndAlso,"AND" }, {ExpressionType.OrElse,"OR" }, {ExpressionType.Equal,"=" }, {ExpressionType.NotEqual,"<>" }, {ExpressionType.GreaterThan,">" }, {ExpressionType.LessThan,"<" }, {ExpressionType.GreaterThanOrEqual,">=" }, {ExpressionType.LessThanOrEqual,"<=" } }; methodCall = new Dictionary<string, Action<SqlInfo, MethodCallExpression, Func<Expression, Expression>, Action<ResolveType>>>() { #region 聚合函數 {"Sum", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push("("); sql.SqlStack.Push("SUM"); } }, {"Max", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push("("); sql.SqlStack.Push("MAX"); } }, {"Min", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push("("); sql.SqlStack.Push("MIN"); } }, {"Count", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push("("); sql.SqlStack.Push("COUNT"); } }, {"Avg", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push("("); sql.SqlStack.Push("AVG"); } }, #endregion #region 日期函數 {"GetDate", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); sql.SqlStack.Push("("); sql.SqlStack.Push("GETDATE"); } }, {"Year", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push("("); sql.SqlStack.Push("YEAR"); } }, {"Month", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push("("); sql.SqlStack.Push("MONTH"); } }, {"Day", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push("("); sql.SqlStack.Push("DAY"); } }, {"Current_Timestamp", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); sql.SqlStack.Push("("); sql.SqlStack.Push("CURRENT_TIMESTAMP"); } }, {"Current_Date", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); sql.SqlStack.Push("("); sql.SqlStack.Push("CURRENT_DATE"); } }, {"Current_Time", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); sql.SqlStack.Push("("); sql.SqlStack.Push("CURRENT_TIME"); } }, #endregion #region 數學函數 {"Abs", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push("("); sql.SqlStack.Push("ABS"); } }, {"Round", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[1]); sql.SqlStack.Push(","); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push("("); sql.SqlStack.Push("ROUND"); } }, #endregion #region 字元串函數 {"Contains", (sql,node,visit,call)=> { call(ResolveType.Like); visit.Invoke(node.Arguments[0]); call(ResolveType.SqlString); sql.SqlStack.Push(" LIKE "); visit.Invoke(node.Object); } }, {"StartsWith", (sql,node,visit,call)=> { call(ResolveType.LeftLike); visit.Invoke(node.Arguments[0]); call(ResolveType.SqlString); sql.SqlStack.Push(" LIKE "); visit.Invoke(node.Object); } }, {"EndsWith", (sql,node,visit,call)=> { call(ResolveType.RightLike); visit.Invoke(node.Arguments[0]); call(ResolveType.SqlString); sql.SqlStack.Push(" LIKE "); visit.Invoke(node.Object); } }, {"Substring", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); if (node.Arguments.Count > 1) { visit.Invoke(node.Arguments[1]); sql.SqlStack.Push(","); } visit.Invoke(node.Arguments[0]); sql.SqlStack.Push(","); visit.Invoke(node.Object); sql.SqlStack.Push("("); sql.SqlStack.Push("SUBSTRING"); } }, {"Replace", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[1]); sql.SqlStack.Push(","); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push(","); visit.Invoke(node.Object); sql.SqlStack.Push("("); sql.SqlStack.Push("REPLACE"); } }, {"ToUpper", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Object); sql.SqlStack.Push("("); sql.SqlStack.Push("UPPER"); } }, {"ToLower", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Object); sql.SqlStack.Push("("); sql.SqlStack.Push("LOWER"); } }, {"Trim", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Object); sql.SqlStack.Push("("); sql.SqlStack.Push("TRIM"); } }, #endregion #region 其它函數 {"Equals", (sql,node,visit,call)=> { visit.Invoke(node.Arguments[0]); sql.SqlStack.Push(" = "); visit.Invoke(node.Object); } }, {"IsNull", (sql,node,visit,call)=> { sql.SqlStack.Push(")"); visit.Invoke(node.Arguments[1]); sql.SqlStack.Push(","); visit.Invoke(node.Arguments[0]); sql.SqlStack.Push("("); sql.SqlStack.Push("ISNULL"); visit.Invoke(node.Object); } } #endregion }; } } /// <summary> /// Sql表達式訪問 /// </summary> public class SqlExpressionVisitor : ExpressionVisitor { /// <summary> /// Sql資訊 /// </summary> public readonly SqlInfo sqlInfo; /// <summary> /// 參數索引 /// </summary> private readonly Dictionary<string, int> parameterIndex; /// <summary> /// 成員資訊 /// </summary> private readonly Stack<MemberInfo> memberInfos; /// <summary> /// 表達式選項 /// </summary> private readonly SqlExpressionOptions options; /// <summary> /// 解析類型 /// </summary> private ResolveType resolveType; /// <summary> /// 構造方法 /// </summary> /// <param name="options">表達式選項</param> public SqlExpressionVisitor(SqlExpressionOptions options, ResolveType resolveType = ResolveType.SqlString) { sqlInfo = new SqlInfo(); parameterIndex = new Dictionary<string, int>(); memberInfos = new Stack<MemberInfo>(); this.options = options; this.resolveType = resolveType; } /// <summary> /// 訪問 /// </summary> /// <param name="node"></param> /// <returns></returns> [return: NotNullIfNotNull("node")] public override Expression Visit(Expression node) { //Console.WriteLine(node?.NodeType); return base.Visit(node); } /// <summary> /// 訪問Lambda /// </summary> /// <typeparam name="T"></typeparam> /// <param name="node">節點</param> /// <returns></returns> protected override Expression VisitLambda<T>(Expression<T> node) { if (!options.IgnoreParameterExpression) { var index = 0; foreach (var parameter in node.Parameters) { parameterIndex.Add(parameter.Name, index); index++; } } return Visit(node.Body); } /// <summary> /// 訪問一元表達式 /// </summary> /// <param name="node">節點</param> /// <returns></returns> protected override Expression VisitUnary(UnaryExpression node) { var memberExpression = (node as UnaryExpression).Operand as MemberExpression; var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call); var array = (sqlExpressionVisitor.Visit(memberExpression) as ConstantExpression).Value as object[]; return Visit(Expression.Constant(array.Length)); } /// <summary> /// 訪問二元表達式 /// </summary> /// <param name="node">節點</param> /// <returns></returns> protected override Expression VisitBinary(BinaryExpression node) { if (node.NodeType == ExpressionType.ArrayIndex) { var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call); var array = (sqlExpressionVisitor.Visit(node.Left) as ConstantExpression).Value as object[]; var value = array[Convert.ToInt32((sqlExpressionVisitor.Visit(node.Right) as ConstantExpression).Value)]; return VisitConstant(Expression.Constant(value)); } else { if (!ExpressionMapper.expressionTypes.ContainsKey(node.NodeType)) { throw new Exception($"暫不支援{node.NodeType}類型解析"); } var op = ExpressionMapper.expressionTypes[node.NodeType]; if (node.Right.NodeType == ExpressionType.Constant && ((ConstantExpression)node.Right).Value == null)//判斷null值 { if (op == "=") { sqlInfo.SqlStack.Push(" IS NULL"); } else if (op == "<>") { sqlInfo.SqlStack.Push(" IS NOT NULL"); } } else { if (node.Right is BinaryExpression && node.Right.NodeType == ExpressionType.Equal) { sqlInfo.SqlStack.Push(")"); Visit(node.Right);//右邊帶括弧 sqlInfo.SqlStack.Push("("); } else { Visit(node.Right);//右邊 } sqlInfo.SqlStack.Push($" {op} ");//操作符 } if (node.Left is BinaryExpression && node.Left.NodeType == ExpressionType.Equal) { sqlInfo.SqlStack.Push(")"); Visit(node.Left); //左邊帶括弧 sqlInfo.SqlStack.Push("("); } else { Visit(node.Left); //左邊 } } return node; } /// <summary> /// 訪問索引 /// </summary> /// <param name="node">節點</param> /// <returns></returns> protected override Expression VisitIndex(IndexExpression node) { return base.VisitIndex(node); } /// <summary> /// 訪問參數 /// </summary> /// <param name="node">節點</param> /// <returns></returns> protected override Expression VisitParameter(ParameterExpression node) { if (!options.IgnoreParameterExpression) { sqlInfo.SqlStack.Push($"p{parameterIndex[node.Name]}."); } return node; } /// <summary> /// 訪問對象 /// </summary> /// <param name="node">節點</param> /// <returns></returns> protected override Expression VisitNew(NewExpression node) { if (resolveType == ResolveType.SqlString) { for (int i = 0; i < node.Arguments.Count; i++) { var name = AddIdentifier(node.Members[i].IsDefined(typeof(ColumnAttribute)) ? node.Members[i].GetCustomAttribute<ColumnAttribute>().Name : node.Members[i].Name); Visit(node.Arguments[i]); var value = string.Join("", sqlInfo.SqlStack.ToList()); sqlInfo.NewKeyValues.Add(name, value); sqlInfo.NewNames.Add(name); sqlInfo.NewValues.Add(value); sqlInfo.NewAssignMapper.Add($"{name} = {value}"); sqlInfo.NewAsMapper.Add($"{value} AS {name}"); sqlInfo.SqlStack.Clear(); } } else if (resolveType == ResolveType.Call) { var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call); var args = new List<object>(); foreach (var item in node.Arguments) { args.Add((sqlExpressionVisitor.Visit(item) as ConstantExpression).Value); } var type = node.Type; object obj = null; if (type.IsGenericTypeDefinition)//泛型類 { type = type.MakeGenericType(type.GetGenericArguments()); } if (type.IsDefined(typeof(CompilerGeneratedAttribute)))//匿名對象 { var argsTypes = args.Select(s => s?.GetType()).ToList().GetTypeDefaultValue(); obj = Activator.CreateInstance(type, argsTypes.ToArray()); for (int i = 0; i < node.Members.Count; i++) { var member = node.Members[i]; type.GetField($"<{member.Name}>i__Field", BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance).SetValue(obj, args[i]); } } else { obj = Activator.CreateInstance(type, args.ToArray()); } return VisitConstant(Expression.Constant(obj)); } return node; } /// <summary> /// 訪問列表初始化 /// </summary> /// <param name="node">節點</param> /// <returns></returns> protected override Expression VisitListInit(ListInitExpression node) { var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call); var args = new List<object>(); args.Add((sqlExpressionVisitor.Visit(node.NewExpression) as ConstantExpression).Value); var type = node.Type; if (type.IsGenericTypeDefinition) { type = type.MakeGenericType(type.GetGenericArguments()); } var list = Activator.CreateInstance(type, args.ToArray()); foreach (var item in node.Initializers) { var args2 = new List<object>(); foreach (var item2 in item.Arguments) { args2.Add((sqlExpressionVisitor.Visit(item2) as ConstantExpression).Value); } item.AddMethod.Invoke(list, args2.ToArray()); } if (resolveType == ResolveType.SqlString) { return VisitConstant(Expression.Constant(string.Join(",", list))); } return VisitConstant(Expression.Constant(list)); } /// <summary> /// 訪問對象數組 /// </summary> /// <param name="node">節點</param> /// <returns></returns> protected override Expression VisitNewArray(NewArrayExpression node) { var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call); var array = Array.CreateInstance(node.Type.GetElementType(), node.Expressions.Count); for (int i = 0; i < array.Length; i++) { array.SetValue((sqlExpressionVisitor.Visit(node.Expressions[i]) as ConstantExpression).Value, i); } if (resolveType == ResolveType.SqlString) { return VisitConstant(Expression.Constant(string.Join(",", array as object[]))); } return VisitConstant(Expression.Constant(array)); } /// <summary> /// 訪問方法 /// </summary> /// <param name="node">節點</param> /// <returns></returns> protected override Expression VisitMethodCall(MethodCallExpression node) { if (ExpressionMapper.methodCall.ContainsKey(node.Method.Name) && resolveType == ResolveType.SqlString) { ExpressionMapper.methodCall[node.Method.Name].Invoke(sqlInfo, node, e => Visit(e), (type) => { resolveType = type;//回調設置類型 }); } else { var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call); var args = new List<object>(); foreach (var item in node.Arguments) { args.Add((sqlExpressionVisitor.Visit(item) as ConstantExpression).Value); } object obj = null; if (node.Object == null) { obj = this; } else { obj = (sqlExpressionVisitor.Visit(node.Object) as ConstantExpression).Value; } var value = node.Method.Invoke(obj, args.ToArray()); resolveType = ResolveType.SqlString; return Visit(Expression.Constant(value)); } return node; } /// <summary> /// 訪問成員初始化 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitMemberInit(MemberInitExpression node) { var expressions = (node.Reduce() as BlockExpression).Expressions.Skip(1).SkipLast(1).ToList();//去除不需要的表達式; if (resolveType == ResolveType.SqlString) { for (int i = 0; i < expressions.Count; i++) { var binaryExpression = (BinaryExpression)expressions[i]; Visit(binaryExpression.Right); var name = AddIdentifier(node.Bindings[i].Member.IsDefined(typeof(ColumnAttribute)) ? node.Bindings[i].Member.GetCustomAttribute<ColumnAttribute>().Name : node.Bindings[i].Member.Name); var value = string.Join("", sqlInfo.SqlStack.ToList()); sqlInfo.NewKeyValues.Add(name, value); sqlInfo.NewNames.Add(name); sqlInfo.NewValues.Add(value); sqlInfo.NewAssignMapper.Add($"{name} = {value}"); sqlInfo.NewAsMapper.Add($"{value} AS {name}"); sqlInfo.SqlStack.Clear(); } } else if (resolveType == ResolveType.Call) { var sqlExpressionVisitor = new SqlExpressionVisitor(options, ResolveType.Call); var obj = (sqlExpressionVisitor.Visit(node.NewExpression) as ConstantExpression).Value; for (int i = 0; i < expressions.Count; i++) { if (node.Bindings[i].Member.MemberType == MemberTypes.Field) { var fields = (FieldInfo)node.Bindings[i].Member; fields.SetValue(obj, (sqlExpressionVisitor.Visit((expressions[i] as BinaryExpression).Right) as ConstantExpression).Value); } if (node.Bindings[i].Member.MemberType == MemberTypes.Property) { var propertyInfo = (PropertyInfo)node.Bindings[i].Member; propertyInfo.SetValue(obj, (sqlExpressionVisitor.Visit((expressions[i] as BinaryExpression).Right) as ConstantExpression).Value); } } return VisitConstant(Expression.Constant(obj)); } return node; } /// <summary> /// 訪問成員 /// </summary> /// <param name="node">節點</param> /// <returns></returns> protected override Expression VisitMember(MemberExpression node) { memberInfos.Push(node.Member); if (node.Expression == null && node.NodeType == ExpressionType.MemberAccess) { return Visit(Expression.Constant(null));//子表達式為空但有成員處理 } if (node.Expression.NodeType == ExpressionType.Parameter) { if (node.Member.IsDefined(typeof(ColumnAttribute))) { sqlInfo.SqlStack.Push(AddIdentifier(node.Member.GetCustomAttribute<ColumnAttribute>().Name)); } else { sqlInfo.SqlStack.Push(AddIdentifier(node.Member.Name)); } } return Visit(node.Expression); } /// <summary> /// 訪問常量 /// </summary> /// <param name="node">節點</param> /// <returns></returns> protected override Expression VisitConstant(ConstantExpression node) { var value = node.Value; if (value != null && value.GetType().FullName.EndsWith("c__DisplayClass0_0")) { foreach (var member in memberInfos) { if (member.MemberType == MemberTypes.Field) { var fieldInfo = (FieldInfo)member; value = fieldInfo.GetValue(value); } else if (member.MemberType == MemberTypes.Property) { var propertyInfo = (PropertyInfo)member; value = propertyInfo.GetValue(value); } } } if (resolveType != ResolveType.Call) { if (value != null && value.GetType().Equals(typeof(DateTime))) { value = Convert.ToDateTime(value).ToString(options.DateTimeFormat);//格式化日期類型 } if (memberInfos.Count > 0) { var parameterName = Guid.NewGuid().ToString().Replace("-", ""); sqlInfo.SqlStack.Push($"{options.ParameterNotation}{parameterName}"); sqlInfo.SqlParameters.Add(parameterName, AddWildcard(value)); } else { value = AddQuotes(AddWildcard(value)); sqlInfo.SqlStack.Push(Convert.ToString(value)); } } //最後一個節點初始化 memberInfos.Clear(); return Expression.Constant(value);//返回新的常量表達式 } #region 私有公共方法 /// <summary> /// 添加引號 /// </summary> /// <param name="value">值</param> /// <returns></returns> private static object AddQuotes(object value) { if (value == null) { return null; } var type = value.GetType(); if (type.Equals(typeof(char)) || type.Equals(typeof(string)) || type.Equals(typeof(DateTime))) { return $"'{value}'"; } return value; } /// <summary> /// 添加識別符 /// </summary> /// <param name="name">名稱</param> /// <returns></returns> private string AddIdentifier(string name) { if (string.IsNullOrWhiteSpace(options.Identifier)) { return name; } return options.Identifier.Insert(1, name); } /// <summary> /// 添加通配符 /// </summary> /// <param name="value">值</param> /// <returns></returns> private object AddWildcard(object value) { if (resolveType == ResolveType.LeftLike) { return $"%{value}"; } else if (resolveType == ResolveType.RightLike) { return $"{value}%"; } else if (resolveType == ResolveType.Like) { return $"%{value}%"; } return value; } #endregion } }
//gitee.com/China-Mr-zhong/Fast.Framework 歡迎Star