Kiểm tra tên cột trong một đối tượng SqlDataReader


212

Làm cách nào để kiểm tra xem một cột có tồn tại trong một SqlDataReaderđối tượng không? Trong lớp truy cập dữ liệu của tôi, tôi đã tạo một phương thức xây dựng cùng một đối tượng cho nhiều lệnh gọi thủ tục được lưu trữ. Một trong các thủ tục được lưu trữ có một cột bổ sung không được sử dụng bởi các thủ tục được lưu trữ khác. Tôi muốn sửa đổi phương thức để phù hợp với mọi kịch bản.

Ứng dụng của tôi được viết bằng C #.

Câu trả lời:


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

Sử dụng Exceptions cho logic điều khiển như trong một số câu trả lời khác được coi là thực tiễn xấu và có chi phí hiệu suất. Nó cũng gửi dương tính giả đến trình lược tả # ngoại lệ được ném và thần giúp bất kỳ ai đặt trình gỡ lỗi của họ phá vỡ các ngoại lệ bị ném.

GetSchemaTable () cũng là một gợi ý khác trong nhiều câu trả lời. Đây sẽ không phải là một cách kiểm tra trước sự tồn tại của một trường vì nó không được triển khai trong tất cả các phiên bản (nó trừu tượng và ném NotSupportedException trong một số phiên bản dotnetcore). GetSchemaTable cũng là hiệu năng quá mức khôn ngoan vì đây là một chức năng khá nặng nếu bạn kiểm tra nguồn .

Vòng qua các trường có thể có một hiệu suất nhỏ nếu bạn sử dụng nó nhiều và bạn có thể muốn xem xét lưu trữ kết quả.


Điều gì nếu một bí danh được sử dụng? Việc so sánh tên sẽ thất bại.
Murphybro2

Thật đáng tranh luận rằng sử dụng dòng ngoại lệ là thực tiễn xấu. Nó đã từng được coi là xấu vì nó tương đối đắt đối với các nhà khai thác khác, nhưng không đáng kể trong một ứng dụng được kết nối. Skeet đã đo 40-118 trường hợp ngoại lệ mỗi ms tùy thuộc vào độ sâu của ngăn xếp trong suốt năm 2006. stackoverflow.com/a/891230/852208 . Hơn nữa mà không cần kiểm tra, có thể mã này thực sự chậm hơn với trường hợp trung bình kiểm tra một nửa tất cả các cột (mặc dù vẫn không đáng kể trong ứng dụng được kết nối db). Tôi sẽ chỉnh sửa câu trả lời này để chỉ bao gồm đoạn giữa vì hai ý kiến ​​còn lại.
b_levitt

3
@b_levitt không có gì phải bàn cãi, đó là mã tào lao và bạn không nên dựa vào ngoại lệ cho luồng kiểm soát
Chad Grant

Giống như hai câu tôi đã chỉ ra, đó là một ý kiến ​​khác không được hỗ trợ với bất kỳ lý do nào ngoài hiệu suất trong ứng dụng tính toán thuần túy. Tôi dám cho bạn đặt trình gỡ lỗi của mình để phá vỡ mọi ngoại lệ và chỉ vô hiệu hóa mã của tôi và bạn sẽ thấy ngay cả khung và các thư viện khác đã làm điều này. Vấn đề với lời khuyên của bạn là nó thúc đẩy các nhà phát triển trả lại mã mà hầu hết đồng ý là một mẫu kém hơn: stackoverflow.com/questions/99683/ . Phương pháp như vậy thất bại trong bài kiểm tra "hố thành công".
b_levitt

Từ góc độ mã, câu trả lời của bạn là hợp lệ. Nhưng ý kiến ​​của bạn đang cố gắng cân nhắc nó như một câu trả lời vượt trội cho câu trả lời với thử / bắt (cũng xử lý bí danh) thì không phù hợp ở đây.
b_levitt

66

Tốt hơn nhiều là sử dụng hàm boolean này:

r.GetSchemaTable().Columns.Contains(field)

Một cuộc gọi - không có ngoại lệ. Nó có thể ném ngoại lệ trong nội bộ, nhưng tôi không nghĩ vậy.

LƯU Ý: Trong các bình luận bên dưới, chúng tôi đã tìm ra điều này ... mã chính xác thực sự là thế này:

public static bool HasColumn(DbDataReader Reader, string ColumnName) { 
    foreach (DataRow row in Reader.GetSchemaTable().Rows) { 
        if (row["ColumnName"].ToString() == ColumnName) 
            return true; 
    } //Still here? Column not found. 
    return false; 
}

5
@Jasmine: Tôi đã nói quá sớm! Mã của bạn kiểm tra một cột trong bảng lược đồ, không phải tập kết quả của bạn. Bạn cần so sánh "trường" (giả sử "trường" là tên cột) với giá trị của trường "Cột Tên" của mỗi hàng. Phá vỡ khi bạn tìm thấy nó, trả lại sai nếu bạn không.
Steve J

4
@Steve J: Khi nào thì resultset KHÔNG có cột trong GetSchemaTable?
Bless Yahu

1
Để bất cứ ai khác bối rối, NÀY KHÔNG LÀM VIỆC. Xem Câu trả lời bên dưới về việc truy xuất hàng Cột Tên từ bảng lược đồ và sử dụng nó.
Jason Jackson

3
Vâng, điều này KHÔNG LÀM VIỆC. Ai đã nâng cấp nó rất nhiều lần ??? Nó sẽ giúp tôi tiết kiệm rất nhiều thời gian gỡ lỗi sau này nếu câu trả lời này không có ở đây!
c00000fd

