Entwickler-Ecke

Datenbanken (inkl. ADO.NET) - Differenztabelle aus zwei DataTable Objekten erzeugen


Marc Dirk - Di 23.03.10 23:46
Titel: Differenztabelle aus zwei DataTable Objekten erzeugen
Hallo,

ich habe folgendes Problem:
Ich beziehe von meinem Lieferanten für meinen Onlineshop täglich seine komplette Artikeldatenbank als CSV-File (55MB).
Meine Warenwirtschaft importiert CSV-Datein allerdings in einem anderen Format (andere Separatoren z.B. ";" statt ",", andere Spaltennamen und diverse Spalten zuviel und andere dafür zuwenig.
Bisher habe ich diese CSV-Files in Excel importiert, manuell umgebaut, sortiert und ergänzt und dann wieder als CSV-Datei abgespeichert. Das dauert Stunden.

Um mir das Leben zu erleichtern habe ich angefangen C# zu lernen (macht mächtig Spaß). Ich möchte ein Programm schreiben, dass diese CSV-Datei automatisch filtert und konvertiert.
Meine jetzigen Programmierkünste reichen bisher aus um ein DataTable mit den Daten aus der CSC-Datei zu befüllen und im DataGridView anzuzeigen.

Als erstes möchte ich nun das DataTable um die Daten (Artikel) erleichtern die ich sowieso nicht im Shop haben möchte.
Wie kann ich nun in der DataTable programmgesteuert gezielt die Zeilen finden die in der Spalte in der ich suche nicht die richtigen Schlüsselworter hat.
z.B.


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
Art.-Nr.  Art.-name  Warengruppe
1    Artikelname1  Schuhe
2    Artikelname2  Schuhe          
3    Artikelname3  Jacken 
4    Artikelname4  Hosen 
5    Artikelname5  Schuhe 
6    Artikelname6  Jacken 
7    Artikelname7  Anzüge 
8    Artikelname8  Hüte 
9    Artikelname9  Hosen


Ich möchte nun das alles, außer den Zeilen die in der Spalte Warengruppe Schuhe und Hosen stehen haben, gelöscht wird, so das das neue DataTable so aussieht:

Quelltext
1:
2:
3:
4:
5:
6:
Art.-Nr.  Art.-name  Warengruppe
1    Artikelname1  Schuhe
2    Artikelname2  Schuhe          
4    Artikelname4  Hosen 
5    Artikelname5  Schuhe 
9    Artikelname9  Hosen


Danach muß die Tabelle um Spalten ergänzt und befüllt werden und andere Spalten müssen gelöscht werden.
Und dann kommt die eigentliche Aufgabe. Ich möchte aus der ebenso bearbeiteten Tabelle des Vortages die Zeilen finden die sich geändert haben bzw. der alten Vortags-Tabelle noch gar nicht enthalten waren (es kommen fast täglich neue Artikel dazu). Alle Zeilen die in beiden Tabellen dann gleich sind können nun auch gelöscht werden.
Die so resultierende dritte "Differenzdaten-Tabelle" ist nun so klein wie möglich und soll dann wieder als CSV gespeichert werden.

Wenn ich die Tabelle nicht um den unnützen Ballast befreie dann dauert der Import in meine WaWi bei 60.000 Artikeln viele Stunden!

Ich bin nun seit einer Woche an dem Ganzen drann und verzweifel nun langsam aber sicher, da ich im Web kaum etwas hilfreiches hierzu gefunden habe. :cry:
:wink: Welcher nette Zeitgenosse kann mir hierbei gute Tipps oder besser noch Code-Beispiele geben um die oben genannten Teilaufgaben zu lösen?

Viele Grüße
Marc

Moderiert von user profile iconKha: Code-Tags hinzugefügt


danielf - Mi 24.03.10 00:17

Hallo und :welcome:,

bei .NET gibt es die Online-Hilfe MSDN. Dort findest du viele Beispiel zu relevanten Themen. Zum Beispiel Filtern von DataTable [http://msdn.microsoft.com/de-de/library/bb979485.aspx#ID0END].

Damit kannst du bequem dein Anliegen umsetzen.

Viel Spaß noch und Gruß


Kha - Mi 24.03.10 00:57

:welcome: !

Die von user profile icondanielf erwähnten Filter sind ganz nett, aber bei komplexeren oder gar dynamischen Abfragen würde ich lieber selbst filtern. Bei einer List<string[]> (ich sehe bis jetzt keinen Grund, die komplexere DataTable zu benutzen) könnte das so aussehen:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
HashSet<string> approvedGroups = new HashSet<string> { "Schuhe""Hosen" };

bool IsApproved(string[] item)
{
  return approvedGroups.Contains(item[2]);
}

List<string[]> filtered = items.FindAll(IsApproved);

So, extra mal auf anonyme Methoden verzichtet ;) . Kann aber natürlich gut sein, dass das trotzdem noch ein wenig zu viel für dich ist - in diesem Falle würde auch eine for-Schleife rückwärts über die Zeilen der DataTable genügen, in der du dann ggf. RemoveAt aufrufst.

Um zu dem wesentlich interessanteren Problem der Diff-Tabelle zu kommen (wieder auf List<string[]>, würde so aber auch mit DataTable funktionieren)... LINQ:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
// Kann sich die Warengruppe auch ändern?
class MyItemComparer : IEqualityComparer<DataTable>
{
  public bool Equals(string[] item1, string[] item2)
  {
    return item1[0] == item2[0] && item1[1] == item2[1];
  }

  public int GetHashCode(string[] item)
  {
    return item[0].GetHashCode() ^ item[1].GetHashCode();
  }
}

List<string[]> diff = itemsToday.Except(itemsYesterday, new MyItemComparer()).ToList(); // Ba Da Bing!

Wieder gäbe es natürlich umständlichere, aber einfacher zu erklärende Möglichkeiten. Aber ich warte lieber erst einmal auf dein genaues Feedback :) .


norman2306 - Mi 24.03.10 01:47

Ich werfe mal das hier noch in den Raum:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
//Holt alle Artikel in eine generische Auflistung die Bezeichnung Hose oder Schuhe beinhalten

            IEnumerable<DataRow> ArtikelInDerNeuenTabelle =
                from DataRow Artikel in NeueTabelle.Rows
                where
                        ((string)Artikel["Warengruppe"]).Contains("Hose") ||
                        ((string)Artikel["Warengruppe"]).Contains("Schuhe")
                select Artikel;

//Holt alle Spalten, die nicht in der alten Tabelle sind oder sich geändert haben.
//Der PrimaryKey von AlteTabelle muss dafür alle Spalten enthalten. Das kann man
//beliebig anpassen und z.B. nur bestimmte Spalten nach Änderung durchsuchen

            IEnumerable<DataRow> NeueArtikel =
                from DataRow Artikel in ArtikelInDerNeuenTabelle
                where !(AlteTabelle.Rows.Contains(Artikel.ItemArray))
                select Artikel;

//Befüllt eine neue Tabelle mit den geänderten Artikeln
            foreach (DataRow dr in NeueArtikel)
            {
                ResultierendeTabelle.ImportRow(dr);
            }


Alle Tabellen sollten mit den gleichen Column-Einträgen erstellt werden. Zumindest muss die resultierende Tabelle die Colums-Einträge der neuen Tabelle enthalten. Es kommt zwar zu keinem Fehler wenn nicht, aber die Daten sind sonst nicht auswertbar.

:-)


Marc Dirk - Mi 24.03.10 12:15

user profile icondanielf hat folgendes geschrieben Zum zitierten Posting springen:
Hallo und :welcome:,

bei .NET gibt es die Online-Hilfe MSDN. Dort findest du viele Beispiel zu relevanten Themen. Zum Beispiel Filtern von DataTable [http://msdn.microsoft.com/de-de/library/bb979485.aspx#ID0END].

Damit kannst du bequem dein Anliegen umsetzen.

Viel Spaß noch und Gruß





Danke für den Hinweis! Dort habe ich eine Menge Lesestoff.


Marc Dirk - Do 25.03.10 00:37

Hallo norman2306!

Vielen Dank für Deine Hilfe. Deine Vorschläge haben mich recht weit gebracht.
Leider kämpfe ich mit einem Fehler rum (Das Objekt des Typs "System.DBNull" kann nicht in Typ "System.String" umgewandelt werden.) der nur Auftritt weil in einigen Zellen der zu filternden Spalte nichts drin steht. Kann man diesen Fehler irgendwie im Code abfangen. Ist der Rest meines Code so o.K., oder kann er weiter optimiert werden, damit er schneller wird?
Ich bin für jeden Vorschlag offen!

Euch allen noch mal ganz herzlichen Dank für Eure Unterstützung!

Hier mein Code!


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
        // Das übergebene DataTable wird mit den Schlüsselwörtern in dem 
        // ebenfalls übergebenen List Objekt gefiltert. Der Filter 
        // wirkt hierbei auf die übergebene Spalte. Das Rückgabe Objekt
        // ist eine DataTable die um all die unerwünschten Zeilen erleichtert 
        // wurde.
        private DataTable GetfilteredDataTable(DataTable dt, List<string> filter, int row)
        {
           IEnumerable<DataRow> ArtikelInDerNeuenTabelle =
                from DataRow Artikel in dt.Rows
                where ((string)Artikel[row]).Contains(filter[0].ToString()) || 
                      ((string)Artikel[row]).Contains(filter[1].ToString())
                select Artikel;
            DataTable boundnewdt = ArtikelInDerNeuenTabelle.CopyToDataTable<DataRow>();
            
            return boundnewdt;
        }


Moderiert von user profile iconKha: C#-Tags hinzugefügt


Kha - Do 25.03.10 01:02

Dafür kannst du LINQ to DataSets benutzen: DataRowExtensions.Field liefert statt DBNull ein "gewöhnliches" null zurück.

C#-Quelltext
1:
var artikelInDerNeuenTabelle = dt.Rows.Where(artikel => filter.Contains(artikel.Field<string>(row));                    


user profile iconMarc Dirk hat folgendes geschrieben Zum zitierten Posting springen:
Ist der Rest meines Code so o.K., oder kann er weiter optimiert werden, damit er schneller wird?
Ich bin für jeden Vorschlag offen!
Ich hatte da zum Beispiel so einen Vorschlag: HashSet<T> könnte noch etwas schneller sein, wenn es etwas mehr als zwei Filter-Kategorien sind/werden.


norman2306 - Do 25.03.10 01:07

Freut mich, das es dir weiter geholfen hat :)

Hier mal ein Tip, wie du den Fehler umschiffen kannst

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
        private DataTable GetfilteredDataTable(DataTable dt, List<string> filter, int row)
        {
           IEnumerable<DataRow> ArtikelInDerNeuenTabelle =
                from DataRow Artikel in dt.Rows
                where Artikel[row] != System.DBNull && (
                      ((string)Artikel[row]).Contains(filter[0].ToString()) || 
                      ((string)Artikel[row]).Contains(filter[1].ToString()))
                select Artikel;
            DataTable boundnewdt = ArtikelInDerNeuenTabelle.CopyToDataTable<DataRow>();
            
            return boundnewdt;
        }


Um es schneller zu machen, müsstest du Khas vorschlag mit dem HashSet nutzen.


norman2306 - Do 25.03.10 01:08

Oh, da hatter er schon selber geschrieben :)


Marc Dirk - Do 25.03.10 02:08

Hallo Norman,

ich bekomme mit Deinem Vorschlag leider den folgenden Fehler "System.DBNull" ist "Typ" und im angegebenen Kontext nicht gültig.
Was kann ich tun? :cry:

Was sind HashSet<T> für Objekte und wie arbeitet man mit ihnen? :?:
Kann jemand meine Methode so umschreiben, dass sie mit HashSet<T> funktioniert?

Danke für Eure Hilfe!

Gruß

Marc


norman2306 - Do 25.03.10 08:12

Servus... war schon im Bett. Habe gerade kein Compiler, deswegen hatte ich es nicht getestet. Sorry, da hatte ich nicht aufgepasst. Dann versuch es mit dem "is" Schlüsselwort. Alternativ ginge auch "typeof". Am besten du testet, ob es als string interpretiert werden kann. Das ist eh besser als nur DBNull auszugrenzen:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
       private DataTable GetfilteredDataTable(DataTable dt, List<string> filter, int row)
        {
           IEnumerable<DataRow> ArtikelInDerNeuenTabelle =
                from DataRow Artikel in dt.Rows
                where Artikel[row] is string && (
                      ((string)Artikel[row]).Contains(filter[0].ToString()) || 
                      ((string)Artikel[row]).Contains(filter[1].ToString()))
                select Artikel;
            DataTable boundnewdt = ArtikelInDerNeuenTabelle.CopyToDataTable<DataRow>();
            
            return boundnewdt;
        }


