/// <summary> /// バインド値がDapperAid固有のSQL条件式記述用メソッドで値指定している場合は、 /// SQL条件式生成メソッドを呼び出してパラメータバインド・SQL組み立てを行います。 /// </summary> /// <param name="parameters">パラメーターオブジェクト</param> /// <param name="column">バインド対象のカラム</param> /// <param name="expression">バインド値をあらわす式木</param> /// <param name="opIsNot">条件式をnotで生成する場合はtrue</param> /// <returns>生成されたSQL表現文字列。ただしバインド値がDapperAid固有のSQL条件式記述用メソッドではなかった場合null</returns> protected string TryBindSqlExpr(DynamicParameters parameters, TableInfo.Column column, Expression expression, bool opIsNot) { var methodCallExpression = ExpressionHelper.CastTo <MethodCallExpression>(expression); if (methodCallExpression != null && methodCallExpression.Method != null && typeof(ISqlExpr).IsAssignableFrom(methodCallExpression.Method.DeclaringType)) { return(InstanceCreator.Create <ISqlExpr>(methodCallExpression.Method.DeclaringType).BuildSql( methodCallExpression.Method.Name, methodCallExpression.Arguments, this, parameters, column, opIsNot)); } return(null); }
/// <summary> /// 引数で指定された条件式ラムダに基づきWhere条件を生成します。 /// </summary> /// <param name="parameters">Dapperパラメーターオブジェクト</param> /// <param name="tableInfo">テーブル情報</param> /// <param name="expression">式木(ラムダ)により記述された条件式</param> /// <returns>条件式のSQL</returns> private string BuildWhere(DynamicParameters parameters, TableInfo tableInfo, Expression expression) { // ローカル関数相当:ExpressionがWhere条件となるカラムなら、そのカラム情報を返す Func <Expression, TableInfo.Column> getIfOperandIsConditionColumn = (exp) => { var me = ExpressionHelper.CastTo <MemberExpression>(exp); if (me == null || me.Expression == null || me.Expression.NodeType != ExpressionType.Parameter) { // ラムダ式の引数についてのメンバーでなければ、Where条件となるカラムとはみなさない return(null); } var prop = me.Member as PropertyInfo; if (prop == null) { // プロパティ項目でなければ、Where条件となるカラムとはみなさない return(null); } // tableInfoのカラムに対応するなら、そのカラム情報を返す return(tableInfo.Columns.Where(c => c.PropertyInfo == prop).FirstOrDefault()); }; var binaryExpr = ExpressionHelper.CastTo <BinaryExpression>(expression); if (binaryExpr == null) { // 二項演算子以外の特殊な表現が指定されている場合の処理 // 「!」NOT指定かどうか/bool型のプロパティが指定されているかを判定 var unaryExpr = ExpressionHelper.CastTo <UnaryExpression>(expression); var isNot = (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Not); var column = getIfOperandIsConditionColumn(isNot ? unaryExpr.Operand : expression); if (column != null && column.PropertyInfo.PropertyType == typeof(bool)) { // boolカラム値の条件として組み立てる return(column.Name + "=" + (isNot ? FalseLiteral : TrueLiteral)); } if (isNot) { // NOT条件として組み立てる return("not(" + BuildWhere(parameters, tableInfo, unaryExpr.Operand) + ")"); } // DapperAid固有のSQL条件式記述用メソッドで式木を記述している場合は、SQL条件式生成メソッドを呼び出してSQLを組み立てる var sqlExprBindResult = TryBindSqlExpr(parameters, null, expression, isNot); if (sqlExprBindResult != null) { return(sqlExprBindResult); } // bool値となる変数や処理を記述していればその値を組み立てる var boolValue = ExpressionHelper.EvaluateValue(expression); if (boolValue is bool) { return(((bool)boolValue == true) ? TrueLiteral : FalseLiteral); } // いずれでもなければ例外 throw new InvalidExpressionException(expression.ToString()); } // And/Or条件の解釈 switch (binaryExpr.NodeType) { case ExpressionType.AndAlso: var andLeft = BuildWhere(parameters, tableInfo, binaryExpr.Left); if (andLeft == FalseLiteral) { // 「left && right」のうちleftでfalseが確定している場合は、右辺の展開を行わない return(andLeft); } var andRight = BuildWhere(parameters, tableInfo, binaryExpr.Right); // 左辺右辺いずれかでtrueが確定していればもう一方のみを条件とし、そうでなければand条件式を組み立て return((andLeft == TrueLiteral) ? andRight : (andRight == TrueLiteral) ? andLeft : andLeft + " and " + andRight); case ExpressionType.OrElse: var orLeft = BuildWhere(parameters, tableInfo, binaryExpr.Left); if (orLeft == TrueLiteral) { // 「left || right」のうちleftでtrueが確定している場合は、右辺の展開を行わない return(orLeft); } var orRight = BuildWhere(parameters, tableInfo, binaryExpr.Right); // 左辺右辺いずれかでfalseが確定していればもう一方のみを条件とし、そうでなければor条件式を組み立て return((orLeft == FalseLiteral) ? orRight : (orRight == FalseLiteral) ? orLeft : "(" + orLeft + " or " + orRight + ")"); } var leftMember = getIfOperandIsConditionColumn(binaryExpr.Left); var rightMember = getIfOperandIsConditionColumn(binaryExpr.Right); if (leftMember == null && rightMember == null) { // 両辺ともカラム指定ではない:具体的なbool値に展開 var boolValue = ExpressionHelper.EvaluateValue(binaryExpr); if (!(boolValue is bool)) { throw new InvalidExpressionException(binaryExpr.ToString()); } return((bool)boolValue ? TrueLiteral : FalseLiteral); } // 比較演算子を解釈 string op; var opEnd = string.Empty; var opIsNot = false; switch (binaryExpr.NodeType) { case ExpressionType.Equal: op = "="; break; case ExpressionType.NotEqual: op = "<>"; opIsNot = true; break; case ExpressionType.GreaterThan: op = ">"; break; case ExpressionType.GreaterThanOrEqual: op = ">="; break; case ExpressionType.LessThan: op = "<"; break; case ExpressionType.LessThanOrEqual: op = "<="; break; default: throw new InvalidExpressionException(expression.ToString()); } if (leftMember != null && rightMember != null) { // 両辺ともカラム名指定の場合はパラメータ値取得不要、両辺のカラム名に基づき条件式を組み立て return(leftMember.Name + op + rightMember.Name); } var condColumn = leftMember ?? rightMember; var valueExpression = ExpressionHelper.CastTo <Expression>(condColumn == leftMember ? binaryExpr.Right : binaryExpr.Left); // DapperAid固有のSQL条件式記述用メソッドで値指定している場合は、SQL条件式生成メソッドを呼び出してSQLを組み立てる { var sqlExprBindResult = TryBindSqlExpr(parameters, condColumn, valueExpression, opIsNot); if (sqlExprBindResult != null) { return(sqlExprBindResult); } } // カラム指定でない側の指定値を把握しSQL条件式を組み立てる var value = ExpressionHelper.EvaluateValue(valueExpression); if (value == null) { return(condColumn.Name + " is" + (opIsNot ? " not" : "") + " null"); } else { var bindedValueSql = AddParameter(parameters, condColumn.PropertyInfo.Name, value); return(condColumn == leftMember ? condColumn.Name + op + bindedValueSql : bindedValueSql + op + condColumn.Name); } }
/// <summary> /// 引数で指定された条件式ラムダに基づきWhere条件を生成します。 /// </summary> /// <param name="parameters">Dapperパラメーターオブジェクト</param> /// <param name="tableInfo">テーブル情報</param> /// <param name="expression">式木(ラムダ)により記述された条件式</param> /// <returns>条件式のSQL</returns> private string BuildWhere(DynamicParameters parameters, TableInfo tableInfo, Expression expression) { // ローカル関数相当:ExpressionがWhere条件となるカラムなら、そのカラム情報を返す Func <Expression, TableInfo.Column> getIfOperandIsConditionColumn = (exp) => { var me = ExpressionHelper.CastTo <MemberExpression>(exp); if (me == null || me.Expression == null || me.Expression.NodeType != ExpressionType.Parameter) { // ラムダ式の引数についてのメンバーでなければ、Where条件となるカラムとはみなさない return(null); } var prop = me.Member as PropertyInfo; if (prop == null) { // プロパティ項目でなければ、Where条件となるカラムとはみなさない return(null); } // tableInfoのカラムに対応するなら、そのカラム情報を返す return(tableInfo.Columns.Where(c => c.PropertyInfo == prop).FirstOrDefault()); }; var binaryExpr = ExpressionHelper.CastTo <BinaryExpression>(expression); if (binaryExpr == null) { // 二項演算子以外の特殊な表現が指定されている場合の処理 // 「!」NOT指定かどうか/bool型のプロパティが指定されているかを判定 var unaryExpr = ExpressionHelper.CastTo <UnaryExpression>(expression); var isNot = (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Not); var column = getIfOperandIsConditionColumn(isNot ? unaryExpr.Operand : expression); if (column != null && column.PropertyInfo.PropertyType == typeof(bool)) { // boolカラム値の条件として組み立てる return(column.Name + "=" + (isNot ? "false" : "true")); } if (isNot) { // NOT条件として組み立てる return("not(" + BuildWhere(parameters, tableInfo, unaryExpr.Operand) + ")"); } // 特定のメソッド呼び出しが指定されているかを判定 var methodCallExpr = ExpressionHelper.CastTo <MethodCallExpression>(expression); if (methodCallExpr != null && methodCallExpr.Method != null && methodCallExpr.Method.DeclaringType == typeof(ToSql)) { // ※C#6.0以降でないとnameofが利用できないため定数定義 const string NameofEval = "Eval"; //nameof(ToSql.Eval); if (methodCallExpr.Method.Name == NameofEval) { // ToSql.Eval(string):指定されたSQL文字列を直接埋め込む var argumentExpression = methodCallExpr.Arguments[0]; if (argumentExpression.Type == typeof(string)) { return("(" + ExpressionHelper.EvaluateValue(argumentExpression) + ")"); } } } // いずれでもなければ例外 throw new InvalidExpressionException(expression.ToString()); } // And/Or条件の解釈 switch (binaryExpr.NodeType) { case ExpressionType.AndAlso: var andLeft = BuildWhere(parameters, tableInfo, binaryExpr.Left); if (andLeft == "false") { // 「left && right」のうちleftでfalseが確定している場合は、右辺の展開を行わない return(andLeft); } var andRight = BuildWhere(parameters, tableInfo, binaryExpr.Right); if (andLeft == "true") { // 「left && right」のうちleftがtrueなら、右辺のみを返す return(andRight); } return(andLeft + " and " + andRight); case ExpressionType.OrElse: var orLeft = BuildWhere(parameters, tableInfo, binaryExpr.Left); if (orLeft == "true") { // 「left || right」のうちleftでtrueが確定している場合は、右辺の展開を行わない return(orLeft); } var orRight = BuildWhere(parameters, tableInfo, binaryExpr.Right); if (orLeft == "false") { // 「left || right」のうちleftがfalseなら、右辺のみを返す return(orRight); } return("(" + orLeft + ") or (" + orRight + ")"); } var leftMember = getIfOperandIsConditionColumn(binaryExpr.Left); var rightMember = getIfOperandIsConditionColumn(binaryExpr.Right); if (leftMember == null && rightMember == null) { // 両辺ともカラム指定ではない:具体的なbool値に展開 var boolValue = ExpressionHelper.EvaluateValue(binaryExpr); if (!(boolValue is bool)) { throw new InvalidExpressionException(binaryExpr.ToString()); } return((bool)boolValue ? "true" : "false"); } // 比較演算子を解釈 string op; var opEnd = string.Empty; var opIsNot = false; switch (binaryExpr.NodeType) { case ExpressionType.Equal: op = "="; break; case ExpressionType.NotEqual: op = "<>"; opIsNot = true; break; case ExpressionType.GreaterThan: op = ">"; break; case ExpressionType.GreaterThanOrEqual: op = ">="; break; case ExpressionType.LessThan: op = "<"; break; case ExpressionType.LessThanOrEqual: op = "<="; break; default: throw new InvalidExpressionException(expression.ToString()); } if (leftMember != null && rightMember != null) { // 両辺ともカラム名指定の場合はパラメータ値取得不要、両辺のカラム名に基づき条件式を組み立て return(leftMember.Name + op + rightMember.Name); } var condColumn = leftMember ?? rightMember; var valueExpression = ExpressionHelper.CastTo <Expression>(condColumn == leftMember ? binaryExpr.Right : binaryExpr.Left); if (valueExpression is MethodCallExpression) { // 値指定時に特定のToSqlメソッドを通している場合は、メソッドに応じたSQLを組み立てる var method = (valueExpression as MethodCallExpression).Method; if (method.DeclaringType == typeof(ToSql)) { // ※C#6.0以降でないとnameofが利用できないため定数定義とする const string NameofLike = "Like"; //nameof(ToSql.Like); const string NameofIn = "In"; //nameof(ToSql.In); const string NameofBetween = "Between"; //nameof(ToSql.Between); if (method.Name == NameofLike) { // ToSql.Like(string):比較演算子をLike演算子とする op = (opIsNot ? " not" : "") + " like "; valueExpression = (valueExpression as MethodCallExpression).Arguments[0]; } else if (method.Name == NameofIn) { valueExpression = (valueExpression as MethodCallExpression).Arguments[0]; if (valueExpression.Type == typeof(string)) { // ToSql.In(string): INサブクエリとして指定された文字列を直接埋め込む return(condColumn.Name + (opIsNot ? " not" : "") + " in(" + ExpressionHelper.EvaluateValue(valueExpression) + ")"); } else { // ToSql.In(コレクション): In演算子を組み立てる return(BuildWhereIn(parameters, condColumn, opIsNot, ExpressionHelper.EvaluateValue(valueExpression))); } } else if (method.Name == NameofBetween) { // ToSql.Between(値1, 値2): Between演算子を組み立て、パラメータを2つバインドする。nullの可能性は考慮しない var value1 = ExpressionHelper.EvaluateValue((valueExpression as MethodCallExpression).Arguments[0]); var value2 = ExpressionHelper.EvaluateValue((valueExpression as MethodCallExpression).Arguments[1]); return(condColumn.Name + (opIsNot ? " not" : "") + " between " + AddParameter(parameters, condColumn.PropertyInfo.Name, value1) + " and " + AddParameter(parameters, condColumn.PropertyInfo.Name, value2)); } else { throw new InvalidExpressionException(expression.ToString()); } } } var value = ExpressionHelper.EvaluateValue(valueExpression); if (value == null) { return(condColumn.Name + " is" + (opIsNot ? " not" : "") + " null"); } else { var bindedValueSql = AddParameter(parameters, condColumn.PropertyInfo.Name, value); return(condColumn == leftMember ? condColumn.Name + op + bindedValueSql : bindedValueSql + op + condColumn.Name); } }