[.net] DBNull을 확인한 다음 변수에 할당하는 가장 효율적인 방법은 무엇입니까?

이 질문은 때때로 나타나지만 만족스러운 답변을 보지 못했습니다.

일반적인 패턴은 다음과 같습니다 (행은 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. 행 [ “값”]이 존재합니다.
  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는 “==”와 동일한 성능을 갖습니다.

가장 흥미로운 결과? 대소 문자를 기준으로 열 이름이 일치하지 않으면 (예 : “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();
 }



답변

뭔가 빠졌을 것입니다. 방법이 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)하거나 다른 옵션을 사용할 수 있습니다 .


관련이없는 메모에 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


답변

다음 방법을 사용해야합니다.

Convert.IsDBNull()

프레임 워크에 내장되어 있다는 것을 고려할 때 이것이 가장 효율적이라고 기대합니다.

나는 다음과 같은 내용을 제안한다.

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

그리고 네, 컴파일러는 당신을 위해 그것을 캐시해야합니다.


답변

컴파일러는 인덱서를 최적화하지 않습니다 (예 : row [ “value”]를 두 번 사용하는 경우). 예, 약간 더 빠릅니다.

object value = row["value"];

그런 다음 값을 두 번 사용하십시오. .GetType ()을 사용하면 null 인 경우 문제가 발생할 수 있습니다.

DBNull.Value실제로는 싱글 톤이므로 4 번째 옵션을 추가합니다. 아마도 ReferenceEquals를 사용할 수 있습니다. 그러나 실제로는, 당신이 여기서 너무 걱정하고 있다고 생각합니다 … “is”, “== “등은 현재보고있는 성능 문제의 원인이 될 것입니다. 전체 코드를 프로파일 링 하고 중요한 것에 집중하십시오. 그렇지 않을 것입니다.


답변

C #에서 다음 코드를 사용합니다 ( VB.NET 은 간단하지 않습니다).

이 코드는 null / DBNull이 아닌 경우 값을 할당하고, 그렇지 않으면 컴파일러에서 할당을 무시할 수 있도록 LHS 값으로 설정할 수있는 기본값을 할당합니다.

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


답변

나는 여기에 접근하는 방법이 거의 없다고 생각하지만 OP가 가장 걱정하는 부분 (Marc Gravell, Stevo3000, Richard Szalay, Neil, Darren Koppand)은 대부분 복잡하지 않습니다. 이것이 쓸모없는 마이크로 최적화라는 것을 완전히 알고 있으므로 기본적으로 다음을 사용해야한다고 말하겠습니다.

1) DataReader / DataRow에서 값을 두 번 읽지 마십시오. 따라서 null 확인 및 캐스트 / 변환 전에 값을 캐시하거나 record[X]적절한 서명을 사용 하여 객체를 사용자 정의 확장 메소드에 직접 전달하는 것이 좋습니다 .

2) 위의 내용을 준수하려면 내부적으로 IsDBNull호출하기 때문에 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, 나는 모두 확장 메서드를 만들 것입니다 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

나는 이것이 런타임 예외가없고 null 값을 유연하게 처리 할 수 ​​있도록 프레임 워크 (예 record.GetInt32: record.GetString등 대신)에 있어야하는 방법이라고 생각 합니다.

내 경험상 데이터베이스에서 읽을 수있는 일반적인 방법이 적었습니다. 난 내 자신 작성했다, 그래서 나는 항상, 사용자 정의 핸들 다양한 형태했다 GetInt, GetEnum, GetGuid장기적 등의 방법을. 기본적으로 db에서 문자열을 읽을 때 공백을 자르거나 DBNull빈 문자열로 취급하려면 어떻게해야합니까? 또는 소수점 이하의 모든 0을자를 경우. 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의 접근 방식을 사용하면 호출이 약간 추악하고 지루한 것으로 나타 났으며 일반적인 기능을 수행하기가 더 어려울 것입니다.


답변

객체가 문자열 일 수있는 번거로운 경우가 있습니다. 아래의 확장 메소드 코드는 모든 경우를 처리합니다. 사용 방법은 다음과 같습니다.

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


답변

필자는이 구문을 선호합니다.이 구문은에 의해 노출 된 명시 적 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 코드의 압축을 위해 한 줄에 맞게 다시 작성했습니다.이 예에서는 int bar = -1if row["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));

인라인 할당은 알지 못하는 경우 혼란 스럽지만 전체 작업을 한 줄로 유지하므로 한 코드 블록의 여러 열에서 속성을 채울 때 가독성이 향상됩니다.