1
@Jasmine cả hai cùng làm việc? Không hẳn vậy. Vui lòng xóa phần đầu tiên của câu trả lời của bạn. Tôi sẽ tự làm, nhưng cho nhận xét cuối cùng của bạn!
nawfal

33

Tôi nghĩ rằng cách tốt nhất của bạn là gọi GetOrdinal ("cộtName") ở phía trước DataReader của bạn và bắt IndexOutOfRangeException trong trường hợp cột không xuất hiện.

Trong thực tế, hãy thực hiện một phương pháp mở rộng:

public static bool HasColumn(this IDataRecord r, string columnName)
{
    try
    {
        return r.GetOrdinal(columnName) >= 0;
    }
    catch (IndexOutOfRangeException)
    {
        return false;
    }
}

Biên tập

Ok, gần đây bài đăng này đang bắt đầu thu được một vài phiếu giảm giá và tôi không thể xóa nó vì đó là câu trả lời được chấp nhận, vì vậy tôi sẽ cập nhật nó và (tôi hy vọng) cố gắng biện minh cho việc sử dụng xử lý ngoại lệ như kiểm soát dòng chảy.

Một cách khác để đạt được điều này, như được đăng bởi Chad Grant , là lặp qua từng trường trong DataReader và thực hiện so sánh không phân biệt chữ hoa chữ thường cho tên trường bạn đang tìm kiếm. Điều này sẽ hoạt động thực sự tốt, và thực sự có thể sẽ hoạt động tốt hơn phương pháp của tôi ở trên. Chắc chắn tôi sẽ không bao giờ sử dụng phương thức trên trong một vòng lặp trong đó hiệu suất là một vấn đề.

Tôi có thể nghĩ về một tình huống trong đó phương thức try / GetOrdinal / Catch sẽ hoạt động trong đó vòng lặp không. Tuy nhiên, đây là một tình huống hoàn toàn giả định ngay bây giờ vì vậy đó là một lời biện minh rất mỏng manh. Bất kể, chịu đựng với tôi và xem những gì bạn nghĩ.

Hãy tưởng tượng một cơ sở dữ liệu cho phép bạn "bí danh" các cột trong một bảng. Hãy tưởng tượng rằng tôi có thể định nghĩa một bảng có một cột có tên là "EmployeeName" nhưng cũng đặt cho nó một bí danh là "EmpName" và thực hiện chọn một trong hai tên sẽ trả về dữ liệu trong cột đó. Với tôi cho đến nay?

Bây giờ hãy tưởng tượng rằng có một nhà cung cấp ADO.NET cho cơ sở dữ liệu đó và họ đã mã hóa một triển khai IDataReader cho nó để tính các bí danh cột.

Bây giờ, dr.GetName(i)(như được sử dụng trong câu trả lời của Chad) chỉ có thể trả về một chuỗi, do đó, nó chỉ phải trả về một trong các "bí danh" trên một cột. Tuy nhiên,GetOrdinal("EmpName") có thể sử dụng triển khai nội bộ của các trường của nhà cung cấp này để kiểm tra bí danh của từng cột cho tên bạn đang tìm kiếm.

Trong tình huống "các cột bí danh" giả định này, phương thức try / GetOrdinal / Catch sẽ là cách duy nhất để chắc chắn rằng bạn đang kiểm tra mọi biến thể của tên cột trong tập kết quả.

Flimsy? Chắc chắn rồi. Nhưng đáng suy nghĩ. Thành thật mà nói tôi rất muốn một phương pháp HasColumn "chính thức" trên IDataRecord.


15
sử dụng ngoại lệ cho logic điều khiển? không không không
Chad Grant

28
Có một điều nhỏ mà mọi người bỏ qua khi ban đầu tôi đăng câu hỏi này ... Tôi đã đặt câu hỏi vào ngày 12/8/08 và Matt đã đăng câu trả lời của mình vào ngày 17/12/08. Mọi người đều tỏ ra khó chịu về việc bắt một ngoại lệ cho logic điều khiển nhưng không cung cấp giải pháp thay thế vững chắc cho đến ngày 5/1/09. Đó là lý do tại sao nó ban đầu được đánh dấu là câu trả lời. Tôi vẫn đang sử dụng giải pháp này ngày hôm nay.
Michael Kniskern

19
Điều này sẽ có một hiệu suất chỉ khi cột không có ở đó. Các phương pháp khác được mô tả sẽ có lần truy cập hiệu suất và lần truy cập hiệu suất lớn hơn mỗi lần. Mặc dù nói chung là thực tế xấu để tránh sử dụng xử lý ngoại lệ cho luồng kiểm soát, nhưng không nên loại trừ giải pháp này mà không xem xét trước nếu nó hoạt động trong trường hợp của bạn.
Nick Harrison

5
+1. Tôi ổn với "Không sử dụng ngoại lệ cho logic điều khiển" như một quy tắc thiết kế rộng. Nó không có nghĩa là "tránh nó bằng mọi giá". Câu trả lời là một cách giải quyết được ghi chép rất tốt và như @Nick nói, hiệu suất đạt được (nếu có ..) chỉ xảy ra khi cột không tồn tại.
Larry

2
Sử dụng Exceptions làm điều khiển logic cũng làm cho việc gỡ lỗi trở nên cồng kềnh hơn trong trải nghiệm của tôi. Bạn phải bỏ chọn "Ném" trên "Ngoại lệ thời gian chạy ngôn ngữ chung" và sau đó khi bạn có một ngoại lệ thực sự, nó có thể bị hỏng trong một trình xử lý ở đâu đó và không phải là vấn đề.
cedd

