可以命名 ODBC 参数占位符吗?

Posted

技术标签:

【中文标题】可以命名 ODBC 参数占位符吗?【英文标题】:Can ODBC parameter place holders be named? 【发布时间】:2011-06-14 01:23:34 【问题描述】:

我进行了一些搜索,但没有找到我问题的明确答案。

有没有办法定义 SQL 查询中的哪个 ? 属于哪个参数? 例如,我需要执行以下操作:

SELECT * FROM myTable WHERE myField = @Param1 OR myField2 = @Param1 
       OR myField1 = @Param2 OR myField2 = @Param2

ODBC 中的同一个查询是:

SELECT * FROM myTable WHERE myField = ? or myField2 = ? or myField1 = ? 
       or myField2 = ?

除了为每个值加载两次参数之外,还有什么方法可以告诉 ODBC 命令哪个参数是哪个?

我怀疑没有但可以使用更有经验的 ODBC 程序员的观点。

编辑:我使用的 ODBC 驱动程序是 BBj ODBC 驱动程序。

【问题讨论】:

我的搜索出现了msdn.microsoft.com/en-us/library/ms715435%28v=vs.85%29.aspx 什么 ODBC 驱动程序?它的什么版本?这对您的问题非常重要 您好,文章指出它只能与存储过程一起使用。我会保留这篇文章以备将来参考。 乔尔,我将它添加到理查德的答案中,但我将编辑我的 OP 以使其更清晰(它是一个 BBj ODBC 驱动程序)。谢谢! 【参考方案1】:

在 MSDN 中明确声明不能命名参数,这是“告诉 ODBC 命令哪个参数是哪个”的唯一方法。

虽然文档可能会产生一些混乱:

来自MSDN, OdbcParameter Class:

当 CommandType 设置为 Text 时,用于 ODBC 的 .NET Framework 数据提供程序不支持将命名参数传递给 SQL 语句或由 OdbcCommand 调用的存储过程。在这两种情况下,请使用问号 (?) 占位符。

OdbcParameter 对象添加到 OdbcParameterCollection 的顺序必须直接对应于命令文本中参数的问号占位符的位置。

从上面看来,当 CommandType 未设置为 Text 时,也许您可​​以使用命名参数,但不幸的是您不能:

来自MSDN, OdbcCommand.CommandType Property:

当 CommandType 属性设置为 StoredProcedure 时,您应该将 CommandText 属性设置为完整的 ODBC 调用语法。然后,当您调用其中一种 Execute 方法(例如,ExecuteReader 或 ExecuteNonQuery)时,该命令会执行此存储过程。

用于 ODBC 的 .NET Framework 数据提供程序不支持将命名参数传递给 SQL 语句或由 OdbcCommand 调用的存储过程。在这两种情况下,请使用问号 (?) 占位符...

【讨论】:

【参考方案2】:

我无法让它使用命名参数——只有位置参数。 您可以像下面一样添加所有您想要的参数,但您必须按顺序添加值。

SELECT * FROM myTable WHERE myField = ? or myField1 = ? or myField2 = ? 
       or myField2 = ?
myOdbcCommand.Parameters.AddWithValue("DoesNotMatter", val1); //myField
myOdbcCommand.Parameters.AddWithValue("WhatYouPutHere", val2); //myField1
myOdbcCommand.Parameters.AddWithValue("DoesNotMatter", val3); //myField2
myOdbcCommand.Parameters.AddWithValue("WhatYouPutHere", val4); //myField2

从上面可以看出,参数名称无关紧要,也不会被使用。如果您愿意,您甚至可以将它们全部命名,或者更好的是,将参数名称留空""

【讨论】:

【参考方案3】:

感谢汤姆的想法和代码。 但是,代码在我的测试中无法正常工作。 因此,我编写了一个更简单(并且至少在我的测试工作中)的解决方案,用位置参数替换命名参数(其中使用 ? 代替名称):

