C# 用于从可能为空的db值分配值的优雅语法

C# 用于从可能为空的db值分配值的优雅语法,c#,C#,对于某些上下文,记录类SaleAmount属性为 public decimal? SaleAmount { get; set; } 理想情况下,我会这样做 record.SaleAmount.Value = sql.Reader.IsDBNull(IDX_SALEAMOUNT) ? null : sql.Reader.GetDecimal(IDX_SALEAMOUNT); 唉,编译器和这不是朋友,因为 无法确定条件表达式的类型,因为“”和“decimal”之间没有

对于某些上下文,记录类SaleAmount属性为

public decimal? SaleAmount
{
    get;
    set;
}
理想情况下,我会这样做

record.SaleAmount.Value = 
sql.Reader.IsDBNull(IDX_SALEAMOUNT) ? null : 
    sql.Reader.GetDecimal(IDX_SALEAMOUNT);
唉,编译器和这不是朋友,因为

无法确定条件表达式的类型,因为“”和“decimal”之间没有隐式转换

那么,你会如何优雅地表达这一点,而不是像下面这样玩明显的牌

if (!sql.Reader.IsDBNull(IDX_SALEAMOUNT))
    record.SaleAmount = sql.Reader.GetDecimal(IDX_SALEAMOUNT);

还是高于最佳解决方案?

您会得到该错误,因为类型
decimal
(值类型)和
null
(参考值)不能一起使用

此外,由于您正在为
可空
类型的
属性赋值,因此您实际上是在尝试将
赋值给
十进制
类型,而该类型同样是不可行的

您可以通过强制转换将
GetDecimal
结果显式设置为可为null的
decimal
类型:

record.SaleAmount = sql.Reader.IsDBNull(IDX_SALEAMOUNT) ? 
    null : (decimal?)sql.Reader.GetDecimal(IDX_SALEAMOUNT);
不过,我同意你的第二种方法。

你可以选择:

record.SaleAmount = 
    (sql.Reader.IsDBNull(IDX_SALEAMOUNT) ? null : 
     sql.Reader.GetDecimal(IDX_SALEAMOUNT)) as decimal?;
您可以尝试另一种方法:

// usage: new GenericDataReader(cm.ExecuteReader())
//        ...Get<decimal?>(...);
public class GenericDataReader : IDataReader
{
    // IDataReader implementation
    public T Get<T>(int ordinal)
    {
        if (_dataReader.IsDBNull(ordinal))
            return default(T);
        else
            return (T)_dataReader.GetValue(ordinal);
    }
}
//用法:新的GenericDataReader(cm.ExecuteReader())
//…得到(…);
公共类GenericDataReader:IDataReader
{
//IDataReader实现
公共T获取(整数顺序)
{
if(_dataReader.IsDBNull(序号))
返回默认值(T);
其他的
return(T)u dataReader.GetValue(序数);
}
}

有关此方法的更多信息。

为什么不使用空合并运算符

record.SaleAmount = (decimal?)sql.Reader.GetDecimal(IDX_SALEAMOUNT)
    ?? null;
如果您使用的是C#3,我会使用一种扩展方法让您自己更容易做到这一点:

public static decimal? GetNullableDecimal(this DbReader reader, 
    int column)
{
    return reader.IsDBNull(column) ? (decimal?) null : 
        reader.GetDecimal(column);
}
然后您的应用程序代码就可以使用:

record.SaleAmount = sql.Reader.GetNullableDecimal(IDX_SALEAMOUNT);

首先,Value属性是只读的,因此无论如何都不能分配给它

如果向条件的false部分添加强制转换以强制转换为可为null的十进制,编译器应该会很高兴。试试下面的方法

record.SaleAmount = sql.Reader.IsDBNull(IDX_SALEAMOUNT) ? 
    null : (decimal?) sql.Reader.GetDecimal(IDX_SALEAMOUNT);

对我来说,最干净的解决方案是将其从业务逻辑层中完全删除,并首先让查询向读取器返回一个非空值

例如,如果使用SQL Server

SELECT IsNull(SalesAmount,0) ...

我将FieldHelper类与可为空的ToXXX(object)方法一起使用。这是我的建议 十进制示例(这是在Linq之前的日子里写的-大约2-3年前, 欢迎您将委托构造替换为Linq):

