programing

SqlDataReader 개체에서 열 이름 확인

stoneblock 2023. 5. 19. 23:56

SqlDataReader 개체에서 열 이름 확인

다음에 열이 있는지 확인하려면 어떻게 해야 합니까?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일부 다른 답변과 같이 제어 논리에 대한 s는 잘못된 관행으로 간주되며 성능 비용이 듭니다.또한 던져진 # 예외의 프로파일러에게 잘못된 긍정을 보내고, 던져진 예외를 깨도록 디버거를 설정하는 모든 사람에게 신의 도움을 줍니다.

GetSchemaTable()은 많은 답변에서 또 다른 제안입니다.필드가 일부 버전에서 구현되지 않았기 때문에 필드의 존재를 확인하는 선호되는 방법은 아닙니다(이 필드는 추상적이며 지원되지 않습니다).dotnetcore 일부 버전에서는 예외입니다.또한 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 방법을 사용하여 결과 집합에서 열 이름의 모든 변동을 확인할 수 있습니다.

어설프다고요? 물론이죠.하지만 생각해 볼 가치가 있습니다.솔직히 나는 IData Record에서 "공식적인" HasColumn 방법을 훨씬 선호합니다.

다음은 Jasmin의 아이디어를 위한 작업 샘플입니다.

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

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

}

다음은 간단하고 효과적입니다.

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

이것은 나에게 도움이 됩니다.

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

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

질문을 읽으면 마이클이 데이터레코드 사람들이 아니라 데이터레코더에 대해 물었습니다.목표를 바로 잡으세요.

사용r.GetSchemaTable().Columns.Contains(field)데이터 레코드에서 작동하지만 BS 열을 반환합니다(아래 스크린샷 참조).

데이터 열이 존재하고 데이터 판독기에 데이터가 포함되어 있는지 확인하려면 다음 확장자를 사용합니다.

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

하기 르기r.GetSchemaTable().Columns데이터 판독기에서 BS 열을 반환합니다.

데이터 판독기에서 GetSchemeTable 호출

TLDR:

실적과 악습에 대한 주장이 담긴 답변들이 많아서 여기서 그것을 명확히 합니다.

예외 경로는 반환된 열의 수가 많을수록 빠르고, 루프 경로는 열의 수가 적으면 더 빠르며, 교차점은 열 11개 전후입니다.그래프와 테스트 코드를 보려면 맨 아래로 스크롤합니다.

전체 답변:

상위 답변 중 일부에 대한 코드는 작동하지만 논리에서 예외 처리의 수용과 관련 성능을 기반으로 한 "더 나은" 답변에 대한 근본적인 논쟁이 있습니다.

그것을 없애기 위해, 저는 예외를 잡는 에 대한 지침이 많지 않다고 생각합니다.Microsoft는 예외를 던지는 관련된 몇 가지 지침을 가지고 있습니다.거기서 그들은 다음과 같이 말합니다.

가능한 경우 컨트롤의 정상적인 흐름에 예외를 사용하지 마십시오.

첫 번째 메모는 "가능한 경우"의 관대함입니다.더 중요한 것은 설명에서 다음과 같은 맥락을 제공한다는 점입니다.

프레임워크 설계자는 사용자가 예외를 범하지 않는 코드를 작성할 수 있도록 API를 설계해야 합니다.

즉, 다른 사용자가 사용할 수 있는 API를 작성하는 경우 시도/캐치 없이 예외를 탐색할 수 있는 기능을 제공합니다.예를 들어 TryParse를 예외 스레싱 Parse 메서드와 함께 제공합니다.하지만 이것이 예외를 잡아서는 안 된다고 말하는 곳은 없습니다.

또한 다른 사용자가 지적했듯이 캐치는 항상 유형별 필터링을 허용했으며 최근에는 when 절을 통해 추가 필터링을 허용합니다.만약 우리가 언어 기능을 사용하지 않는다면, 이것은 언어 기능의 낭비처럼 보입니다.