public static class OdbcCommandExtensions

    public static void ConvertNamedParametersToPositionalParameters(this OdbcCommand command)
    
        //1. Find all occurrences of parameters references in the SQL statement (such as @MyParameter).
        //2. For each occurrence find the corresponding parameter in the command's parameters list.
        //3. Add the parameter to the newParameters list and replace the parameter reference in the SQL with a question mark (?).
        //4. Replace the command's parameters list with the newParameters list.

        var newParameters = new List<OdbcParameter>();

        command.CommandText = Regex.Replace(command.CommandText, "(@\\w*)", match =>
        
            var parameter = command.Parameters.OfType<OdbcParameter>().FirstOrDefault(a => a.ParameterName == match.Groups[1].Value);
            if (parameter != null)
            
                var parameterIndex = newParameters.Count;

                var newParameter = command.CreateParameter();
                newParameter.OdbcType = parameter.OdbcType;
                newParameter.ParameterName = "@parameter" + parameterIndex.ToString();
                newParameter.Value = parameter.Value;

                newParameters.Add(newParameter);
            

            return "?";
        );

        command.Parameters.Clear();
        command.Parameters.AddRange(newParameters.ToArray());
    

【讨论】:

这看起来不错,尽管您可以改用IDbDataParameternewParameter.DbType 使其更通用。我还建议将正则表达式更改为 (@?@\\w+) 以便能够处理像 @@IDENTITY 这样的特殊情况,否则会被破坏 正则表达式不支持所有选项。例如。 @ 列中(My@Col),逗号代表参数(SET `COL1` = @c1, `COL2` = @c2)等【参考方案4】:

我知道在使用 Oracle Rdb ODBC 时,我不能使用占位符名称,而必须使用“?”;我觉得非常烦人。

【讨论】:

我正在使用 BBj(Java 业务基础)ODBC 驱动程序,如果有帮助的话,但我想语法不会有太大差异。【参考方案5】:

我需要编写代码来处理将命名参数转换为带有问号的序数参数。我需要的是 OleDb 而不是 Odbc……但我相信如果您更改类型,这对您有用。

using System;
using System.Collections.Generic;
using System.Data.OleDb;
using System.Linq;
using System.Text.RegularExpressions;

namespace OleDbParameterFix 
    static class Program 
        [STAThread]
        static void Main() 
            string connectionString = @"provider=vfpoledb;data source=data\northwind.dbc";
            using (var connection = new OleDbConnection(connectionString))
            using (var command = connection.CreateCommand()) 
                command.CommandText = "select count(*) from orders where orderdate=@date or requireddate=@date or shippeddate=@date";
                command.Parameters.Add("date", new DateTime(1996, 7, 11));

                connection.Open();

                OleDbParameterRewritter.Rewrite(command);
                var count = command.ExecuteScalar();

                connection.Close();
            
        
    

    public class OleDbParameterRewritter 
        public static void Rewrite(OleDbCommand command) 
            HandleMultipleParameterReferences(command);
            ReplaceParameterNamesWithQuestionMark(command);
        

        private static void HandleMultipleParameterReferences(OleDbCommand command) 
            var parameterMatches = command.Parameters
                                          .Cast<OleDbParameter>()
                                          .Select(x => Regex.Matches(command.CommandText, "@" + x.ParameterName))
                                          .ToList();

            // Check to see if any of the parameters are listed multiple times in the command text. 
            if (parameterMatches.Any(x => x.Count > 1)) 
                var newParameters = new List<OleDbParameter>();

                // order by descending to make the parameter name replacing easy 
                var matches = parameterMatches.SelectMany(x => x.Cast<Match>())
                                              .OrderByDescending(x => x.Index);

                foreach (Match match in matches) 
                    // Substring removed the @ prefix. 
                    var parameterName = match.Value.Substring(1);

                    // Add index to the name to make the parameter name unique. 
                    var newParameterName = parameterName + "_" + match.Index;
                    var newParameter = (OleDbParameter)((ICloneable)command.Parameters[parameterName]).Clone();
                    newParameter.ParameterName = newParameterName;

                    newParameters.Add(newParameter);

                    // Replace the old parameter name with the new parameter name.   
                    command.CommandText = command.CommandText.Substring(0, match.Index)
                                            + "@" + newParameterName
                                            + command.CommandText.Substring(match.Index + match.Length);
                

                // The parameters were added to the list in the reverse order to make parameter name replacing easy. 
                newParameters.Reverse();
                command.Parameters.Clear();
                newParameters.ForEach(x => command.Parameters.Add(x));
            
        

        private static void ReplaceParameterNamesWithQuestionMark(OleDbCommand command) 
            for (int index = command.Parameters.Count - 1; index >= 0; index--) 
                var p = command.Parameters[index];
                command.CommandText = command.CommandText.Replace("@" + p.ParameterName, "?");
            
        
    

