这个问题偶尔出现,但我没有看到令人满意的答案。
典型的模式是(行是 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?
This表示.GetType()应该更快,但编译器可能知道一些我不知道的技巧?
第二个问题,是否值得缓存row [“value”]的值,或者编译器是否优化了索引器呢?
例如:
object valueHolder;
if (DBNull.Value == (valueHolder = row["value"])) {}
注意:
我对一些情景进行了基准测试(时间以秒为单位,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与“==”
具有相同的性能最有趣的结果?如果你不匹配列的名称(例如,“值”而不是“值”,则大约需要十倍的时间(对于字符串):
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();
}
答案 0 :(得分:71)
我一定错过了什么。是不是要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");
如果您不希望Nullable<T>
返回GetValue<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
答案 1 :(得分:34)
您应该使用以下方法:
Convert.IsDBNull()
考虑到它是框架内置的,我希望这是最有效的。
我建议采用以下方式:
int? myValue = (Convert.IsDBNull(row["column"]) ? null : (int?) Convert.ToInt32(row["column"]));
是的,编译器应该为你缓存它。
答案 2 :(得分:20)
编译器不会优化索引器(即如果你使用row [“value”]两次),那么是稍微更快做:
object value = row["value"];
然后使用两次值;使用.GetType()可能会出现问题,如果它为null ...
DBNull.Value
实际上是一个单身人士,所以要添加第四个选项 - 你也许可以使用ReferenceEquals - 但实际上,我觉得你在这里太担心......我不认为速度不同在“是”之间,“==”等会导致您遇到任何性能问题。 描述您的整个代码并专注于重要的事情......不会是这个。
答案 3 :(得分:9)
我会在C#中使用以下代码(VB.NET并不那么简单。)
如果值不是null / DBNull,则代码分配值,否则它将默认值设置为LHS值,允许编译器忽略赋值。
oSomeObject.IntMemeber = oRow["Value"] as int? ?? iDefault;
oSomeObject.StringMember = oRow["Name"] as string ?? sDefault;
答案 4 :(得分:8)
我觉得这里只有极少数的方法不会给前景OP带来最大的担忧(Marc Gravell,Stevo3000,Richard Szalay,Neil,Darren Koppand)并且大多数都是不必要的复杂。充分意识到这是无用的微优化,让我说你基本上应该使用这些:
1)不要两次从DataReader / DataRow读取值 - 因此要么在空检查和强制转换之前对其进行缓存,要么更好地直接将record[X]
对象传递给具有适当签名的自定义扩展方法。 / p>
2)要遵守上述规定,请不要在DataReader / DataRow上使用内置的IsDBNull
函数,因为它会在内部调用record[X]
,因此实际上您将执行两次。
3)作为一般规则,类型比较总是比值比较慢。只需更好地record[X] == DBNull.Value
。
4)直接投射比调用Convert
类转换更快,但我担心后者会更少动摇。
5)最后,按索引而不是列名访问记录将再次更快。
我觉得通过Szalay,Neil和Darren Koppand的方法会更好。我特别喜欢Darren Koppand的扩展方法方法,它接收IDataRecord
(尽管我想将其缩小到IDataReader
)和索引/列名称。
小心打电话给它:
record.GetColumnValue<int?>("field");
而不是
record.GetColumnValue<int>("field");
如果您需要区分0
和DBNull
。例如,如果枚举字段中包含空值,否则default(MyEnum)
会冒返回第一个枚举值的风险。所以最好打电话给record.GetColumnValue<MyEnum?>("Field")
。
由于您正在阅读DataRow
,我会按DRYing公共代码为DataRow
和IDataReader
创建扩展方法。
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.GetInt32
,record.GetString
等方法) - 没有运行时异常,并且我们可以灵活处理空值。
根据我的经验,我从一个通用方法中读取数据库的运气不太好。我总是需要自定义处理各种类型,因此从长远来看,我必须编写自己的GetInt
,GetEnum
,GetGuid
等方法。如果您想在默认情况下从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的方法,我发现调用有点丑陋和繁琐,并且更难以制作泛型函数。
答案 5 :(得分:7)
有一个麻烦的情况,对象可能是一个字符串。以下扩展方法代码处理所有情况。以下是您将如何使用它:
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));
}
答案 6 :(得分:6)
我个人赞成这种语法,它使用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代码中的紧凑性的单行 - 请注意,在此示例中,如果int bar = -1
为空,我们将分配row["Bar"]
。
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));
如果您不知道内联分配,那么内联赋值可能会令人困惑,但它会将整个操作保留在一行上,我认为当您从一个代码块中的多个列填充属性时,可以提高可读性。
答案 7 :(得分:5)
并非我已经完成了这项工作,但您可以绕过双索引器调用并使用静态/扩展方法保持代码清洁。
即
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>();
还具有将空检查逻辑保持在一个位置的好处。当然,下行是一种额外的方法调用。
只是一个想法。
答案 8 :(得分:5)
我尽量避免这种检查。
显然不需要对不能保留null
的列进行。
如果您要存储为Nullable值类型(int?
等),则可以使用as int?
进行转换。
如果您不需要区分string.Empty
和null
,则可以调用.ToString()
,因为DBNull将返回string.Empty
。
答案 9 :(得分:4)
我总是使用:
if (row["value"] != DBNull.Value)
someObject.Member = row["value"];
发现它简短而全面。
答案 10 :(得分:4)
这是我处理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代码道具Monsters Got My .Net。
答案 11 :(得分:4)
我用扩展方法做了类似的事情。这是我的代码:
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)
答案 12 :(得分:4)
如果在DataRow中,行[“fieldname”] isDbNull将其替换为0,否则获取小数值:
decimal result = rw["fieldname"] as decimal? ?? 0;
答案 13 :(得分:3)
我在一个从数据库中读取大量数据的程序中有IsDBNull。使用IsDBNull,它可以在大约20秒内加载数据。 没有IsDBNull,大概1秒钟。
所以我认为最好使用:
public String TryGetString(SqlDataReader sqlReader, int row)
{
String res = "";
try
{
res = sqlReader.GetString(row);
}
catch (Exception)
{
}
return res;
}
答案 14 :(得分:3)
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"])