C# -- lambda表示式轉為where條件sql語句的方法
阿新 • • 發佈:2019-02-19
原文轉至:http://www.cnblogs.com/FengCodes/p/LambdaToSqlWhere.html
有更改
/// <summary> /// 根據Expression表示式生成SQL-Where部分的語句 /// </summary> public class SqlGenerate { /// <summary> /// 生成SQL-Where語句 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="expression">表示式</param> /// <param name="databaseType">資料型別(用於欄位是否加引號)</param> /// <returns></returns> public string GetWhereByLambda<T>(Expression<Func<T, bool>> expression, string databaseType = "SqlServer") { bool withQuotationMarks = GetWithQuotationMarks(databaseType); ConditionBuilder conditionBuilder = new ConditionBuilder(); conditionBuilder.WithQuotationMarks = withQuotationMarks; //欄位是否加引號(PostGreSql,Oracle) conditionBuilder.DataBaseType = databaseType.ToEnum<DataBaseType>(true); conditionBuilder.Build(expression); object ce = null; for (int i = 0; i < conditionBuilder.Arguments.Length; i++) { ce = conditionBuilder.Arguments[i]; if (ce == null) { conditionBuilder.Arguments[i] = DBNull.Value; continue; } if (ce is string || ce is char) { bool isQuote = ce.ToString().ToLower().Trim().IndexOf(@"in(") == 0 || ce.ToString().ToLower().Trim().IndexOf(@"not in(") == 0 || ce.ToString().ToLower().Trim().IndexOf(@" like '") == 0 || ce.ToString().ToLower().Trim().IndexOf(@"not like") == 0; conditionBuilder.Arguments[i] = string.Format(" {1}{0}{2} ", ce.ToString(), isQuote ? "" : "'", isQuote ? "" : "'"); continue; } if (ce is int || ce is long || ce is short || ce is decimal || ce is double || ce is float || ce is bool || ce is byte || ce is sbyte || ce is ValueType) { conditionBuilder.Arguments[i] = ce.ToString(); continue; } conditionBuilder.Arguments[i] = string.Format("'{0}'", ce.ToString()); } return string.Format(conditionBuilder.Condition, conditionBuilder.Arguments); } /// <summary> /// 獲取是否欄位加雙引號 /// </summary> /// <param name="databaseType"></param> /// <returns></returns> private bool GetWithQuotationMarks(string databaseType) { bool result = false; switch (databaseType.ToEnum<DataBaseType>(true)) { case DataBaseType.PostGreSql: case DataBaseType.Oracle: result = true; break; } return result; } }
/// <summary> /// 指令碼生成實體 /// </summary> internal sealed class ConditionBuilder : ExpressionVisitor { /// <summary> /// 資料庫型別 /// </summary> private DataBaseType dataBaseType = DataBaseType.SqlServer; /// <summary> /// 欄位是否加引號 /// </summary> private bool withQuotationMarks = false; private List<object> arguments; private Stack<string> conditionParts; /// <summary> /// 加雙引號 /// </summary> /// <param name="str">字串</param> /// <returns></returns> private string AddQuotationMarks(string str) { if (str.IsEmpty() && withQuotationMarks) return "\"" + str.Trim() + "\""; return str; } /// <summary> /// 執行生成 /// </summary> /// <param name="expression"></param> public void Build(Expression expression) { PartialEvaluator evaluator = new PartialEvaluator(); Expression evaluatedExpression = evaluator.Eval(expression); this.arguments = new List<object>(); this.conditionParts = new Stack<string>(); this.Visit(evaluatedExpression); } protected sealed override Expression VisitBinary(BinaryExpression b) { if (b == null) return b; string opr; switch (b.NodeType) { case ExpressionType.Equal: opr = "="; break; case ExpressionType.NotEqual: opr = "<>"; break; case ExpressionType.GreaterThan: opr = ">"; break; case ExpressionType.GreaterThanOrEqual: opr = ">="; break; case ExpressionType.LessThan: opr = "<"; break; case ExpressionType.LessThanOrEqual: opr = "<="; break; case ExpressionType.AndAlso: opr = "and"; break; case ExpressionType.OrElse: opr = "or"; break; case ExpressionType.Add: opr = "+"; break; case ExpressionType.Subtract: opr = "-"; break; case ExpressionType.Multiply: opr = "*"; break; case ExpressionType.Divide: opr = "/"; break; default: throw new NotSupportedException(b.NodeType + "is not supported."); } this.Visit(b.Left); this.Visit(b.Right); string right = this.conditionParts.Pop(); string left = this.conditionParts.Pop(); string condition = String.Format("({0} {1} {2})", left, opr, right); this.conditionParts.Push(condition); return b; } protected sealed override Expression VisitConstant(ConstantExpression c) { if (c == null) return c; this.arguments.Add(c.Value); this.conditionParts.Push(String.Format("{{{0}}}", this.arguments.Count - 1)); return c; } protected sealed override Expression VisitMemberAccess(MemberExpression m) { if (m == null) return m; PropertyInfo propertyInfo = m.Member as PropertyInfo; if (propertyInfo == null) return m; this.conditionParts.Push(String.Format(" {0} ", AddQuotationMarks(propertyInfo.Name))); return m; } private string BinarExpressionProvider(Expression left, Expression right, ExpressionType type) { string stringInfo = "("; //先處理左邊 stringInfo += ExpressionRouter(left); stringInfo += ExpressionTypeCast(type); //再處理右邊 string tmpStr = ExpressionRouter(right); if (tmpStr.SameAS("null")) { if (stringInfo.EndsWith(" =")) stringInfo = stringInfo.Substring(0, stringInfo.Length - 1) + " is null"; else if (stringInfo.EndsWith("<>")) stringInfo = stringInfo.Substring(0, stringInfo.Length - 1) + " is not null"; } else stringInfo += tmpStr; return stringInfo += ")"; } private string ExpressionRouter(Expression exp) { if (exp is BinaryExpression) { BinaryExpression be = ((BinaryExpression)exp); return BinarExpressionProvider(be.Left, be.Right, be.NodeType); } if (exp is MemberExpression) { MemberExpression me = ((MemberExpression)exp); return me.Member.Name; } if (exp is NewArrayExpression) { NewArrayExpression ae = ((NewArrayExpression)exp); StringBuilder tmpstr = new StringBuilder(); foreach (Expression ex in ae.Expressions) { tmpstr.Append(ExpressionRouter(ex)); tmpstr.Append(","); } return tmpstr.ToString(0, tmpstr.Length - 1); } if (exp is MethodCallExpression) { MethodCallExpression mce = (MethodCallExpression)exp; if (mce.Method.Name.SameAS("Like")) return string.Format("({0} like {1})", ExpressionRouter(mce.Arguments[0]), ExpressionRouter(mce.Arguments[1])); else if (mce.Method.Name.SameAS("NotLike")) return string.Format("({0} not like {1})", ExpressionRouter(mce.Arguments[0]), ExpressionRouter(mce.Arguments[1])); else if (mce.Method.Name.SameAS("In")) return string.Format("{0} in ({1})", ExpressionRouter(mce.Arguments[0]), ExpressionRouter(mce.Arguments[1])); else if (mce.Method.Name.SameAS("NotIn")) return string.Format("{0} not in ({1})", ExpressionRouter(mce.Arguments[0]), ExpressionRouter(mce.Arguments[1])); else if (mce.Method.Name.SameAS("StartWith")) return string.Format("{0} like '{1}%'", ExpressionRouter(mce.Arguments[0]), ExpressionRouter(mce.Arguments[1])); return null; } if (exp is ConstantExpression) { ConstantExpression ce = ((ConstantExpression)exp); if (ce.Value == null) return "null"; else if (ce.Value is ValueType) return ce.Value.ToString(); else if (ce.Value is string || ce.Value is DateTime || ce.Value is char) return string.Format("'{0}'", ce.Value.ToString()); return null; } if (exp is UnaryExpression) { UnaryExpression ue = ((UnaryExpression)exp); return ExpressionRouter(ue.Operand); } return null; } private string ExpressionTypeCast(ExpressionType type) { switch (type) { case ExpressionType.And: case ExpressionType.AndAlso: return " and "; case ExpressionType.Equal: return " ="; case ExpressionType.GreaterThan: return " >"; case ExpressionType.GreaterThanOrEqual: return ">="; case ExpressionType.LessThan: return "<"; case ExpressionType.LessThanOrEqual: return "<="; case ExpressionType.NotEqual: return "<>"; case ExpressionType.Or: case ExpressionType.OrElse: return " or "; case ExpressionType.Add: case ExpressionType.AddChecked: return "+"; case ExpressionType.Subtract: case ExpressionType.SubtractChecked: return "-"; case ExpressionType.Divide: return "/"; case ExpressionType.Multiply: case ExpressionType.MultiplyChecked: return "*"; default: return null; } } /// <summary> /// ConditionBuilder 並不支援生成Like操作,如 字串的 StartsWith,Contains,EndsWith 並不能生成這樣的SQL: Like ‘xxx%’, Like ‘%xxx%’ , Like ‘%xxx’ . 只要override VisitMethodCall 這個方法即可實現上述功能。 /// </summary> /// <param name="m"></param> /// <returns></returns> protected sealed override Expression VisitMethodCall(MethodCallExpression m) { if (m == null) return m; string connectorWords = GetLikeConnectorWords(dataBaseType); string format; switch (m.Method.Name.ToUpper()) { case "StartsWith": format = "({0} like ''" + connectorWords + "{1}" + connectorWords + "'%')"; break; case "Contains": format = "({0} like '%'" + connectorWords + "{1}" + connectorWords + "'%')"; break; case "EndsWith": format = "({0} like '%'" + connectorWords + "{1}" + connectorWords + "'')"; break; case "Equals": format = "({0} {1} )"; break; default: throw new NotSupportedException(m.NodeType + " is not supported!"); } this.Visit(m.Object); this.Visit(m.Arguments[0]); string right = this.conditionParts.Pop(); string left = this.conditionParts.Pop(); this.conditionParts.Push(String.Format(format, left, right)); return m; } /// <summary> /// 獲得like語句連結符 /// </summary> /// <param name="databaseType"></param> /// <returns></returns> private string GetLikeConnectorWords(DataBaseType databaseType) { string result = "+"; switch (databaseType) { case DataBaseType.PostGreSql: case DataBaseType.Oracle: case DataBaseType.MySql: case DataBaseType.Sqlite: result = "||"; break; } return result; } /// <summary> /// 獲取或者設定資料庫型別 /// </summary> public DataBaseType DataBaseType { get { return this.dataBaseType; } set { this.dataBaseType = value; } } /// <summary> /// /// </summary> public string Condition { get { if (this.conditionParts.Count > 0) return this.conditionParts.Pop(); return string.Empty; } } /// <summary> /// /// </summary> public object[] Arguments { get { return this.arguments.ToArray(); } } /// <summary> /// 欄位是否加引號 /// </summary> public bool WithQuotationMarks { get { return this.withQuotationMarks; } set { this.withQuotationMarks = value; } } }
/// <summary> /// /// </summary> class PartialEvaluator : ExpressionVisitor { private Func<Expression, bool> fnCanBeEvaluated; private HashSet<Expression> candidates; /// <summary> /// /// </summary> public PartialEvaluator() : this(CanBeEvaluatedLocally) { } /// <summary> /// /// </summary> /// <param name="fnCanBeEvaluated"></param> public PartialEvaluator(Func<Expression, bool> fnCanBeEvaluated) { this.fnCanBeEvaluated = fnCanBeEvaluated; } /// <summary> /// /// </summary> /// <param name="exp"></param> /// <returns></returns> public Expression Eval(Expression exp) { this.candidates = new Nominator(this.fnCanBeEvaluated).Nominate(exp); return this.Visit(exp); } /// <summary> /// /// </summary> /// <param name="exp"></param> /// <returns></returns> protected override Expression Visit(Expression exp) { if (exp == null) return null; if (this.candidates.Contains(exp)) return this.Evaluate(exp); return base.Visit(exp); } /// <summary> /// /// </summary> /// <param name="e"></param> /// <returns></returns> private Expression Evaluate(Expression e) { if (e.NodeType == ExpressionType.Constant) return e; LambdaExpression lambda = Expression.Lambda(e); Delegate fn = lambda.Compile(); return Expression.Constant(fn.DynamicInvoke(null), e.Type); } /// <summary> /// /// </summary> /// <param name="exp"></param> /// <returns></returns> private static bool CanBeEvaluatedLocally(Expression exp) { return exp.NodeType != ExpressionType.Parameter; } /// <summary> /// Performs bottom-up analysis to determine which nodes can possibly /// be part of an evaluated sub-tree. /// </summary> private class Nominator : ExpressionVisitor { private Func<Expression, bool> m_fnCanBeEvaluated; private HashSet<Expression> m_candidates; private bool m_cannotBeEvaluated; internal Nominator(Func<Expression, bool> fnCanBeEvaluated) { this.m_fnCanBeEvaluated = fnCanBeEvaluated; } internal HashSet<Expression> Nominate(Expression expression) { this.m_candidates = new HashSet<Expression>(); this.Visit(expression); return this.m_candidates; } protected override Expression Visit(Expression expression) { if (expression != null) { bool saveCannotBeEvaluated = this.m_cannotBeEvaluated; this.m_cannotBeEvaluated = false; base.Visit(expression); if (!this.m_cannotBeEvaluated) { if (this.m_fnCanBeEvaluated(expression)) { this.m_candidates.Add(expression); } else { this.m_cannotBeEvaluated = true; } } this.m_cannotBeEvaluated |= saveCannotBeEvaluated; } return expression; } } }