Trình đọc dữ liệu SQL - xử lý các giá trị cột Null


297

Tôi đang sử dụng SQLdatareader để xây dựng POCO từ cơ sở dữ liệu. Mã này hoạt động trừ khi nó gặp một giá trị null trong cơ sở dữ liệu. Ví dụ: nếu cột FirstName trong cơ sở dữ liệu chứa giá trị null, một ngoại lệ sẽ được đưa ra.

employee.FirstName = sqlreader.GetString(indexFirstName);

Cách tốt nhất để xử lý các giá trị null trong tình huống này là gì?

Câu trả lời:


470

Bạn cần kiểm tra IsDBNull:

if(!SqlReader.IsDBNull(indexFirstName))
{
  employee.FirstName = sqlreader.GetString(indexFirstName);
}

Đó là cách đáng tin cậy duy nhất của bạn để phát hiện và xử lý tình huống này.

Tôi đã bọc những thứ đó vào các phương thức mở rộng và có xu hướng trả về một giá trị mặc định nếu cột thực sự là null:

public static string SafeGetString(this SqlDataReader reader, int colIndex)
{
   if(!reader.IsDBNull(colIndex))
       return reader.GetString(colIndex);
   return string.Empty;
}

Bây giờ bạn có thể gọi nó như thế này:

employee.FirstName = SqlReader.SafeGetString(indexFirstName);

và bạn sẽ không bao giờ phải lo lắng về một ngoại lệ hoặc một nullgiá trị nữa.


64
Nếu ai đó cần tên cột thay vì chỉ mục, bạn có thể thực hiện: int colIndex = reader.GetOrdinal(fieldname);và dễ dàng quá tải SafeGetStringchức năng của @ marc_s .
ilans

Không thể tin rằng tôi ở đây vào năm 2019, trong VB không kém .......... Tuy nhiên, xin cảm ơn, sự giúp đỡ tuyệt vời
JimmyB

Cũng có thể được thực hiện như thế này: int ordinal = reader.GetOrdinal ("col_name"); uint? val = reader.IsDBNull (thứ tự)? (uint?) null: reader.GetUInt32 (thứ tự);
ed22

Xin chào các bạn! Tôi đã cố gắng sao chép và dán vào mẫu và bị lỗi. "Phương thức mở rộng phải được định nghĩa trong một lớp tĩnh không chung.".
Jansen Malaggay

Nếu bạn đang sử dụng reader.GetOrindal bên trong SafeGetString và bạn muốn sử dụng SafeGetString trong một vòng lặp, làm thế nào để đạt được điều này mà không cần hiệu năng?
AspUser7724

223

Bạn nên sử dụng astoán tử kết hợp với ??toán tử cho các giá trị mặc định. Các loại giá trị sẽ cần phải được đọc là nullable và được mặc định.

employee.FirstName = sqlreader[indexFirstName] as string;
employee.Age = sqlreader[indexAge] as int? ?? default(int);

Nhà asđiều hành xử lý việc đúc bao gồm kiểm tra DBNull.


6
Nếu ai đó thay đổi cột Tuổi từ một int thành một bigint SQL (c # dài), mã của bạn sẽ thất bại trong âm thầm bằng cách trả về 0. Câu trả lời từ ZXX là IMO đáng tin cậy hơn.
Martin rding-Thomsen

Tôi tự hỏi nếu bạn có thể ghi đè mặc định (int) thành -1 thay vì 0
Chris

5
@Chris - Bạn chỉ có thể thay thế mặc định (int) bằng -1.
stevehipwell

@ Stevo3000 Bạn đã đúng! Tôi đã thử nó và nó hoạt động như bạn đã nói ngay sau khi tôi đăng, nhưng tôi quên quay lại trang này :)
Chris

5
Xin lưu ý rằng việc sử dụng "as" ở đây có thể ẩn các lỗi chỉ mục. Nếu bạn vô tình sử dụng sqlreader[indexAge] as string ?? "", bạn sẽ luôn nhận được "". Xem xét liệu bạn có thực sự muốn (int?)sqlreader[indexAge] ?? defaultValuethay thế hay không, vì vậy nếu SQL của bạn thay đổi, bạn sẽ có ngoại lệ thay vì các giá trị xấu. @ Stevo3000: mặc định (int) là 0, không phải -1. @Chris: Hãy chắc chắn rằng bạn đang sử dụng thứ bạn thực sự muốn.
me22

30

Đối với một chuỗi, bạn chỉ cần tạo phiên bản đối tượng (được truy cập bằng toán tử mảng) và kết thúc bằng chuỗi null cho null:

