SqlDataReader
オブジェクトに列が存在するかどうかを確認するにはどうすればよいですか?データアクセスレイヤーで、複数のストアドプロシージャ呼び出しに対して同じオブジェクトを作成するメソッドを作成しました。ストアドプロシージャの1つには、他のストアドプロシージャでは使用されない追加の列があります。すべてのシナリオに対応できるようにメソッドを変更したい。
私のアプリケーションはC#で書かれています。
他の回答のように、制御ロジックにException
sを使用するのは悪い習慣と見なされ、パフォーマンスコストがかかります。
フィールドをループすると、使用頻度が高くなり、結果のキャッシュを検討する必要がある場合、パフォーマンスがわずかに低下する可能性があります
これを行うより適切な方法は次のとおりです。
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;
}
}
このブール関数を使用することをお勧めします。
r.GetSchemaTable().Columns.Contains(field)
1回の呼び出し-例外なし。内部で例外をスローするかもしれませんが、そうは思いません。
注:以下のコメントで、これを理解しました...正しいコードは実際にはこれです:
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;
}
}
編集
OK、この投稿は最近いくつかのダウン投票を集め始めています、そしてそれは受け入れられた答えなので削除することはできません、それで私はそれを更新し、(希望する)として例外処理の使用を正当化しようとします制御フロー。
Chad Grantによる投稿 のようにこれを実現するもう1つの方法は、DataReaderの各フィールドをループし、探しているフィールド名の大文字と小文字を区別しない比較を行うことです。これは非常にうまく機能し、正直なところ、おそらく上記の私の方法よりも優れたパフォーマンスを発揮します。確かに、パフォーマンスが問題となるループ内で上記のメソッドを使用することはありません。
Try/GetOrdinal/catchメソッドがループが機能しない場所で機能する状況を考えることができます。ただし、現時点では完全に仮説的な状況であるため、非常に薄っぺらな正当化です。とにかく、私と一緒に我慢して、あなたの考えを見てください。
テーブル内の列を「エイリアス」できるデータベースを想像してください。 「EmployeeName」という列を持つテーブルを定義できるが、エイリアスに「EmpName」という名前を付け、いずれかの名前を選択すると、その列のデータが返されると想像してください。これまで私と一緒に?
ここで、そのデータベース用のADO.NETプロバイダーがあり、列エイリアスを考慮に入れたIDataReader実装をコード化したと想像してください。
現在、dr.GetName(i)
(Chadの回答で使用されている)は単一の文字列のみを返すことができるため、列の「エイリアス」のoneのみを返す必要があります。ただし、GetOrdinal("EmpName")
は、このプロバイダーのフィールドの内部実装を使用して、探している名前の各列のエイリアスを確認できます。
この架空の "エイリアス列"の状況では、try/GetOrdinal/catchメソッドが、結果セット内の列名のすべてのバリエーションを確認していることを確認する唯一の方法です。
薄っぺら?承知しました。しかし、考える価値があります。正直なところ、IDataRecordの "公式の" HasColumnメソッドを使用したいです。
1行で、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);
質問を読んだ場合、マイケルはDataRecordの人々ではなく、DataReaderについて尋ねました。オブジェクトを正しく取得します。
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列が返されます。
ここでは、Jasmineからのソリューションを1行で...(もう1つ、簡単です!):
reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;
受け入れられた答えの1ライナーlinqバージョンは次のとおりです。
Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")
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);
}
このコードは、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;
}
テーブルから列の名前ではなく、これらの役に立たない列名をすべて取得する理由は、スキーマ列の名前(つまり、Schemaテーブルの列名)を取得しているためです。
注:これは最初の列の名前のみを返すようです...
編集:すべての列の名前を返すコードを修正しましたが、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
}
コードを堅牢でクリーンな状態に保つには、次のような単一の拡張関数を使用します。
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
this way が見つかるまで、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で GetSchemaTable() を呼び出すこともできます...
私のデータアクセスクラスには後方互換性が必要なので、データベースにまだ存在しないリリースの列にアクセスしようとしている可能性があります。かなり大きなデータセットが返されるため、各プロパティの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 class DataRecordExtensions
{
public static bool HasColumn(IDataReader dataReader, string columnName)
{
dataReader.GetSchemaTable().DefaultView.RowFilter = $"ColumnName= '{columnName}'";
return (dataReader.GetSchemaTable().DefaultView.Count > 0);
}
}
特定の状況(1列に1列が追加されていることを除いて、すべての手順に同じ列があります)では、リーダーをチェックする方が適切で高速です。それらを区別するFieldCountプロパティ。
const int NormalColCount=.....
if(reader.FieldCount > NormalColCount)
{
// Do something special
}
私はそれが古い投稿であることを知っていますが、同じ状況で他の人を助けるために答えることに決めました。 (パフォーマンス上の理由で)このソリューションとソリューション反復ソリューションを混在させることもできます。
公開されたメソッドはありませんが、SqlDataReader
が依存する内部クラスSystem.Data.ProviderBase.FieldNameLookup
にはメソッドが存在します。
これにアクセスしてネイティブパフォーマンスを取得するには、実行時にILGeneratorを使用してメソッドを作成する必要があります。次のコードを使用すると、System.Data.ProviderBase.FieldNameLookup
クラスのint IndexOf(string fieldName)
に直接アクセスでき、副作用が発生しないようにSqlDataReader.GetOrdinal()
doesを維持するブックを実行できます。生成されたコードは、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));
}
}
これらの回答はすでにここに投稿されています。ちょっと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);
2つ目はよりクリーンで、はるかに高速です。最初のアプローチでGetSchemaTable
を毎回実行しなくても、ルックアップは非常に遅くなります。