将null赋值给SqlParameter。

232
下面的代码出现错误 - "没有从DBnull到int的隐式转换。"
SqlParameter[] parameters = new SqlParameter[1];    
SqlParameter planIndexParameter = new SqlParameter("@AgeIndex", SqlDbType.Int);
planIndexParameter.Value = (AgeItem.AgeIndex== null) ? DBNull.Value : AgeItem.AgeIndex;
parameters[0] = planIndexParameter;

4
我认为你需要将AgeItem.AgeIndex转换为对象类型... https://dev59.com/I3VC5IYBdhLWcg3wtzus (顺便问一下,第三行末尾的==是为什么?) - Greg
20个回答

398
问题在于三元操作符?:无法确定返回类型,因为你既可以返回一个int值,也可以返回一个不兼容的DBNull类型值。
你当然可以将AgeIndex实例强制转换为object类型,以满足?:的要求。
你可以使用??空值合并运算符,如下所示。
SqlParameter[] parameters = new SqlParameter[1];     
SqlParameter planIndexParameter = new SqlParameter("@AgeIndex", SqlDbType.Int);
planIndexParameter.Value = (object)AgeItem.AgeIndex ?? DBNull.Value;
parameters[0] = planIndexParameter; 

下面是来自MSDN文档中关于?:运算符问题的引用:

要么第一个表达式和第二个表达式的类型必须相同,要么必须存在一种从一种类型到另一种类型的隐式转换。


为什么将 null 强制转换为对象时没有抛出异常?我认为应该是 AgeItem.AgeIndex as object - Niels Brinch
@Niels Brinch,这里不会出现异常,因为null是一个对象,只要您不尝试取消引用它,就是完全合法的。但是,在这个示例中,被转换为对象的并不是null,而是DBNull.Value,它实际上是一个值类型。 ?? 运算符说,“如果 AgetItem.AgeIndex 为 null,则返回 DBNull.Value,否则返回 AgeItem.AgeIndex”,然后将响应转换为对象。有关更多详细信息,请参见空合并运算符。http://msdn.microsoft.com/en-us/library/ms173224.aspx - Chris Taylor
5
从技术上讲,使用null合并运算符??的解决方案与使用常规三元运算符?:的解决方案相同 - 您仍然需要将AgeItem.AgeIndex强制转换为一个对象:planIndexParameter.Value = AgeItem.AgeIndex.HasValue ? (object)AgeItem.AgeIndex : DBNull.Value; - newfurniturey
如果您要使用常规的三元运算符 ?: 进行类型特定比较,那么转换整个表达式是行不通的。您必须像这样转换非 DBNull 参数:someID == 0 ? DBNull.Value : (object)someID - ingredient_15939
这是正确的,但如果您需要使用可空值作为函数的入参,那么结果会消耗SqlParameter,如果它为空,您将会得到错误,这种方式不起作用,您应该只使用简单的If-Else方式。例如:sample.Text.Trim() != "" ? func(sample.Text) : DBNull.Value; 不会像?:和??一样工作。 - QMaster

127

被接受的答案建议使用转换。但是,大多数SQL类型都有一个特殊的Null字段,可以用来避免这种转换。

例如,SqlInt32.Null "表示可以分配给SqlInt32类实例的DBNull"。

int? example = null;
object exampleCast = (object) example ?? DBNull.Value;
object exampleNoCast = example ?? SqlInt32.Null;

2
这个建议看起来很有前途,所以我尝试了"System.Data.SqlTypes.SqlString.Null",但它不起作用。 它将实际的字符串“Null”('N','u','l','l')放入字段中,而不是留下真正的空值(null)。 然而,旧的2010年使用(object) ?? DBNull.Value进行转换的“已接受答案”是正确的。(我使用的ADO.NET提供程序是SQLite,但我不确定是否会有区别。)我建议其他人仔细测试Brian的提示,以确保空值行为按预期工作。 - JasDev
6
我模糊地记得自己曾在评论中向一个高声望的用户(我想是Marc Gravell)描述了这个技巧,并被告知它只适用于Microsoft SQL Server。 - Brian
@JasDev 供应商会是关键因素,就像Brain point指出的那样,在SQL Server中这个功能可以正常运行。 - user692942
这个答案只是将显式转换为对象的操作替换为隐式转换。在示例代码中,exampleNoCast 被声明为对象,因此仍然发生了对象转换。如果像 OP 的代码一样,将值直接赋给类型为对象的 SqlParameter.Value,则仍然会进行转换。 - Scott
即使在 SQL Server 上,使用 DataTable/SqlBulkCopy 似乎也会失败。 - Brian

51

在使用SQLCommand时,如果没有指定默认值,则需要将DBNull.Value作为空参数传递,除非您使用存储过程。最好的方法是在查询执行之前为任何缺失的参数分配DBNull.Value,以下foreach代码可以完成这项工作。