던져진 예외에 대해 약간의 비용이 있으며, 그 비용이 무거운 루프의 성능에 영향을 미칠 수 있다고 말할 수 있습니다.그러나 "연결된 응용프로그램"에서 예외 비용은 무시할 수 있습니다.실제 비용은 10년 이상 전에 조사되었습니다.C#의 예외는 얼마나 비쌉니까?

즉, 데이터베이스의 연결 및 쿼리 비용이 예외 발생 비용보다 작을 수 있습니다.

이 모든 것을 제쳐두고, 저는 어떤 방법이 정말로 더 빠른지 결정하고 싶었습니다.예상대로 구체적인 답은 없습니다.

열 위에 루프하는 코드는 열 수가 증가함에 따라 속도가 느려집니다.또한 예외에 의존하는 코드는 쿼리를 찾지 못하는 속도에 따라 느려진다고 할 수 있습니다.

Chad Grant와 Matt Hamilton 모두의 답변을 받아 최대 20개의 열과 최대 50%의 오류율로 두 가지 방법을 모두 실행했습니다(OP는 그가 서로 다른 저장 프로시저 사이에서 이 두 가지 테스트를 사용하고 있음을 나타냈기 때문에 두 가지 정도로 가정했습니다).

다음은 LINQPad로 표시된 결과입니다.

결과 - 시리즈 1은 루프, 2는 예외

여기서 지그재그는 각 열 카운트 내의 고장률(열을 찾을 수 없음)입니다.

더 좁은 결과 세트에서는 루프를 사용하는 것이 좋습니다.그러나 GetOrdinal/Exception 방법은 열 수에 거의 민감하지 않으며 11개 열을 중심으로 루프 방법을 능가하기 시작합니다.

그렇다고는 해도 전체 애플리케이션에서 반환되는 평균 열 수만큼 열 11개가 합리적인 것처럼 들리기 때문에 선호하는 성능은 별로 없습니다.어느 경우든 여기서 우리는 1밀리초의 분수에 대해 이야기하고 있습니다.

그러나 코드 단순성 측면과 별칭 지원 측면에서 볼 때 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; }


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

다음은 승인된 답변의 한 줄 LINQ 버전입니다.

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

자스민의 해결책은 여기 한 줄로...(단순하지만 하나 더!):

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

코드를 견고하고 깨끗하게 유지하려면 다음과 같은 단일 확장 기능을 사용합니다.

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

당신의 테이블에서 열 이름이 아닌 쓸모없는 열 이름을 가져온 이유는...스키마 열 이름(즉, 스키마 테이블의 열 이름)을 가져오기 때문입니다.

참고: 첫 번째 열의 이름만 반환하는 것 같습니다...

EDIT: 모든 열의 이름을 반환하는 수정된 코드이지만 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
}

도 ㅠㅠㅠㅠㅠㅠㅠㅠㅠ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
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대소문자를 구분하지 않습니다.

내 데이터 액세스 클래스는 이전 버전과 호환되어야 하므로 데이터베이스에 아직 존재하지 않는 릴리스의 열에 액세스하려고 합니다.반환되는 데이터 세트가 상당히 크기 때문에 각 속성에 대해 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;
    }
}

전체 문제의 핵심은 다음과 같습니다.

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

언급된 세 72, 73,, 참된현세줄을제(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;
        }
    }
}

특정 상황(추가 열이 하나 있는 경우를 제외하고 모든 프로시저에 동일한 열이 있음)에서는 판독기의 FieldCount 속성을 확인하여 해당 열을 구분하는 것이 더 빠르고 효율적입니다.

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

성능상의 이유로 이 솔루션을 반복 솔루션과 혼합할 수도 있습니다.

는 을사하는것좋습다니이용을 사용하는 것을 합니다.try{} catch{}이 단순한 문제에 대하여.하지만 저는 캐치에서 예외를 처리하는 것을 추천하지 않습니다.

