Cách hiệu quả nhất để kiểm tra DBNull và sau đó gán cho một biến?


151

Câu hỏi này thỉnh thoảng xuất hiện, nhưng tôi chưa thấy câu trả lời thỏa đáng.

Một mẫu điển hình là (hàng là DataRow ):

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

Câu hỏi đầu tiên của tôi là cái nào hiệu quả hơn (tôi đã lật điều kiện):

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

Điều này chỉ ra rằng .GetType () sẽ nhanh hơn, nhưng có lẽ trình biên dịch biết một vài thủ thuật mà tôi không biết?

Câu hỏi thứ hai, có đáng lưu vào bộ đệm giá trị của hàng ["value"] hay trình biên dịch có tối ưu hóa trình chỉ mục không?

Ví dụ:

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

Ghi chú:

  1. hàng ["giá trị"] tồn tại.
  2. Tôi không biết chỉ số cột của cột (do đó tra cứu tên cột).
  3. Tôi đang hỏi cụ thể về việc kiểm tra DBNull và sau đó chỉ định (không phải về tối ưu hóa sớm, v.v.).

Tôi đã điểm chuẩn một vài kịch bản (thời gian tính bằng giây, 10.000.000 thử nghiệm):

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 có hiệu suất tương tự như "=="

Kết quả thú vị nhất? Nếu bạn không khớp tên của cột theo trường hợp (ví dụ: "Giá trị" thay vì "giá trị", sẽ mất khoảng mười lần lâu hơn (đối với một chuỗi):

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

Đạo đức của câu chuyện dường như là nếu bạn không thể tra cứu một cột theo chỉ mục của nó, thì hãy đảm bảo rằng tên cột bạn cung cấp cho người lập chỉ mục khớp chính xác với tên của DataColumn.

Bộ nhớ đệm giá trị cũng xuất hiện nhanh gần gấp đôi :

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

Vì vậy, phương pháp hiệu quả nhất dường như là:

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

1
Bạn có thể làm rõ liệu hàng là DataRow hay IDataRecord / IDataReader không?
Marc Gravell

7
Bây giờ chúng ta có .NET Framework tốt hơn nhiều và chúng ta có thể sử dụng Phương thức DataRowExtensions .
Pavel Hodek

Nếu bạn không khớp tên của cột theo trường hợp (ví dụ: "Giá trị" thay vì "giá trị", sẽ mất khoảng mười lần lâu hơn (đối với một chuỗi) Điều này hoàn toàn phụ thuộc vào việc triển khai. Tôi nhớ đây là trường hợp (thay đổi trong trường hợp tên cột chậm hơn nhiều) với trình kết nối MySQL ADO.NET, nhưng hoàn toàn không phải đối với SqlServer hoặc SQLite (không nhớ). Mọi thứ có thể đã thay đổi ngay bây giờ. Vâng, hướng dẫn cơ bản là, khi nghi ngờ, sẽ đi theo quy tắc.
nawfal

@PavelHodek thật xấu hổ chỉ dành cho DataRow. Sẽ có IDataRecordphần mở rộng yêu thích .
nawfal

Câu trả lời:


72

Chắc chắn là tôi đang thiếu gì đó. Không kiểm tra DBNullchính xác những gìDataRow.IsNull phương pháp làm?

Tôi đã sử dụng hai phương pháp mở rộng sau:

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

Sử dụng:

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

Nếu bạn không muốn Nullable<T>trả về giá trị cho GetValue<T>, bạn có thể dễ dàng trả về default(T)hoặc một số tùy chọn khác thay thế.


Trên một ghi chú không liên quan, đây là một thay thế VB.NET cho đề xuất của 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

3
Dan rủi ro này một lần nữa những gì OP muốn tránh. Bằng cách viết row.IsNull(columnName)bạn đang đọc nó một lần và đọc lại. Không nói rằng điều đó sẽ tạo ra sự khác biệt, nhưng về mặt lý thuyết nó có thể kém hiệu quả hơn ..
nawfal

2
System.Data.DataSetExtensions.DataRowExtensions.Field<T>(this System.Data.DataRow, string)Về cơ bản không phải là làm như phương pháp đầu tiên sao?
Dennis G

35

Bạn nên sử dụng phương pháp:

Convert.IsDBNull()

Xem xét nó được tích hợp vào Khung, tôi hy vọng nó sẽ hiệu quả nhất.

Tôi muốn đề xuất một cái gì đó dọc theo dòng:

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

Và có, trình biên dịch sẽ lưu trữ nó cho bạn.


5
Chà, tất cả các tùy chọn được đề cập đều được tích hợp vào khung ... Trên thực tế, Convert.IsDBNull thực hiện rất nhiều công việc bổ sung liên quan đến IConvertible ...
Marc Gravell

1
Và lại bộ đệm - nếu bạn muốn nói với ví dụ có điều kiện, thì không - nó thực sự không nên (và không). Nó sẽ thực hiện bộ chỉ mục hai lần.
Marc Gravell

Ồ, và mã đó không biên dịch - nhưng thêm một (int?) Cho một trong số chúng, và bạn sẽ thấy (trong IL) 2 của: đối tượng callvirt [System.Data] System.Data.DataRow :: get_Item (chuỗi)
Marc Gravell

20

Trình biên dịch sẽ không tối ưu hóa bộ chỉ mục (nghĩa là nếu bạn sử dụng hàng ["value"] hai lần), do đó, có một chút nhanh hơn để làm:

object value = row["value"];

và sau đó sử dụng giá trị hai lần; sử dụng .GetType () có nguy cơ xảy ra sự cố nếu nó không ...

DBNull.Valuethực sự là một singleton, vì vậy để thêm tùy chọn thứ 4 - có lẽ bạn có thể sử dụng ReferenceEquals - nhưng thực tế, tôi nghĩ rằng bạn đang lo lắng quá nhiều ở đây ... Tôi không nghĩ tốc độ khác nhau giữa "là", "== "Vv sẽ là nguyên nhân của bất kỳ vấn đề hiệu suất nào bạn đang thấy. Hồ sơ toàn bộ mã của bạn và tập trung vào một cái gì đó quan trọng ... nó sẽ không phải là cái này.


2
Trong hầu hết tất cả các trường hợp == sẽ tương đương với ReferenceEquals (đặc biệt là DBNull) và nó dễ đọc hơn nhiều. Sử dụng tối ưu hóa của @Marc Gravell nếu bạn muốn, nhưng tôi với anh ấy - có lẽ sẽ không giúp được gì nhiều. BTW, bình đẳng tham chiếu phải luôn luôn đánh bại kiểm tra loại.
tvanfosson

1
Bây giờ cũ, nhưng gần đây tôi đã thấy một số trường hợp đây là chính xác những gì trình hồ sơ nói để sửa chữa. Hãy tưởng tượng việc đánh giá các tập dữ liệu lớn, trong đó mọi ô cần thực hiện kiểm tra này. Tối ưu hóa có thể gặt hái những phần thưởng lớn. Nhưng phần quan trọng của câu trả lời vẫn là tốt: hồ sơ đầu tiên, để biết nơi tốt nhất để dành thời gian của bạn.
Joel Coehoorn

Tôi đoán giới thiệu C # 6 của toán tử Elvis giúp dễ dàng tránh ngoại lệ tham chiếu null trong kiểm tra mà bạn đề xuất. giá trị? .GetType () == typeof (DBNull)
Eniola

Vâng tôi đồng ý. nói chung là một cách tốt hơn để đi nhưng đối với những người muốn sử dụng .GetType () có rủi ro mà bạn đã chỉ ra thì sao? cung cấp một cách xung quanh nó.
Eniola

9

Tôi sẽ sử dụng đoạn mã sau trong C # ( VB.NET không đơn giản).

Mã gán giá trị nếu nó không phải là null / DBNull, nếu không, nó sẽ gán giá trị mặc định có thể được đặt thành giá trị LHS cho phép trình biên dịch bỏ qua việc gán.

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

1
Phiên bản VB.NET đơn giản: oSomeObject.IntMember = If(TryCast(oRow("Value), Integer?), iDefault).
Dan Tao

1
@Dan Tao - Tôi không nghĩ bạn đã biên dịch mã đó. Nhìn vào một câu hỏi cũ của tôi giải thích tại sao mã của bạn sẽ không hoạt động. stackoverflow.com/questions/746767/
hy

Và một lần nữa, bình luận về một câu hỏi SO trong khi rời khỏi máy tính của tôi (với các công cụ dev trên đó) đã được chứng minh là một sai lầm! Bạn đúng rồi; Tôi ngạc nhiên khi biết rằng TryCastkhông cung cấp chức năng tiện lợi giống như astoán tử của C # cho Nullable(Of T)các loại. Cách gần nhất mà tôi có thể nghĩ ra để bắt chước điều này là viết chức năng của riêng bạn, như tôi đã gợi ý trong câu trả lời của mình.
Dan Tao

Bạn sẽ gặp khó khăn trong việc tái cấu trúc điều này thành một phương thức chung, và ngay cả khi bạn thực hiện, việc truyền quá nhiều liên quan sẽ khiến nó kém hiệu quả hơn.
nawfal

8

Tôi cảm thấy chỉ có rất ít cách tiếp cận ở đây không có nguy cơ khiến OP lo lắng nhất (Marc Gravell, Stevo3000, Richard Szalay, Neil, Darren Koppand) và hầu hết đều phức tạp không cần thiết. Nhận thức đầy đủ đây là tối ưu hóa vi mô vô dụng, hãy để tôi nói rằng về cơ bản bạn nên sử dụng những điều sau:

1) Không đọc giá trị từ DataReader / DataRow hai lần - vì vậy hãy lưu trữ giá trị đó trước khi kiểm tra null và chuyển đổi / chuyển đổi hoặc thậm chí tốt hơn trực tiếp vượt qua record[X] đối tượng sang phương thức tiện ích mở rộng tùy chỉnh với chữ ký thích hợp.

2) Để tuân theo những điều trên, không sử dụng IsDBNullchức năng tích hợp trên DataReader / DataRow của bạn vì điều đó gọirecord[X] nội bộ, do đó, thực tế bạn sẽ thực hiện hai lần.

3) So sánh loại sẽ luôn chậm hơn so với so sánh giá trị như một quy tắc chung. Cứ làm đirecord[X] == DBNull.Value tốt hơn.

4) Truyền trực tiếp sẽ nhanh hơn gọi Convertlớp để chuyển đổi, mặc dù tôi sợ cái sau sẽ chùn bước ít hơn.

5) Cuối cùng, việc truy cập bản ghi theo chỉ mục thay vì tên cột sẽ nhanh hơn một lần nữa.