employee.FirstName = (string)sqlreader[indexFirstName];

hoặc là

employee.FirstName = sqlreader[indexFirstName] as string;

Đối với số nguyên, nếu bạn chuyển thành int nullable, bạn có thể sử dụng GetValueOrDefault ()

employee.Age = (sqlreader[indexAge] as int?).GetValueOrDefault();

hoặc toán tử hợp nhất null ( ??).

employee.Age = (sqlreader[indexAge] as int?) ?? 0;

2
Diễn viên rõ ràng, như trong ví dụ đầu tiên của bạn, không hoạt động. Nó ném cùng một lỗi
musefan

@musefan: Trường của bạn có thực sự là một chuỗi không? Nếu không bạn sẽ nhận được một lỗi khác. Điều này không hoạt động và đây không phải là sự khác biệt thực tế giữa các ví dụ 1 và 2 (ngoài cú pháp).
Mã hóa

1
@GoneCoding: Có, đó là một chuỗi nullable và chắc chắn đây là trường hợp thứ nhất gây ra vấn đề khi chuỗi thứ hai hoạt động. Tôi tưởng tượng vấn đề được gây ra bởi cách xử lý các giá trị null. Như trong, họ không phải nullmà thay vào đó là một đối tượng DBNull. Sự khác biệt giữa hai câu lệnh là câu lệnh đầu tiên sẽ thất bại nếu đó không phải là một chuỗi, trong khi câu lệnh thứ hai sẽ trả về null nếu đó không phải là một chuỗi.
musefan

23

IsDbNull(int)thường chậm hơn nhiều so với việc sử dụng các phương thức như thế GetSqlDateTimevà sau đó so sánh với DBNull.Value. Hãy thử các phương pháp mở rộng cho SqlDataReader.

public static T Def<T>(this SqlDataReader r, int ord)
{
    var t = r.GetSqlValue(ord);
    if (t == DBNull.Value) return default(T);
    return ((INullable)t).IsNull ? default(T) : (T)t;
}

public static T? Val<T>(this SqlDataReader r, int ord) where T:struct
{
    var t = r.GetSqlValue(ord);
    if (t == DBNull.Value) return null;
    return ((INullable)t).IsNull ? (T?)null : (T)t;
}

public static T Ref<T>(this SqlDataReader r, int ord) where T : class
{
    var t = r.GetSqlValue(ord);
    if (t == DBNull.Value) return null;
    return ((INullable)t).IsNull ? null : (T)t;
}

Sử dụng chúng như thế này:

var dd = r.Val<DateTime>(ords[4]);
var ii = r.Def<int>(ords[0]);
int nn = r.Def<int>(ords[0]);

5
Tôi thấy rằng các toán tử rõ ràng trên các loại System.Data.SqlTypes đang xuất hiện lỗi ở khắp mọi nơi khi cố gắng sử dụng mã này ...
Tetsujin no Oni

Xem stackoverflow.com/a/21024873/1508467 để biết giải thích tại sao điều này đôi khi không thành công (thử sử dụng Val <int> để đọc cột int SQL).
Rhys Jones

12

Một cách để làm điều đó là kiểm tra db null:

employee.FirstName = (sqlreader.IsDBNull(indexFirstName) 
    ? ""
    : sqlreader.GetString(indexFirstName));

12

reader.IsDbNull(ColumnIndex) hoạt động như nhiều câu trả lời nói.

Và tôi muốn đề cập đến nếu bạn làm việc với các tên cột, chỉ cần so sánh các loại có thể thoải mái hơn.