【讨论】:

【参考方案6】:

这是帖子的简短解决方案:https://***.com/a/21925683/2935383

我为 OpenEdge (Progress) ODBC 包装器编写了这段代码。 DatabaseAdapter-class 就是这个包装器,这里就不展示了。

string _convertSql( string queryString, List<DatabaseAdapter.Parameter> parameters, 
                    ref List<System.Data.Odbc.OdbcParameter> odbcParameters ) 
    List<ParamSorter> sorter = new List<ParamSorter>();
    foreach (DatabaseAdapter.Parameters item in parameters) 
        string parameterName = item.ParameterName;
        int indexSpace = queryString.IndexOf(paramName + " "); // 0
        int indexComma = queryString.IndexOf(paramName + ","); // 1

        if (indexSpace > -1)
            sorter.Add(new ParamSorter()  p = item, index = indexSpace, type = 0 );
        
        else 
            sorter.Add(new ParamSorter()  p = item, index = indexComma, type = 1 );
        
    

    odbcParameters = new List<System.Data.Odbc.OdbcParameter>();
    foreach (ParamSorter item in sorter.OrderBy(x => x.index)) 
        if (item.type == 0)  //SPACE
            queryString = queryString.Replace(item.p.ParameterName + " ", "? ");
        
        else  //COMMA
            queryString = queryString.Replace(item.p.ParameterName + ",", "?,");
        
        odbcParameters.Add(
                new System.Data.Odbc.OdbcParameter(item.p.ParameterName, item.p.Value));
    

用于排序的实用程序类

class ParamSorter
    public DatabaseAdapter.Parameters p;
    public int index;
    public int type;

如果命名参数是字符串中的最后一个 - 你必须添加一个空格。 例如"SELECT * FROM tab WHERE col = @mycol"必须"SELECT * FROM tab WHERE col = @mycol "

【讨论】:

【参考方案7】:

我将 David Liebeherr 提供的 answer 更改为下面的代码。

它允许 Select @@Identity 被 mfeineis 设置为 mentioned。

public static IDbCommand ReplaceCommndTextAndParameters(this IDbCommand command, string commandText, List<IDbDataParameter> parameters) 
  command.CommandText = commandText;
  command.Parameters.Clear();
  foreach (var p in parameters) 
      command.Parameters.Add(p);
  
  return command;


public static IDbCommand ConvertNamedParametersToPositionalParameters(this IDbCommand command) 
  var newCommand = command.GetConvertNamedParametersToPositionalParameters();
  return command.ReplaceCommndTextAndParameters(newCommand.CommandText, newCommand.Parameters);


public static (string CommandText, List<IDbDataParameter> Parameters) GetConvertNamedParametersToPositionalParameters(this IDbCommand command) 
  //1. Find all occurrences parameters references in the SQL statement (such as @MyParameter).
  //2. Find the corresponding parameter in the command's parameters list.
  //3. Add the found parameter to the newParameters list and replace the parameter reference in the SQL with a question mark (?).
  //4. Replace the command's parameters list with the newParameters list.
  var oldParameters = command.Parameters;
  var oldCommandText = command.CommandText;
  var newParameters = new List<IDbDataParameter>();
  var newCommandText = oldCommandText;
  var paramNames = oldCommandText.Replace("@@", "??").Split('@').Select(x => x.Split(new[]  ' ', ')', ';', '\r', '\n' ).FirstOrDefault().Trim()).ToList().Skip(1);
  foreach (var p in paramNames) 
    newCommandText = newCommandText.Replace("@" + p, "?");
    var parameter = oldParameters.OfType<IDbDataParameter>().FirstOrDefault(a => a.ParameterName == p);
    if (parameter != null) 
      parameter.ParameterName = $"parameter.ParameterName_newParameters.Count";
      newParameters.Add(parameter);
    
  
  return (newCommandText, newParameters);

【讨论】:

以上是关于可以命名 ODBC 参数占位符吗?的主要内容,如果未能解决你的问题,请参考以下文章

Java:“$1”是占位符吗? [复制]

使用maven修改Java文件占位符

截断 Django CMS 占位符

多个 UIActivityViewController 占位符项目?

用户键入时如何保持输入占位符可见

在 PDO 中的执行函数中将 NULL 值绑定到具有关联数组的命名占位符的问题