最有效的方法来检查DBNull,然后分配给一个variables?

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

一个典型的模式是(行是一个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()应该更快,但编译器可能知道一些我不知道的技巧?

第二个问题,值得cachingrow [“value”]的值还是编译器优化索引器呢?

例如:

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

笔记:

  1. 行[“值”]存在。
  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 

Object.ReferenceEquals具有与“==”相同的性能

最有趣的结果? 如果大小写不匹配(例如,“值”而不是“值”),大约需要十倍的长度(对于一个string):

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

故事的寓意似乎是,如果您无法通过索引查找列,请确保提供给索引器的列名与DataColumn的名称完全匹配。

caching价值也似乎快了两倍

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

我一定会错过一些东西。 是不是检查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的build议:

 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 

你应该使用这个方法:

 Convert.IsDBNull() 

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

我会提出一些build议:

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

是的,编译器应该为你caching。

编译器不会优化索引器(也就是说,如果你使用两次row [“value”]),所以可以稍微快一些:

 object value = row["value"]; 

然后使用值两次; 使用.GetType()风险问题,如果它为空…

DBNull.Value实际上是一个单身人士,所以添加第四个选项 – 你也许可以使用ReferenceEquals – 但实际上,我认为你在这里担心太多…我不认为“是”之间的速度不同, “==”等将成为你所看到的任何性能问题的原因。 分析你的整个代码,并专注于重要的事情……不会是这样的。

我会在C#中使用下面的代码( VB.NET不是那么简单)。

如果代码不为null / DBNull,代码将赋值,否则它将默认值赋给LHS值,从而允许编译器忽略赋值。

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

对象可能是一个string是麻烦的事情。 下面的扩展方法代码处理所有情况。 以下是您将如何使用它的方法:

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

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

1)不要从DataReader / DataRow中读取两次值 – 所以要么在空值检查和转换/转换之前将其caching,要么直接将record[X]对象传递给具有适当签名的自定义扩展方法。

2)遵守上面的规定,不要在DataReader / DataRow中使用内build的IsDBNull函数,因为它在内部调用record[X] ,所以实际上你会这样做两次。

3)作为一般规则,types比较始终比值比较慢。 只要record[X] == DBNull.Value更好。

4)直接投射比Convert类更快,但我担心后者会更less。

5)最后,索引访问logging而不是列名访问logging会再次更快。


我觉得通过Szalay,Neil和Darren Koppand会更好。 我特别喜欢Darren Koppand的扩展方法,它采用了IDataRecord (尽pipe我想进一步缩小到IDataReader )和索引/列名称。

小心打电话吧:

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

并不是

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

如果你需要区分0DBNull 。 例如,如果在枚举字段中default(MyEnum)值,则default(MyEnum)会返回第一个枚举值。 所以更好地调用record.GetColumnValue<MyEnum?>("Field")

由于您正在从DataRow读取数据,我将通过DRYing通用代码为DataRowIDataReader创build扩展方法。

 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等方法)应该在一开始 – 没有运行时exception,并给我们处理空值的灵活性。

从我的经验来看,我从一个通用的方法中读取数据库的运气不多。 我总是需要自定义处理各种types,所以我必须从GetInt来编写自己的GetIntGetEnumGetGuid等方法。 如果你想在默认情况下从db读取string时修剪空格,或者将DBNull视为空string呢? 或者如果你的小数应该被截断所有尾随零。 对于Guidtypes,我遇到了很多问题,其中不同的连接器驱动程序的行为不同,当底层数据库可以将它们存储为string或二进制时。 我有这样的过载:

 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的方法,我觉得这个调用有点丑陋和乏味,而且通用函数更难以实现。

我个人更喜欢这种语法,它使用IDataRecord公开的显式IsDbNull方法,并caching列索引以避免重复的string查找。

为了便于阅读,扩展如下:

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

如果你不知道它是否在那里,那么内联分配会让你感到困惑,但是它将整个操作保留在一行中,当你在一个代码块中填充多列的属性时,我认为这增强了可读性。

不是我已经这样做了,但是可以绕过双索引器调用,并使用静态/扩展方法仍然保持代码清洁。

IE浏览器。

 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检查逻辑在一个地方的好处。 当然,缺点是这是一个额外的方法调用。

只是一个想法。

我总是使用:

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

发现它简短而全面。

这是我如何处理从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")) }; } 

道具怪物有我的.Net for ChageTypeTo代码。

我尽量避免这个检查。

显然不需要做不能为null列。

如果你正在存储一个Nullable值types( int?等),你可以只使用as int?转换as int?

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

我已经做了类似的扩展方法。 这是我的代码:

 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) 

如果在DataRow中,行[“fieldname”] isDbNull将其replace为0,否则获取十进制值:

 decimal result = rw["fieldname"] as decimal? ?? 0; 
 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"]) 

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

所以我觉得最好使用:

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