//
///获取一个可为空的值。
/// 
///要转换的值。
///转换后的值。
公共静态Nullable ToDecimal(对象可用)
{
返回音调可调(aValue,
委托(对象A可转换值)
{
返回Convert.ToDecimal(aConvertableValue);
});
}
/// 
///如有必要,转换给定值。
/// 
///来自数据库的值。
///转换委托。
///null或给定的值。
私有静态可空可调(
对象变量,转换器(转换器),其中T:struct
{
if(aValue==DBNull.Value | | aValue==null)
返回null;
else if(aValue是T)
返回(T)aValue;
其他的
返回一个转换器(aValue);
}

我喜欢使用以下通用扩展方法(可以很容易地转换为静态帮助函数)从读取器获取可能为空的值:

    public static T Get<T>(this IDataReader reader, string columnName)
    {
        if (reader[columnName] == DbNull.Value)
        {
            return default(T);
        }

        return (T)reader[columnName];
    }
public static T Get(此IDataReader读取器,字符串列名)
{
if(读卡器[columnName]==DbNull.Value)
{
返回默认值(T);
}
返回(T)读卡器[列名称];
}

要想在不支付太多运行时开销的情况下实现这一点,恐怕你不能太挑剔

但是,还有一种更简洁的语法:

public static Nullable<T> GetNullableValue<T>(this IDataRecord record, 
    int columnIndex, Func<int, T> getValue)
  where T: struct;
{
  if (record.IsDbNull(columnIndex))
    return null;
  else
    return getValue(columnIndex);
}

var xyz = reader.GetNullableValue(0, reader.GetDecimal);
公共静态Nullable GetNullableValue(此IDataRecord记录,
int columnIndex,Func getValue)
其中T:struct;
{
if(记录IsDbNull(列索引))
返回null;
其他的
返回getValue(columnIndex);
}
var xyz=reader.GetNullableValue(0,reader.GetDecimal);
IsDbNull(int)通常比使用GetSqlInt32之类的方法,然后与DBNull.Value或使用它自己的.IsNull之类的方法慢得多:

    public static int Int32(this SqlDataReader r, int ord)
    {
        var t = r.GetSqlInt32(ord);
        return t.IsNull ? default(int) : t.Value;
    }
尝试了一些模板解决方案,但到目前为止没有任何效果。问题是,所有Sql类型(这里是SqlInt32)实际上都是结构,而它们都有.Value属性C#,没有真正的模板来处理它。此外,它们还有自己的INullable接口,该接口只有.IsNull,不能与Nyllable兼容

我怀疑需要一整套Sql类型作为C#模板,或者向它们添加ICOnvertible,以便只需要一个或两个模板化方法


如果有人有一个或两个功能性技巧的想法,请大声说出来:-)

我对方法二的唯一问题是,对于JR程序员来说,它并不完全明显(毕竟,人们必须维护它);事实上,我们只在有值的时候赋值,而在其他情况下保持不变,这需要一秒钟的时间来理解。我知道这看起来很琐碎,但相信我,人们会对简单的东西感到困惑。你必须将其与使用类型转换和三元条件运算符进行权衡,我发现这至少同样令人困惑。那么只需显式地编写上面的代码,或者添加一条注释,说明非赋值意味着
null
。这使我认为Skeet的扩展方法的想法可能最有意义,因为它最明显、最可读。但我拒绝给他打勾,他得分太多了!嗯,这是有原因的,不是吗;)只需继续=)
GetDecimal
返回
decimal
,它永远不会是
null
,因此
不会工作。我看到你更新了你的帖子,但现在更糟了<代码>??null在逻辑上是错误的(如果它不是
null
则返回值,如果它是
null
,则返回
null
,因为
decimal
不能是
null
),请参阅备注部分,如果它不是decimal+1,则应抛出InvalidCastException:聪明的可读函数。在我看来,在ea中定义这样一个静态助手方法甚至是值得的
    public static int Int32(this SqlDataReader r, int ord)
    {
        var t = r.GetSqlInt32(ord);
        return t.IsNull ? default(int) : t.Value;
    }