簡體   English   中英

檢查 SqlDataReader 對象中的列名

[英]Check for column name in a SqlDataReader object

如何檢查SqlDataReader對象中是否存在列? 在我的數據訪問層中,我創建了一個為多個存儲過程調用構建相同對象的方法。 其中一個存儲過程有一個其他存儲過程不使用的附加列。 我想修改該方法以適應每個場景。

我的應用程序是用 C# 編寫的。

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

像在其他一些答案中一樣使用Exception進行控制邏輯被認為是不好的做法,並且會產生性能成本。 它還將誤報發送到拋出異常的分析器,並且上帝幫助任何人設置他們的調試器來中斷拋出的異常。

GetSchemaTable() 也是許多答案中的另一個建議。 這不是檢查字段是否存在的首選方法,因為它並未在所有版本中實現(它是抽象的,在某些 dotnetcore 版本中會拋出 NotSupportedException)。 GetSchemaTable 在性能方面也是過度的,因為如果您查看源代碼,它是一個非常繁重的功能。

如果您經常使用這些字段,循環遍歷這些字段可能會對性能造成很小的影響,並且您可能需要考慮緩存結果。

正確的代碼是:

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

在一行中,在 DataReader 檢索后使用它:

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

然后,

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

編輯

不需要加載模式的更高效的單行:

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

我認為最好的辦法是預先在 DataReader 上調用GetOrdinal("columnName") ,並在列不存在的情況下捕獲 IndexOutOfRangeException。

其實我們來做一個擴展方法:

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

編輯

好的,這篇文章最近開始獲得一些反對票,我無法刪除它,因為它是公認的答案,所以我將更新它並(我希望)嘗試證明異常處理的使用是合理的控制流。

正如Chad Grant發布的那樣,實現此目的的另一種方法是遍歷 DataReader 中的每個字段,並對您要查找的字段名稱進行不區分大小寫的比較。 這將非常有效,並且說實話可能會比我上面的方法表現得更好。 當然,我永遠不會在性能有問題的循環中使用上述方法。

我可以想到一種情況,在這種情況下,try/GetOrdinal/catch 方法將在循環不起作用的情況下起作用。 然而,現在這是一個完全假設的情況,所以這是一個非常脆弱的理由。 無論如何,請耐心等待,看看您的想法。

想象一個數據庫,它允許您“別名”表中的列。 想象一下,我可以定義一個包含名為“EmployeeName”的列的表,但也給它一個別名“EmpName”,並且對任一名稱進行選擇都會返回該列中的數據。 陪我到此為止?

現在假設該數據庫有一個 ADO.NET 提供程序,並且他們已經為它編寫了一個 IDataReader 實現,它將列別名考慮在內。

現在, dr.GetName(i) (在 Chad 的回答中使用)只能返回一個字符串,因此它必須只返回列中的一個“別名”。 但是, GetOrdinal("EmpName")可以使用此提供程序字段的內部實現來檢查您要查找的名稱的每個列的別名。

在這種假設的“別名列”情況下,try/GetOrdinal/catch 方法將是確保您檢查結果集中列名稱的每個變體的唯一方法。

脆弱? 當然。 但值得深思。 老實說,我更喜歡 IDataRecord 上的“官方”HasColumn 方法。

以下是 Jasmin 想法的工作示例:

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

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

}

這對我有用:

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

以下很簡單,對我有用:

 bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);

我是為 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

我認為這個功能更強大,用法是:

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

如果您閱讀了這個問題,Michael 會詢問 DataReader,而不是 DataRecord 人員。 讓你的對象正確。

在 DataRecord 上使用r.GetSchemaTable().Columns.Contains(field)確實有效,但它返回 BS 列(請參見下面的屏幕截圖。)

要查看數據列是否存在並包含 DataReader 中的數據,請使用以下擴展:

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

用法:

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

在 DataReader 上調用r.GetSchemaTable().Columns返回 BS 列:

在 DataReader 中調用 GetSchemeTable

域名注冊地址:

有很多關於性能和不良做法的說法的答案,所以我在這里澄清一下。

返回的列數越多,異常路由越快,列數越少,循環路由越快,交叉點大約為 11 列。 滾動到底部以查看圖表和測試代碼。

完整答案:

一些頂級答案的代碼有效,但基於對邏輯中異常處理及其相關性能的接受程度,這里存在關於“更好”答案的潛在爭論。

為了清除這一點,我認為沒有太多關於捕獲異常的指導。 Microsoft 確實有一些關於拋出異常的指導 他們確實在那里聲明:

如果可能,不要在正常的控制流中使用異常。

第一個注釋是“如果可能”的寬大處理。 更重要的是,描述給出了這個上下文:

框架設計者應該設計 API 以便用戶可以編寫不會拋出異常的代碼

這意味着,如果您正在編寫可能被其他人使用的 API,請讓他們能夠在沒有 try/catch 的情況下導航異常。 例如,為拋出異常的 Parse 方法提供 TryParse。 盡管這並沒有說明您不應該捕獲異常。

此外,正如另一位用戶指出的那樣,捕獲一直允許按類型過濾,並且最近允許通過when 子句進一步過濾。 如果我們不應該使用它們,這似乎是對語言功能的浪費。

