SqlDataReader 개체에서 열 이름 확인


212

열이 존재하는지 확인하는 방법 SqlDataReader개체에 ? 내 데이터 액세스 계층에서 여러 저장 프로 시저 호출에 대해 동일한 개체를 작성하는 메서드를 만들었습니다. 저장 프로 시저 중 하나에 다른 저장 프로 시저에서 사용하지 않는 추가 열이 있습니다. 모든 시나리오에 맞게 방법을 수정하고 싶습니다.

내 응용 프로그램은 C #으로 작성되었습니다.

답변:


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

Exception다른 답변과 마찬가지로 제어 논리에 s를 사용하는 것은 나쁜 습관 으로 간주됩니다 성능 비용이 있습니다. 또한 발생 된 예외 # 개에 대해 오탐 (false positive)을 전송하고 예외 발생시 디버거를 설정하는 사람이 누구든지 도와줍니다.

GetSchemaTable ()도 많은 답변에서 또 다른 제안입니다. 모든 버전에서 구현되지 않았기 때문에 필드의 존재 여부를 확인하는 바람직한 방법은 아닙니다 (추상적이고 일부 버전의 dotnetcore에서는 NotSupportedException이 발생 함). GetSchemaTable은 소스체크 아웃 하면 꽤 무거운 기능이기 때문에 성능이 지나치게 뛰어납니다 .

필드를 많이 사용하면 필드를 루핑하면 성능이 약간 저하 될 수 있으며 결과 캐싱을 고려할 수 있습니다.


별명이 사용되면 어떻게됩니까? 이름 비교에 실패합니다.
Murphybro2

예외 흐름을 사용하는 것은 나쁜 습관이라는 것은 논란의 여지가 있습니다. 다른 운영자에게는 상대적으로 비싸지 만 연결된 응용 프로그램에서는 무시할 수 있기 때문에 한 번 나쁜 생각이었습니다. Skeet은 2006 년까지 스택 깊이에 따라 ms 당 40-118 개의 예외를 측정했습니다. stackoverflow.com/a/891230/852208 . 또한 테스트하지 않으면이 코드가 실제로 모든 열의 절반을 검사하는 경우가 더 느려질 수 있습니다 (DB 연결 앱에서는 여전히 사소합니다). 다른 두 개는 의견이므로 중간 답변 만 포함하도록이 답변을 편집합니다.
b_levitt

3
@b_levitt 그것은 논란의 여지가 없으며, 쓰레기 코드이며 제어 흐름에 대한 예외에 의존해서는 안됩니다
Chad Grant

내가 지적한 두 문장처럼, 그것은 순수한 전산 응용에서 성능 이상의 이론적 근거로는지지되지 않는 또 다른 의견이다. 디버거가 모든 예외를 극복하고 내 코드 만 비활성화하도록 감히 설정하면 프레임 워크 및 기타 라이브러리가 이미 얼마나 많은 작업을 수행하고 있는지 알 수 있습니다. stackoverflow.com/questions/99683/… 라는 열등한 패턴에 동의합니다 . 이러한 방법론은 "성공"테스트에 실패합니다.
b_levitt

코드 관점에서 귀하의 답변은 유효한 답변입니다. 그러나 try / catch (별명도 처리)의 답변에 대한 우수한 답변으로 가중치를 부여하려는 귀하의 의견은 여기에 없습니다.
b_levitt

66

이 부울 함수를 사용하는 것이 훨씬 좋습니다.

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

한 번의 전화-예외는 없습니다. 내부적으로 예외가 발생할 수 있지만 그렇게 생각하지 않습니다.

참고 : 아래 의견에서 우리는 이것을 알아 냈습니다 ... 정확한 코드는 실제로 다음과 같습니다.

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
@ 재스민 : 너무 빨리 말했다! 코드는 결과 집합이 아니라 스키마 테이블에서 열을 확인합니다. "field"( "field"가 열 이름이라고 가정)를 각 행의 "ColumnName"필드 값과 비교해야합니다. 당신이 그것을 찾을 때 휴식, 당신이하지 않으면 거짓을 반환합니다.
Steve J

4
@Steve J : 결과 집합에 GetSchemaTable에 열이없는 경우는 언제입니까?
Bless Yahu

1
다른 사람에게는 혼란 스러울 수 있습니다. 스키마 테이블에서 ColumnName 행을 검색하고 사용하는 방법에 대한 아래 답변을 참조하십시오.
Jason Jackson

3
예, 작동하지 않습니다. 누가 그렇게 많이 찬성 했습니까 ??? 이 답변이 없으면 나중에 디버깅 시간을 많이 절약했을 것입니다!
c00000fd

1
@ 재스민 그들은 모두 작동합니까? 실제로는 아닙니다. 답변의 첫 부분을 친절하게 제거하십시오. 나는 나 자신을했을 것입니다, 그러나 당신의 마지막 코멘트!
nawfal

33

최선의 방법은 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 메소드를 사용하고 싶습니다.


15
제어 로직에 예외를 사용합니까? no no no
Chad Grant

28
내가이 질문을 처음 게시했을 때 모두가 간과하는 한 가지 작은 것이 있습니다 .08 / 08 / 08에 질문을했고 Matt은 12/17/08에 답변을 게시했습니다. 모두 제어 논리의 예외를 잡는 것에 대해 악취를 냈지만 2009 년 5 월 1 일까지 확실한 대안 솔루션을 제공하지 않았습니다. 그렇기 때문에 원래 답변으로 표시되었습니다. 나는 오늘도 여전히이 솔루션을 사용하고 있습니다.
Michael Kniskern

19
열이없는 경우에만 성능이 저하됩니다. 설명 된 다른 방법은 매번 성능이 저하되고 성능이 크게 향상됩니다. 제어 흐름에 예외 처리를 사용하지 않는 것이 일반적으로 좋지 않지만이 솔루션이 사용자의 경우에 적합한 지 먼저 고려하지 않고 배제해서는 안됩니다.
닉 해리슨

5
+1. "제어 로직에 예외를 사용하지 마십시오"를 광범위한 설계 규칙으로 사용하는 것이 좋습니다. "모든 비용을 피해야한다"는 의미는 아닙니다. 대답은 매우 잘 문서화 된 해결 방법이며 @Nick이 말했듯이 성능 적중 (있는 경우)은 열이 존재하지 않을 때만 발생합니다.
래리

2
컨트롤 로직으로 예외를 사용하면 경험상 디버깅이 더 번거로워집니다. "Common Language Runtime Exceptions"에서 "Thrown"을 해제해야합니다. 그런 다음 실제 예외가 발생하면 문제가있는 줄이 아닌 어딘가에서 핸들러에서 중단 될 수 있습니다.
cedd

30

한 줄에서 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));

필드 이름을 여러 번 열거하거나 포함으로 스캔하기 위해 다른 배열을 할당하면 트래픽이 많은 코드에서 성능이 훨씬 떨어집니다.
채드 그랜트

@ChadGrant는 물론 Linq one liner는 한 번의 반복 만 수행하므로 훨씬 효율적입니다.
Larry

18

Jasmin의 아이디어에 대한 실제 샘플은 다음과 같습니다.

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

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

}

1
시도 / 캐치를 감싸는 경우에만
Donald. Record

reader.GetSchemaTable (). Columns.Contains ( "myFiled")
Lev Z

GetSchemaTable ()을 사용하는 것은 열 이름을 찾기에 너무 과도하게 할당됩니다. 소스 확인 github.com/microsoft/referencesource/blob/...
차드 그랜트



8

질문을 읽으면 Michael은 DataRecord 담당자가 아닌 DataReader에 대해 물었습니다. 당신의 개체를 올바르게하십시오.

사용하여 r.GetSchemaTable().Columns.Contains(field)DataRecord에서를 작동하지만 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");
    }

r.GetSchemaTable().ColumnsDataReader를 호출 하면 BS 열이 반환됩니다.

DataReader에서 GetSchemeTable 호출


마츠 대답 아래 설명 참조
nawfal

에 의해 당신은 무엇을 의미합니까 DataRecord이 작업을 수행 하지만 BS 열을 반환 ? 당신은 그것이 실행되고 잘못된 결과를 제공한다는 것을 의미합니까?
nawfal

2
"객체를 올바르게 잡으십시오." -하지만 IDataReader구현 IDataRecord합니다. 그것들은 같은 객체의 다른 인터페이스입니다 – ICollection<T>그리고 같은 IEnumerable<T>인터페이스입니다 List<T>. IDataReader다음 레코드로 IDataRecord진행할 수 있으며 현재 레코드에서 읽을 수 있습니다. 이 답변에 사용되는 방법은 모두 IDataRecord인터페이스 에서 나옵니다 . 매개 변수를 선언하는 것이 바람직한 이유에 대한 설명은 stackoverflow.com/a/1357743/221708 을 참조하십시오 IDataRecord.
Daniel Schilling

r.GetSchemaTable().Columns이 질문에 대한 답이 왜 틀린지 보여 주십시오.
Daniel Schilling

GetName ()은 IDataRecord 인터페이스에서 IDataReader로 상속됩니다. 기본 인터페이스를 타겟팅하는 것이 올바른 코드입니다.
채드 그랜트

7

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

4

허용되는 답변의 라이너 linq 버전은 다음과 같습니다.

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

대소 문자 구분 비교 ... 왜?
채드 그랜트

4

한 줄로 Jasmine의 해결책은 다음과 같습니다 (하나 더 간단합니다).

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

GetSchemaTable ()을 사용하는 것은 열 이름을 찾기에 너무 과도하게 할당됩니다. 소스 확인 github.com/microsoft/referencesource/blob/...
차드 그랜트

@ChadGrant 가능. 상황과 빈도에 따라 현명하게 선택해야한다고 생각합니다.
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 :

성능과 나쁜 연습에 대한 주장과 함께 많은 답변이 있으므로 여기에서 분명히 설명합니다.

예외 경로는 반환 된 열 수가 많을수록 빠르며 루프 경로는 열 수가 적을수록 빠르며 교차점은 약 11 열입니다. 그래프와 테스트 코드를 보려면 맨 아래로 스크롤하십시오.

전체 답변 :

최고의 답변 중 일부에 대한 코드는 작동하지만 논리에서 예외 처리를 수락하고 관련 성능에 대한 "더 나은"답변에 대한 근본적인 토론이 있습니다.

이를 명확히하기 위해 CATCHING 예외에 관한 많은 지침이 있다고 생각하지 않습니다. Microsoft는 THROWING 예외에 대한 몇 가지 지침 을 제공합니다. 그곳에서 그들은 이렇게 말합니다.

가능한 경우 정상적인 제어 흐름에는 예외를 사용하지 마십시오.

첫 번째 메모는 "가능한 경우"의 부담입니다. 더 중요한 것은 설명은이 컨텍스트를 제공한다는 것입니다.

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

즉, 다른 사람이 사용할 수있는 API를 작성하는 경우 try / catch없이 예외를 탐색 할 수 있습니다. 예를 들어, 예외 발생 구문 분석 방법으로 TryParse를 제공하십시오. 예외를 잡아서는 안된다고 말하는 곳은 없습니다.

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

예외가 발생하는 데 약간의 비용이 있으며 그 비용은 많은 루프에서 성능에 영향을 줄 수 있습니다. 그러나 "연결된 응용 프로그램"에서는 예외 비용이 무시할 만하다고 말할 수도 있습니다. 실제 비용은 10 년 전에 조사되었습니다 : https://stackoverflow.com/a/891230/852208 즉, 데이터베이스 연결 및 쿼리의 비용이 던진 예외 난쟁이 가능성이 높습니다.

그 외에도 모든 방법이 실제로 더 빠른지 확인하고 싶었습니다. 예상대로 구체적인 답변이 없습니다.

열 수에 따라 열을 반복하는 코드가 느려집니다. 또한 예외에 의존하는 코드는 쿼리를 찾지 못하는 비율에 따라 느려질 수 있습니다.

채드 그랜트와 매트 해밀턴의 답변을 모두 취하여 최대 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; }


}

1
예외에 대한 일종의 이상한 강박 관념이 분명히 있습니다. 더 나은 접근 방식은 성능을 위해 정적 조회에서 열 위치를 캐시하고 정수 조회를 사용하는 것입니다.
Chad Grant

예외를 제어 흐름으로 사용하는 또 다른 문제는 제안 된 코드에서 예외가 아닌 의도적 인 예외가 발생했을 때 예외 수로 프로파일 러에 표시된다는 것입니다. 예외가 발생했을 때 디버거를 설정하는 것은 말할 것도 없습니다. 본질적으로 오류가 아닌 오류를보고합니다. 이러면 안됩니다.
채드 그랜트

