检查 DBNull 然后分配给变量的最有效方法是什么?

这个问题偶尔会出现,但我没有看到一个令人满意的答案。

典型的模式是(row 是 DataRow) :

 if (row["value"] != DBNull.Value)
{
someObject.Member = row["value"];
}

我的第一个问题是哪个更有效率(我已经翻转了条件) :

  row["value"] == DBNull.Value; // Or
row["value"] is DBNull; // Or
row["value"].GetType() == typeof(DBNull) // Or... any suggestions?

这个 表明. GetType ()应该更快,但是也许编译器知道一些我不知道的技巧?

第二个问题,是否值得缓存 row [“ value”]的值,或者编译器是否优化了索引器?

例如:

  object valueHolder;
if (DBNull.Value == (valueHolder = row["value"])) {}

备注:

  1. Row [“ value”]存在。
  2. 我不知道列的列索引(因此需要查找列名)。
  3. 我特别要求检查 DBNull 然后分配(而不是过早优化等)。

我对一些场景(以秒为单位的时间,10,000,000次试验)进行了基准测试:

row["value"] == DBNull.Value: 00:00:01.5478995
row["value"] is DBNull: 00:00:01.6306578
row["value"].GetType() == typeof(DBNull): 00:00:02.0138757

ReferenceEquals 的性能与“ = =”相同