try 
{
  if (string.IsNullOrEmpty(reader["Name"].ToString())) 
  {
    name = reader["Name"].ToString();
  }
}
catch
{
  //Do nothing
}

이것은 꽤 오래된 실입니다. 하지만 저는 제 2센트를 제공하고 싶었습니다.

대부분의 제안된 솔루션의 문제는 확인 중인 모든 열에 대해 모든 행에 대해 매번 모든 필드를 열거해야 한다는 것입니다.

은 다른사는자를 하고 있습니다.GetSchemaTable전역적으로 지원되지 않는 메서드입니다.

개인적으로 필드가 존재하는지 확인하기 위해 예외를 던지고 잡는 것은 문제가 없습니다.사실, 저는 이것이 아마도 프로그래밍 관점에서 가장 간단한 해결책일 것이고 디버깅하고 확장을 만드는 가장 쉬운 방법이라고 생각합니다.다른 트랜잭션이나 이상한 롤백 로직이 포함된 경우를 제외하고 예외를 삼킬 때 부정적인 성능 히트는 없습니다.

를사구를 try-catch블록

using System;
using System.Collections.Generic;
using System.Data.SqlClient;

public class MyModel {
    public int ID { get; set; }
    public int UnknownColumn { get; set; }
}


public IEnumerable<MyModel> ReadData(SqlCommand command) {
    using (SqlDataReader reader = command.ExecuteReader()) {
        try {
            while (reader.Read()) {
                // init the row
                MyModel row = new MyModel();

                // bind the fields
                row.ID = reader.IfDBNull("ID", row.ID);
                row.UnknownColumn = reader.IfDBNull("UnknownColumn", row.UnknownColumn);

                // return the row and move forward
                yield return row;
            }
        } finally {
            // technically the disposer should handle this for you
            if (!reader.IsClosed) reader.Close();
        }
    }
}

// I use a variant of this class everywhere I go to help simplify data binding
public static class IDataReaderExtensions {
    // clearly separate name to ensure I don't accidentally use the wrong method
    public static T IfDBNull<T>(this IDataReader reader, string name, T defaultValue) {
        T value;
        try {
            // attempt to read the value
            // will throw IndexOutOfRangeException if not available
            object objValue = reader[name];

            // the value returned from SQL is NULL
            if (Convert.IsDBNull(objValue)) {
                // use the default value
                objValue = defaultValue;
            }
            else if (typeof(T) == typeof(char)) {
                // chars are returned from SQL as strings
                string strValue = Convert.ToString(objValue);

                if (strValue.Length > 0) objValue = strValue[0];
                else objValue = defaultValue;
            }

            value = (T)objValue;
        } catch (IndexOutOfRangeException) {
            // field does not exist
            value = @defaultValue;
        } catch (InvalidCastException, ex) {
            // The type we are attempting to bind to is not the same as the type returned from the database
            // Personally, I want to know the field name that has the problem
            throw new InvalidCastException(name, ex);
        }

        return value;
    }

    // clearly separate name to ensure I don't accidentally use the wrong method
    // just overloads the other method so I don't need to pass in a default
    public static T IfDBNull<T>(this IDataReader reader, string name) {
        return IfDBNull<T>(reader, name, default(T));
    }
}

처리를 를 예외처를방지결다저것좋이습다장니는하음에과를에 저장하는합니다.HashSet<string>판독기를 초기화한 다음 원하는 열을 다시 확인합니다. 미세 최적화를 을 " " " " " " " " " " " " " " " " 로 할 수 .Dictionary<string, int>의 중복된 결의안을 방지하기 위해NameordinalSqlDataReader물건.

를사한구를 사용한 HashSet<string>

using System;
using System.Collections.Generic;
using System.Data.SqlClient;

public class MyModel {
    public int ID { get; set; }
    public int UnknownColumn { get; set; }
}

