当ExecuteScalar()没有返回结果时如何处理

74

我正在使用以下SQL查询和ExecuteScalar()方法从Oracle数据库获取数据:

sql = "select username from usermst where userid=2"
string getusername = command.ExecuteScalar();

我看到了这个错误信息:

System.NullReferenceException: 对象引用未设置为对象的实例。

当数据库表中没有userid=2的行时,就会出现这个错误。
我应该如何处理这种情况呢?


你使用的是哪个 ADO.NET 提供程序?ODP.NET 吗? - Branko Dimitrijevic
2
OracleCommandExecuteScalar方法返回一个结果对象。由于SQL语句是任意的,所以它不是强类型的,因此在解析之前(由DB引擎而不是.NET运行时解析),类型是未知的。返回的对象可以为null。您假设它是一个字符串,它可能是,很多类型都可以隐式转换为字符串,但这是一个非常危险的假设 - 该对象可以是任何类型。与您不明确信任的任何其他对象引用一样,首先应确保它不为null - Ed B
这个问题能得到一个正确的答案就太好了。 - Eric Bishard
23个回答

61
根据 DbCommand.ExecuteScalar的MSDN文档,如果结果集中第一行的第一列未找到,则返回空引用(在Visual Basic中为Nothing)。如果数据库中的值为空,则查询返回DBNull.Value。
请考虑以下代码片段:
using (var conn = new OracleConnection(...)) {
    conn.Open();
    var command = conn.CreateCommand();
    command.CommandText = "select username from usermst where userid=2";
    string getusername = (string)command.ExecuteScalar();
}

运行时(在ODP.NET下测试但在任何ADO.NET提供程序下应该是相同的),其行为如下:

  • 如果行不存在,则command.ExecuteScalar()的结果为null,并被转换为null字符串并赋值给getusername
  • 如果行存在,但是带有NULL的username(在您的数据库中可能吗?),则command.ExecuteScalar()的结果为DBNull.Value,导致InvalidCastException异常。

在任何情况下,NullReferenceException都不应该发生,因此您的问题可能出在其他地方。


其他问题呢?比如在他没有发布代码的情况下使用getusername而没有检查它是否为空?毕竟,NullReferenceException意味着有人试图取消引用(而不是分配)一个空值。 - iheanyi
@iheanyi 显然是这样。 - Branko Dimitrijevic
如果第一行的第一列包含NULL,那么这将失败,因为ExecuteScalar会返回DBNull。该行应该写成:string getusername = command.ExecuteScalar() as string; - Olivier Jacot-Descombes
@OlivierJacot-Descombes 是的,我在我的回答中提到了 InvalidCastException - Branko Dimitrijevic

55

首先,您应该确保您的命令对象不为 null。然后,您应该将命令的 CommandText 属性设置为您的 SQL 查询。最后,您应该将返回值存储在一个对象变量中,并在使用它之前检查它是否为 null:

command = new OracleCommand(connection)
command.CommandText = sql
object userNameObj = command.ExecuteScalar()
if (userNameObj != null)
  string getUserName = userNameObj.ToString()
 ...

我不确定关于VB的语法,但是你可以理解这个想法。


1
这可能帮助不大,因为引发异常的是对ExecuteScalar的调用。 - Fredrik Mörk
嗯...那可能是问题所在,但海报描述的问题是“id = 2的行不存在”,因此我认为数据库连接已正确设置。无论如何,我会更新我的答案。 - Rune Grimstad
是的,它能工作,但我不明白为什么我们要避免使用executescalar()函数。 - Hemant Kothiyal
6
不要避免使用ExecuteScalar,当您仅需要从查询中获取单个返回值时,这是推荐的方法。问题在于它返回一个对象,而不是您想要的类型的值。原因是数据库中的列可能包含空值,并且查询可能根本不返回值。这就是为什么您将该值存储在对象类型变量中,然后将其转换为适当的类型。 - Rune Grimstad

32

我刚刚用了这个:

    int? ReadTerminalID()
    {
        int? terminalID = null;

        using (FbConnection conn = connManager.CreateFbConnection())
        {
            conn.Open();
            FbCommand fbCommand = conn.CreateCommand();
            fbCommand.CommandText = "SPSYNCGETIDTERMINAL";
            fbCommand.CommandType = CommandType.StoredProcedure;

            object result = fbCommand.ExecuteScalar(); // ExecuteScalar fails on null
            if (result.GetType() != typeof(DBNull))
            {
                terminalID = (int?)result;
            }
        }

        return terminalID;
    }

我尝试了你的代码,发现在if条件语句中抛出了一个异常,所以我将它简单地改为:if (result != null),然后它就可以工作了。仍然给你的答案点个赞。谢谢。 - WhySoSerious
即使进行了空值检查,代码仍会进入if条件语句。DBNull正常工作!result.GetType() != typeof(DBNull) - Nishantha