最有趣的结果是什么?如果按大小写不匹配列的名称(例如,“ Value”而不是“ Value”,大约需要10倍的时间(对于字符串) :

row["Value"] == DBNull.Value: 00:00:12.2792374

这个故事的寓意似乎是,如果不能通过索引查找列,那么就要确保提供给索引器的列名与 DataColumn 的名称完全匹配。

缓存值的速度似乎也接近 两次:

No Caching: 00:00:03.0996622
With Caching: 00:00:01.5659920

因此,最有效的方法 看起来是:

 object temp;
string variable;
if (DBNull.Value != (temp = row["value"]))
{
variable = temp.ToString();
}
174096 次浏览

你应该使用以下方法:

Convert.IsDBNull()

考虑到它是框架内置的,我希望这是最有效的。

我的建议大致如下:

int? myValue = (Convert.IsDBNull(row["column"]) ? null : (int?) Convert.ToInt32(row["column"]));

是的,编译器应该为您缓存它。

编译器不会优化掉索引器(例如,如果您使用 row [“ value”]两次) ,所以是的,有点做起来更快:

object value = row["value"];

然后使用值两次; 如果值为空,则使用.GetType ()可能会出现问题..。

DBNull.Value实际上是一个单例模式,所以添加第四个选项——你可以使用 ReferenceEquals ——但实际上,我认为你在这里担心得太多了... ... 我不认为“ is”,“ = =”等之间的速度差异会导致你看到的任何性能问题。把注意力集中在重要的事情上... 不会是这个。

我总是用:

if (row["value"] != DBNull.Value)
someObject.Member = row["value"];

简短而全面。

我个人喜欢这种语法,它使用由 IDataRecord公开的显式 IsDbNull 方法,并缓存列索引以避免重复的字符串查找。

为了增加可读性,它是这样的:

int columnIndex = row.GetOrdinal("Foo");
string foo; // the variable we're assigning based on the column value.
if (row.IsDBNull(columnIndex)) {
foo = String.Empty; // or whatever
} else {
foo = row.GetString(columnIndex);
}

为了适应 DAL 代码中的紧凑性,我们重写了一行代码——注意,在这个例子中,如果 row["Bar"]为空,我们将分配 int bar = -1

int i; // can be reused for every field.
string foo  = (row.IsDBNull(i  = row.GetOrdinal("Foo")) ? null : row.GetString(i));
int bar = (row.IsDbNull(i = row.GetOrdinal("Bar")) ? -1 : row.GetInt32(i));

如果您不知道它的存在,内联赋值可能会让您感到困惑,但它将整个操作保持在一行上,我认为当您在一个代码块中填充来自多个列的属性时,这将增强可读性。

虽然我没有这样做,但是您可以避免使用双索引器调用,并通过使用静态/扩展方法保持代码的整洁。

我。

public static IsDBNull<T>(this object value, T default)
{
return (value == DBNull.Value)
? default
: (T)value;
}


public static IsDBNull<T>(this object value)
{
return value.IsDBNull(default(T));
}

然后:

IDataRecord record; // Comes from somewhere


entity.StringProperty = record["StringProperty"].IsDBNull<string>(null);
entity.Int32Property = record["Int32Property"].IsDBNull<int>(50);


entity.NoDefaultString = record["NoDefaultString"].IsDBNull<string>();
entity.NoDefaultInt = record["NoDefaultInt"].IsDBNull<int>();

还有一个好处是可以将 null 检查逻辑保存在一个地方。当然,缺点是它是一个额外的方法调用。

只是个想法。

这就是我如何处理从 DataRows 读取数据的方法

///<summary>
/// Handles operations for Enumerations
///</summary>
public static class DataRowUserExtensions
{
/// <summary>
/// Gets the specified data row.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dataRow">The data row.</param>
/// <param name="key">The key.</param>
/// <returns></returns>
public static T Get<T>(this DataRow dataRow, string key)
{
return (T) ChangeTypeTo<T>(dataRow[key]);
}


private static object ChangeTypeTo<T>(this object value)
{
Type underlyingType = typeof (T);
if (underlyingType == null)
throw new ArgumentNullException("value");


if (underlyingType.IsGenericType && underlyingType.GetGenericTypeDefinition().Equals(typeof (Nullable<>)))
{
if (value == null)
return null;
var converter = new NullableConverter(underlyingType);
underlyingType = converter.UnderlyingType;
}


// Try changing to Guid
if (underlyingType == typeof (Guid))
{
try
{
return new Guid(value.ToString());
}
catch


{
return null;
}
}
return Convert.ChangeType(value, underlyingType);
}
}

用法例子:

if (dbRow.Get<int>("Type") == 1)
{
newNode = new TreeViewNode
{
ToolTip = dbRow.Get<string>("Name"),
Text = (dbRow.Get<string>("Name").Length > 25 ? dbRow.Get<string>("Name").Substring(0, 25) + "..." : dbRow.Get<string>("Name")),
ImageUrl = "file.gif",
ID = dbRow.Get<string>("ReportPath"),
Value = dbRow.Get<string>("ReportDescription").Replace("'", "\'"),
NavigateUrl = ("?ReportType=" + dbRow.Get<string>("ReportPath"))
};
}

支持 怪兽收到了我的网的 ChageTypeTo 代码。

我曾经对扩展方法做过类似的工作,下面是我的代码:

public static class DataExtensions
{
/// <summary>
/// Gets the value.
/// </summary>
/// <typeparam name="T">The type of the data stored in the record</typeparam>
/// <param name="record">The record.</param>
/// <param name="columnName">Name of the column.</param>
/// <returns></returns>
public static T GetColumnValue<T>(this IDataRecord record, string columnName)
{
return GetColumnValue<T>(record, columnName, default(T));
}


/// <summary>
/// Gets the value.
/// </summary>
/// <typeparam name="T">The type of the data stored in the record</typeparam>
/// <param name="record">The record.</param>
/// <param name="columnName">Name of the column.</param>
/// <param name="defaultValue">The value to return if the column contains a <value>DBNull.Value</value> value.</param>
/// <returns></returns>
public static T GetColumnValue<T>(this IDataRecord record, string columnName, T defaultValue)
{
object value = record[columnName];
if (value == null || value == DBNull.Value)
{
return defaultValue;
}
else
{
return (T)value;
}
}
}

为了使用它,你可以这样做

int number = record.GetColumnValue<int>("Number",0)

我尽量避免这张支票。

显然不需要对不能保存 null的列执行此操作。

如果存储在 Nullable 值类型(int?等)中,只需使用 as int?进行转换。

如果不需要区分 string.Emptynull,只需调用 .ToString(),因为 DBNull 将返回 string.Empty

我将使用 C # 中的以下代码(VB.NET并不那么简单)。

如果这个值不是 null/DBNull,代码就会赋值,否则它会将默认值赋给 LHS 值,这样编译器就可以忽略这个赋值。

oSomeObject.IntMemeber = oRow["Value"] as int? ?? iDefault;
oSomeObject.StringMember = oRow["Name"] as string ?? sDefault;

我有一个从数据库中读取大量数据的程序 IsDBNull。使用 IsDBNull 可以在大约20秒内加载数据。 如果没有 IsDBNull,大约1秒。

因此,我认为最好使用:

public String TryGetString(SqlDataReader sqlReader, int row)
{
String res = "";
try
{
res = sqlReader.GetString(row);
}
catch (Exception)
{
}
return res;
}

有一种麻烦的情况,对象可能是一个字符串。下面的扩展方法代码处理所有情况。你可以这样使用它:

    static void Main(string[] args)
{
object number = DBNull.Value;


int newNumber = number.SafeDBNull<int>();


Console.WriteLine(newNumber);
}






public static T SafeDBNull<T>(this object value, T defaultValue)
{
if (value == null)
return default(T);


if (value is string)
return (T) Convert.ChangeType(value, typeof(T));


return (value == DBNull.Value) ? defaultValue : (T)value;
}


public static T SafeDBNull<T>(this object value)
{
return value.SafeDBNull(default(T));
}

我一定漏掉了什么。检查 DBNull不正是 DataRow.IsNull方法所做的吗?

我一直在使用以下两种扩展方法:

public static T? GetValue<T>(this DataRow row, string columnName) where T : struct
{
if (row.IsNull(columnName))
return null;


return row[columnName] as T?;
}


public static string GetText(this DataRow row, string columnName)
{
if (row.IsNull(columnName))
return string.Empty;


return row[columnName] as string ?? string.Empty;
}

用法:

int? id = row.GetValue<int>("Id");
string name = row.GetText("Name");
double? price = row.GetValue<double>("Price");

如果您不希望 GetValue<T>返回值为 Nullable<T>,那么您可以轻松地返回 default(T)或其他选项。


另外,下面是 VB.NET 对 Stevo3000建议的替代方案:

oSomeObject.IntMember = If(TryConvert(Of Integer)(oRow("Value")), iDefault)
oSomeObject.StringMember = If(TryCast(oRow("Name"), String), sDefault)


Function TryConvert(Of T As Structure)(ByVal obj As Object) As T?
If TypeOf obj Is T Then
Return New T?(DirectCast(obj, T))
Else
Return Nothing
End If
End Function
public static class DBH
{
/// <summary>
/// Return default(T) if supplied with DBNull.Value
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static T Get<T>(object value)
{
return value == DBNull.Value ? default(T) : (T)value;
}
}

像这样使用

DBH.Get<String>(itemRow["MyField"])

我觉得这里只有很少的方法不会让前景 OP 最担心(Marc Gravell,Stevo3000,Richard Szalay,Neil,Darren Koppand) ,而且大多数都是不必要的复杂。充分意识到这是无用的微观优化,让我说你基本上应该使用这些:

1)不要从 DataReader/DataRow 读取两次值——所以要么在 null 检查和强制转换/转换之前缓存它,要么更好地直接将 record[X]对象传递给具有适当签名的自定义扩展方法。