public IEnumerable<MyModel> ReadData(SqlCommand command) {
    using (SqlDataReader reader = command.ExecuteReader()) {
        try {
            // first read
            if (reader.Read()) {
                // use whatever *IgnoreCase comparer that you're comfortable with
                HashSet<string> columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

                // init the columns HashSet<string, int>
                for (int i = 0; i < reader.FieldCount; i++) {
                    string fieldName = reader.GetName(i);
                    columns.Add(fieldName);
                }

                // implemented as a do/while since we already read the first row
                do {
                    // init a new instance of your class
                    MyModel row = new MyModel();

                    // check if column exists
                    if (columns.Contains("ID") &&
                        // ensure the value is not DBNull
                        !Convert.IsDBNull(reader["ID"])) {
                        // bind value
                        row.ID = (int)reader["ID"];
                    }

                    // check if column exists
                    if (columns.Contains("UnknownColumn") &&
                        // ensure the value is not DBNull
                        !Convert.IsDBNull(reader["UnknownColumn"])) {
                        // bind value
                        row.UnknownColumn = (int)reader["UnknownColumn"];
                    }

                    // return the row and move forward
                    yield return row;
                } while (reader.Read());
            }
        } finally {
            // technically the disposer should handle this for you
            if (!reader.IsClosed) reader.Close();
        }
    }
}

를사한구를 사용한 Dictionary<string, int>

using System;
using System.Collections.Generic;
using System.Data.SqlClient;

public class MyModel {
    public int ID { get; set; }
    public int UnknownColumn { get; set; }
}

public IEnumerable<MyModel> ReadData(SqlCommand command) {
    using (SqlDataReader reader = command.ExecuteReader()) {
        try {
            // first read
            if (reader.Read()) {
                // use whatever *IgnoreCase comparer that you're comfortable with
                Dictionary<string, int> columns = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

                // init the columns Dictionary<string, int>
                for (int i = 0; i < reader.FieldCount; i++) {
                    string fieldName = reader.GetName(i);
                    columns[fieldName] = i;
                }

                // implemented as a do/while since we already read the first row
                do {
                    // init a new instance of your class
                    MyModel row = new MyModel();

                    // stores the resolved ordinal from your dictionary
                    int ordinal;

                    // check if column exists
                    if (columns.TryGetValue("ID", out ordinal) &&
                        // ensure the value is not DBNull
                        !Convert.IsDBNull(reader[ordinal])) {
                        // bind value
                        row.ID = (int)reader[ordinal];
                    }

                    // check if column exists
                    if (columns.TryGetValue("UnknownColumn", out ordinal) &&
                        // ensure the value is not DBNull
                        !Convert.IsDBNull(reader[ordinal])) {
                        // bind value
                        row.UnknownColumn = (int)reader[ordinal];
                    }

                    // return the row and move forward
                    yield return row;
                } while (reader.Read());
            }
        } finally {
            // technically the disposer should handle this for you
            if (!reader.IsClosed) reader.Close();
        }
    }
}

열 목록을 원하지만 예외가 발생하지 않으려면 DataReader에서 GetSchemaTable()을 호출할 수도 있습니다.

방법은 클래스에 합니다.System.Data.ProviderBase.FieldNameLookup어떤.SqlDataReader에 의존합니다.

액세스하고 기본 성능을 얻으려면 IL Generator를 사용하여 런타임에 메서드를 만들어야 합니다.다음 코드를 통해 에 직접 액세스할 수 있습니다.int IndexOf(string fieldName)에 시대에System.Data.ProviderBase.FieldNameLookup을 할 하는 것을 합니다.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));
   }

}

사용:

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

그것은 아마도 순환에서 그렇게 효율적이지 않을 것입니다.

이것은 나에게 효과가 있습니다.

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

자세한 내용은 SqlDataReader에서 열 이름을 가져올있습니까?에서 확인할 수 있습니다.

언급URL : https://stackoverflow.com/questions/373230/check-for-column-name-in-a-sqldatareader-object