151

この質問は時々出てきますが、満足のいく答えを見たことがありません。

典型的なパターンは次のとおりです (行は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() の方が高速であることを示していますが、コンパイラは私が知らないいくつかのトリックを知っているのではないでしょうか?

2 番目の質問です。row["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 は "==" と同じパフォーマンスです

最も興味深い結果は?大文字と小文字を区別して列の名前を一致させない場合 (たとえば、「値」ではなく「値」にすると、(文字列の場合) 約 10 倍の時間がかかります。

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

この話の教訓は、インデックスで列を検索できない場合は、インデクサーに渡す列名が DataColumn の名前と正確に一致していることを確認することです。

値のキャッシュも、ほぼ2 倍高速に表示されます。

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

15 に答える 15

73

私は何かが欠けているに違いない。メソッドがDBNull何をするかを正確にチェックしていませんか?DataRow.IsNull

私は次の2つの拡張方法を使用しています。

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>の戻り値が必要ない場合は、代わりに return またはその他のオプションをGetValue<T>簡単に使用できます。default(T)


無関係なメモとして、Stevo3000 の提案に代わる VB.NET を次に示します。

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
于 2010-06-16T04:37:18.217 に答える
36

次の方法を使用する必要があります。

Convert.IsDBNull()

フレームワークに組み込まれていることを考えると、これが最も効率的であると予想されます。

次の行に沿って何かを提案します。

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

はい、コンパイラはそれをキャッシュする必要があります。

于 2008-10-21T12:01:04.840 に答える
20

コンパイラはインデクサーを最適化しません (つまり、row["value"] を 2 回使用した場合)

object value = row["value"];

値を 2 回使用します。.GetType() を使用すると、null の場合に問題が発生するリスクがあります...

DBNull.Valueは実際にはシングルトンなので、4番目のオプションを追加するには-おそらくReferenceEqualsを使用できます-しかし、実際には、ここで心配しすぎていると思います...「is」、「==」の間で速度が異なるとは思いません」などは、表示されているパフォーマンスの問題の原因になります。コード全体をプロファイリングし、重要なことに焦点を当てます...これではありません。

于 2008-10-21T12:02:57.690 に答える
9

C#では次のコードを使用します(VB.NETはそれほど単純ではありません)。

null / DBNullでない場合、コードは値を割り当てます。それ以外の場合は、LHS値に設定できるデフォルトを割り当て、コンパイラーが割り当てを無視できるようにします。

oSomeObject.IntMemeber = oRow["Value"] as int? ?? iDefault;
oSomeObject.StringMember = oRow["Name"] as string ?? sDefault;
于 2009-04-14T20:27:17.963 に答える
8

ここで、見込み客の OP が最も心配する危険を冒さないアプローチはごくわずかであると感じています (Marc Gravell、Stevo3000、Richard Szalay、Neil、Darren Koppand)。ほとんどのアプローチは不必要に複雑です。これは役に立たないマイクロ最適化であることを十分に認識しているため、基本的にこれらを採用する必要があります。

1) DataReader/DataRow から値を 2 回読み取らないでください。そのため、null チェックとキャスト/変換の前にキャッシュするか、record[X]オブジェクトを適切なシグネチャを持つカスタム拡張メソッドに直接渡すことをお勧めします。

IsDBNull2) 上記に従うために、DataReader/DataRow で組み込み関数を使用しないでください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。たとえば、列挙フィールドに null 値がある場合、default(MyEnum)最初の列挙値が返されるリスクがあります。より良い電話record.GetColumnValue<MyEnum?>("Field")

から読んでいるのでDataRow、両方の拡張メソッドを作成し、共通コードDataRowDRYします。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。実行時の例外がなく、null 値を処理する柔軟性が得られます。

私の経験からすると、データベースから読み取るための一般的な方法が 1 つしかありませんでした。私は常にさまざまなタイプをカスタム処理する必要があったため、長期的には独自の 、 、 などのメソッドを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 のアプローチでは、呼び出しが少し見苦しく退屈で、そこから汎用関数を作成するのが難しくなります。

于 2013-02-06T08:58:20.953 に答える
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)); 
    } 
于 2010-06-16T04:10:15.270 に答える
6

個人的には、 によって公開された明示的な IsDbNull メソッドを使用しIDataRecord、重複した文字列検索を避けるために列インデックスをキャッシュするこの構文を好みます。

読みやすくするために拡張すると、次のようになります。

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 コードをコンパクトにするために、1 行に収まるように書き直しました。この例ではint bar = -1、ifrow["Bar"]が null であることに注意してください。

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

インライン割り当ては、そこにあることがわからないと混乱する可能性がありますが、操作全体が 1 行に収まるため、1 つのコード ブロックで複数の列からプロパティを設定する場合の読みやすさが向上すると思います。

于 2008-10-21T12:07:16.497 に答える
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>();

また、null チェック ロジックを 1 か所に保持できるという利点もあります。もちろん、欠点は、それが余分なメソッド呼び出しであることです。

ちょっとした考え。

于 2008-10-21T13:36:43.557 に答える
5

私はこのチェックをできるだけ避けるようにしています。

を保持できない列に対して行う必要はありませんnull

Nullable 値型 (int?など) に格納している場合は、 を使用して変換するだけas int?です。

string.Emptyとを区別する必要がない場合は、 DBNull が を返すので、nullを呼び出すだけでかまいません。.ToString()string.Empty

于 2009-01-22T19:50:01.667 に答える
4

私はいつも使用します:

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

短くて包括的であることがわかりました。

于 2008-10-21T12:03:03.930 に答える
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"))
                  };
}

Props to Monsters Got My .Net for ChageTypeTo コード。

于 2008-11-25T20:08:18.303 に答える
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)
于 2008-11-26T01:22:53.160 に答える
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"])
于 2011-05-05T13:56:40.773 に答える
3

データベースから大量のデータを読み取るプログラムに IsDBNull があります。IsDBNull を使用すると、約 20 秒でデータがロードされます。IsDBNull を使用しない場合、約 1 秒。

したがって、使用する方が良いと思います:

public String TryGetString(SqlDataReader sqlReader, int row)
{
    String res = "";
    try
    {
        res = sqlReader.GetString(row);
    }
    catch (Exception)
    { 
    }
    return res;
}
于 2010-05-20T07:14:00.310 に答える