1
finallys / sec 및 filters / sec에 대한 카운터도 있습니다. 이것들도 나쁜가요? 나는 그것을 당신이 제공 한 첫 번째 진짜 경고라고 부를 것입니다. 카운터는 정보 일뿐입니다. 성능 문제에 해당하지 않는 한 아무런 의미가 없습니다.이 경우 예외가 더 나은 성능을 갖는 지점을 이미 보여주었습니다. 또한 프레임 워크와 라이브러리가 이미 많은 예외를 throw한다고 표시했습니다. 지금 60 ex / s를 던지는 Visual Studio 인스턴스가 있습니다. 포착되지 않는 한 예외는 오류가 아닙니다.
b_levitt

훌륭한 분석. 나는 그 결과를 새로운 대답에 사용했습니다.
yazanpro

1

이 코드 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
}

또는 한 줄에 return r.GetSchemaTable().Rows.Cast<DataRow>().Select(x => (string)x["ColumnName"]).ToList();:)
nawfal

GetSchemaTable ()을 사용하는 것은 열 이름을 찾기에 너무 과도하게 할당됩니다. 소스 확인 github.com/microsoft/referencesource/blob/...
차드 그랜트

1

코드를 강력하고 깨끗하게 유지하려면 다음과 같이 단일 확장 기능을 사용하십시오.

    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

이 방법을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

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 대소 문자를 구분하지 않는 btw입니다.


Contains ()는 예외를 발생시키지 않습니다.이 코드는 의미가 없습니다. 널 포인터 예외 만 포착합니다.
채드 그랜트

0

특정 상황에서 (모든 프로 시저에는 1 개의 열이 추가 된 1을 제외하고 동일한 열이 있음) 독자를 확인하는 것이 더 빠르고 빠릅니다. 그들을 구별하기위한 FieldCount 속성.

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

나는 그것이 오래된 게시물이라는 것을 알고 있지만 같은 상황에서 다른 사람들을 돕기 위해 대답하기로 결정했습니다. 성능상의 이유로이 솔루션을 솔루션 반복 솔루션과 혼합 할 수도 있습니다.


언급 한 솔루션의 이름을 지정하십시오. 어떤 두 솔루션을 혼합해야합니까?
Pablo Jomer

0

내 데이터 액세스 클래스는 이전 버전과 호환되어야하므로 아직 데이터베이스에없는 릴리스의 열에 액세스하려고 할 수 있습니다. 우리는 다소 큰 데이터 세트가 반환되므로 각 속성에 대해 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;
    }
}

0

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

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


-1

어때요?

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

아마도 루프에서 효율적이지 않을 것입니다


참조 Levitikon의 대답 물건의 종류 볼 수있는 dr.GetSchemaTable().Columns당신이 찾고있는 게 아니에요 -이 포함되어 있습니다.
Daniel Schilling

-1

더 공개적으로 노출 방법은 없지만, 방법은 내부 클래스의 존재를 수행 System.Data.ProviderBase.FieldNameLookup하는SqlDataReader 에 의존합니다.

액세스하고 기본 성능을 얻으려면 ILGenerator를 사용하여 런타임에 메소드를 작성해야합니다. 다음 코드는 클래스 int IndexOf(string fieldName)에서 직접 액세스 할 수 System.Data.ProviderBase.FieldNameLookup있게 해주 며 SqlDataReader.GetOrdinal()부작용이 없도록 책을 유지합니다 . 생성 된 코드 SqlDataReader.GetOrdinal()FieldNameLookup.IndexOf()대신 호출 하는 것을 제외하고 기존 코드를 미러링합니다 FieldNameLookup.GetOrdinal(). 이 GetOrdinal()메서드는 IndexOf()함수를 호출하고 -1반환되면 예외를 throw 하므로 해당 동작을 무시합니다.

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
내부 코드는이 이상한 반성 / 위임의 필요없이 내 대답과 거의 동일한 작업을 수행합니다. 실제 인스턴스에서 쿼리를 처음 실행할 때 서수를 캐시하고 앱 수명 기간 동안 해당 캐시를 사용하고 모든 쿼리에 새 캐시를 작성하지 않기 때문에 객체 인스턴스별로 조회를 캐싱하고 있습니다.
채드 그랜트

-1

이 일은 나에게

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.