if(reader["TeacherImage"].GetType() == typeof(DBNull)) { //logic }

Điều này cũng hoạt động trên các phiên bản cũ của System.Data và .NET FW
RaSor

11

Tôi không nghĩ có giá trị cột NULL , khi các hàng được trả về trong bộ dữ liệu sử dụng tên cột.

Nếu bạn làm điều datareader["columnName"].ToString();đó sẽ luôn cung cấp cho bạn một giá trị có thể là một chuỗi rỗng ( String.Emptynếu bạn cần so sánh).

Tôi sẽ sử dụng những điều sau đây và sẽ không lo lắng quá nhiều:

employee.FirstName = sqlreader["columnNameForFirstName"].ToString();

4
Bạn có thể làm một trình đọc [FieldName] == DBNull.Value, để kiểm tra NULL's
Ralph Willgoss

11

Giải pháp này ít phụ thuộc vào nhà cung cấp và hoạt động với SQL, OleDB và MySQL Reader:

public static string GetStringSafe(this IDataReader reader, int colIndex)
{
    return GetStringSafe(reader, colIndex, string.Empty);
}

public static string GetStringSafe(this IDataReader reader, int colIndex, string defaultValue)
{
    if (!reader.IsDBNull(colIndex))
        return reader.GetString(colIndex);
    else
        return defaultValue;
}

public static string GetStringSafe(this IDataReader reader, string indexName)
{
    return GetStringSafe(reader, reader.GetOrdinal(indexName));
}

public static string GetStringSafe(this IDataReader reader, string indexName, string defaultValue)
{
    return GetStringSafe(reader, reader.GetOrdinal(indexName), defaultValue);
}

1
Sao chép và tùy chỉnh mã này trực tiếp vào một lớp mở rộng ngay bây giờ.
qxotk

8

Những gì tôi có xu hướng làm là thay thế các giá trị null trong câu lệnh SELECT bằng một cái gì đó phù hợp.

SELECT ISNULL(firstname, '') FROM people

Ở đây tôi thay thế mọi null bằng một chuỗi trống. Mã của bạn sẽ không bị lỗi trong trường hợp đó.


Nếu có thể, sử dụng điều này tránh null. Mặt khác, tôi thích câu trả lời của Sonny Boy về phương pháp trợ giúp.
Không hoàn lại tiền Không trả lại

3
Tại sao một phương pháp trợ giúp tĩnh, riêng biệt? Không phải là một phương thức mở rộng trên SqlDataReader có vẻ hấp dẫn và trực quan hơn sao ??
marc_s

7

Bạn có thể viết một hàm Generic để kiểm tra Null và bao gồm giá trị mặc định khi nó là NULL. Gọi cái này khi đọc Datareader

public T CheckNull<T>(object obj)
        {
            return (obj == DBNull.Value ? default(T) : (T)obj);
        }

Khi đọc Datareader, hãy sử dụng

                        while (dr.Read())
                        {
                            tblBPN_InTrRecon Bpn = new tblBPN_InTrRecon();
                            Bpn.BPN_Date = CheckNull<DateTime?>(dr["BPN_Date"]);
                            Bpn.Cust_Backorder_Qty = CheckNull<int?>(dr["Cust_Backorder_Qty"]);
                            Bpn.Cust_Min = CheckNull<int?>(dr["Cust_Min"]);
                         }

6

Kiểm tra sqlreader.IsDBNull(indexFirstName)trước khi bạn cố gắng đọc nó.


4

Làm thế nào để tạo phương thức trợ giúp

Đối với chuỗi

private static string MyStringConverter(object o)
    {
        if (o == DBNull.Value || o == null)
            return "";

        return o.ToString();
    }

Sử dụng

MyStringConverter(read["indexStringValue"])

Dành cho quốc tế

 private static int MyIntonverter(object o)
    {
        if (o == DBNull.Value || o == null)
            return 0;

        return Convert.ToInt32(o);
    }

Sử dụng

MyIntonverter(read["indexIntValue"])

Cho ngày

private static DateTime? MyDateConverter(object o)
    {
        return (o == DBNull.Value || o == null) ? (DateTime?)null : Convert.ToDateTime(o);
    }

Sử dụng

MyDateConverter(read["indexDateValue"])

Lưu ý: đối với DateTime khai báo varialbe là

DateTime? variable;

4

Ngoài câu trả lời của marc_s, bạn có thể sử dụng phương thức mở rộng chung hơn để nhận các giá trị từ SqlDataReader:

public static T SafeGet<T>(this SqlDataReader reader, int col)
    {
        return reader.IsDBNull(col) ? default(T) : reader.GetFieldValue<T>(col);
    }

Tôi sẽ không gọi phương thức này là "SafeGet" bởi vì nếu T là một cấu trúc, nó sẽ chuyển đổi null thành mặc định không null cho T - không thực sự an toàn. Có lẽ "GetValueOrDefault".
Rhys Jones

@RhysJones Tại sao bạn có T làm cấu trúc trong trường hợp này? Ngay cả khi bạn làm tôi sẽ lập luận rằng mặc định không null trong cấu trúc là hành vi dự kiến.
getpsyched

@RhysJones Nhưng tôi đồng ý rằng phương pháp này có thể không an toàn, nó sẽ cần xử lý các trường hợp ngoại lệ như UnlimitedCastException từ SqlDataReader.
getpsyched

4

Bằng cách ảnh hưởng từ câu trả lời của getpsyched , tôi đã tạo ra một phương thức chung để kiểm tra giá trị cột theo tên của nó

public static T SafeGet<T>(this System.Data.SqlClient.SqlDataReader reader, string nameOfColumn)
{
  var indexOfColumn = reader.GetOrdinal(nameOfColumn);
  return reader.IsDBNull(indexOfColumn) ? default(T) : reader.GetFieldValue<T>(indexOfColumn);
}

Sử dụng:

var myVariable = SafeGet<string>(reader, "NameOfColumn")

Tôi không biết bạn là ai, nhưng chúc lành cho bạn. Chức năng này đã lưu trong ba giờ làm việc.
Ramneek Singh

3

Tôi nghĩ bạn sẽ muốn sử dụng:

SqlReader.IsDBNull(indexFirstName)

2

Chúng tôi sử dụng một loạt các phương thức tĩnh để kéo tất cả các giá trị ra khỏi các trình đọc dữ liệu của chúng tôi. Vì vậy, trong trường hợp này, chúng tôi sẽ gọiDBUtils.GetString(sqlreader(indexFirstName)) Lợi ích của việc tạo các phương thức tĩnh / chia sẻ là bạn không phải thực hiện kiểm tra lặp đi lặp lại nhiều lần ...

(Các) phương thức tĩnh sẽ chứa mã để kiểm tra null (xem các câu trả lời khác trên trang này).


2

Bạn có thể sử dụng toán tử điều kiện:

employee.FirstName = sqlreader["indexFirstName"] != DBNull.Value ? sqlreader[indexFirstName].ToString() : "";

Giống như một trong những câu trả lời dưới đây, nhưng 8 năm sau!
beercohol

2

Có rất nhiều câu trả lời ở đây với thông tin hữu ích (và một số thông tin sai) được lan truyền, tôi muốn kết hợp tất cả lại với nhau.

Câu trả lời ngắn cho câu hỏi là kiểm tra DBNull - hầu như mọi người đều đồng ý với bit này :)

Thay vì sử dụng một phương thức trợ giúp để đọc các giá trị null cho mỗi kiểu dữ liệu SQL, một phương thức chung cho phép chúng tôi giải quyết vấn đề này với ít mã hơn. Tuy nhiên, bạn không thể có một phương thức chung duy nhất cho cả loại giá trị null và loại tham chiếu, điều này được thảo luận ở độ dài trong loại Nullable như một tham số chung có thể? ràng buộc kiểu chung C # cho mọi thứ nullable .

Vì vậy, tiếp theo các câu trả lời từ @ZXX và @getpsyched, chúng tôi kết thúc với phương pháp này, 2 phương pháp để nhận các giá trị null và tôi đã thêm một thứ 3 cho các giá trị không null (nó hoàn thành bộ dựa trên cách đặt tên phương thức).

public static T? GetNullableValueType<T>(this SqlDataReader sqlDataReader, string columnName) where T : struct
{
    int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
    return sqlDataReader.IsDBNull(columnOrdinal) ? (T?)null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}

public static T GetNullableReferenceType<T>(this SqlDataReader sqlDataReader, string columnName) where T : class
{
    int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
    return sqlDataReader.IsDBNull(columnOrdinal) ? null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}

public static T GetNonNullValue<T>(this SqlDataReader sqlDataReader, string columnName)
{
    int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
    return sqlDataReader.GetFieldValue<T>(columnOrdinal);
}

Tôi thường sử dụng tên cột, thay đổi chúng nếu bạn sử dụng chỉ mục cột. Dựa trên các tên phương thức này, tôi có thể biết liệu tôi có mong đợi dữ liệu là null hay không, khá hữu ích khi xem mã được viết từ lâu.

Lời khuyên;

  • Không có cột nullable trong cơ sở dữ liệu sẽ tránh được vấn đề này. Nếu bạn có quyền kiểm soát cơ sở dữ liệu thì các cột sẽ không phải là null theo mặc định và chỉ có thể là null khi cần thiết.
  • Không truyền các giá trị cơ sở dữ liệu với toán tử C # 'as' vì nếu truyền sai, nó sẽ âm thầm trả về null.
  • Sử dụng biểu thức giá trị mặc định sẽ thay đổi null cơ sở dữ liệu thành giá trị không null cho các loại giá trị như int, datetime, bit, v.v.

Cuối cùng, trong khi thử nghiệm các phương thức trên trên tất cả các loại dữ liệu SQL Server, tôi phát hiện ra rằng bạn không thể trực tiếp nhận được char [] từ SqlDataReader, nếu bạn muốn có char [], bạn sẽ phải lấy một chuỗi và sử dụng ToCharArray ().