16
下面这行代码:
string getusername = command.ExecuteScalar();

...将尝试将结果隐式转换为字符串,例如:

string getusername = (string)command.ExecuteScalar();

如果对象为null,常规的强制转换运算符将失败。 请尝试使用as-operator,像这样:

string getusername = command.ExecuteScalar() as string;

谢谢,它可以工作,但你能否将你的答案与其他人(Rune Grimstad,Fredrik Mörk)进行比较?我现在很困惑哪一个是最佳实践。 - Hemant Kothiyal
1
如果您只想获取一个值,ExecuteScalar 是一个很好的方法。您需要小心处理您得到的对象。使用 as 运算符在将 null 转换为字符串时不会引发 NullReferenceException。 - Tommy Carlier
1
object o = null; string s = (string)o; 这段代码不会抛出异常,所以我在想是否真的是因为 ExecuteScalar() 返回了 null 才导致了异常?@HemantKothiyal 你是否在运行时测试并确认用 as 替换强制转换可以避免异常?并且你在 getusername 中得到了预期的结果吗? - Branko Dimitrijevic
1
顺便说一下,string getusername = command.ExecuteScalar();不能编译 - *error CS0266: 不能隐式地将类型'object'转换为'string'。存在显式转换(您是否缺少强制转换?)*您可以发布您所使用的实际代码吗? - Branko Dimitrijevic
@HemantKothiyal,请查看有关“as”运算符的MSDN文档(https://msdn.microsoft.com/zh-tw/library/cscsdfbt.aspx)。as运算符类似于强制转换操作。但是,如果转换不可能,as会返回null而不是引发异常。请考虑以下示例: - Hsu Wei Cheng
在这种特定情况下,as运算符非常有用,因为当ExecuteScalar返回一个DBNull对象(第一列包含NULL时)时,它将返回null,否则将返回字符串或null(当行未找到时)。最后一行是正确的,但解释有缺陷。 - Olivier Jacot-Descombes

10
sql = "select username from usermst where userid=2"
var _getusername = command.ExecuteScalar();
if(_getusername != DBNull.Value)
{
    getusername = _getusername.ToString();
}  

9

请看下面的示例:

using System;
using System.Data;
using System.Data.SqlClient;

class ExecuteScalar
{
  public static void Main()
  {
    SqlConnection mySqlConnection =new SqlConnection("server=(local)\\SQLEXPRESS;database=MyDatabase;Integrated Security=SSPI;");
    SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
    mySqlCommand.CommandText ="SELECT COUNT(*) FROM Employee";
    mySqlConnection.Open();

    int returnValue = (int) mySqlCommand.ExecuteScalar();
    Console.WriteLine("mySqlCommand.ExecuteScalar() = " + returnValue);

    mySqlConnection.Close();
  }
}

from this here


7

SQL NULL

  • C#中的等价物是DBNull.Value
  • 如果可空列没有值,则返回此值
  • 在SQL中的比较:IF ( value IS NULL )
  • 在C#中的比较:if (obj == DBNull.Value)
  • 在C#快速查看中以{}形式呈现

从数据读取器中读取时的最佳实践:

var reader = cmd.ExecuteReader();
...
var result = (reader[i] == DBNull.Value ? "" : reader[i].ToString());

根据我的经验,有些情况下返回值可能会丢失,因此返回null导致执行失败。例如:

select MAX(ID) from <table name> where <impossible condition>

上述脚本无法找到任何最大值,因此失败。在这种情况下,我们必须用老旧的方法进行比较(与C#的null进行比较)。
var obj = cmd.ExecuteScalar();
var result = (obj == null ? -1 : Convert.ToInt32(obj));

5
如果您希望在某些情况下返回字符串,或在为空时返回空字符串,而不会出现任何问题,请使用以下方法:
using (var cmd = new OdbcCommand(cmdText, connection))
{
    var result = string.Empty;
    var scalar = cmd.ExecuteScalar();
    if (scalar != DBNull.Value) // Case where the DB value is null
    {
        result = Convert.ToString(scalar); // Case where the query doesn't return any rows. 
        // Note: Convert.ToString() returns an empty string if the object is null. 
        //       It doesn't break, like scalar.ToString() would have.
    }
    return result;
}

5

在读取行之前,务必进行检查。

if (SqlCommand.ExecuteScalar() == null)
{ 

}

1
对于简单而逻辑清晰的答案,加1。 - varsha
3
这不会导致查询执行两次吗?先检查查询结果是否为空,如果不为空,再将查询结果赋值给变量执行? - Kritner

2

这是最简单的方式...

sql = "select username from usermst where userid=2"
object getusername = command.ExecuteScalar();
if (getusername!=null)
{
    //do whatever with the value here
    //use getusername.toString() to get the value from the query
}

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