为什么我成功读取的Npgsql数据会消失?

3

我有以下代码结构。看起来我误解了C#方法返回值。一个“完整的”枚举器如何可能作为空枚举器返回?

class ThingDoer
{
    public NpgsqlDataReader DoQuery()
    {
        NpgsqlCommand c = new NpgsqlCommand(...);
        NpgsqlDataReader dataread = c.ExecuteReader();
        return dataread;  // Debugger confirms that six data are enumerable here.
    }
}

...

class OtherThing
{
    public void higherLevelFunction()
    {
        NpgsqlDataReader result = myThingDoer.DoQuery();
        result.Read();  // No data! result's enumerable returns nothing!
    }
}

调试器如何“确认”这一点?您在调试器中调用了什么?空结果仍然具有字段,但没有行。此外,在所有这些内容中,NpgsqlConnection 在哪里? - Jon Hanna
@Jon Hanna 我已经消除了所有额外的工作,包括连接等琐碎细节,以突出实际问题。通过设置断点,我能够检查在返回之前和之后读取的数据。 - Andres Jaan Tack
2个回答

3
您没有详细说明您的连接来源。假设它是这样的:
public NpgsqlDataReader DoQuery()
{
    using(NpgsqlConnection = GetConnectionCode())
    {
        NpgsqlCommand c = new NpgsqlCommand(...);
        NpgsqlDataReader dataread = c.ExecuteReader();
        return dataread;
    }//Connection closes at this using-scope being left because that triggers Dispose()
}

然后将其更改为:
public NpgsqlDataReader DoQuery()
{
    bool ownershipPassed = false;
    NpgsqlConnection conn = GetConnectionCode();
    try
    {
        NpgsqlCommand c = new NpgsqlCommand(...);
        NpgsqlDataReader dataread = c.ExecuteReader(CommandBehavior.CloseConnection);
        ownershipPassed = true;
        return dataread;
    }
    finally
    {
        if(!ownershipPassed)//only if we didn't create the reader than takes charge of the connection
          conn.Dispose();
    }
}

当您使用阅读器时,您必须对其进行处理,以便依次处理连接到数据库的底层连接:

public void higherLevelFunction()
{
    using(NpgsqlDataReader result = myThingDoer.DoQuery())
      result.Read();
}

啊!我现在明白你为什么问连接的问题了。抱歉之前有点刻薄。 :-) - Andres Jaan Tack
事实上,我明确地Close()了连接,认为它已经完成了它的任务。 - Andres Jaan Tack
我并没有把它看作是挖苦的。当我们认为某些东西对于问题来说是多余的,而实际上它是至关重要的,这种情况发生在我们每个人身上。顺便说一下,在早期版本的Npgsql中,调用Close()方法是可以正常工作的,但这实际上是一个缺陷,因为它会导致大型数据集被完全读取而不是按需读取,从而影响了性能,如http://npgsql.projects.postgresql.org/docs/manual/preloadScalabilty.html所述,但由于一些旧代码(包括一个NUnit测试)采用了您的方法,因此“Preload Reader”命令字符串选项回到了旧方法... - Jon Hanna
然而,虽然这个选项可以让你之前的代码工作,但它会对性能产生严重影响(如链接的图表所示)。此外,由于IDataReader规范不保证在这种情况下保留结果,如果你将来转移到不同的数据库提供程序,你仍会遇到同样的问题。 - Jon Hanna
2
你明确或隐含地 Close() 它。你会知道为什么要关闭连接以及使用 using 可以更容易地避免这种情况。当你使用 CommandBehavior.CloseConnection 调用 ExecuteDataReader 时,IDataReader 将 "拥有" 连接,并且关闭读取器将关闭连接。因此,在这种情况下,虽然我们不需要自己关闭或处理连接,但我们仍然必须确保关闭读取器。可以通过 Close()Dispose() 或者使用 using 调用 Dispose() 来实现。更多信息请参见我上面链接的答案。 - Jon Hanna
显示剩余2条评论

1
NpgsqlCommand c = new NpgsqlCommand(...);
        NpgsqlDataReader dataread = c.ExecuteReader();

以上代码行与方法DoQuery非常相关。因此,一旦控制权从该方法中退出,该方法内部创建的每个对象都会失去其作用域。因此,您正在失去数据,因为您在调用方方法中引用了引用类型。

那么我该如何避免失去这个对象呢? - Andres Jaan Tack
将连接对象设为类变量,这样它就不会失去作用域。同样的,对于DataReader也是如此,你可以在需要时实例化它,即当你执行DoQuery时,实例化它,例如new SqlCommand(),但让变量保持在类作用域内。无论如何,在你的消费者代码中创建DoQuery的类对象,所以没问题! - Zenwalker
1
亲爱的神啊!不要把连接作为类变量。 - Jon Hanna
如果OP在全局作用域中创建了连接,那么您是正确的,但是在他的代码中,似乎他正在做一些类似于new SqlCommand(new SqlConnection....)的事情,因此很明显它失去了它的作用域,并且自动地可能会或可能不会关闭连接。我认为,除非使用Using(){},否则连接不会在本地范围内自动关闭。如果我错了,请纠正我。 - Zenwalker
@AndresJaanTack 是的,Jon 在这里是正确的,不要将连接设为全局,而是将数据收集设为全局。抱歉,我在想什么!请原谅! - Zenwalker
显示剩余4条评论

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