2008-11-19 37 views
11

tôi có một loạt các phương pháp mở rộng để giúp đỡ với null-kiểm tra trên các đối tượng IDataRecord, mà tôi đang thực hiện như thế này:Phương pháp chung có thể xử lý cả hai loại Giá trị tham chiếu và Giá trị không hợp lệ?

public static int? GetNullableInt32(this IDataRecord dr, int ordinal) 
{ 
    int? nullInt = null; 
    return dr.IsDBNull(ordinal) ? nullInt : dr.GetInt32(ordinal); 
} 

public static int? GetNullableInt32(this IDataRecord dr, string fieldname) 
{ 
    int ordinal = dr.GetOrdinal(fieldname); 
    return dr.GetNullableInt32(ordinal); 
} 

và như vậy, đối với từng loại tôi cần phải giải quyết.

Tôi muốn thực hiện lại các phương pháp này như một phương pháp chung, một phần để giảm dự phòng và một phần để tìm hiểu cách viết các phương pháp chung chung.

Tôi đã viết này:

public static Nullable<T> GetNullable<T>(this IDataRecord dr, int ordinal) 
{ 
    Nullable<T> nullValue = null; 
    return dr.IsDBNull(ordinal) ? nullValue : (Nullable<T>) dr.GetValue(ordinal); 
} 

mà làm việc miễn là T là một loại giá trị, nhưng nếu T là một loại tài liệu tham khảo nó sẽ không.

Phương pháp này sẽ cần phải trả về kiểu Nullable nếu T là một loại giá trị và mặc định (T) nếu không. Tôi sẽ thực hiện hành vi này như thế nào?

Trả lời

10

Bạn chỉ có thể tuyên bố phương pháp của bạn như thế này:

public static T GetNullable<T>(this IDataRecord dr, int ordinal) 
{ 
    return dr.IsDBNull(ordinal) ? default(T) : (T) dr.GetValue(ordinal); 
} 

Bằng cách này, nếu T là một giá trị nullable int hoặc bất kỳ giá trị nullable nào khác, nó sẽ trả về null. Nếu đó là một kiểu dữ liệu thông thường, nó sẽ chỉ trả về giá trị mặc định cho kiểu đó.

+2

Đó là giải pháp rất tồi. Nếu nằm trong giá trị NULL datarecord, bạn không muốn giá trị mặc định của int. NULL và ZERO là các giá trị khác nhau. – TcKs

+5

NẾU giá trị trong cơ sở dữ liệu là nullable, thì T sẽ là một Nullable trong trường hợp NULL là giá trị mặc định sẽ được trả về. – BFree

+2

Nếu vậy, tại sao phương pháp mở rộng này, nếu bạn có thể sử dụng chỉ đơn giản là "dr [thứ tự] như int?" ? – TcKs

-2

tôi làm điều đó theo cách này:

DataRow record = GetSomeRecord(); 
int? someNumber = record[15] as int? 
Guid? someUID = record["MyPrimaryKey"] as Guid?; 
string someText = GetSomeText(); 
record["Description"] = someText.ToDbString(); 

// ........ 

public static class StringExtensionHelper { 
    public static object ToDbString(this string text) { 
     object ret = null != text ? text : DBNull.Value 
     return ret; 
    } 
} 

EDIT: Bạn có thể (hoặc bạn nên) có một "ToDbInt32, ToDbBool, vv ..." phương pháp khuyến nông cho các kiểu dữ liệu khác ofcourse.

CHỈNH SỬA 2: Bạn cũng có thể mở rộng lớp "đối tượng" cơ sở bằng "ToDbValue".

public static class StringExtensionHelper { 
    public static object ToDbValue(this object value) { 
     object ret = object.ReferenceEquals(value, null) ? (object)DBNull.Value : value; 
     return ret; 
    } 
} 
+0

Tôi có thể, nhưng toàn bộ điểm của bài tập generics này là tránh viết một phương pháp riêng cho từng kiểu dữ liệu. Và tôi không thấy làm thế nào ví dụ của bạn thậm chí còn thích hợp ... Tôi đang mở rộng IDataRecord để null kiểm tra dữ liệu được lấy từ kho dữ liệu. –