1

Tôi đang sử dụng mã được liệt kê dưới đây để xử lý các ô null trong một bảng Excel được đọc thành dữ liệu.

if (!reader.IsDBNull(2))
{
   row["Oracle"] = (string)reader[2];
}

1
private static void Render(IList<ListData> list, IDataReader reader)
        {
            while (reader.Read())
            {

                listData.DownUrl = (reader.GetSchemaTable().Columns["DownUrl"] != null) ? Convert.ToString(reader["DownUrl"]) : null;
                //没有这一列时,让其等于null
                list.Add(listData);
            }
            reader.Close();
        }

1

và / hoặc sử dụng toán tử ternary với sự phân công:

employee.FirstName = rdr.IsDBNull(indexFirstName))? 
                     String.Empty: rdr.GetString(indexFirstName);

thay thế giá trị mặc định (khi null) cho phù hợp với từng loại thuộc tính ...


1

Phương thức này phụ thuộc vào indexFirstName, nên là cột thứ tự dựa trên zero.

if(!sqlReader.IsDBNull(indexFirstName))
{
  employee.FirstName = sqlreader.GetString(indexFirstName);
}

Nếu bạn không biết chỉ mục cột nhưng không muốn kiểm tra tên, bạn có thể sử dụng phương thức tiện ích mở rộng này để thay thế:

public static class DataRecordExtensions
{
    public static bool HasColumn(this IDataRecord dr, string columnName)
    {
        for (int i=0; i < dr.FieldCount; i++)
        {
            if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
                return true;
        }
        return false;
    }
}

Và sử dụng phương pháp như thế này:

if(sqlReader.HasColumn("FirstName"))
{
  employee.FirstName = sqlreader["FirstName"];
}

1

Câu hỏi cũ nhưng có lẽ ai đó vẫn cần một câu trả lời

trong thực tế tôi đã làm việc xung quanh vấn đề này như thế

Đối với int:

public static object GatDataInt(string Query, string Column)
    {
        SqlConnection DBConn = new SqlConnection(ConnectionString);
        if (DBConn.State == ConnectionState.Closed)
            DBConn.Open();
        SqlCommand CMD = new SqlCommand(Query, DBConn);
        SqlDataReader RDR = CMD.ExecuteReader();
        if (RDR.Read())
        {
            var Result = RDR[Column];
            RDR.Close();
            DBConn.Close();
            return Result;
        }
        return 0;
    }

tương tự đối với chuỗi chỉ trả về "" thay vì 0 vì "" là chuỗi rỗng

vì vậy bạn có thể sử dụng nó như

int TotalPoints = GatDataInt(QueryToGetTotalPoints, TotalPointColumn) as int?;

string Email = GatDatastring(QueryToGetEmail, EmailColumn) as string;

rất linh hoạt để bạn có thể chèn bất kỳ truy vấn nào để đọc bất kỳ cột nào và nó sẽ không bao giờ bị lỗi


0

Đây là lớp trợ giúp mà người khác có thể sử dụng nếu họ cần dựa trên câu trả lời @marc_s:

public static class SQLDataReaderExtensions
    {
        public static int SafeGetInt(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.IsDBNull(fieldIndex) ? 0 : dataReader.GetInt32(fieldIndex);
        }

        public static int? SafeGetNullableInt(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.GetValue(fieldIndex) as int?;
        }

        public static string SafeGetString(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.IsDBNull(fieldIndex) ? string.Empty : dataReader.GetString(fieldIndex);
        }

        public static DateTime? SafeGetNullableDateTime(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.GetValue(fieldIndex) as DateTime?;
        }

        public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName)
        {
            return SafeGetBoolean(dataReader, fieldName, false);
        }

        public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName, bool defaultValue)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.IsDBNull(fieldIndex) ? defaultValue : dataReader.GetBoolean(fieldIndex);
        }
    }

0

Chuyển đổi tay cầm DbNull hợp lý.

employee.FirstName = Convert.ToString(sqlreader.GetValue(indexFirstName));

Lưu ý rằng DBNull được chuyển đổi thành một chuỗi rỗng, không phải là giá trị null.
Rhys Jones

-2

bạn cũng có thể kiểm tra điều này

if(null !=x && x.HasRows)
{ ....}

-1 Đây không phải là vấn đề: chúng tôi đang xử lý trường hợp giá trị cột null, không phải là giá trị null hoặc trống rỗngSqlDataReader
màu xanh lam
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.