Oracle 的 C# 参数化查询 - 严重且危险的错误!

Posted

技术标签:

【中文标题】Oracle 的 C# 参数化查询 - 严重且危险的错误!【英文标题】:C# parameterized queries for Oracle - serious & dangerous bug! 【发布时间】:2011-04-22 01:39:33 【问题描述】:

这绝对是咆哮。我不敢相信自己的眼睛,如果这是 C# 中的一个真正的错误,我也不敢相信在我之前没有人会发现这一点,所以我将它发布给开发人员社区的其他人,告诉我我做错了什么。我确信这个问题会让我说“DOH!”并用我的手掌重重地敲打着我的头——但不管怎样……

为了测试,我创建了一个表Test_1,脚本如下:

CREATE TABLE TEST_1 (
  COLUMN1 NUMBER(12) NOT NULL,
  COLUMN2 VARCHAR2(20),
  COLUMN3 NUMBER(12))
TABLESPACE USERS
STORAGE (
  INITIAL 64K
  MAXEXTENTS UNLIMITED
)
LOGGING;

现在我执行以下代码:

var conn = new OracleConnection("connectionblahblah");
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandText = 
  "insert into Test_1(Column1, Column2, Column3) " +
  "values(:Column1, :Column2, :Column3)";
var p = cmd.Parameters;
p.Add("Column1", 1);
p.Add("Column3", null);
p.Add("Column2", "record 1");
cmd.ExecuteNonQuery();

哇!我收到 ORA-01722 错误 - “无效号码”!怎么了? Column1 是数字,值为 1,这很好; Column2 是一个字符串,Column3 是一个可以为空的列,所以应该不会造成任何麻烦...

现在坐下来看看这个...这里的问题是Column3Column2 是按照它们添加到OracleParameterCollection 的顺序转置的。切换它们,然后!有效!

当然,这将我引向下一个明显的实验......让我们更改代码块以添加参数,如下所示:

p.Add("Foo", 1);
p.Add("Bar", "record 1");
p.Add("hahahahahahaha", null);

你认为这会奏效吗?猜猜看 - 它做了

我坐在这里完全惊呆了。我不敢相信我所看到的,我同样不敢相信在我之前没有人发现这种行为(除非我不知道如何正确使用 Google)。

这不仅令人烦恼,而且非常危险。如果我转置了相同数据类型的两列会发生什么?我什至不会出错 - 我只是将错误的数据插入到错误的列中,而且一点也不聪明。

除了注意不要以错误的顺序添加参数之外,是否有人对解决方法有任何想法?

【问题讨论】:

您应该提到您使用的是 Oracle 提供程序 (ODP.NET),而不是 Microsoft 的 Oracle 提供程序。前者的行为就像你描述的那样;后者按您的预期工作,但现在已弃用... 可悲的是,Access OleDb 也会发生这种情况.. @nawfal 确实很伤心。但没有严肃的企业使用 Access 数据库。另一方面,甲骨文... 好问题。我刚刚有完全一样的反应。 “哇!WTF 正在进行中!”。 2022,它还在这里。 【参考方案1】:

这是您在 column2 之前添加 column3 的错字吗?

因为冒号语法表示绑定变量——名称与 PLSQL 中的 BIND 变量无关,它们按提交顺序填充。这意味着您将尝试将 column2 值设置为“记录 1”,这将解释无效数字错误...

您目前拥有:

p.Add("Column1", 1);
p.Add("Column3", null);
p.Add("Column2", "record 1");

...看看这个改动是否能解决您的问题:

p.Add("Column1", 1);
p.Add("Column2", "record 1");
p.Add("Column3", null);

让命名参数起作用?

我必须请有更多 C# 经验的人来解释如何让命名参数工作。但我很高兴我们确认冒号似乎被解释为 Oracle BIND 变量。

【讨论】:

那么传递命名参数而不是有序的正确方法是什么? @OMG:你还没有真正回答我的问题;您刚刚重申了我的发现,即忽略了参数名称,并且它仅使用添加它们的顺序。我希望能够按 name 添加参数,并且不在乎我添加它们的顺序。我该怎么做? @OMG:无论如何,这就是我所说的“公共领域的绊脚石”——如果你只是想忽略它,为什么还要在接口中设置参数名称呢?它所做的只是让程序员感到困惑,并导致他们犯下可能非常严重的错误,例如在字段中转置值。如果它没有抛出异常呢? @OMG:“酷”!!?我还有其他词来形容这不忍出版......! ;) @Shaul:在这样的情况下,我和下一个人一样对幸灾乐祸感到内疚。如果只是因为下次会是 :)【参考方案2】:

这不是错误,而是在 Oracle ODP.Net 文档中明确提及。在 OracleCommand 类中,参数默认由位置绑定。如果要按名称绑定,请显式设置属性cmd.BindByName = true;

参考 Oracle 文档。 http://download.oracle.com/docs/cd/E11882_01/win.112/e12249/OracleCommandClass.htm#i997666

【讨论】:

没错。这不是一个错误,只是一个愚蠢的“功能”。所有提供程序都按名称绑定参数,但不是 ODP.NET,可能是因为 Oracle 不喜欢像其他人那样做......当我使用 Oracle DB 时,我曾经对此感到厌烦,显然没有简单的方法可以默认情况下按名称绑定...有关该主题的更多信息,请参阅this question 好吧,我能说什么呢?你的回答是正确的——但我必须同意@Thomas,这是一个愚蠢的“功能”。更强:这是一个令人难以置信的愚蠢、误导和危险的功能,它应该被归类为错误。为什么任何人都想按顺序而不是按名称绑定参数?并默认有这种行为?!这绝对是令人发指的! @Shaul:我同意你的看法。但即便如此,该错误仍是 Oracle 而不是 C#。您可以将其发布到 oracle 网站上的 ODP.Net 论坛,看看是否有人回复了原因。但是在与 Oracle 合作之后,你会发现很多这样的小宝石。 我猜这是一个非常愚蠢的“优化”。按索引绑定会节省几个周期。为了这些周期,我花了好几个小时来弄清楚发生了什么,而且我认为我并不孤单。 (当然,正如已经指出的那样,它也很危险!) Fine print trap: 你期望一些东西(使用参数名意味着命名参数),但是当你运行它时,你会得到一个错误(如果你幸运的话!)。然后你会被告知:“惊喜,看看细则,如果你没有阅读,你自己的错!”。如果 Oracle 默认使用位置参数,它们应该只允许在 SQL 中使用问号,只允许按顺序或索引赋值!如果他们让用户指定参数名称并按名称分配值,那就是必须提供命名参数的合同!我认为这个错误已经很老了。【参考方案3】:
p.Add(":Column1", 1);
p.Add(":Column2", "record 1");
p.Add(":Column3", null);

//注意我已经添加:到oracle数据客户端识别的参数名称

【讨论】:

这是否意味着这个错误终于被修复了?我从 2003 年发现了关于它的报告。将其称为错误确实被低估了,我更喜欢诸如精美陷阱或普通破坏之类的术语!如果在 SQL 和参数列表中都指定参数名称,驱动程序必须要么使用命名参数,要么抛出异常,但不能偷偷将它们作为位置处理。当被告知“让它与 Oracle 一起运行”时,没有一个普通的开发人员会通读这些细节。这就像现在签订购买计算机的合同,但在细则中,您将在 2 年的订阅期内每月购买一台新洗衣机!

以上是关于Oracle 的 C# 参数化查询 - 严重且危险的错误!的主要内容,如果未能解决你的问题,请参考以下文章

c# dapper mysql like 参数化

Oracle 参数化查询性能

解析参数化查询C#时出错[重复]

来自 .Net 的 Oracle 参数化查询

MySQL 参数化查询的功能就像它在 C# 应用程序中未参数化一样

C#中用于简单登录的参数化查询[重复]