+0

Nếu bạn muốn kiểm tra giá trị từ bản ghi, bạn có thể sử dụng từ khóa "làm", thực hiện những gì bạn muốn. Nếu là trong hồ sơ DbNull, sau đó null sẽ được trả lại. Nếu không, sẽ được trả về "int" là "Nullable ". Không có phương pháp đặc biệt được yêu cầu. – TcKs

+0

Bạn không nên ** mở rộng đối tượng bằng một phương thức mở rộng vì nó sẽ không có sẵn ở tất cả các ngôn ngữ, như VB.NET –

-1

Cấu trúc Nullable chỉ với nhiều loại giá trị, vì các loại tài liệu tham khảo là nullable anyway ...

+2

Đó là lý do tại sao tôi không thể thực hiện việc trả lại này là Nullable , đó là lý do tôi hỏi câu hỏi. –

1

Tôi không nghĩ rằng bạn có thể thực hiện điều này bằng một hàm duy nhất. Nếu C# hỗ trợ quá tải dựa trên kiểu trả về, bạn có thể có, nhưng thậm chí sau đó tôi sẽ khuyên bạn không nên làm như vậy.

Bạn sẽ có thể thực hiện điều tương tự bằng cách không sử dụng các loại dữ liệu không có giá trị và trả lại giá trị thực hoặc giá trị rỗng, là suggested bởi BFree.

+0

Thực ra, bạn có thể có hai phương thức quá tải chỉ khác nhau về kiểu trả về. Đây là cú pháp hợp pháp: Chuỗi công cộng Phương thức() { trả lại ""; } phương thức công khai int() { trả về 0; } – BFree

+0

@BFree: Loại 'xyz' đã xác định thành viên được gọi là 'Phương thức' với cùng các loại thông số. –

+0

@BFree: Tôi vừa thử mã chính xác của bạn và nhận được lỗi sau: Nhập 'Program1' đã định nghĩa thành viên được gọi là 'Phương thức' với cùng các loại thông số. –

2

này hoạt động:

public static T Get<T>(this IDataRecord dr, int ordinal) 
{ 
    T nullValue = default(T); 
    return dr.IsDBNull(ordinal) ? nullValue : (T) dr.GetValue(ordinal); 
} 


public void Code(params string[] args) 
{ 
    IDataRecord dr= null; 
    int? a = Get<int?>(dr, 1); 
    string b = Get<string>(dr, 2); 
} 
+1

Điều này khá giống với câu trả lời của BFree. Việc sử dụng mẫu là hữu ích. –

0

Bạn không thể làm điều đó với một phương pháp nhưng bạn làm điều đó với ba:

public static T GetData<T>(this IDataReader reader, Func<int, T> getFunc, int index) 
{ 
    if (!reader.IsClosed) 
    { 
     return getFunc(index); 
    } 
    throw new ArgumentException("Reader is closed.", "reader"); 
} 

public static T GetDataNullableRef<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : class 
{ 
    if (!reader.IsClosed) 
    { 
     return reader.IsDBNull(index) ? null : getFunc(index); 
    } 
    throw new ArgumentException("Reader is closed.", "reader"); 
} 

public static T? GetDataNullableValue<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : struct 
{ 
    if (!reader.IsClosed) 
    { 
     return reader.IsDBNull(index) ? (T?)null : getFunc(index); 
    } 
    throw new ArgumentException("Reader is closed.", "reader"); 
} 

Sau đó, để sử dụng nó, bạn sẽ làm gì:

private static Whatever CreateObject(IDataReader reader) 
{ 
    Int32? id = reader.GetDataNullableValue<Int32>(reader.GetInt32, 0); 
    string name = reader.GetDataNullableRef<string>(reader.GetString, 1); 
    Int32 x = reader.GetData<Int32>(reader.GetInt32, 2); 
} 
0
public static T Get<T>(this IDataRecord rec, Func<int, T> GetValue, int ordinal) 
{ 
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(ordinal); 
} 

hoặc nhiều người biểu diễn

public static T Get<T>(this IDataRecord rec, Func<IDataRecord, int, T> GetValue, int ordinal) 
{ 
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(rec, ordinal); 
} 

public static Func<IDataRecord, int, int> GetInt32 = (rec, i) => rec.GetInt32(i); 
public static Func<IDataRecord, int, bool> GetBool = (rec, i) => rec.GetBoolean(i); 
public static Func<IDataRecord, int, string> GetString = (rec, i) => rec.GetString(i); 

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

rec.Get(GetString, index); 
rec.Get(GetInt32, index); 
+0

Bạn đã quản lý để thực hiện một chức năng chung mà không nhận ra lợi ích nhỏ nhất từ ​​nó. Không phải viết một hàm riêng biệt cho từng loại là _whole point_ của generics, và lý do cho câu hỏi này được đăng. –

+0

Có thể, nhưng sự khác biệt từ giải pháp của bạn là bạn chỉ có một phương pháp mà bạn kiểm tra tính không có khả năng và tránh truyền mà có thể tốn kém nếu bạn có các tập dữ liệu thực sự lớn. – SeeR

+0

Cũng so sánh với giải pháp BFree bạn tránh boxing – SeeR

1

Tôi không thể hiểu tại sao cần phải phức tạp hơn toàn bộ quá trình này. Tại sao không giữ nó đẹp và đơn giản và sử dụng các dòng mã sau đây:

Đối với các loại giá trị trong đó null là hợp lệ sử dụng int? iNullable = dr[ordinal] as int?;.

Đối với các loại giá trị trong đó null không hợp lệ, hãy sử dụng int iNonNullable = dr[ordinal] as int? ?? default(int);.

Để tham khảo các loại sử dụng string sValue = dr[ordinal] as string;.

Đối với bất kỳ ai nghĩ rằng mã sẽ không hoạt động và dr[ordinal] sẽ ném ngoại lệ cho DBNull đây là một phương pháp ví dụ mà một khi đã đưa ra chuỗi kết nối hợp lệ sẽ chứng minh khái niệm.

private void Test() 
{ 
    int? iTestA; 
    int? iTestB; 
    int iTestC; 
    string sTestA; 
    string sTestB; 

    //Create connection 
    using (SqlConnection oConnection = new SqlConnection(@"")) 
    { 
    //Open connection 
    oConnection.Open(); 

    //Create command 
    using (SqlCommand oCommand = oConnection.CreateCommand()) 
    { 
     //Set command text 
     oCommand.CommandText = "SELECT null, 1, null, null, '1'"; 

     //Create reader 
     using (SqlDataReader oReader = oCommand.ExecuteReader()) 
     { 
     //Read the data 
     oReader.Read(); 

     //Set the values 
     iTestA = oReader[0] as int?; 
     iTestB = oReader[1] as int?; 
     iTestC = oReader[2] as int? ?? -1; 
     sTestA = oReader[3] as string; 
     sTestB = oReader[4] as string; 
     } 
    } 
    } 
} 
+0

như tôi đã giải thích cho BFree trong các ý kiến, dr [thứ tự] ném một ngoại lệ nếu nó là DBNull. Ví dụ của bạn sẽ không hoạt động. –

+0

@Adam Lassek - Tôi không biết tại sao bạn cho rằng mã của tôi không hoạt động, bạn đã thử chưa? Tôi có nó chạy trên hệ thống sản xuất, xin vui lòng thực sự kiểm tra mã trước khi bỏ phiếu xuống. Tôi nghĩ rằng hầu hết các noobs có thể cho bạn biết rằng 'dr [thứ tự]' sẽ không ném một ngoại lệ nếu giá trị là DBNull. – stevehipwell

+0

phiên bản framework nào? Vào thời điểm tôi viết câu hỏi này, tôi đã sử dụng 3,5 và nó chắc chắn đã ném một ngoại lệ nếu lĩnh vực này là DBNull. –

Các vấn đề liên quan