可以說拋出異常是代價的,這個代價可能會影響重循環中的性能。 但是,也可以說在“連接的應用程序”中,異常的成本可以忽略不計。 十多年前調查了實際成本: C# 中的異常有多昂貴?

換句話說,連接和查詢數據庫的成本可能比拋出異常的成本相形見絀。

除此之外,我想確定哪種方法真正更快。 正如預期的那樣,沒有具體的答案。

隨着列數的增加,任何在列上循環的代碼都會變慢。 也可以說,任何依賴於異常的代碼都會根據查詢失敗的速度變慢。

根據Chad GrantMatt Hamilton的答案,我運行了兩種方法,最多 20 列,錯誤率高達 50%(OP 表示他在不同的存儲過程之間使用這兩個測試,所以我假設只有兩個)。

以下是使用LINQPad繪制的結果:

結果 - 系列 1 是循環,2 是異常

此處的鋸齒形是每個列計數內的故障率(未找到列)。

在較窄的結果集上,循環是一個不錯的選擇。 但是,GetOrdinal/Exception 方法對列數幾乎沒有那么敏感,並且在 11 列左右開始優於循環方法。

也就是說,我真的沒有偏好性能明智,因為 11 列聽起來合理,因為整個應用程序返回的平均列數。 在任何一種情況下,我們都在這里討論幾分之一毫秒。

但是,從代碼簡單性和別名支持方面來看,我可能會選擇 GetOrdinal 路線。

這是 LINQPad 形式的測試。 隨意使用您自己的方法重新發布:

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


}

這是已接受答案的單行 LINQ 版本:

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

這是 Jasmine 在一行中的解決方案......(還有一個,雖然很簡單!):

reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;
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);
}

為了保持您的代碼健壯和干凈,請使用單個擴展函數,如下所示:

    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

此代碼糾正了 Levitikon 對其代碼的問題:(改編自:[1]: http : //msdn.microsoft.com/en-us/library/system.data.datatablereader.getschematable.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;
}

獲取所有這些無用的列名而不是表中列名的原因是因為您正在獲取架構列的名稱(即架構表的列名)

注意:這似乎只返回第一列的名稱......

編輯:更正了返回所有列名稱的代碼,但您不能使用 SqlDataReader 來執行此操作

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
}

這些答案已經發布在這里。 只是Linq-ing有點:

bool b = reader.GetSchemaTable().Rows
                                .Cast<DataRow>()
                                .Select(x => (string)x["ColumnName"])
                                .Contains(colName, StringComparer.OrdinalIgnoreCase);
//or

bool b = Enumerable.Range(0, reader.FieldCount)
                   .Select(reader.GetName)
                   .Contains(colName, StringComparer.OrdinalIgnoreCase);

第二個更干凈,速度更快。 即使你在第一種方法中每次都沒有運行GetSchemaTable ,查找也會非常慢。

在您的特定情況下(所有過程都具有相同的列,除了一個額外的一列),檢查讀取器的FieldCount屬性以區分它們會更好更快。

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

您還可以(出於性能原因)將此解決方案與解決方案迭代解決方案混合使用。

我也沒有讓GetSchemaTable工作,直到我找到這種方式

基本上我這樣做:

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

我的數據訪問類需要向后兼容,因此我可能試圖訪問數據庫中尚不存在的版本中的列。 我們返回了一些相當大的數據集,所以我不喜歡必須為每個屬性迭代 DataReader 列集合的擴展方法。

我有一個實用程序類,它創建一個私有的列列表,然后有一個通用方法,它嘗試根據列名和輸出參數類型解析一個值。

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

然后我可以像這樣調用我的代碼

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;
    }
}
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不區分大小寫順便說一句。

整個問題的關鍵在這里

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

如果引用的三行(當前第 72、73 和 74 行)被取出,那么您可以輕松檢查-1以確定該列是否不存在。

在確保本機性能的同時解決此問題的唯一方法是使用基於Reflection的實現,如下所示:

用途:

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

基於反射的擴展方法:

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

如果您想要列列表並且不想獲得異常,您也可以在 DataReader 上調用GetSchemaTable() ...

用:

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

在循環中它可能不會那么有效。

雖然沒有公開暴露的方法,但在SqlDataReader依賴的內部類System.Data.ProviderBase.FieldNameLookup確實存在一個方法。

為了訪問它並獲得本機性能,您必須使用 ILGenerator 在運行時創建一個方法。 以下代碼將讓您直接訪問System.Data.ProviderBase.FieldNameLookup類中的int IndexOf(string fieldName)並執行SqlDataReader.GetOrdinal()所做的簿記,以便沒有副作用。 生成的代碼反映了現有的SqlDataReader.GetOrdinal()只是它調用FieldNameLookup.IndexOf()而不是FieldNameLookup.GetOrdinal() GetOrdinal()方法調用IndexOf()函數並在返回-1拋出異常,因此我們繞過該行為。

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

}

這對我有用:

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

用:

if(Enumerable.Range(0,reader.FieldCount).Select(reader.GetName).Contains("columName"))
{
     employee.EmployeeId= Utility.ConvertReaderToLong(reader["EmployeeId"]);
}

您可以從Can you get the column names from a SqlDataReader? .

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM