SqlDataReaderオブジェクトの列名を確認します


212

SqlDataReaderオブジェクトに列が存在するかどうかを確認するにはどうすればよいですか?私のデータアクセスレイヤーでは、複数のストアドプロシージャの呼び出しに対して同じオブジェクトを構築するメソッドを作成しました。ストアドプロシージャの1つに、他のストアドプロシージャで使用されない追加の列があります。すべてのシナリオに対応できるようにメソッドを変更したいと思います。

私のアプリケーションは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を使用することは悪い習慣と見なされ、パフォーマンスコストがかかります。また、スローされた#例外のプロファイラーに誤検知を送信し、スローされた例外でブレークするようにデバッガーを設定する人を神が助けます。

GetSchemaTable()も多くの回答で別の提案です。すべてのバージョンで実装されているわけではないため、これはフィールドの存在を確認するための好ましい方法ではありません(抽象であり、dotnetcoreの一部のバージョンではNotSupportedExceptionをスローします)。GetSchemaTableは、ソースチェックアウトする場合、非常にヘビーデューティーな関数であるため、パフォーマンスも優れています

フィールドをループすると、それを頻繁に使用し、結果をキャッシュすることを検討したい場合、パフォーマンスに小さな影響を与える可能性があります。


エイリアスが使用されている場合はどうなりますか?名前の比較は失敗します。
Murphybro2 2017年

例外フローの使用は悪い習慣であることは議論の余地があります。他のオペレーターにとっては比較的高価であるが、接続されたアプリケーションでは無視できるため、かつては悪いと考えられていました。Skeetは、2006年までスタック深度に応じて、1ミリ秒あたり40〜118の例外を測定しました。 stackoverflow.com/a/891230/852208を。さらにテストを行わないと、このコードは実際にはすべての列の半分をチェックする平均的なケースで実際に遅くなる可能性があります(ただし、db接続アプリケーションではまだ簡単です)。他の2つは意見なので、この回答を編集して、中央の段落のみを含めます。
b_levitt

3
@b_levittそれは議論の余地がありません、それはくだらないコードであり、あなたは制御フローの例外に頼るべきではありません
Chad Grant

私が指摘した2つの文のように、これは純粋に計算アプリケーションでのパフォーマンスを超える根拠でサポートされていないもう1つの意見です。すべての例外でブレークするようにデバッガーを設定し、私のコードだけを無効にしてみてください。フレームワークや他のライブラリーでもすでにこれを実行していることがわかります。同意は劣ったパターンです:stackoverflow.com/questions/99683/…。そのような方法論は「成功の落とし穴」テストに失敗します。
b_levitt

コードの観点から、あなたの答えは有効なものです。しかし、try / catch(エイリアスも処理する)を使用した回答よりも優れた回答として重み付けしようとするあなたの意見は、ここでは適切ではありません。
b_levitt

66

このブール関数を使用する方がはるかに優れています。

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

5
@ジャスミン:私はすぐに話しました!コードは、結果セットではなく、スキーマテーブルの列をチェックします。「フィールド」(「フィールド」は列名であると想定)を各行の「ColumnName」フィールドの値と比較する必要があります。見つけたらブレークし、見つからなければfalseを返します。
スティーブJ

4
@Steve J:結果セットがGetSchemaTableに列を持たないのはいつですか?
祝福ヤフー2009

1
混乱している他の人にとって、これはうまくいきません。スキーマテーブルからColumnName行を取得して使用する方法については、以下の回答をご覧ください。
Jason Jackson

3
はい、これは機能しません。誰がそんなに何度も賛成したの??? この答えがなければ、デバッグ時間を大幅に節約できました。
c00000fd 2013

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

編集する

わかりました、この投稿は最近いくつかの反対票を集め始めています。これは承認された回答であるため削除できません。更新し、(例外的に)例外処理の使用を正当化しようとします。制御フロー。

チャド・グラントが投稿した、これを実現するもう1つの方法、DataReaderの各フィールドをループして、探しているフィールド名の大文字と小文字を区別しない比較を行うことです。これは非常にうまく機能し、おそらく上記の私の方法よりもおそらくパフォーマンスが優れています。確かに、performaceが問題であるループ内では、上記のメソッドを使用しませんでした。

ループが機能しない場所で、try / GetOrdinal / catchメソッドが機能する1つの状況を考えることができます。しかし、それは現在完全に仮説的な状況なので、非常に薄っぺらな正当化です。とにかく、私と一緒に耐え、あなたの考えを見てください。

テーブル内の列を「エイリアス」できるデータベースを想像してみてください。「EmployeeName」という名前の列を持つテーブルを定義し、それに「EmpName」のエイリアスを与えることを想像してください。どちらかの名前に対して選択を行うと、その列にデータが返されます。これまでのところ私と?

ここで、そのデータベース用のADO.NETプロバイダーがあり、列のエイリアスを考慮に入れるためのIDataReader実装をコード化したとします。

現在、dr.GetName(i)(チャドの回答で使用されているように)1つの文字列のみを返すことができるため、列の「エイリアス」の1つだけを返す必要があります。ただし、GetOrdinal("EmpName")このプロバイダーのフィールドの内部実装を使用して、探している名前の各列のエイリアスを確認できます。

この架空の「エイリアスされた列」の状況では、try / GetOrdinal / catchメソッドが、結果セット内の列の名前のすべてのバリエーションを確認していることを確認する唯一の方法になります。

薄っぺら?承知しました。しかし、考える価値があります。正直なところ、私はむしろIDataRecordの「公式の」HasColumnメソッドを使用します。


15
制御ロジックに例外を使用していますか?いいえいいえいいえ
チャドグラント

28
私がこの質問を最初に投稿したときに誰もが見落としている小さなことが1つあります。私は12/8/08に質問し、Mattは12/17/08に回答を投稿しました。誰もが制御ロジックの例外をキャッチすることについて悪臭を放ちましたが、2009年5月1日まで、確実な代替ソリューションを提供しませんでした。それがもともと答えとしてマークされていた理由です。今日もこのソリューションを使用しています。
Michael Kniskern 2010

19
これにより、列が存在しない場合にのみパフォーマンスが低下します。説明されている他の方法では、毎回パフォーマンスヒットと、より大きなパフォーマンスヒットが発生します。制御のフローに例外処理を使用しないようにすることは一般的に悪い習慣ですが、この解決策は、それがあなたのケースで機能するかどうかを最初に考慮することなしに除外されるべきではありません。
Nick Harrison、

5
+1。広範なデザインルールとして、「制御ロジックに例外を使用しない」で大丈夫です。「絶対に避けてください」という意味ではありません。答えは非常によく文書化された回避策であり、@ Nickが言うように、パフォーマンスヒット(もしあれば)は、列が存在しない場合にのみ発生します。
ラリー

2
例外を制御ロジックとして使用すると、私の経験ではデバッグがさらに面倒になります。「Common Language Runtime Exceptions」で「Thrown」のチェックを外す必要があります。実際の例外が発生した場合、問題のある行ではなく、どこかでハンドラーが壊れる可能性があります。
2014

30

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

フィールド名を複数回列挙している/ containsでスキャンする別の配列を割り当てている場合、これはトラフィックの多いコードではパフォーマンスが大幅に低下します。
チャドグラント

もちろん、@ ChadGrant。これは、1回の反復しか実行しないため、Linqの1つのライナーがはるかに効率的である理由です。
Larry

18

Jasminのアイデアの実用的なサンプルを以下に示します。

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

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

}

1
try / catchをラップする場合のみ
Donald.Record

このアイデアは、reader.GetSchemaTable()。Columns.Contains( "myFiled")
Lev Z

列名を見つけるだけでは、GetSchemaTable()の使用は(割り当てに関して)過剰です。ソースgithub.com/microsoft/referencesource/blob/…を
Chad Grant

12

これは私にとってはうまくいきます:

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

列名を見つけるだけでは、GetSchemaTable()の使用は(割り当てに関して)過剰です。また、dotnetコアのすべてのバージョンに実装されているわけではありません。ソースgithub.com/microsoft/referencesource/blob/…を
Chad Grant


8

質問を読むと、マイケルはDataRecordの人ではなく、DataReaderについて尋ねました。オブジェクトを正しく取得します。

を使って r.GetSchemaTable().Columns.Contains(field)DataRecordでaても機能しますが、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を呼び出す



DataRecord が機能するとはどういう意味ですか?それはBS列を返しますか?あなたはそれが走る(そして間違った結果を与える)という意味ですか?
nawfal 2013

2
「あなたのオブジェクトを正しくしなさい。」- IDataReader実装しIDataRecordます。それらは同じオブジェクトの異なるインターフェースです-のようにICollection<T>IEnumerable<T>異なるインターフェースですList<T>IDataReader次のレコードに進むIDataRecordことができ、現在のレコードから読み取ることができます。この回答で使用されているメソッドはすべてIDataRecordインターフェースからのものです。パラメータをとして宣言することが望ましい理由の説明については、stackoverflow.com / a / 1357743/221708を参照してくださいIDataRecord
ダニエルシリング

なぜr.GetSchemaTable().Columnsこの質問に対する完全に間違った答えであるかを示す賛成票を投じてください。
ダニエルシリング

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

これは、受け入れられた回答の1ライナーlinqバージョンです。

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

大文字と小文字を区別する比較...なぜですか?
チャドグラント

4

ここで、ジャスミンからの解決策を1行で...(もう1つ、簡単です!):

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