foreach (SqlParameter parameter in sqlCmd.Parameters)
{
    if (parameter.Value == null)
    {
        parameter.Value = DBNull.Value;
    }
}

否则,修改这行代码:

planIndexParameter.Value = (AgeItem.AgeIndex== null) ? DBNull.Value : AgeItem.AgeIndex;

如下:

if (AgeItem.AgeIndex== null)
    planIndexParameter.Value = DBNull.Value;
else
    planIndexParameter.Value = AgeItem.AgeIndex;

因为你无法在条件语句中使用不同类型的值,比如DBNull和int是不同的。希望这可以帮助到你。


这个答案非常好,因为它提供了各种可能的示例。我喜欢第一种方法,通常我使用EF,但在这个要求中不能使用它,这节省了我很多时间。谢谢! - Leandro Bardelli

32

只需一行代码,试试这个:

var piParameter = new SqlParameter("@AgeIndex", AgeItem.AgeIndex ?? (object)DBNull.Value);

你也可以将 AgeItem.AgeIndex 强制转换为一个对象:(object)AgeItem.AgeIndex。但是我也更喜欢你的方法。 - Jordec

7
如果您使用条件(三元)运算符,则编译器需要两种类型之间的隐式转换,否则会出现异常。
因此,您可以通过将其中一个强制转换为System.Object来修复它:
planIndexParameter.Value = (AgeItem.AgeIndex== null) ? DBNull.Value : (object) AgeItem.AgeIndex;

但由于结果不太好看,而且你总是要记住这种类型转换,所以你可以使用这样的扩展方法:

public static object GetDBNullOrValue<T>(this T val)
{
    bool isDbNull = true;
    Type t = typeof(T);

    if (Nullable.GetUnderlyingType(t) != null)
        isDbNull = EqualityComparer<T>.Default.Equals(default(T), val);
    else if (t.IsValueType)
        isDbNull = false;
    else
        isDbNull = val == null;

    return isDbNull ? DBNull.Value : (object) val;
}

然后您可以使用这个简洁的代码:
planIndexParameter.Value = AgeItem.AgeIndex.GetDBNullOrValue();

6

试试这个:

SqlParameter[] parameters = new SqlParameter[1];    
SqlParameter planIndexParameter = new SqlParameter("@AgeIndex", SqlDbType.Int);

planIndexParameter.IsNullable = true; // Add this line

planIndexParameter.Value = (AgeItem.AgeIndex== null) ? DBNull.Value : AgeItem.AgeIndex== ;
parameters[0] = planIndexParameter;

2

我认为更好的方法是使用SqlCommand类的Parameters属性:

public static void AddCommandParameter(SqlCommand myCommand)
{
    myCommand.Parameters.AddWithValue(
        "@AgeIndex",
        (AgeItem.AgeIndex== null) ? DBNull.Value : AgeItem.AgeIndex);
}

但是,如果值为DBNull.Value,ADO.NET可能会有些难以猜测它可能是什么SqlDbType... 这很方便 - 但有点危险... - marc_s

1
您可以这样做。 这里的startDateendDate是可为空的日期时间参数。
var Statistics= db.Database.SqlQuery<ViewStatistics>("YourStoreProcedure_Or_sqlQuery  @startDate,@endDate",
        new SqlParameter("startDate", startDate?? (object)DBNull.Value),
        new SqlParameter("endDate", endDate?? (object)DBNull.Value)
        ).ToList();

1
一个简单的扩展方法可以是:

    public static void AddParameter(this SqlCommand sqlCommand, string parameterName, 
        SqlDbType sqlDbType, object item)
    {
        sqlCommand.Parameters.Add(parameterName, sqlDbType).Value = item ?? DBNull.Value;
    }

1

试试这个:

if (AgeItem.AgeIndex != null)
{
   SqlParameter[] parameters = new SqlParameter[1];
   SqlParameter planIndexParameter = new SqlParameter("@AgeIndex", SqlDbType.Int);
   planIndexParameter.Value = AgeItem.AgeIndex;
   parameters[0] = planIndexParameter;
}

换句话说,如果参数为空,就不要将其发送到存储过程中(当然,假设存储过程接受空参数,这是你问题的前提)。

但是现在,您只是省略了一个参数 - 我非常怀疑存储过程会对此感到满意...很可能,调用将失败并声明“未提供参数@AgeIndex的值,这是预期的”... - marc_s
哇,太严厉了。只需编写存储过程以默认值(@AgeIndex int = 0)为参数,如果未传递参数,则使用默认值。这种情况经常发生。客户可以接受默认值,或通过传递参数来覆盖它。为什么要点踩? - Flipster

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