열이 존재하는지 확인하는 방법 SqlDataReader
개체에 ? 내 데이터 액세스 계층에서 여러 저장 프로 시저 호출에 대해 동일한 개체를 작성하는 메서드를 만들었습니다. 저장 프로 시저 중 하나에 다른 저장 프로 시저에서 사용하지 않는 추가 열이 있습니다. 모든 시나리오에 맞게 방법을 수정하고 싶습니다.
내 응용 프로그램은 C #으로 작성되었습니다.
열이 존재하는지 확인하는 방법 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를 사용하는 것은 나쁜 습관 으로 간주됩니다 성능 비용이 있습니다. 또한 발생 된 예외 # 개에 대해 오탐 (false positive)을 전송하고 예외 발생시 디버거를 설정하는 사람이 누구든지 도와줍니다.
GetSchemaTable ()도 많은 답변에서 또 다른 제안입니다. 모든 버전에서 구현되지 않았기 때문에 필드의 존재 여부를 확인하는 바람직한 방법은 아닙니다 (추상적이고 일부 버전의 dotnetcore에서는 NotSupportedException이 발생 함). GetSchemaTable은 소스 를 체크 아웃 하면 꽤 무거운 기능이기 때문에 성능이 지나치게 뛰어납니다 .
필드를 많이 사용하면 필드를 루핑하면 성능이 약간 저하 될 수 있으며 결과 캐싱을 고려할 수 있습니다.
이 부울 함수를 사용하는 것이 훨씬 좋습니다.
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;
}
최선의 방법은 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 메소드를 사용하고 싶습니다.
한 줄에서 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));
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);
질문을 읽으면 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().Columns
DataReader를 호출 하면 BS 열이 반환됩니다.
IDataReader
구현 IDataRecord
합니다. 그것들은 같은 객체의 다른 인터페이스입니다 – ICollection<T>
그리고 같은 IEnumerable<T>
인터페이스입니다 List<T>
. IDataReader
다음 레코드로 IDataRecord
진행할 수 있으며 현재 레코드에서 읽을 수 있습니다. 이 답변에 사용되는 방법은 모두 IDataRecord
인터페이스 에서 나옵니다 . 매개 변수를 선언하는 것이 바람직한 이유에 대한 설명은 stackoverflow.com/a/1357743/221708 을 참조하십시오 IDataRecord
.
r.GetSchemaTable().Columns
이 질문에 대한 답이 왜 틀린지 보여 주십시오.
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
한 줄로 Jasmine의 해결책은 다음과 같습니다 (하나 더 간단합니다).
reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;
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는 다른 프로세스 간에이 두 가지 테스트를 사용하고 있음을 나타 냈습니다. .
여기의 지그재그는 각 열 개수 내의 결함 비율 (열을 찾을 수 없음)입니다.
더 좁은 결과 집합보다 루핑이 좋습니다. 그러나 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; }
}
이 코드 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();
:)
코드를 강력하고 깨끗하게 유지하려면 다음과 같이 단일 확장 기능을 사용하십시오.
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
이 방법을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
대소 문자를 구분하지 않는 btw입니다.
특정 상황에서 (모든 프로 시저에는 1 개의 열이 추가 된 1을 제외하고 동일한 열이 있음) 독자를 확인하는 것이 더 빠르고 빠릅니다. 그들을 구별하기위한 FieldCount 속성.
const int NormalColCount=.....
if(reader.FieldCount > NormalColCount)
{
// Do something special
}
나는 그것이 오래된 게시물이라는 것을 알고 있지만 같은 상황에서 다른 사람들을 돕기 위해 대답하기로 결정했습니다. 성능상의 이유로이 솔루션을 솔루션 반복 솔루션과 혼합 할 수도 있습니다.
내 데이터 액세스 클래스는 이전 버전과 호환되어야하므로 아직 데이터베이스에없는 릴리스의 열에 액세스하려고 할 수 있습니다. 우리는 다소 큰 데이터 세트가 반환되므로 각 속성에 대해 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 및 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
아마도 루프에서 효율적이지 않을 것입니다
dr.GetSchemaTable().Columns
당신이 찾고있는 게 아니에요 -이 포함되어 있습니다.
더 공개적으로 노출 방법은 없지만, 방법은 내부 클래스의 존재를 수행 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));
}
}
if(Enumerable.Range(0,reader.FieldCount).Select(reader.GetName).Contains("columName"))
{
employee.EmployeeId= Utility.ConvertReaderToLong(reader["EmployeeId"]);
}
여기에서 자세한 내용 을 얻을 수 있습니다. SqlDataReader에서 열 이름을 얻을 수 있습니까?