/// <summary> /// Merges multiple parameters into this list. <br /> /// Checks for name clashes, and will rename parameters if necessary. <br /> /// If some parameter is renamed the returned Sql statement will containg the original sql replaced with new names, else (if nothing changed) returns null. <br /> /// </summary> public string MergeParameters(ParameterInfos parameters, string sql) { Dictionary <string, string> renamedParameters = new Dictionary <string, string>(); foreach (var parameter in parameters._parms) { string newParameterName = MergeParameter(parameter); if (newParameterName != null) { string oldName = InterpolatedStatementParser.AutoGeneratedParameterNameFactory(parameter.Name.Substring(InterpolatedStatementParser.AutoGeneratedParameterObjectNameFn("0").Length - 1)); string newName = InterpolatedStatementParser.AutoGeneratedParameterNameFactory(newParameterName.Substring(InterpolatedStatementParser.AutoGeneratedParameterObjectNameFn("0").Length - 1)); renamedParameters.Add(oldName, newName); } } if (renamedParameters.Any()) { Regex matchParametersRegex = new Regex("(?:[a-zA-Z0-9~=<>*/%+&|^-]|\\s|\\b|^) (" + string.Join("|", renamedParameters.Select(p => p.Key.Replace("{", "\\{").Replace("}", "\\}"))) + ") (?:[a-zA-Z0-9~=<>*/%+&|^-]|\\s|\\b|$)", RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); string newSql = matchParametersRegex.Replace(sql, match => { Group group = match.Groups[match.Groups.Count - 1]; // last match is the inner parameter string replace = renamedParameters[group.Value]; return(String.Format("{0}{1}{2}", match.Value.Substring(0, group.Index - match.Index), replace, match.Value.Substring(group.Index - match.Index + group.Length))); }); return(newSql); } return(null); }
/// <inheritdoc/> public void MergeParameters(ParameterInfos target) { foreach (IFilter filter in this) { filter.MergeParameters(target); } }
/// <summary> /// New Filter statement. <br /> /// Example: $"[CategoryId] = {categoryId}" <br /> /// Example: $"[Name] LIKE {productName}" /// </summary> public Filter(FormattableString filter) { var parsedStatement = new InterpolatedStatementParser(filter); Sql = parsedStatement.Sql; Parameters = parsedStatement.Parameters; _parametersStr = string.Join(", ", Parameters.ParameterNames.ToList().Select(n => "@" + n + "='" + Convert.ToString(Parameters.Get <dynamic>(n)) + "'")); }
/// <summary> /// Merges parameters from this query/statement into a CommandBuilder. <br /> /// Checks for name clashes, and will rename parameters (in CommandBuilder) if necessary. <br /> /// If some parameter is renamed the underlying Sql statement will have the new parameter names replaced by their new names.<br /> /// This method does NOT append Parser SQL to CommandBuilder SQL (you may want to save this SQL statement elsewhere) /// </summary> public void MergeParameters(ParameterInfos target) { string newSql = target.MergeParameters(Parameters, Sql); if (newSql != null) { Sql = newSql; _parametersStr = string.Join(", ", Parameters.ParameterNames.ToList().Select(n => "@" + n + "='" + Convert.ToString(Parameters.Get <dynamic>(n)) + "'")); // filter parameters in Sql were renamed and won't match the previous passed filters - discard original parameters to avoid reusing wrong values Parameters = null; } }
/// <summary> /// New CommandBuilder. /// </summary> /// <param name="dbSet"></param> public CommandBuilder(DbSet <TEntity> dbSet) { _dbSet = dbSet; _command = new StringBuilder(); _parameters = new ParameterInfos(); }
private InterpolatedStatementParser(string format, params object[] arguments) { Parameters = new ParameterInfos(); if (string.IsNullOrEmpty(format)) { return; } StringBuilder sb = new StringBuilder(); var matches = _formattableArgumentRegex.Matches(format); int currentBlockEndPos = 0; int previousBlockEndPos = 0; for (int i = 0; i < matches.Count; i++) { previousBlockEndPos = currentBlockEndPos; currentBlockEndPos = matches[i].Index + matches[i].Length; // unescape escaped curly braces string sql = format.Substring(previousBlockEndPos, matches[i].Index - previousBlockEndPos).Replace("{{", "{").Replace("}}", "}"); // arguments[i] may not work because same argument can be used multiple times int argPos = int.Parse(matches[i].Groups["ArgPos"].Value); string argFormat = matches[i].Groups["Format"].Value; List <string> argFormats = argFormat.Split(new char[] { ',', '|' }, StringSplitOptions.RemoveEmptyEntries).Select(f => f.Trim()).ToList(); object arg = arguments[argPos]; if (argFormats.Contains("raw")) // example: {nameof(Product.Name):raw} -> won't be parametrized, we just emit raw string! { sb.Append(sql); sb.Append(arg); continue; } else if (arg is FormattableString fsArg) //Support nested FormattableString { sb.Append(sql); var nestedStatement = new InterpolatedStatementParser(fsArg); if (nestedStatement.Parameters.Any()) { sb.Append(Parameters.MergeParameters(nestedStatement.Parameters, nestedStatement.Sql)); } else { sb.Append(nestedStatement.Sql); } continue; } // If user passes " column LIKE '{variable}' ", we assume that he used single quotes incorrectly as if interpolated string was a sql literal if (quotedVariableStart.IsMatch(sql) && quotedVariableEnd.IsMatch(format.Substring(currentBlockEndPos))) { sql = sql.Substring(0, sql.Length - 1); // skip starting quote currentBlockEndPos++; // skip closing quote } sb.Append(sql); var direction = System.Data.ParameterDirection.Input; System.Data.DbType?dbType = null; if (argFormats.Contains("out")) { direction = System.Data.ParameterDirection.Output; } System.Data.DbType parsedDbType; Match m; foreach (var f in argFormats) { /* * if (arg is string && (m = regexDbTypeString.Match(f)) != null && m.Success) // String(maxlength) / nvarchar(maxlength) / String / nvarchar * arg = new DbString() * { * IsAnsi = false, * IsFixedLength = false, * Value = (string)arg, * Length = (string.IsNullOrEmpty(m.Groups["maxlength"].Value) ? Math.Max(DbString.DefaultLength, ((string)arg).Length) : int.Parse(m.Groups["maxlength"].Value)) * }; * else if (arg is string && (m = regexDbTypeAnsiString.Match(f)) != null && m.Success) // AnsiString(maxlength) / varchar(maxlength) / AnsiString / varchar * arg = new DbString() * { * IsAnsi = true, * IsFixedLength = false, * Value = (string)arg, * Length = (string.IsNullOrEmpty(m.Groups["maxlength"].Value) ? Math.Max(DbString.DefaultLength, ((string)arg).Length) : int.Parse(m.Groups["maxlength"].Value)) * }; * else if (arg is string && (m = regexDbTypeStringFixedLength.Match(f)) != null && m.Success) // StringFixedLength(length) / nchar(length) / StringFixedLength / nchar * arg = new DbString() * { * IsAnsi = false, * IsFixedLength = true, * Value = (string)arg, * Length = (string.IsNullOrEmpty(m.Groups["length"].Value) ? ((string)arg).Length : int.Parse(m.Groups["length"].Value)) * }; * else if (arg is string && (m = regexDbTypeAnsiStringFixedLength.Match(f)) != null && m.Success) // AnsiStringFixedLength(length) / char(length) / AnsiStringFixedLength / char * arg = new DbString() * { * IsAnsi = true, * IsFixedLength = true, * Value = (string)arg, * Length = (string.IsNullOrEmpty(m.Groups["length"].Value) ? ((string)arg).Length : int.Parse(m.Groups["length"].Value)) * }; * else if (arg is string && (m = regexDbTypeText.Match(f)) != null && m.Success) // text / varchar(MAX) / varchar(-1) * arg = new DbString() * { * IsAnsi = false, * IsFixedLength = true, * Value = (string)arg, * Length = int.MaxValue * }; * else if (arg is string && (m = regexDbTypeNText.Match(f)) != null && m.Success) // ntext / nvarchar(MAX) / nvarchar(-1) * arg = new DbString() * { * IsAnsi = true, * IsFixedLength = true, * Value = (string)arg, * Length = int.MaxValue * }; * * else if (!(arg is DbString) && dbType == null && Enum.TryParse<System.Data.DbType>(value: f, ignoreCase: true, result: out parsedDbType)) * { * dbType = parsedDbType; * } */ if (dbType == null && Enum.TryParse <System.Data.DbType>(value: f, ignoreCase: true, result: out parsedDbType)) { dbType = parsedDbType; } //TODO: parse SqlDbTypes? // https://stackoverflow.com/questions/35745226/net-system-type-to-sqldbtype // https://gist.github.com/tecmaverick/858392/53ddaaa6418b943fa3a230eac49a9efe05c2d0ba } sb.Append(Parameters.Add(arg, dbType, direction)); } string lastPart = format.Substring(currentBlockEndPos).Replace("{{", "{").Replace("}}", "}"); sb.Append(lastPart); Sql = sb.ToString(); _parametersStr = string.Join(", ", Parameters.ParameterNames.ToList().Select(n => "@" + n + "='" + Convert.ToString(Parameters.Get <dynamic>(n)) + "'")); }