30

Trong một dòng, sử dụng điều này sau khi truy xuất DataReader của bạn:

var fieldNames = Enumerable.Range(0, dr.FieldCount).Select(i => dr.GetName(i)).ToArray();

Sau đó,

if (fieldNames.Contains("myField"))
{
    var myFieldValue = dr["myField"];
    ...

Biên tập

Một lớp lót hiệu quả hơn nhiều mà không cần phải tải lược đồ:

var exists = Enumerable.Range(0, dr.FieldCount).Any(i => string.Equals(dr.GetName(i), fieldName, StringComparison.OrdinalIgnoreCase));

Bạn đang liệt kê tên trường nhiều lần / phân bổ một mảng khác để quét với chứa, điều này sẽ ít hiệu quả hơn trong mã lưu lượng truy cập cao.
Chad Grant

@ChadGrant tất nhiên, đó là lý do tại sao Linq one liner hiệu quả hơn nhiều vì nó chỉ thực hiện một lần lặp.
Larry

18

Đây là một mẫu làm việc cho ý tưởng của Jasmin:

var cols = r.GetSchemaTable().Rows.Cast<DataRow>().Select
    (row => row["ColumnName"] as string).ToList(); 

if (cols.Contains("the column name"))
{

}

1
Chỉ khi bạn quấn thử / bắt xung quanh nó
Donald.Record

Bạn có thể đơn giản hóa ý tưởng này với: reader.GetSchemaTable (). Columns.Contains ("myFiled")
Lev Z

sử dụng GetSchemaTable () là quá mức (phân bổ khôn ngoan) cho việc chỉ tìm tên cột. Kiểm tra nguồn github.com/microsoft/referencesource/blob/ từ
Chad Grant

12

cái này hiệu quả với tôi

bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");

sử dụng GetSchemaTable () là quá mức (phân bổ khôn ngoan) cho việc chỉ tìm tên cột. Và nó không được triển khai trong tất cả các phiên bản lõi dotnet. Kiểm tra nguồn github.com/microsoft/referencesource/blob/ từ
Chad Grant


8

Nếu bạn đọc câu hỏi, Michael đã hỏi về DataReader, không phải folks DataRecord. Nhận đồ vật của bạn ngay.

Sử dụng một r.GetSchemaTable().Columns.Contains(field) trên DataRecord không hoạt động, nhưng nó trả về các cột BS (xem ảnh chụp màn hình bên dưới.)

Để xem liệu cột dữ liệu có tồn tại VÀ chứa dữ liệu trong DataReader hay không, hãy sử dụng các tiện ích mở rộng sau:

public static class DataReaderExtensions
{
    /// <summary>
    /// Checks if a column's value is DBNull
    /// </summary>
    /// <param name="dataReader">The data reader</param>
    /// <param name="columnName">The column name</param>
    /// <returns>A bool indicating if the column's value is DBNull</returns>
    public static bool IsDBNull(this IDataReader dataReader, string columnName)
    {
        return dataReader[columnName] == DBNull.Value;
    }

    /// <summary>
    /// Checks if a column exists in a data reader
    /// </summary>
    /// <param name="dataReader">The data reader</param>
    /// <param name="columnName">The column name</param>
    /// <returns>A bool indicating the column exists</returns>
    public static bool ContainsColumn(this IDataReader dataReader, string columnName)
    {
        /// See: http://stackoverflow.com/questions/373230/check-for-column-name-in-a-sqldatareader-object/7248381#7248381
        try
        {
            return dataReader.GetOrdinal(columnName) >= 0;
        }
        catch (IndexOutOfRangeException)
        {
            return false;
        }
    }
}

Sử dụng:

    public static bool CanCreate(SqlDataReader dataReader)
    {
        return dataReader.ContainsColumn("RoleTemplateId") 
            && !dataReader.IsDBNull("RoleTemplateId");
    }

Gọi r.GetSchemaTable().Columnsvào DataReader trả về các cột BS:

Gọi GetSchemeTable trong DataReader


xem bình luận dưới câu trả lời của Matts
nawfal

Ý anh là gì bởi DataRecord làm việc , nhưng nó sẽ trả về cột BS ? Bạn có nghĩa là nó chạy (và cho kết quả sai)?
nawfal

2
"Nhận đồ vật của bạn ngay." - nhưng IDataReaderthực hiện IDataRecord. Chúng là các giao diện khác nhau của cùng một đối tượng - giống như ICollection<T>IEnumerable<T>là các giao diện khác nhau List<T>. IDataReadercho phép tiến tới bản ghi tiếp theo, trong khi IDataRecordcho phép đọc từ bản ghi hiện tại. Các phương pháp đang được sử dụng trong câu trả lời này đều đến từ IDataRecordgiao diện. Xem stackoverflow.com/a/1357743/221708 để được giải thích lý do tại sao khai báo tham số IDataRecordlà thích hợp hơn.
Daniel Schilling

Upvote cho thấy tại sao r.GetSchemaTable().Columnsmột câu trả lời hoàn toàn sai cho câu hỏi này.
Daniel Schilling

GetName () được kế thừa từ giao diện IDataRecord thành IDataReader. Nhắm mục tiêu giao diện cơ sở là mã chính xác.
Chad Grant

7

Tôi đã viết cho người dùng Visual Basic:

Protected Function HasColumnAndValue(ByRef reader As IDataReader, ByVal columnName As String) As Boolean
    For i As Integer = 0 To reader.FieldCount - 1
        If reader.GetName(i).Equals(columnName) Then
            Return Not IsDBNull(reader(columnName))
        End If
    Next

    Return False
End Function

Tôi nghĩ rằng điều này là mạnh mẽ hơn và việc sử dụng là:

If HasColumnAndValue(reader, "ID_USER") Then
    Me.UserID = reader.GetDecimal(reader.GetOrdinal("ID_USER")).ToString()
End If

4

Đây là một phiên bản linq lót của câu trả lời được chấp nhận:

Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")

trường hợp so sánh nhạy cảm ... tại sao?
Chad Grant

4

Đây là giải pháp từ Jasmine trong một dòng ... (thêm một lần nữa, đơn giản!):

reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;

sử dụng GetSchemaTable () là quá mức (phân bổ khôn ngoan) cho việc chỉ tìm tên cột. Kiểm tra nguồn github.com/microsoft/referencesource/blob/ từ
Chad Grant

@ChadGrant Có thể. Tôi đoán người ta phải chọn một cách khôn ngoan tùy thuộc vào bối cảnh và tần suất cần thiết để sử dụng điều này ...
spaark

3
Hashtable ht = new Hashtable();
    Hashtable CreateColumnHash(SqlDataReader dr)
    {
        ht = new Hashtable();
        for (int i = 0; i < dr.FieldCount; i++)
        {
            ht.Add(dr.GetName(i), dr.GetName(i));
        }
        return ht;
    }

    bool ValidateColumn(string ColumnName)
    {
        return ht.Contains(ColumnName);
    }

3

TLDR:

Rất nhiều câu trả lời với tuyên bố về hiệu suất và thực hành xấu, vì vậy tôi làm rõ điều đó ở đây.

Tuyến ngoại lệ nhanh hơn cho số cột được trả về cao hơn, tuyến vòng lặp nhanh hơn cho số cột thấp hơn và điểm giao nhau là khoảng 11 cột. Cuộn xuống phía dưới để xem biểu đồ và mã kiểm tra.

Câu trả lời đầy đủ:

Mã cho một số câu trả lời hàng đầu hoạt động, nhưng có một cuộc tranh luận cơ bản ở đây cho câu trả lời "tốt hơn" dựa trên sự chấp nhận xử lý ngoại lệ trong logic và hiệu suất liên quan.

Để xóa điều đó, tôi không tin rằng có nhiều hướng dẫn liên quan đến các ngoại lệ CATCHING. Microsoft có một số hướng dẫn liên quan đến các trường hợp ngoại lệ. Ở đó họ làm nhà nước:

KHÔNG sử dụng ngoại lệ cho luồng điều khiển thông thường, nếu có thể.

Lưu ý đầu tiên là sự khoan hồng của "nếu có thể". Quan trọng hơn, mô tả cho bối cảnh này:

framework designers should design APIs so users can write code that does not throw exceptions

Điều đó có nghĩa là nếu bạn đang viết một API có thể bị người khác sử dụng, hãy cho họ khả năng điều hướng một ngoại lệ mà không cần thử / bắt. Ví dụ: cung cấp một TryPude với phương pháp Parse ném ngoại lệ của bạn. Không nơi nào nói điều này mặc dù bạn không nên bắt một ngoại lệ.

Hơn nữa, như một người dùng khác chỉ ra, các sản phẩm khai thác luôn cho phép lọc theo loại và gần đây cho phép lọc thêm thông qua mệnh đề khi . Điều này có vẻ như lãng phí các tính năng ngôn ngữ nếu chúng ta không nên sử dụng chúng.

Có thể nói rằng có MỘT SỐ chi phí cho một ngoại lệ bị ném và chi phí đó CÓ THỂ ảnh hưởng đến hiệu suất trong một vòng lặp nặng. Tuy nhiên, cũng có thể nói rằng chi phí của một ngoại lệ sẽ không đáng kể trong một "ứng dụng được kết nối". Chi phí thực tế đã được điều tra hơn một thập kỷ trước: https://stackoverflow.com/a/891230/852208 Nói cách khác, chi phí kết nối và truy vấn của cơ sở dữ liệu có thể thấp hơn ngoại lệ bị ném.

Ngoài ra, tôi muốn xác định phương pháp nào thực sự nhanh hơn. Như dự kiến ​​không có câu trả lời cụ thể.

Bất kỳ mã nào lặp trên các cột sẽ trở nên chậm hơn khi số lượng cột tồn tại. Cũng có thể nói rằng bất kỳ mã nào dựa trên các ngoại lệ sẽ chậm tùy thuộc vào tốc độ không tìm thấy truy vấn.

Nhận câu trả lời của cả Chad Grant và Matt Hamilton, tôi đã chạy cả hai phương pháp với tối đa 20 cột và tỷ lệ lỗi lên tới 50% (OP cho biết anh ta đang sử dụng hai bài kiểm tra này giữa các procs khác nhau, vì vậy tôi giả sử chỉ có hai) .

Dưới đây là kết quả, được vẽ với LinqPad: Kết quả - Dòng 1 là Vòng lặp, 2 là Ngoại lệ

Các đường ngoằn ngoèo ở đây là tỷ lệ lỗi (không tìm thấy cột) trong mỗi số cột.

Trong các tập kết quả hẹp hơn, vòng lặp là một lựa chọn tốt. Tuy nhiên, phương thức GetOrdinal / Exception gần như không nhạy cảm với số lượng cột và bắt đầu vượt trội so với phương thức lặp ngay khoảng 11 cột.

Điều đó nói rằng tôi thực sự không có hiệu suất ưu tiên vì 11 cột nghe có vẻ hợp lý vì số cột trung bình được trả về trên toàn bộ ứng dụng. Trong cả hai trường hợp, chúng ta đang nói về phân số của một phần nghìn giây ở đây.

Tuy nhiên, từ khía cạnh đơn giản mã và hỗ trợ bí danh, có lẽ tôi sẽ đi theo tuyến đường GetOrdinal.

Đây là bài kiểm tra ở dạng linqpad. Vui lòng đăng lại bằng phương pháp của riêng bạn:

void Main()
{
    var loopResults = new List<Results>();
    var exceptionResults = new List<Results>();
    var totalRuns = 10000;
    for (var colCount = 1; colCount < 20; colCount++)
    {
        using (var conn = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDb;Initial Catalog=master;Integrated Security=True;"))
        {
            conn.Open();

            //create a dummy table where we can control the total columns
            var columns = String.Join(",",
                (new int[colCount]).Select((item, i) => $"'{i}' as col{i}")
            );
            var sql = $"select {columns} into #dummyTable";
            var cmd = new SqlCommand(sql,conn);
            cmd.ExecuteNonQuery();

            var cmd2 = new SqlCommand("select * from #dummyTable", conn);

            var reader = cmd2.ExecuteReader();
            reader.Read();

            Func<Func<IDataRecord, String, Boolean>, List<Results>> test = funcToTest =>
            {
                var results = new List<Results>();
                Random r = new Random();
                for (var faultRate = 0.1; faultRate <= 0.5; faultRate += 0.1)
                {
                    Stopwatch stopwatch = new Stopwatch();
                    stopwatch.Start();
                    var faultCount=0;
                    for (var testRun = 0; testRun < totalRuns; testRun++)
                    {
                        if (r.NextDouble() <= faultRate)
                        {
                            faultCount++;
                            if(funcToTest(reader, "colDNE"))
                                throw new ApplicationException("Should have thrown false");
                        }
                        else
                        {
                            for (var col = 0; col < colCount; col++)
                            {
                                if(!funcToTest(reader, $"col{col}"))
                                    throw new ApplicationException("Should have thrown true");
                            }
                        }
                    }
                    stopwatch.Stop();
                    results.Add(new UserQuery.Results{
                        ColumnCount = colCount, 
                        TargetNotFoundRate = faultRate,
                        NotFoundRate = faultCount * 1.0f / totalRuns, 
                        TotalTime=stopwatch.Elapsed
                    });
                }
                return results;
            };
            loopResults.AddRange(test(HasColumnLoop));

            exceptionResults.AddRange(test(HasColumnException));

        }

    }
    "Loop".Dump();
    loopResults.Dump();

    "Exception".Dump();
    exceptionResults.Dump();

    var combinedResults = loopResults.Join(exceptionResults,l => l.ResultKey, e=> e.ResultKey, (l, e) => new{ResultKey = l.ResultKey, LoopResult=l.TotalTime, ExceptionResult=e.TotalTime});
    combinedResults.Dump();
    combinedResults
        .Chart(r => r.ResultKey, r => r.LoopResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
        .AddYSeries(r => r.ExceptionResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
        .Dump();
}
public static bool HasColumnLoop(IDataRecord dr, string columnName)
{
    for (int i = 0; i < dr.FieldCount; i++)
    {
        if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
            return true;
    }
    return false;
}

public static bool HasColumnException(IDataRecord r, string columnName)
{
    try
    {
        return r.GetOrdinal(columnName) >= 0;
    }
    catch (IndexOutOfRangeException)
    {
        return false;
    }
}

public class Results
{
    public double NotFoundRate { get; set; }
    public double TargetNotFoundRate { get; set; }
    public int ColumnCount { get; set; }
    public double ResultKey {get => ColumnCount + TargetNotFoundRate;}
    public TimeSpan TotalTime { get; set; }


}

1
Bạn rõ ràng có một số nỗi ám ảnh kỳ lạ với các ngoại lệ. Một cách tiếp cận tốt hơn sẽ chỉ lưu trữ vị trí cột trong một tra cứu tĩnh cho hiệu suất và sử dụng tra cứu số nguyên
Chad Grant

một vấn đề khác với việc sử dụng các ngoại lệ làm luồng điều khiển là chúng hiển thị trong trình lược tả dưới dạng # ngoại lệ được ném khi trong mã được đề xuất của bạn, chúng là cố ý ... không phải là ngoại lệ. Không đề cập đến việc thiết lập trình gỡ lỗi của bạn để phá vỡ các ngoại lệ được ném. Về cơ bản báo cáo lỗi không phải là lỗi. Bạn không nên làm điều này.
Chad Grant

1
Ngoài ra còn có bộ đếm cho cuối cùng / giây và bộ lọc / giây. Có phải những điều đó cũng xấu? Tôi sẽ gọi nó là một cảnh báo có thể - người thực sự đầu tiên bạn đã cung cấp. Số lượt truy cập chỉ là thông tin. Chúng không có nghĩa gì cả trừ khi chúng tương ứng với một vấn đề về hiệu suất - và trong trường hợp này tôi đã chỉ ra điểm mà các ngoại lệ có hiệu suất TỐT HƠN. Tôi cũng đã chỉ ra rằng khung và thư viện đã có rất nhiều ngoại lệ. Tôi đã có một ví dụ về phòng thu hình ảnh ném 60 ex / s ngay bây giờ. Ngoại lệ không phải là lỗi trừ khi chúng chưa được phát hiện.
b_levitt

Phân tích tuyệt vời. Tôi đã sử dụng kết quả của nó trong câu trả lời mới của tôi.
yazanpro

1

Mã này sửa các vấn đề mà Levitikon gặp phải với mã của họ: (được điều chỉnh từ: [1]: http://msdn.microsoft.com/en-us/l Library / system.data.datatablereader.getschemitable.aspx )

public List<string> GetColumnNames(SqlDataReader r)
{
    List<string> ColumnNames = new List<string>();
    DataTable schemaTable = r.GetSchemaTable();
    DataRow row = schemaTable.Rows[0];
    foreach (DataColumn col in schemaTable.Columns)
    {
        if (col.ColumnName == "ColumnName") 
        { 
            ColumnNames.Add(row[col.Ordinal].ToString()); 
            break; 
        }
    }
    return ColumnNames;
}

Lý do nhận được tất cả các tên cột vô dụng đó và không phải tên của cột từ bảng của bạn ... Là bởi vì bạn đang lấy tên của cột lược đồ (tức là tên cột cho bảng Schema)

LƯU Ý: điều này dường như chỉ trả về tên của cột đầu tiên ...

EDIT: mã đã sửa trả về tên của tất cả các cột, nhưng bạn không thể sử dụng SqlDataReader để làm điều đó

public List<string> ExecuteColumnNamesReader(string command, List<SqlParameter> Params)
{
    List<string> ColumnNames = new List<string>();
    SqlDataAdapter da = new SqlDataAdapter();
    string connection = ""; // your sql connection string
    SqlCommand sqlComm = new SqlCommand(command, connection);
    foreach (SqlParameter p in Params) { sqlComm.Parameters.Add(p); }
    da.SelectCommand = sqlComm;
    DataTable dt = new DataTable();
    da.Fill(dt);
    DataRow row = dt.Rows[0];
    for (int ordinal = 0; ordinal < dt.Columns.Count; ordinal++)
    {
        string column_name = dt.Columns[ordinal].ColumnName;
        ColumnNames.Add(column_name);
    }
    return ColumnNames; // you can then call .Contains("name") on the returned collection
}

Hoặc trong một dòng return r.GetSchemaTable().Rows.Cast<DataRow>().Select(x => (string)x["ColumnName"]).ToList();:)
nawfal 12/12/13

sử dụng GetSchemaTable () là quá mức (phân bổ khôn ngoan) cho việc chỉ tìm tên cột. Kiểm tra nguồn github.com/microsoft/referencesource/blob/ từ
Chad Grant

1

Để giữ cho mã của bạn mạnh mẽ và sạch sẽ, hãy sử dụng một chức năng mở rộng, như thế này:

    Public Module Extensions

        <Extension()>
        Public Function HasColumn(r As SqlDataReader, columnName As String) As Boolean

            Return If(String.IsNullOrEmpty(columnName) OrElse r.FieldCount = 0, False, Enumerable.Range(0, r.FieldCount).Select(Function(i) r.GetName(i)).Contains(columnName, StringComparer.OrdinalIgnoreCase))

        End Function

    End Module

0

Tôi cũng không GetSchemaTableđi làm, cho đến khi tôi tìm thấy cách này .

Về cơ bản tôi làm điều này:

Dim myView As DataView = dr.GetSchemaTable().DefaultView
myView.RowFilter = "ColumnName = 'ColumnToBeChecked'"

If myView.Count > 0 AndAlso dr.GetOrdinal("ColumnToBeChecked") <> -1 Then
  obj.ColumnToBeChecked = ColumnFromDb(dr, "ColumnToBeChecked")
End If

0
public static bool DataViewColumnExists(DataView dv, string columnName)
{
    return DataTableColumnExists(dv.Table, columnName);
}

public static bool DataTableColumnExists(DataTable dt, string columnName)
{
    string DebugTrace = "Utils::DataTableColumnExists(" + dt.ToString() + ")";
    try
    {
        return dt.Columns.Contains(columnName);
    }
    catch (Exception ex)
    {
        throw new MyExceptionHandler(ex, DebugTrace);
    }
}

Columns.Contains là trường hợp btw không nhạy cảm.


Chứa () không ném ngoại lệ, mã này là vô nghĩa. Bạn sẽ chỉ bắt được ngoại lệ con trỏ null.
Chad Grant

0

Trong tình huống cụ thể của bạn (tất cả các quy trình có cùng một cột trừ 1 có thêm 1 cột), sẽ tốt hơn và nhanh hơn để kiểm tra đầu đọc. Thuộc tính FieldCount để phân biệt giữa chúng.

const int NormalColCount=.....
if(reader.FieldCount > NormalColCount)
{
// Do something special
}

Tôi biết đó là một bài viết cũ nhưng tôi quyết định trả lời để giúp đỡ người khác trong tình huống tương tự. bạn cũng có thể (vì lý do hiệu suất) trộn giải pháp này với giải pháp lặp lại giải pháp.


Vui lòng đặt tên cho giải pháp mà bạn đang đề cập đến. Nên trộn hai giải pháp nào?
Pablo Jomer

0

Lớp truy cập dữ liệu của tôi cần phải tương thích ngược, vì vậy tôi có thể đang cố truy cập vào một cột trong một bản phát hành nơi nó chưa tồn tại trong cơ sở dữ liệu. Chúng tôi có một số bộ dữ liệu khá lớn được trả về vì vậy tôi không phải là một fan hâm mộ lớn của phương thức tiện ích mở rộng phải lặp lại bộ sưu tập cột DataReader cho mỗi thuộc tính.

Tôi có một lớp tiện ích tạo một danh sách các cột riêng tư và sau đó có một phương thức chung cố gắng giải quyết một giá trị dựa trên tên cột và loại tham số đầu ra.

private List<string> _lstString;

public void GetValueByParameter<T>(IDataReader dr, string parameterName, out T returnValue)
{
    returnValue = default(T);

    if (!_lstString.Contains(parameterName))
    {
        Logger.Instance.LogVerbose(this, "missing parameter: " + parameterName);
        return;
    }

    try
    {
        if (dr[parameterName] != null && [parameterName] != DBNull.Value)
            returnValue = (T)dr[parameterName];
    }
    catch (Exception ex)
    {
        Logger.Instance.LogException(this, ex);
    }
}

/// <summary>
/// Reset the global list of columns to reflect the fields in the IDataReader
/// </summary>
/// <param name="dr">The IDataReader being acted upon</param>
/// <param name="NextResult">Advances IDataReader to next result</param>
public void ResetSchemaTable(IDataReader dr, bool nextResult)
{
    if (nextResult)
        dr.NextResult();

    _lstString = new List<string>();

    using (DataTable dataTableSchema = dr.GetSchemaTable())
    {
        if (dataTableSchema != null)
        {
            foreach (DataRow row in dataTableSchema.Rows)
            {
                _lstString.Add(row[dataTableSchema.Columns["ColumnName"]].ToString());
            }
        }
    }
}

Sau đó tôi chỉ có thể gọi mã của mình như vậy

using (var dr = ExecuteReader(databaseCommand))
{
    int? outInt;
    string outString;

    Utility.ResetSchemaTable(dr, false);        
    while (dr.Read())
    {
        Utility.GetValueByParameter(dr, "SomeColumn", out outInt);
        if (outInt.HasValue) myIntField = outInt.Value;
    }

    Utility.ResetSchemaTable(dr, true);
    while (dr.Read())
    {
        Utility.GetValueByParameter(dr, "AnotherColumn", out outString);
        if (!string.IsNullOrEmpty(outString)) myIntField = outString;
    }
}

0

Chìa khóa của toàn bộ vấn đề là ở đây :

if (-1 == index) {
    throw ADP.IndexOutOfRange(fieldName);
}

Nếu ba dòng được tham chiếu (hiện tại là dòng 72, 73 và 74) bị loại ra, thì bạn có thể dễ dàng kiểm tra -1 để xác định xem cột có tồn tại không.

Cách duy nhất để giải quyết vấn đề này trong khi đảm bảo hiệu suất riêng là sử dụng Reflection triển khai dựa trên, như sau:

Công dụng:

using System;
using System.Data;
using System.Reflection;
using System.Data.SqlClient;
using System.Linq;
using System.Web.Compilation; // I'm not sure what the .NET Core equivalent to BuildManager.cs

Phương pháp mở rộng dựa trên Reflection:

/// Gets the column ordinal, given the name of the column.
/// </summary>
/// <param name="reader"></param>
/// <param name="name">The name of the column.</param>
/// <returns> The zero-based column ordinal. -1 if the column does not exist.</returns>
public static int GetOrdinalSoft(this SqlDataReader reader, string name)
{
    try
    {
        // Note that "Statistics" will not be accounted for in this implemenation
        // If you have SqlConnection.StatisticsEnabled set to true (the default is false), you probably don't want to use this method
        // All of the following logic is inspired by the actual implementation of the framework:
        // https://referencesource.microsoft.com/#System.Data/fx/src/data/System/Data/SqlClient/SqlDataReader.cs,d66096b6f57cac74
        if (name == null)
            throw new ArgumentNullException("fieldName");

        Type sqlDataReaderType = typeof(SqlDataReader);
        object fieldNameLookup = sqlDataReaderType.GetField("_fieldNameLookup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(reader);
        Type fieldNameLookupType;
        if (fieldNameLookup == null)
        {
            MethodInfo checkMetaDataIsReady = sqlDataReaderType.GetRuntimeMethods().First(x => x.Name == "CheckMetaDataIsReady" && x.GetParameters().Length == 0);
            checkMetaDataIsReady.Invoke(reader, null);
            fieldNameLookupType = BuildManager.GetType("System.Data.ProviderBase.FieldNameLookup", true, false);
            ConstructorInfo ctor = fieldNameLookupType.GetConstructor(new[] { typeof(SqlDataReader), typeof(int) });
            fieldNameLookup = ctor.Invoke(new object[] { reader, sqlDataReaderType.GetField("_defaultLCID", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(reader) });
        }
        else
            fieldNameLookupType = fieldNameLookup.GetType();

        MethodInfo indexOf = fieldNameLookupType.GetMethod("IndexOf", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string) }, null);

        return (int)indexOf.Invoke(fieldNameLookup, new object[] { name });
    }
    catch
    {
        // .NET Implemenation might have changed, revert back to the classic solution.
        if (reader.FieldCount > 11) // Performance observation by b_levitt
        {
            try
            {
                return reader.GetOrdinal(name);
            }
            catch
            {
                return -1;
            }
        }
        else
        {
            var exists = Enumerable.Range(0, reader.FieldCount).Any(i => string.Equals(reader.GetName(i), name, StringComparison.OrdinalIgnoreCase));
            if (exists)
                return reader.GetOrdinal(name);
            else
                return -1;
        }
    }
}


-1

Làm thế nào về

if (dr.GetSchemaTable().Columns.Contains("accounttype"))
   do something
else
   do something

Nó có thể sẽ không hiệu quả trong một vòng lặp


Xem câu trả lời của Levitikon để xem loại vật dr.GetSchemaTable().Columnschứa - đó không phải là thứ bạn đang tìm kiếm.
Daniel Schilling

-1

Mặc dù không có phương thức phơi bày công khai, một phương thức tồn tại trong lớp bên System.Data.ProviderBase.FieldNameLookuptrongSqlDataReader dựa vào.

Để truy cập nó và có được hiệu năng riêng, bạn phải sử dụng ILGenerator để tạo một phương thức khi chạy. Các mã sau đây sẽ cung cấp cho bạn quyền truy cập trực tiếp int IndexOf(string fieldName)vào System.Data.ProviderBase.FieldNameLookuplớp cũng như thực hiện việc giữ sách SqlDataReader.GetOrdinal()để không có tác dụng phụ. Mã được tạo phản ánh hiện tại SqlDataReader.GetOrdinal()ngoại trừ việc nó gọi FieldNameLookup.IndexOf()thay vì FieldNameLookup.GetOrdinal(). Các GetOrdinal()phương pháp các cuộc gọi đến các IndexOf()chức năng và ném một ngoại lệ nếu -1được trả lại, vì vậy chúng tôi bỏ qua hành vi đó.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using System.Reflection.Emit;

public static class SqlDataReaderExtensions {

   private delegate int IndexOfDelegate(SqlDataReader reader, string name);
   private static IndexOfDelegate IndexOf;

   public static int GetColumnIndex(this SqlDataReader reader, string name) {
      return name == null ? -1 : IndexOf(reader, name);
   }

   public static bool ContainsColumn(this SqlDataReader reader, string name) {
      return name != null && IndexOf(reader, name) >= 0;
   }

   static SqlDataReaderExtensions() {
      Type typeSqlDataReader = typeof(SqlDataReader);
      Type typeSqlStatistics = typeSqlDataReader.Assembly.GetType("System.Data.SqlClient.SqlStatistics", true);
      Type typeFieldNameLookup = typeSqlDataReader.Assembly.GetType("System.Data.ProviderBase.FieldNameLookup", true);

      BindingFlags staticflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Static;
      BindingFlags instflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance;

      DynamicMethod dynmethod = new DynamicMethod("SqlDataReader_IndexOf", typeof(int), new Type[2]{ typeSqlDataReader, typeof(string) }, true);
      ILGenerator gen = dynmethod.GetILGenerator();
      gen.DeclareLocal(typeSqlStatistics);
      gen.DeclareLocal(typeof(int));

      // SqlStatistics statistics = (SqlStatistics) null;
      gen.Emit(OpCodes.Ldnull);
      gen.Emit(OpCodes.Stloc_0);
      // try {
      gen.BeginExceptionBlock();
      //    statistics = SqlStatistics.StartTimer(this.Statistics);
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Call, typeSqlDataReader.GetProperty("Statistics", instflags | BindingFlags.GetProperty, null, typeSqlStatistics, Type.EmptyTypes, null).GetMethod);
      gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StartTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null));
      gen.Emit(OpCodes.Stloc_0); //statistics
      //    if(this._fieldNameLookup == null) {
      Label branchTarget = gen.DefineLabel();
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField));
      gen.Emit(OpCodes.Brtrue_S, branchTarget);
      //       this.CheckMetaDataIsReady();
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Call, typeSqlDataReader.GetMethod("CheckMetaDataIsReady", instflags | BindingFlags.InvokeMethod, null, Type.EmptyTypes, null));
      //       this._fieldNameLookup = new FieldNameLookup((IDataRecord)this, this._defaultLCID);
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_defaultLCID", instflags | BindingFlags.GetField));
      gen.Emit(OpCodes.Newobj, typeFieldNameLookup.GetConstructor(instflags, null, new Type[] { typeof(IDataReader), typeof(int) }, null));
      gen.Emit(OpCodes.Stfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.SetField));
      //    }
      gen.MarkLabel(branchTarget);
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField));
      gen.Emit(OpCodes.Ldarg_1); //name
      gen.Emit(OpCodes.Call, typeFieldNameLookup.GetMethod("IndexOf", instflags | BindingFlags.InvokeMethod, null, new Type[] { typeof(string) }, null));
      gen.Emit(OpCodes.Stloc_1); //int output
      Label leaveProtectedRegion = gen.DefineLabel();
      gen.Emit(OpCodes.Leave_S, leaveProtectedRegion);
      // } finally {
      gen.BeginFaultBlock();
      //    SqlStatistics.StopTimer(statistics);
      gen.Emit(OpCodes.Ldloc_0); //statistics
      gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StopTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null));
      // }
      gen.EndExceptionBlock();
      gen.MarkLabel(leaveProtectedRegion);
      gen.Emit(OpCodes.Ldloc_1);
      gen.Emit(OpCodes.Ret);

      IndexOf = (IndexOfDelegate)dynmethod.CreateDelegate(typeof(IndexOfDelegate));
   }

}

1
Mã nội bộ thực hiện gần như chính xác những gì câu trả lời của tôi đang làm, mà không cần sự phản ánh / ủy nhiệm kỳ lạ này. Đó là lưu trữ bộ đệm tìm kiếm trên mỗi đối tượng sẽ không có ích vì trong thế giới thực, bạn muốn lưu trữ bộ đệm trong lần truy vấn đầu tiên được chạy và sử dụng bộ đệm đó trong suốt vòng đời của ứng dụng, không tạo bộ đệm mới cho mỗi truy vấn.
Chad Grant

-1

công việc này với tôi

public static class DataRecordExtensions
{
        public static bool HasColumn(IDataReader dataReader, string columnName)
        {
            dataReader.GetSchemaTable().DefaultView.RowFilter = $"ColumnName= '{columnName}'";
            return (dataReader.GetSchemaTable().DefaultView.Count > 0);
        }
}

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.