Tôi cảm thấy đi theo cách tiếp cận của Szalay, Neil và Darren Koppand sẽ tốt hơn. Tôi đặc biệt thích cách tiếp cận phương pháp mở rộng của Darren Koppand IDataRecord(mặc dù tôi muốn thu hẹp hơn nữa IDataReader) và tên chỉ mục / cột.

Hãy cẩn thận để gọi nó:

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

và không

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

trong trường hợp bạn cần phân biệt giữa 0DBNull. Ví dụ: nếu bạn có giá trị null trong các trường enum, nếu không, default(MyEnum)rủi ro là giá trị enum đầu tiên được trả về. Vì vậy, tốt hơn gọi record.GetColumnValue<MyEnum?>("Field").

Vì bạn đang đọc từ a DataRow, tôi sẽ tạo phương thức mở rộng cho cả hai DataRowIDataReaderbằng cách DRYing mã chung.

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

Vì vậy, bây giờ gọi nó như:

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

Tôi tin rằng đây là cách nó nên có trong khung (thay vì các phương thức record.GetInt32, record.GetStringv.v.) ở vị trí đầu tiên - không có ngoại lệ trong thời gian chạy và cho phép chúng tôi linh hoạt xử lý các giá trị null.

Từ kinh nghiệm của tôi, tôi đã gặp ít may mắn hơn với một phương pháp chung để đọc từ cơ sở dữ liệu. Tôi luôn luôn phải tùy chỉnh xử lý nhiều loại hình, vì vậy tôi đã phải viết riêng của tôi GetInt, GetEnum, GetGuid, vv phương pháp trong thời gian dài. Điều gì xảy ra nếu bạn muốn cắt các khoảng trắng khi đọc chuỗi từ db theo mặc định hoặc coi DBNulllà chuỗi rỗng? Hoặc nếu số thập phân của bạn nên được cắt bớt tất cả các số 0 ở cuối. Tôi gặp rắc rối nhất với Guidloại trình điều khiển trình kết nối khác nhau cũng hoạt động khác nhau khi cơ sở dữ liệu cơ bản có thể lưu trữ chúng dưới dạng chuỗi hoặc nhị phân. Tôi có một quá tải như thế này:

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

