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;
}
}
Die Verwendung von Exception
s für die Steuerlogik wie in einigen anderen Antworten wird als schlechte Praxis angesehen und hat Leistungskosten. Es sendet auch falsch positive Ergebnisse an den Profiler für # ausgelöste Ausnahmen und Gott hilft jedem, der seinen Debugger so einstellt, dass er bei ausgelösten Ausnahmen abbricht.
GetSchemaTable () ist auch ein weiterer Vorschlag in vielen Antworten. Dies wäre keine bevorzugte Methode, um die Existenz eines Felds zu überprüfen, da es nicht in allen Versionen implementiert ist (es ist abstrakt und löst in einigen Versionen von dotnetcore eine NotSupportedException aus). GetSchemaTable ist auch in Bezug auf die Leistung übertrieben, da es eine ziemlich leistungsstarke Funktion ist, wenn Sie die Quelle überprüfen .
Das Durchlaufen der Felder kann einen kleinen Leistungseinbruch haben, wenn Sie es häufig verwenden und möglicherweise die Ergebnisse zwischenspeichern möchten.
Es ist viel besser, diese boolesche Funktion zu verwenden:Ein Anruf - keine Ausnahmen. Es könnte intern Ausnahmen auslösen, aber ich denke nicht.HINWEIS: In den Kommentaren unten haben wir dies herausgefunden ... der richtige Code lautet tatsächlich:
quelle
Ich denke, Ihre beste Wette ist es, GetOrdinal ("columnName") in Ihrem DataReader im Voraus aufzurufen und eine IndexOutOfRangeException abzufangen, falls die Spalte nicht vorhanden ist.
Lassen Sie uns eine Erweiterungsmethode erstellen:
Bearbeiten
Ok, dieser Beitrag fängt in letzter Zeit an, ein paar Abstimmungen zu erhalten, und ich kann ihn nicht löschen, da er die akzeptierte Antwort ist. Deshalb werde ich ihn aktualisieren und (ich hoffe) versuchen, die Verwendung der Ausnahmebehandlung als zu rechtfertigen Kontrollfluss.
Der andere Weg , dies zu erreichen, wie von Chad Grant - geschrieben , ist durch jedes Feld in den Datareader Schleife und macht einen Groß- und Kleinschreibung Vergleich für die Feldnamen , die Sie suchen. Dies wird sehr gut funktionieren und wird wahrheitsgemäß wahrscheinlich besser funktionieren als meine obige Methode. Sicherlich würde ich die oben beschriebene Methode niemals in einer Schleife verwenden, in der die Leistung ein Problem darstellt.
Ich kann mir eine Situation vorstellen, in der die Methode try / GetOrdinal / catch funktioniert, in der die Schleife nicht funktioniert. Es ist jedoch momentan eine völlig hypothetische Situation, daher ist es eine sehr schwache Rechtfertigung. Egal, trage mich und schau, was du denkst.
Stellen Sie sich eine Datenbank vor, mit der Sie Spalten in einer Tabelle "aliasen" können. Stellen Sie sich vor, ich könnte eine Tabelle mit einer Spalte namens "EmployeeName" definieren, ihr aber auch den Alias "EmpName" geben. Wenn Sie für einen der beiden Namen eine Auswahl treffen, werden die Daten in dieser Spalte zurückgegeben. Bisher bei mir?
Stellen Sie sich nun vor, es gibt einen ADO.NET-Anbieter für diese Datenbank, und sie haben eine IDataReader-Implementierung dafür codiert, die Spaltenaliasnamen berücksichtigt.
Jetzt kann
dr.GetName(i)
(wie in der Antwort von Chad verwendet) nur eine einzelne Zeichenfolge zurückgegeben werden, sodass nur einer der "Aliase" in einer Spalte zurückgegeben werden muss. Sie könnenGetOrdinal("EmpName")
jedoch die interne Implementierung der Felder dieses Anbieters verwenden, um den Alias jeder Spalte auf den gesuchten Namen zu überprüfen.In dieser hypothetischen Situation mit "Alias-Spalten" ist die try / GetOrdinal / catch-Methode die einzige Möglichkeit, um sicherzustellen, dass Sie nach jeder Variation des Spaltennamens in der Ergebnismenge suchen.
Schwach? Sicher. Aber einen Gedanken wert. Ehrlich gesagt würde ich lieber eine "offizielle" HasColumn-Methode für IDataRecord verwenden.
quelle
Verwenden Sie dies in einer Zeile nach dem Abrufen von DataReader:
Dann,
Bearbeiten
Viel effizienterer Einzeiler, bei dem das Schema nicht geladen werden muss:
quelle
Hier ist ein Arbeitsbeispiel für Jasmins Idee:
quelle
das funktioniert bei mir:
quelle
Folgendes ist einfach und hat für mich funktioniert:
quelle
Wenn Sie die Frage lesen, fragt Michael nach DataReader, nicht nach DataRecord. Machen Sie Ihre Objekte richtig.
Die Verwendung von a
r.GetSchemaTable().Columns.Contains(field)
in einem DataRecord funktioniert zwar, gibt jedoch BS-Spalten zurück (siehe Abbildung unten).Verwenden Sie die folgenden Erweiterungen, um festzustellen, ob eine Datenspalte vorhanden ist UND Daten in einem DataReader enthält:
Verwendung:
Das Aufrufen
r.GetSchemaTable().Columns
eines DataReader gibt BS-Spalten zurück:quelle
IDataReader
implementiertIDataRecord
. Sie sind verschiedene Schnittstellen desselben Objekts - genau wieICollection<T>
undIEnumerable<T>
sind verschiedene Schnittstellen vonList<T>
.IDataReader
Ermöglicht das Weitergehen zum nächsten Datensatz, während dasIDataRecord
Lesen aus dem aktuellen Datensatz ermöglicht wird. Die Methoden, die in dieser Antwort verwendet werden, stammen alle von derIDataRecord
Schnittstelle. Unter stackoverflow.com/a/1357743/221708 finden Sie eine Erklärung, warum Sie den Parameter alsIDataRecord
vorzuziehen deklarieren .r.GetSchemaTable().Columns
eine absolut falsche Antwort auf diese Frage ist.Ich habe für Visual Basic-Benutzer geschrieben:
Ich denke, das ist mächtiger und die Verwendung ist:
quelle
Hier ist eine einzeilige Linq-Version der akzeptierten Antwort:
quelle
Hier die Lösung von Jasmine in einer Zeile ... (noch eine, tho simple!):
quelle
quelle
TLDR:
Viele Antworten mit Behauptungen über Leistung und schlechte Praxis, deshalb kläre ich das hier.
Die Ausnahmeroute ist schneller für eine höhere Anzahl zurückgegebener Spalten, die Schleifenroute ist schneller für eine niedrigere Anzahl von Spalten und der Überkreuzungspunkt liegt bei etwa 11 Spalten. Scrollen Sie nach unten, um ein Diagramm und einen Testcode anzuzeigen.
Vollständige Antwort:
Der Code für einige der Top-Antworten funktioniert, aber es gibt hier eine grundlegende Debatte für die "bessere" Antwort, die auf der Akzeptanz der Ausnahmebehandlung in der Logik und der damit verbundenen Leistung basiert.
Um das zu klären, glaube ich nicht, dass es viele Hinweise zu CATCHING-Ausnahmen gibt. Microsoft hat einige Anleitungen zum Werfen von Ausnahmen. Dort heißt es:
Die erste Anmerkung ist die Nachsicht von "wenn möglich". Noch wichtiger ist, dass die Beschreibung diesen Kontext angibt:
Dies bedeutet, dass Sie beim Schreiben einer API, die möglicherweise von einer anderen Person verwendet wird, die Möglichkeit erhalten, eine Ausnahme ohne Versuch / Fang zu navigieren. Stellen Sie beispielsweise TryParse mit Ihrer ausnahmeauslösenden Parse-Methode bereit. Nirgendwo heißt es jedoch, dass Sie keine Ausnahme abfangen sollten.
Wie ein anderer Benutzer betont, haben Fänge außerdem immer das Filtern nach Typ und in jüngster Zeit das weitere Filtern über die when-Klausel ermöglicht . Dies scheint eine Verschwendung von Sprachfunktionen zu sein, wenn wir sie nicht verwenden sollen.
Es kann gesagt werden, dass es einige Kosten für eine ausgelöste Ausnahme gibt, und diese Kosten können die Leistung in einer schweren Schleife beeinflussen. Es kann jedoch auch gesagt werden, dass die Kosten einer Ausnahme in einer "verbundenen Anwendung" vernachlässigbar sein werden. Die tatsächlichen Kosten wurden vor über einem Jahrzehnt untersucht: https://stackoverflow.com/a/891230/852208 Mit anderen Worten, die Kosten für eine Verbindung und Abfrage einer Datenbank werden wahrscheinlich die Kosten einer ausgelösten Ausnahme in den Schatten stellen.
Abgesehen davon wollte ich herausfinden, welche Methode wirklich schneller ist. Wie erwartet gibt es keine konkrete Antwort.
Jeder Code, der die Spalten durchläuft, wird langsamer, wenn die Anzahl der Spalten vorhanden ist. Es kann auch gesagt werden, dass jeder Code, der auf Ausnahmen beruht, abhängig von der Rate, in der die Abfrage nicht gefunden wird, langsamer wird.
Ich nahm die Antworten von Chad Grant und Matt Hamilton und führte beide Methoden mit bis zu 20 Spalten und einer Fehlerrate von bis zu 50% aus (das OP gab an, dass er diese beiden Tests zwischen verschiedenen Prozessen verwendete, also nahm ich nur zwei an). .
Hier sind die mit LinqPad gezeichneten Ergebnisse:
Die Zickzacklinien hier sind Fehlerraten (Spalte nicht gefunden) innerhalb jeder Spaltenanzahl.
Bei engeren Ergebnismengen ist das Schleifen eine gute Wahl. Die GetOrdinal / Exception-Methode reagiert jedoch bei weitem nicht so empfindlich auf die Anzahl der Spalten und beginnt, die Schleifenmethode um 11 Spalten herum zu übertreffen.
Das heißt, ich habe keine Präferenz für die Leistung, da 11 Spalten als durchschnittliche Anzahl von Spalten, die über eine gesamte Anwendung zurückgegeben werden, vernünftig klingen. In beiden Fällen handelt es sich hier um Bruchteile einer Millisekunde.
Unter dem Aspekt der Code-Einfachheit und der Alias-Unterstützung würde ich mich wahrscheinlich für die GetOrdinal-Route entscheiden.
Hier ist der Test in Linqpad-Form. Fühlen Sie sich frei, mit Ihrer eigenen Methode neu zu posten:
quelle
Dieser Code behebt die Probleme, die Levitikon mit seinem Code hatte: (angepasst von: [1]: http://msdn.microsoft.com/en-us/library/system.data.datatablereader.getschematable.aspx )
Der Grund dafür, dass all diese nutzlosen Spaltennamen und nicht der Name der Spalte aus Ihrer Tabelle abgerufen werden, liegt darin, dass Sie den Namen der Schemaspalte erhalten (dh die Spaltennamen für die Schematabelle).
HINWEIS: Dies scheint nur den Namen der ersten Spalte zurückzugeben ...
BEARBEITEN: Der Code wurde korrigiert, der den Namen aller Spalten zurückgibt. Sie können jedoch keinen SqlDataReader verwenden, um dies zu tun
quelle
return r.GetSchemaTable().Rows.Cast<DataRow>().Select(x => (string)x["ColumnName"]).ToList();
:)Verwenden Sie eine einzelne Erweiterungsfunktion wie folgt, um Ihren Code robust und sauber zu halten:
quelle
Ich habe mich auch nicht
GetSchemaTable
an die Arbeit gemacht, bis ich diesen Weg gefunden habe .Grundsätzlich mache ich das:
quelle
Columns.Contains
unterscheidet übrigens zwischen Groß- und Kleinschreibung.quelle
In Ihrer speziellen Situation (alle Prozeduren haben die gleichen Spalten mit Ausnahme von 1 mit einer zusätzlichen Spalte von 1) ist es besser und schneller, den Leser zu überprüfen. FieldCount-Eigenschaft zur Unterscheidung.
Ich weiß, dass es ein alter Beitrag ist, aber ich habe mich entschlossen zu antworten, um anderen in der gleichen Situation zu helfen. Sie können diese Lösung auch (aus Leistungsgründen) mit der Lösungsiterationslösung mischen.
quelle
Meine Datenzugriffsklasse muss abwärtskompatibel sein, daher versuche ich möglicherweise, auf eine Spalte in einer Version zuzugreifen, in der sie noch nicht in der Datenbank vorhanden ist. Wir haben einige ziemlich große Datenmengen zurückgegeben, daher bin ich kein großer Fan einer Erweiterungsmethode, die die DataReader-Spaltensammlung für jede Eigenschaft iterieren muss.
Ich habe eine Dienstprogrammklasse, die eine private Liste von Spalten erstellt und dann eine generische Methode hat, die versucht, einen Wert basierend auf einem Spaltennamen und einem Ausgabeparametertyp aufzulösen.
Dann kann ich einfach meinen Code so aufrufen
quelle
Der Schlüssel zum ganzen Problem ist hier :
Wenn die drei referenzierten Zeilen (derzeit die Zeilen 72, 73 und 74) entfernt werden, können Sie leicht überprüfen
-1
, ob die Spalte nicht vorhanden ist.Die einzige Möglichkeit, dies zu umgehen und gleichzeitig die native Leistung sicherzustellen, besteht darin, eine
Reflection
basierte Implementierung wie die folgende zu verwenden:Verwendung:
Die auf Reflexion basierende Erweiterungsmethode:
quelle
Sie können GetSchemaTable () auch in Ihrem DataReader aufrufen, wenn Sie die Liste der Spalten möchten und keine Ausnahme erhalten möchten ...
quelle
Wie wäre es mit
In einer Schleife wäre es wahrscheinlich nicht so effizient
quelle
dr.GetSchemaTable().Columns
enthalten ist - es ist nicht das, wonach Sie suchen.Obwohl es keine öffentlich zugängliche Methode gibt, existiert in der internen Klasse eine Methode, auf die sich
System.Data.ProviderBase.FieldNameLookup
die MethodeSqlDataReader
stützt.Um darauf zuzugreifen und native Leistung zu erhalten, müssen Sie den ILGenerator verwenden, um zur Laufzeit eine Methode zu erstellen. Mit dem folgenden Code erhalten Sie direkten Zugriff auf
int IndexOf(string fieldName)
dieSystem.Data.ProviderBase.FieldNameLookup
Klasse und können die Buchführung durchführen,SqlDataReader.GetOrdinal()
damit keine Nebenwirkungen auftreten. Der generierte Code spiegelt den vorhandenen Code wider,SqlDataReader.GetOrdinal()
außer dass erFieldNameLookup.IndexOf()
anstelle von aufruftFieldNameLookup.GetOrdinal()
. DieGetOrdinal()
Methode ruft dieIndexOf()
Funktion auf und löst eine Ausnahme aus, wenn sie-1
zurückgegeben wird. Daher umgehen wir dieses Verhalten.quelle
diese Arbeit für mich
quelle
Weitere Informationen erhalten Sie hier: Können Sie die Spaltennamen von einem SqlDataReader erhalten?
quelle