Mono是否与.NET在处理System.Data.SqlCommands时有所不同?

4
我正在处理一个我们正在移植到Mono的应用程序的问题。(参考文献:.NET运行时是4.0,而mono版本是2.6.7。)作为应用程序启动的一部分,它会将数据读入内存。对于这部分,我只是使用内联SQL命令,但由于某种原因,在Linux/Mono上看到了不一致的行为(当整个过程在Windows/.NET上工作时)。我有一个查询在某些情况下能够正常工作,但在其他情况下则不能。以下是一个特定的示例,其无法正常工作:
var cmd = new SqlCommand("SELECT ID, Name, VATTerritoryID, NativeCurrencyID FROM PricingZones", conn);
var reader = cmd.ExecuteReader();

var objectToLoad = new SomeObjectType();

while (reader.Read())
{
    objectToLoad.Property1 = reader.GetInt32(row.GetOrdinal("ID"));
    objectToLoad.Property2 = reader.GetString(row.GetOrdinal("Name"));
    objectToLoad.Property3 = reader.GetInt32(row.GetOrdinal("VATTerritoryID"));
    objectToLoad.Property3 = reader.GetInt32(row.GetOrdinal("NativeCurrencyID"));
}

编辑说明:作为比较,下面有一个有效的示例:

var cmd = new SqlCommand("SELECT VATTerritoryID, ProductDescriptionID, VATBandID FROM VATTerritoryBandExceptions", conn);
var reader = cmd.ExecuteReader();

var someOtherObjectToLoad = new SomeOtherObjectType();

while (reader.Read())
{
    someOtherObjectToLoad.Property1 = reader.GetInt32(row.GetOrdinal("VATTerritoryID"));
    someOtherObjectToLoad.Property2 = reader.GetString(row.GetOrdinal("ProductDescriptionID"));
    someOtherObjectToLoad.Property3 = reader.GetInt32(row.GetOrdinal("VATBandID"));
}

我怀疑可能存在以下差异:

  • 大小写(因为我知道Windows/Linux上不同),但将所有内容转换为小写并没有解决问题
  • 列名(也许Mono更关心保留字?),但似乎用[Name]或'Name'替换名称并没有任何区别

在第一种情况下出现的错误是:

[IndexOutOfRangeException: Array index is out of range.]
at System.Data.SqlClient.SqlDataReader.GetInt32(Int32 i)

建议返回结果集中没有“column1”。
(编辑:为了更清晰,更新了此部分)
奇怪的是,如果我这样做:
var cmd = new SqlCommand("SELECT ID, Name, VATTerritoryID, NativeCurrencyID FROM PricingZones", conn);
var reader = cmd.ExecuteReader();

var objectToLoad = new SomeObjectType();

while (reader.Read())
{
    Console.WriteLine("First row, first column is " + row.GetValue(0));
    Console.WriteLine("First row, second column is " + row.GetValue(1));
    Console.WriteLine("First row, third column is " + row.GetValue(2));
    Console.WriteLine("First row, fourth column is " + row.GetValue(3));
}

输出结果为:
First row, first column is 0
First row, second column is New
Array index is out of range.

我猜这个问题可能与Mono框架有关,但我找不到相关的错误报告,并且我无法确定为什么在某些情况下会发生这种情况而在其他情况下不会!是否有其他人有过类似的经验?
编辑:我已经更改了一些语句以精确匹配失败查询中的语句,以防保留字或类似问题。请注意,我在第二个案例中发出的查询确实请求四列,并且似乎只返回两个非常奇怪的值(0 | New)。

你有一个具体的例子说明它失败了吗? - Jon Skeet
如果您使用(Int32)reader["column1"]会怎样? - Mike Christensen
你能确认在这两种情况下你是否使用的是同一个 MS SQL Server 数据库吗? - LukeH
1
你的第三个读取操作在列名中有一个不规范的空格 -- row.GetOrdinal("column ") -- 这不是导致问题的原因吧? - LukeH
@JonSkeet:我已经进行了一些编辑,希望能够更清楚地描述问题和我看到的输出。 - simple
显示剩余4条评论
5个回答

2

好的,伙计,我成功地复制了你的问题。你那里有一个线程问题。这是我唯一能想到可能导致此问题的原因,并且我实际上成功地重现了它。