列名を見つけるだけでは、GetSchemaTable()の使用は(割り当てに関して)過剰です。ソースgithub.com/microsoft/referencesource/blob/…を
Chad Grant

@ChadGrant可能です。コンテキストとこれを使用する必要がある頻度に応じて賢明に選択する必要があると
思い

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には、例外のスローに関するいくつかのガイダンスがあります。そこで彼らは述べています:

可能であれば、通常の制御フローに例外を使用しないでください。

最初の注意は、「可能であれば」の寛大さです。さらに重要なことに、この説明はこのコンテキストを提供します。

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

つまり、他の誰かが使用する可能性のあるAPIを作成している場合は、try / catchなしで例外をナビゲートできるようにします。たとえば、例外をスローするParseメソッドをTryParseに提供します。 例外をキャッチしてはならないということはどこにもありません。

さらに、別のユーザーが指摘しているように、catchは常にタイプによるフィルタリングを許可し、最近ではwhen句によるフィルタリングをさらに許可しています。。言語機能を使用することになっていない場合、これは言語機能の無駄のようです。

スローされた例外にはいくらかのコストがかかり、そのコストは重いループのパフォーマンスに影響を与える可能性があります。ただし、「接続されたアプリケーション」では、例外のコストはごくわずかであるとも言えます。実際のコストは10年以上前に調査されました:https : //stackoverflow.com/a/891230/852208 つまり、データベースの接続とクエリのコストがスローされた例外の可能性が高い小人にあります。

それはさておき、どの方法が本当に速いのかを判断したかったのです。予想通り、具体的な答えはありません。

列の数が存在するため、列をループするコードはすべて遅くなります。例外に依存するコードは、クエリが見つからない割合に応じて遅くなるとも言えます。

チャドグラントとマットハミルトンの両方の答えを取り入れて、最大20カラムと最大50%のエラー率で両方のメソッドを実行しました(OPは、異なる2つのプロシージャ間でこの2つのテストを使用していることを示したため、わずか2つと仮定しました)。 。

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つの問題は、提案されたコードで意図的である場合にスローされる例外の数としてプロファイラーに表示されることです...例外ではありません。スローされた例外でブレークするようにデバッガーを設定することは言うまでもありません。基本的にエラーではないエラーを報告します。これを行うべきではありません。
チャドグラント

1
finally /秒とフィルター/秒のカウンターもあります。それらも悪いですか?私はそれを可能性のある警告と呼びます-あなたが提供した最初の実際のものです。カウンターは単なる情報です。パフォーマンスの問題に対応しない限り、何の意味もありません。この場合、例外のパフォーマンスが向上している点をすでに示しました。また、フレームワークとライブラリがすでに多くの例外をスローしていることも示しました。現在、60のex / sを投げているビジュアルスタジオのインスタンスがあります。キャッチされない限り、例外はエラーではありません。
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
}

または1行でreturn r.GetSchemaTable().Rows.Cast<DataRow>().Select(x => (string)x["ColumnName"]).ToList();:)
nawfal

列名を見つけるだけでは、GetSchemaTable()の使用は(割り当てに関して)過剰です。ソースgithub.com/microsoft/referencesource/blob/…を
Chad Grant

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 大文字と小文字は区別されません。


Contains()は例外をスローしません。このコードは無意味です。nullポインタ例外のみをキャッチします。
チャドグラント

0

特定の状況(追加の1つの列がある1を除いて、すべての手順に同じ列があります)では、リーダーをチェックする方がより速く、より速くなります。それらを区別するためのFieldCountプロパティ。

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

私はそれが古い記事であることを知っていますが、同じ状況で他の人を助けるために答えることに決めました。また、(パフォーマンス上の理由から)このソリューションをソリューション反復ソリューションと混在させることもできます。


参照しているソリューションに名前を付けてください。どの2つのソリューションを混合する必要がありますか?
Pablo Jomer 14

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

参照されている3行(現在は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、それはあなたが探しているものではありません-が含まれています。
ダニエルシリング

-1

公開されたメソッドはありませんが、メソッドは内部クラスに存在しますSystem.Data.ProviderBase.FieldNameLookupSqlDataReaderことに依存します。

これにアクセスしてネイティブパフォーマンスを得るには、ILGeneratorを使用して実行時にメソッドを作成する必要があります。次のコードを使用する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));
   }

}

1
内部コードは、この奇妙なリフレクション/デリゲートを必要とせずに、私の答えが行っているのとほぼ同じことを行います。これは、オブジェクトインスタンスごとのルックアップをキャッシュします。これは、クエリが初めて実行されるときに序数をキャッシュし、アプリの存続期間にわたってそのキャッシュを使用したいので、すべてのクエリで新しいキャッシュを構築するのではなく、有益ではありません。
Chad Grant、

-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.