【问题标题】:Why does my successfully-read Npgsql data disappear?为什么我成功读取的 Npgsql 数据消失了?
【发布时间】:2011-12-15 17:49:30
【问题描述】:

我有以下代码形状。看来我误解了 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 我已经消除了围绕这个问题的所有额外工作,包括像连接这样的平凡细节,以突出实际问题。通过设置断点,我能够检查返回前后读取的数据。

标签: c# npgsql


【解决方案1】:

你没有详细说明你的连接来自哪里。假设它是这样的:

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();
}

【讨论】:

  • 啊!我明白你为什么现在问连接。很抱歉很刻薄。 :-)
  • 事实上我明确地关闭()连接,认为它已经完成了。
  • 我没有把它读成刻薄的。决定某件事与问题无关,而事实上它对我们每个人都至关重要。顺便说一句,使用早期版本的 Npgsql,调用 Close() 本来可以正常工作,但这实际上是一个缺陷,因为它会导致完全读取大型数据集而不是按需读取,这会影响性能,如 npgsql.projects.postgresql.org/docs/manual/… 但由于一些旧代码(包括 NUnit 测试之一)采用您的方法,Preload Reader 命令字符串选项回到旧方法...
  • ...但是,虽然该选项可以使您以前的代码正常工作,但它会对性能产生不利影响(根据链接到的图表)。此外,由于 IDataReader 规范不承诺在这种情况下保留结果,因此如果您曾经移动到不同的数据库提供程序,您会遇到同样的问题。
  • 你明确或隐含地Close()它。你会知道为什么你应该关闭连接的所有原因,以及关于using 让你更容易避免在这方面出现失误。当您使用CommandBehavior.CloseConnection 调用ExecuteDataReader 时,IDataReader“获得”连接的“所有权”,关闭它将关闭连接。因此,虽然在这种情况下我们不会自己关闭或处理连接,但我们仍然必须确保关闭阅读器。这可以通过Close()Dispose() 或让using 致电Dispose()。有关更多信息,请参阅我上面链接的答案。
【解决方案2】:
NpgsqlCommand c = new NpgsqlCommand(...);
        NpgsqlDataReader dataread = c.ExecuteReader();

以上几行对于 DoQuery 方法来说是非常局部的。因此,一旦控件脱离方法,在该方法内部创建的每个对象都将失去其作用域。因此,您正在丢失数据,因为它是您在调用者方法中引用的引用类型。

【讨论】:

  • 那我该如何避免丢失对象呢?
  • 使连接对象成为类变量。这样它就不会失去它的范围。即使 DataReader 也是如此,您可以在需要时实例化它,我的意思是当您可以 DoQuery 时,然后实例化它,即 new SqlCommand(),但让变量在类范围内。在消费者代码中创建 DoQuery 的类对象的任何方式,都可以!
  • 不,您当然可以从方法返回 NpgsqlDataReader。您需要确保 NpgsqlConnection 没有关闭(最好使用 CommandBehaviour.CloseConnection 来确保它最终与读者一起关闭)。他们也有可能正在关闭他们的连接(问题中没有提到),所以你可能在问题的正确轨道上,但即使那样,范围本身也不是问题。
  • 亲爱的众神!不要将连接设为类变量。
  • @JonHanna 如果 OP 已经在全局范围内创建了连接,您的权利,但在他的代码中,他似乎正在做一些类似 new SqlCommand(new SqlConnection....)代码,因此显然它失去了它的范围并自动可能会或可能不会关闭连接。而且我认为连接不会因为它在本地范围内打开而自动关闭,除非您使用 Using(){}。如果我错了,请纠正我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-28
  • 1970-01-01
  • 2019-11-18
  • 1970-01-01
  • 2017-09-06
  • 2015-05-22
  • 2022-08-17
相关资源
最近更新 更多