2)为了遵守上述规定,不要在 DataReader/DataRow 上使用内置的 IsDBNull函数,因为这会在内部调用 record[X],所以实际上你将执行两次。

3)类型比较总是慢于值比较作为一般规则。只要做 record[X] == DBNull.Value更好。

4)直接铸造将比调用 Convert类转换更快,虽然我担心后者会动摇较少。

5)最后,通过索引访问记录比通过列名访问记录更快。


我觉得通过萨莱,尼尔和达伦科潘的方法会更好。我特别喜欢 Darren Koppand 的扩展方法,它采用了 IDataRecord(尽管我想将范围进一步缩小到 IDataReader)和 index/column name。

注意称之为:

record.GetColumnValue<int?>("field");

而不是

record.GetColumnValue<int>("field");

以防你需要区分 0DBNull。例如,如果在枚举字段中有 null 值,则 default(MyEnum)冒着返回第一个枚举值的风险。所以最好打电话给 record.GetColumnValue<MyEnum?>("Field")

因为您是从 DataRow读取数据,所以我将通过 脱水公共代码为 DataRowIDataReader创建扩展方法。

public static T Get<T>(this DataRow dr, int index, T defaultValue = default(T))
{
return dr[index].Get<T>(defaultValue);
}


static T Get<T>(this object obj, T defaultValue) //Private method on object.. just to use internally.
{
if (obj.IsNull())
return defaultValue;


return (T)obj;
}


public static bool IsNull<T>(this T obj) where T : class
{
return (object)obj == null || obj == DBNull.Value;
}


public static T Get<T>(this IDataReader dr, int index, T defaultValue = default(T))
{
return dr[index].Get<T>(defaultValue);
}

所以现在可以这样说:

record.Get<int>(1); //if DBNull should be treated as 0
record.Get<int?>(1); //if DBNull should be treated as null
record.Get<int>(1, -1); //if DBNull should be treated as a custom value, say -1

我相信这就是它在框架(而不是 record.GetInt32record.GetString等方法)中应该放在首位的方式——没有运行时异常,并给予我们处理空值的灵活性。

根据我的经验,从数据库中读取的一般方法就没那么幸运了。我总是必须自定义处理各种类型,所以从长远来看,我必须编写自己的 GetIntGetEnumGetGuid等方法。如果您希望在默认情况下从 db 读取字符串时修剪空白,或者将 DBNull视为空字符串,该怎么办?或者您的小数是否应该截断所有后跟零。我在使用 Guid类型时遇到的最大麻烦是,当底层数据库可以将它们存储为字符串或二进制文件时,不同的连接器驱动程序的行为也不同。我有一个这样的超负荷:

static T Get<T>(this object obj, T defaultValue, Func<object, T> converter)
{
if (obj.IsNull())
return defaultValue;


return converter  == null ? (T)obj : converter(obj);
}

使用 Stevo3000的方法,我发现这个调用有点丑陋和乏味,而且很难用它来创建一个通用函数。

如果在 DataRow 中,行[“ fieldname”] isDbNull 将其替换为0,否则得到十进制值:

decimal result = rw["fieldname"] as decimal? ?? 0;