为了解决这个问题,你需要按照以下步骤进行:

  1. Make sure every reader has a reader.Close() call after parsing the data.

  2. Use the following code to do thread-safe calls:

    Object executeLock = new Object();
    
    private IDataReader ExecuteThreadSafe(IDbCommand sqlCommand)
    {
        lock (executeLock)
        {
            return sqlCommand.ExecuteReader(CommandBehavior.CloseConnection);
        }
    }
    

看起来Mono对SQL对象的实现与.NET不同。事实上,在Windows上我无法重现它!


1

我会先尝试找出差异所在。

首先,您有以下代码:

objectToLoad.Property1 = reader.GetInt32(row.GetOrdinal("column1"));
objectToLoad.Property2 = reader.GetString(row.GetOrdinal("column2"));
objectToLoad.Property3 = reader.GetString(row.GetOrdinal("column ")); //Is the space on the end intended?

我相信你说前两行代码可以工作,但第三行代码出错了。我们首先需要找出问题所在。建议尝试:

int test = row.GetOrdinal("column ");

test是否等于有效的列索引?如果不是,那就是你的问题。确保这是精确的列名,尝试不同的大小写等。如果索引是有效的,请尝试:

object foo = reader[test];
Console.WriteLine(foo.GetType().Name);

试图找出这一列的数据类型是什么。也许存在一个强制转换问题。

如果失败了,我建议将读取器加载到 DataSet 对象中,以便您可以更仔细地查看确切的模式。

如果您发现Mono和.NET Framework之间存在行为差异,Mono团队通常非常愿意修复这些问题。我强烈建议将其报告为错误。

希望这有所帮助!


我在原问题中没有表述清楚 - 我相信是 GetOrdinal 函数出了问题。我会重新尝试以缩小范围。 - simple
请参见上文以更好地解释我的问题 - GetValue调用表明我得到的数据集非常奇怪。 - simple

0
Mono不是.NET,而且在早期版本中有很多差异。连接到SQL的根方法是使用C#中的TDS(表格数据流)实现,这对于早期版本的Mono(以及作为结果的TDS)可能会引发很多问题。几乎所有与SQL和数据相关的关键类都存在差异,这可能导致异常。也许值得尝试使用Mono 2.10+,因为Mono团队不断改进整个项目。

我对我正在使用的盒子没有完全的控制,所以这不是最容易的选择,但如果调查证明无果,我一定会考虑升级Mono版本 - 谢谢。 - simple
不幸的是,更改Mono版本并没有改变任何东西 - 无论如何还是谢谢。 - simple
很遗憾。抱歉。如果你有时间的话,下载源代码、查看并进行代码分析会很有趣。 - user1088520

0
尝试以这种方式访问您的读取器:
reader["column1isastring"].ToString();
(Int32)reader["column2isInt32"];

另外,顺便提一下,请确保在使用一次性对象时使用“using”指令。我不确定Mono是否实现了它,但值得一试。using指令的作用是在使用完一次性对象后立即清理它们。这非常方便,并可以形成干净的代码。快速示例:
using (MySqlCommand command = new MySqlCommand("SELECT column1, column2, column FROM tablename", conn))
{
    try
    {
        conn.Open();
        using (MySqlDataReader reader = command.ExecuteReader())
        {
                reader.Read();
                var someString = reader["column1isastring"].ToString();
                var whatever = (Int32)reader["column2isInt32"];
        } //reader closes and disposes here
    }
    catch (Exception ex)
    {
        //log this exception
    }
    finally
    {
        conn.Close();
    }
} //conn gets closed and disposed of here

此外,如果您正在获取用户输入并将其直接传递给命令,请确保使用MySqlParameter类来防止恶意参数删除表格等操作。

我会尝试一下,但我认为collection[index]表示法只是语法糖,并在底层调用与collection.GetValue(index)相同的方法。 - simple

0

我正在使用Mono 4.0,遇到了类似的问题。我的研究表明,这个问题最可能的原因是Mono中连接池实现有缺陷。至少,如果我关闭连接池,问题就消失了。

要关闭连接池,您需要在连接字符串中添加以下字符串:Pooling=false;

之后,创建连接对象应该像这样:

var conn = new SqlConnection("Server=localhost; Database=somedb; user=user1; password=qwerty; Pooling=false;");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接