Oder mit:

C#-Quelltext
1:
Artikel[row].GetType() == typeof(string)                    

Oder

C#-Quelltext
1:
!(Artikel[row] is System.DBNull)                    




Ist nicht getestet, das musst du machen.
Mit den Hashset, das wäre jetzt etwas aufwendiger zu erklären, da habe ich leider keine Zeit, muss auf arbeit. Ich schau aber mal da, ob ich Zeit finde.


Marc Dirk - Do 25.03.10 10:27

Super Danke!!!!!

"where Artikel[row] is string && (" hat einwandfrei funktioniert!

Jetzt muß ich nur noch rausbekommen wie ich den ganzen Inhalt von filter abgefragt bekomme, egal wie lang die Liste ist.

Danke!
Marc


norman2306 - Do 25.03.10 10:37

So jetzt:

Statt in ein DataTable zu importieren, importierst du dein csv in ein IEnumerable aus string[]. Das String-Array stellt dabei jeweils eine Zeile(Row) deiner Tabelle dar. Für jede Zeile fügst du der Liste ein neues String-Array hinzu. Somit generierst du eine eigene Tabelle, mit der du aber keine Typ-Konversation durchführen musst.

Dann nutzt du den Code den Kha gepostet hat, oder den, den ich gepostet habe. Welcher von den beiden schneller ist, musst du testen. Im Prinzip sparst du dir dadurch die Typkonversation und -überprüfung. Außerdem die ganzen DataBinding anfragen, Überprüfungen etc. die im DataGrid ablaufen. Damit dürftest du dann gut 30% schneller sein.

Hier mal ein Beispiel:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
namesspace MyApp
{
    public static class AddOnMethods
    {
        /// <summary>
        /// Überprüft, ob der String einen der gesuchten Strings enthält
        /// </summary>
        /// <param name="item"></param>
        /// <param name="searchStrings">Die Strings, nach denen gesucht werden soll</param>
        /// <returns></returns>
        public static bool ContainsAny(this string item, string[] searchStrings)
        {
            bool res = false;
            foreach (string ss in searchStrings)
            {
                res |= item.Contains(ss);
            }

            return res;
        }
    }

    class DeineMainClass
    {
        private IEnumerable<string[]> GetfilteredDataTable(IEnumerable<string[]> dt, string[] filter, int row)
        {
            return
                    from string[] Artikel in dt
                    where Artikel[row].ContainsAny(filter)
                    select Artikel;      
        }
    }
}


Marc Dirk - Do 25.03.10 11:03

Danke!
Ich habe jetzt noch einen Termin, werde danach aber versuchen Deinen Code zu implementieren, so fern ich ihn verstehe.
Gruß
Marc


Marc Dirk - Do 25.03.10 16:10

Hallo Kha,

auch Dir vielen Dank für Deinen Vorschlag.
Die Mischung aus dem Code von Dir und von Norman hat dann endlich den Durchbruch gebracht und er ist auch ausreichend schnell, so das ich im Augenblick nicht auf das HashSet<T> umsteigen werde.

Den Rest des Projektes werde ich dann auch noch posten wenn ich soweit bin.
Hier also der letztgültige Code für die erste Teillösung für alle die Ihn auch noch gebrauchen können.

Gruß
Marc


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
        // Das übergebene DataTable wird mit den Schlüsselwörtern in dem 
        // ebenfalls übergebenen List Objekt gefiltert. Der Filter 
        // wirkt hierbei auf die übergebene Spalte. Das Rückgabe Objekt
        // ist eine DataTable die um all die unerwünschten Zeilen erleichtert 
        // wurde.
        private DataTable GetfilteredDataTable(DataTable dt, List<string> filter, int col)
        {
            IEnumerable<DataRow> ArticleInNewDataTable =
                 from DataRow Article in dt.Rows
                 where Article[col] is string && (filter.Contains(Article.Field<string>(col)))
                 select Article;
            return ArticleInNewDataTable.CopyToDataTable<DataRow>();
        }