Với cách tiếp cận của Stevo3000, tôi thấy cách gọi hơi xấu xí và tẻ nhạt, và sẽ khó hơn để tạo ra một chức năng chung từ nó.


7

Có trường hợp rắc rối trong đó đối tượng có thể là một chuỗi. Mã phương thức mở rộng dưới đây xử lý tất cả các trường hợp. Đây là cách bạn sẽ sử dụng nó:

    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

Cá nhân tôi ủng hộ cú pháp này, sử dụng phương thức IsDbNull rõ ràng được hiển thị bởi IDataRecordvà lưu trữ chỉ mục cột để tránh tra cứu chuỗi trùng lặp.

Mở rộng để dễ đọc, nó đi một cái gì đó như:

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

Viết lại để phù hợp với một dòng duy nhất cho sự gọn nhẹ trong mã DAL - lưu ý rằng trong ví dụ này, chúng tôi sẽ gán int bar = -1nếu row["Bar"]là 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));

Việc gán nội tuyến có thể gây nhầm lẫn nếu bạn không biết nó ở đó, nhưng nó giữ toàn bộ hoạt động trên một dòng, điều mà tôi nghĩ là tăng cường khả năng đọc khi bạn điền các thuộc tính từ nhiều cột trong một khối mã.


3
DataRow không triển khai IDataRecord mặc dù.
ilitrite

