Autor |
Beitrag |
Marc Dirk
      
Beiträge: 41
|
Verfasst: Di 23.03.10 23:46
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.
 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 Kha: Code-Tags hinzugefügt
|
|
danielf
      
Beiträge: 1012
Erhaltene Danke: 24
Windows XP
C#, Visual Studio
|
Verfasst: Mi 24.03.10 00:17
Hallo und  ,
bei .NET gibt es die Online-Hilfe MSDN. Dort findest du viele Beispiel zu relevanten Themen. Zum Beispiel Filtern von DataTable.
Damit kannst du bequem dein Anliegen umsetzen.
Viel Spaß noch und Gruß
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: Mi 24.03.10 00:57
 !
Die von danielf 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:
| 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(); |
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
      
Beiträge: 222
Erhaltene Danke: 16
Win XP, Win 7 64-Bit
C# 4.0 (VS2010)
|
Verfasst: 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:
| IEnumerable<DataRow> ArtikelInDerNeuenTabelle = from DataRow Artikel in NeueTabelle.Rows where ((string)Artikel["Warengruppe"]).Contains("Hose") || ((string)Artikel["Warengruppe"]).Contains("Schuhe") select Artikel;
IEnumerable<DataRow> NeueArtikel = from DataRow Artikel in ArtikelInDerNeuenTabelle where !(AlteTabelle.Rows.Contains(Artikel.ItemArray)) select Artikel;
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 
      
Beiträge: 41
|
Verfasst: Mi 24.03.10 12:15
|
|
Marc Dirk 
      
Beiträge: 41
|
Verfasst: 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:
| 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 Kha: C#-Tags hinzugefügt
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: 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)); |
Marc Dirk hat folgendes geschrieben : | 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
      
Beiträge: 222
Erhaltene Danke: 16
Win XP, Win 7 64-Bit
C# 4.0 (VS2010)
|
Verfasst: 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
      
Beiträge: 222
Erhaltene Danke: 16
Win XP, Win 7 64-Bit
C# 4.0 (VS2010)
|
Verfasst: Do 25.03.10 01:08
Oh, da hatter er schon selber geschrieben 
|
|
Marc Dirk 
      
Beiträge: 41
|
Verfasst: 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?
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
      
Beiträge: 222
Erhaltene Danke: 16
Win XP, Win 7 64-Bit
C# 4.0 (VS2010)
|
Verfasst: 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 
      
Beiträge: 41
|
Verfasst: 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
      
Beiträge: 222
Erhaltene Danke: 16
Win XP, Win 7 64-Bit
C# 4.0 (VS2010)
|
Verfasst: 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:
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 { 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 
      
Beiträge: 41
|
Verfasst: 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 
      
Beiträge: 41
|
Verfasst: 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:
| 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 Kha: C#-Tags hinzugefügt
Zuletzt bearbeitet von Marc Dirk am Do 25.03.10 18:32, insgesamt 1-mal bearbeitet
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: Do 25.03.10 17:44
Marc Dirk hat folgendes geschrieben : | 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 
      
Beiträge: 41
|
Verfasst: 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
      
Beiträge: 222
Erhaltene Danke: 16
Win XP, Win 7 64-Bit
C# 4.0 (VS2010)
|
Verfasst: 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 
      
Beiträge: 41
|
Verfasst: Do 25.03.10 19:17
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: 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  .
_________________ >λ=
|
|