Moderiert von user profile iconKha: C#-Tags hinzugefügt


Kha - Do 25.03.10 17:44

user profile iconMarc Dirk hat folgendes geschrieben Zum zitierten Posting springen:
auch Dir vielen Dank für Deinen Vorschlag.
Die Mischung aus dem Code von Dir und von Norman hat dann endlich den Durchbruch gebracht und er ist auch ausreichend schnell, so das ich im Augenblick nicht auf das HashSet<T> umsteigen werde.
Ja, das HashSet würde wahrscheinlich nicht viel ändern. Solange die Verarbeitung in O(n) geschieht, spielt sie im Vergleich zum Einlesen der Datei wahrscheinlich sowieso keine Rolle.
Und wie ich geschrieben habe: Über .Field umgehst du die gesamte DBNull-Problematik, das is ist damit unnötig. Du kannst die gesamte from-Query durch meinen Code ersetzen.


Marc Dirk - Do 25.03.10 18:43

Hallo Kha,

ich habe Dein Beispiel mit ".Field" nicht kapiert. Dafür bin ich wohl noch zu grün hinter den Ohren.
Das bloße ersetzen der Query mit der einen Zeile aus Deinem Beispiel liefert logischerweise jede Menge Fehler zurück.
Ich vermag sie aber nicht entsprechend anzupassen.

Kannst Du meine Methode komplett so umschreiben wie Du es meinst und ein wenig kommentieren.
Ich hoffe das ich dann kapiere wie Du es gemeint hast.

Gruß

Marc


norman2306 - Do 25.03.10 19:01

einfach nur:


C#-Quelltext
1:
artikelInDerNeuenTabelle = dt.Rows.Where(artikel => filter.Contains(artikel.Field<string>(row));                    


das from und select brauchst dann nicht mehr. Das wird durch den Lambda-Ausdruck (=>) ersetzt. Das ist so eine Art Delegate ohne den Delegate explizit zu definieren. Aber dann kann man während des Debuggens keine Codeänderungen mehr machen, da gibt es dann Fehlermeldung.


Marc Dirk - Do 25.03.10 19:17

user profile iconnorman2306 hat folgendes geschrieben Zum zitierten Posting springen:
einfach nur:


C#-Quelltext
1:
artikelInDerNeuenTabelle = dt.Rows.Where(artikel => filter.Contains(artikel.Field<string>(row));                    


das from und select brauchst dann nicht mehr. Das wird durch den Lambda-Ausdruck (=>) ersetzt. Das ist so eine Art Delegate ohne den Delegate explizit zu definieren. Aber dann kann man während des Debuggens keine Codeänderungen mehr machen, da gibt es dann Fehlermeldung.



Aber bei mir klappt das nicht!


C#-Quelltext
1:
2:
3:
4:
5:
     private DataTable GetfilteredDataTable(DataTable dt, List<string> filter, int col)
        {
            var ArticleInNewDataTable = dt.Rows.where(Article => filter.Contains(Article.Field<string>(col)));
            return ArticleInNewDataTable.CopyToDataTable<DataRow>();
        }


Fehler "System.Data.DataRowCollection" enthält keine Definition für "where", und es konnte keine Erweiterungsmethode "where" gefunden werden, die ein erstes Argument vom Typ "System.Data.DataRowCollection" akzeptiert. (Fehlt eine Using-Direktive oder ein Assemblyverweis?)

What to do?????


Kha - Do 25.03.10 20:46

Diese verdammten untypisierten 1.1-Collections :< . Nimm dt.Rows.Cast<DataRow>().Where (Enumerable-Extension: IEnumerable -> IEnumerable<T>) oder in diesem speziellen Fall auch dt.AsEnumerable().Where (DataTableExtentions). Wusste gar nicht, dass Query Expressions mit einem expliziten Typ automatisch Cast() einfügen :zustimm: .


norman2306 - Do 25.03.10 21:29

ist sowie so egal, wie du es machst. Bei where wird die iteration nicht durchlaufen, sondern ein Verweis auf das orginal behalten und das predicate erst beim MoveNext() des Enumerable ausgewertet und wenn die auswertung negativ war, wird ein weiteres MoveNext ausgelöst, bevor er dir das element wirklich wieder gibt, wenn ich es richtig weiß. Deshalb ist es an der stelle etwas schneller- aber die Rechenzeit geht dann eben beim auslesen drauf... Also wenn du z.B. mit einer foreach-Schleife wieder aus dem Enumerable rausschreibst oder es umwandelst.


Kha - Do 25.03.10 21:35

Auf wen antwortest du gerade und was vergleichst du womit :gruebel: ?


norman2306 - Fr 26.03.10 17:34

Die Antwort bezog sich auf den Versuch (und die Beschwerde, dass es nicht klappt) von Marc


C#-Quelltext
1:
2:
3:
4:
  return
        from DataRow Article in dt.Rows
        where Article[col] is string && (filter.Contains(Article.Field<string>(col)))
        select Article;


durch so etwas wie


C#-Quelltext
1:
2:
return
   dt.Rows.Cast<DataGridViewRow>().Where(artikel => filter.Contains(artikel.Field<string>(row));


zu ersetzen. Ich habe versucht darauf hinzuweisen, dass ich das nicht für nötig erachte, da es weder etwas in der Geschwindigkeit noch in der Lesbarkeit des Codes bringt. Ich habe es nochmal überprüft. Die Funktionen machen sogar exakt das Gleiche. Die IEnumerable.Where-Funktion überschreibt lediglich die MoveNext()-Funktion des IEnumerable (Genaugenommen liefert es ein neues WhereIterator-Object zurück, das IEnumerable implementiert und ein MoveNext() besitzt, der den Delegate (predicate) verwendet, um zu entscheiden, ob er ein Element zurück gibt oder überspringt). Bei meiner Variante wird ebenfalls intern ein Verweis auf die where-Klausel hinterlegt und beim Iterieren abgerufen. Geschwindigkeitsmäßig auch absolut identisch
(für 4000000 Einträge brauchte er bei meiner Variante beim ersten Test {00:00:03.4154747} und beim Zweiten {00:00:03.1981564} und bei deiner {00:00:03.2910341} im Ersten und {00:00:03.6841353} im Zweiten (Abh. von der Reihenfolge des Aufrufs). Das würde ich mal als nahezu identisch einstufen. Der Ausschluss von DBNull muss in jedem Fall gemacht werden, dass habe ich auch gecheckt.

Naja, aber du hast wahrscheinlich recht und das will garniemand wissen:). Aber mir ist grad furchtbar langweilig, weil ich heute Urlaub habe:)


Kha - Sa 27.03.10 04:15

user profile iconnorman2306 hat folgendes geschrieben Zum zitierten Posting springen:
Ich habe versucht darauf hinzuweisen, dass ich das nicht für nötig erachte, da es weder etwas in der Geschwindigkeit noch in der Lesbarkeit des Codes bringt.
Na knapp kürzer ist zweite Version jedenfalls :zwinker: . Ich empfinde from x ... select x persönlich einfach als Code Smell, denn das select ist damit quasi nutzlos und es könnte eben (semantisch) prägnanter ohne Query Expression ausgedrückt werden.
Sowieso halte ich mich mit dem Gebrauch von Query Expressions eher zurück, da man meistens früher oder später auf irgendeine Funktion stößt, die kein Query-Expression-Äquivalent besitzt und damit die gesamte Abfrage zu einem Mischmasch aus beiden Formen verkommt. Aber wie gesagt, das fällt wahrscheinlich eher unter "persönliche Meinung" ;) .

user profile iconnorman2306 hat folgendes geschrieben Zum zitierten Posting springen:
Ich habe es nochmal überprüft. Die Funktionen machen sogar exakt das Gleiche.
Kein Wunder. Schau dir das Ergebnis einmal im Reflector an, beide erzeugen exakt den gleichen IL-Code :idea: . Gleichheit in Performance und Laziness-Verhalten also garantiert.

user profile iconnorman2306 hat folgendes geschrieben Zum zitierten Posting springen:
Der Ausschluss von DBNull muss in jedem Fall gemacht werden, dass habe ich auch gecheckt.
Interessant, warum funktioniert meine Version dann bei mir?

user profile iconnorman2306 hat folgendes geschrieben Zum zitierten Posting springen:
Naja, aber du hast wahrscheinlich recht und das will garniemand wissen:).
Für andere kann ich nicht mitreden, aber mich interessiert sowas auf jeden Fall :D .