5

Không phải là tôi đã làm điều này, nhưng bạn có thể thực hiện cuộc gọi lập chỉ mục kép và vẫn giữ mã của mình sạch sẽ bằng cách sử dụng phương thức tĩnh / mở rộng.

I E.

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

Sau đó:

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

Cũng có lợi ích của việc giữ logic kiểm tra null ở một nơi. Nhược điểm là, tất nhiên, đó là một cuộc gọi phương thức bổ sung.

Chỉ là một ý nghĩ.


2
Thêm một phương thức mở rộng trên đối tượng là rất rộng, mặc dù. Cá nhân tôi có thể đã xem xét một phương thức mở rộng trên DataRow, nhưng không phải đối tượng.
Marc Gravell

Đúng, mặc dù hãy nhớ rằng các phương thức mở rộng chỉ khả dụng khi không gian tên của lớp mở rộng được nhập.
Richard Szalay

5

Tôi cố gắng tránh kiểm tra này càng nhiều càng tốt.

Rõ ràng là không cần phải thực hiện đối với các cột không thể giữ null .

Nếu bạn đang lưu trữ trong một loại giá trị Nullable ( int?, v.v.), bạn chỉ có thể chuyển đổi bằng cách sử dụng as int?.

Nếu bạn không cần phân biệt giữa string.Emptynull, bạn chỉ cần gọi .ToString(), vì DBNull sẽ trở lại string.Empty.


4

Tôi luôn luôn sử dụng:

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

Tìm thấy nó ngắn và toàn diện.


4

Đây là cách tôi xử lý việc đọc từ 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);
    }
}

Ví dụ sử dụng:

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

Đạo cụ cho Quái vật Got My .Net cho mã ChageTypeTo.


4

Tôi đã làm một cái gì đó tương tự với các phương pháp mở rộng. Đây là mã của tôi:

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

Để sử dụng nó, bạn sẽ làm một cái gì đó như

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

4

nếu trong DataRow, hàng ["tên trường"] isDbNull thay thế nó bằng 0 nếu không thì lấy giá trị thập phân:

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

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

sử dụng như thế này

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

3

Tôi có IsDBNull trong một chương trình đọc rất nhiều dữ liệu từ cơ sở dữ liệu. Với IsDBNull, nó tải dữ liệu trong khoảng 20 giây. Không có IsDBNull, khoảng 1 giây.

Vì vậy, tôi nghĩ rằng tốt hơn là sử dụng:

public String TryGetString(SqlDataReader sqlReader, int row)
{
    String res = "";
    try
    {
        res = sqlReader.GetString(row);
    }
    catch (Exception)
    { 
    }
    return res;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.