Autor |
Beitrag |
KostaK
      
Beiträge: 34
|
Verfasst: Di 23.03.10 18:56
Hallo Leut,
hab ein Grundlegendes Problem (generell) mit DataTable und benötige die vorgehensweise wie es gehen soll.
Ist:
Und zwar importiere ich Daten von einer csv Datei in eine Tabelle (access DB).
Zur Zeit jeden Datensatz mit einem INSET oder UPDATE Befehl.
Bei 40000 datensätzen dauert es eine Stunde -> zu lange.
Sol:
hab ne Testanwendung geschrieben und vesuche nun zuerst alles in ein DataTable zu speichern und in einem Rutsch in die DB zu sichern -> das auch funktioniert. Und auch seht schnell (sekunden)
DataTable comp = new DataTable("companies");
//comp.Columns.Add("ID",typeof(int));
comp.Columns.Add("firmname", typeof(string));
comp.Columns.Add("hrnummer", typeof(string));
comp.Columns.Add("isolcode", typeof(string));
comp.Columns.Add("solstatus", typeof(string));
comp.Columns.Add("bstag", typeof(string));
for (int j = 1; j < parsedData.Count; j++)
{
comp.Rows.Add(parsedData[j][0].Replace("'", ""), parsedData[j][1].Replace("'", ""),
parsedData[j][2].Replace("'", ""), parsedData[j][3].Replace("'", ""), parsedData[j][4].Replace("'", ""));
}
OleDbDataAdapter objAdapterTest = new OleDbDataAdapter("Select * from companies", objCon);
OleDbCommandBuilder objCB = new OleDbCommandBuilder(objAdapterTest);
objAdapterTest.Update(comp);
Das Problem:
Es geht mir dadrum wenn ich die gleiche csv Datei importiere doppelte einträge zu vermeiden, der Code oben importiert alles aufs neue.
Wie kann ich Prüfen ob ein Datensatz vorhanden ist oder nicht -> in der DB tabelle ist "ID" der PK.
Gruß Kosta
|
|
danielf
      
Beiträge: 1012
Erhaltene Danke: 24
Windows XP
C#, Visual Studio
|
Verfasst: Di 23.03.10 19:18
Hallo,
mit [ cs]code[ /cs] (ohne Leerzeichen) kannst du Code als solchen deklarieren. Das macht das Lesen für uns einfacher, Danke.
Zu deinem Problem: Ich vermute du hast in deinem cvs-Datei kein ID oder ähnliches. Deshalb musst du definieren woran du erkennst, das dies ein doppelter Eintrag ist. Ich vermute es ist eine kombination aus isolcode und hrnnummer?
Wenn du diesen Schlüssel erstellt hast (kannst eine Klasse machen und die equals Methode überschreiben) benötigst du einen DatenContainer (List, Array, Hashset,..) in dem du die eingefügten Schlüssel hinzufügst. Bei jedem Schleifendurchlauf musst du nun halt überprüfen, ob der Schlüssel bereits existiert oder nicht. Pseudo:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
| HashSet<Key> keys = new HashSet<Key>();
for (int j = 1; j < parsedData.Count; j++) { Key k = new Key(parsedData[j][1].Replace("'", ""), parsedData[j][2].Replace("'", "");
if (!keys.Contains(k)) { comp.Rows.Add(parsedData[j][0].Replace("'", ""), parsedData[j][1].Replace("'", ""), parsedData[j][2].Replace("'", ""), parsedData[j][3].Replace("'", ""), parsedData[j][4].Replace("'", "")); keys.Add(k); } } |
Gruß
|
|
JüTho
      
Beiträge: 2021
Erhaltene Danke: 6
Win XP Prof
C# 2.0 (#D für NET 2.0, dazu Firebird); früher Delphi 5 und Delphi 2005 Pro
|
Verfasst: Di 23.03.10 20:33
Hallo,
ich vermute, dass auch das Einlesen der csv-Datei noch verbessert werden kann. Schau einmal, was unter ConnectionStrings für Textdateien angeboten wird. Vor allem der Link auf Schema.ini sollte nützlich sein.
Zu dem genannten Problem gilt in der Tat: Woran willst du unterscheiden, ob der Datensatz schon vorhanden ist? Vielleicht gibt es noch andere Wege, nämlich mit den Möglichkeiten von DataTable.
Gruß Jürgen
|
|
KostaK 
      
Beiträge: 34
|
Verfasst: Di 23.03.10 21:26
Hi, super Danke für die schnellen Antworten.
Die csv habe ich in ein Array eingelesen (ging schnell).
Schema.ini schaue ich mir nacher an danke.
zu danielf.
dazu müsste ich doch in das DataTable die Tabele von der DB vollständig einglesen haben oder nicht ?
Gruß Kosta
|
|
JüTho
      
Beiträge: 2021
Erhaltene Danke: 6
Win XP Prof
C# 2.0 (#D für NET 2.0, dazu Firebird); früher Delphi 5 und Delphi 2005 Pro
|
Verfasst: Mi 24.03.10 10:18
KostaK hat folgendes geschrieben : | zu danielf.
dazu müsste ich doch in das DataTable die Tabele von der DB vollständig einglesen haben oder nicht ? |
Was die Menge der Zeilen betrifft: ja. Als Spalten benötigst du aber nur diejenigen, die zum Vergleich der Zeilen relevant sind (ID wegen des späteren Speicherns, isolcode und hrnnummer bei Daniels Vermutung zur Identifizierung).
Merke: "SELECT * ..." macht man sowieso nur in Ausnahmefällen. Damit nicht mehr Daten übertragen müssen als notwendig, werden die Spalten fast immer gezielt angegeben.
Gruß Jürgen
|
|
KostaK 
      
Beiträge: 34
|
Verfasst: Sa 27.03.10 15:43
Hi, ist etwas Zeit verstrichen aber jetzt bin ich wieder da. Danke.
also ich muß die ganze Tabelle einlesen (nicht alle Felder sondern nur die die benötigt werden) dann die Änderungen machen und mit dem Adapter die Updatemethode aufrufen und fertig.
Hab ich auch so gemacht und es geht super nur ein Problem die DB Tabelle (bzw die Tabellen) ist mittlerweilen zu groß geworden Sie hat 1,5 Mio Datensätze das sind die Positionen der Firmen und die companies Tabelle mit ca. 400.000 Datensätze.
Ich hab nun eine OutOfMemory Exception (beim Update -> OleDbAdapter) es sind zu viele DS.
Die erste möglichkeit jeden DS einzeln zu behandel war OK nur zu langsam.
Dei zweite möglichkeit alle zusammen zu behandeln super aber nun das Speicherproblem.
Vieleicht gibt es irgendwelche Einstellungen (in access oder ?) die weterhelfen.
Gruß Kosta Danke
|
|
JüTho
      
Beiträge: 2021
Erhaltene Danke: 6
Win XP Prof
C# 2.0 (#D für NET 2.0, dazu Firebird); früher Delphi 5 und Delphi 2005 Pro
|
Verfasst: Sa 27.03.10 16:07
Für so etwas nutzt man Transactions; ein richtiges DBMS bietet das an. Bei Access gibt es das IMHO nicht; also musst du es selbst nachahmen: Die csv-Datei kann vermutlich noch komplett eingelesen werden. Für den Datenabgleich und das Update solltest du durch geeignete WHERE-Bedingungen dafür sorgen, dass jeweils etwa 1000 Datensätze "am Stück" verarbeitet werden. Das sollte einen geeigneten Kompromiss darstellen.
Gruß Jürgen
|
|
KostaK 
      
Beiträge: 34
|
Verfasst: Sa 27.03.10 16:32
Hi JüTho,
das war aber schnell.
Wie meinst du das mit der WHERE beim Adapter wird doch die DataTable in die Ubdate übergeben.
Ich stell mal den Code hier rein das Select * from positions brauche ich letztendlich doch.
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: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170:
| List<string[]> parsedData = new List<string[]>();
try { using (StreamReader readFile = new StreamReader(csvfile, System.Text.Encoding.Default)) { string line; string[] row;
while ((line = readFile.ReadLine()) != null) { row = line.Split(';');
for (int i = 0; i < row.Length; i++) { if (row[i] == "" || row[i] == null) row[i] = "0"; }
parsedData.Add(row); } } } catch (Exception e) { }
int co = parsedData.Count / 100; int co2 = parsedData.Count / 100;
OleDbDataAdapter objAdapterComp = new OleDbDataAdapter("Select ID,firmname, hrnummer, isolcode, solstatus ,bstag from companies", objCon); OleDbCommandBuilder objCBComp = new OleDbCommandBuilder(objAdapterComp); DataTable companies = new DataTable();
objAdapterComp.Fill(companies);
companies.PrimaryKey = new DataColumn[] { companies.Columns["firmname"], companies.Columns["hrnummer"] };
List<string> errorfile = new List<string>(); for (int j = 1; j < parsedData.Count; j++) { Object[] Pkey = new object[] { parsedData[j][0].Replace("'", ""), parsedData[j][1].Replace("'", "") }; DataRow findRow = companies.Rows.Find(Pkey); try { if (findRow == null) { DataRow row = companies.NewRow(); row["firmname"] = parsedData[j][0].Replace("'", ""); row["hrnummer"] = parsedData[j][1].Replace("'", ""); row["isolcode"] = parsedData[j][2].Replace("'", ""); row["solstatus"] = parsedData[j][3].Replace("'", ""); row["bstag"] = parsedData[j][4].Replace("'", "");
companies.Rows.Add(row); } else { findRow["isolcode"] = parsedData[j][2].Replace("'", ""); findRow["solstatus"] = parsedData[j][3].Replace("'", ""); findRow["bstag"] = parsedData[j][4].Replace("'", ""); } } catch (Exception eee) { errorfile.Add("companies PK: " + Pkey.ToString()); }
if (j == co) { OnThreadEventSetProgress(); co += (co2 * 2); } }
co = parsedData.Count / 100; co2 = parsedData.Count / 100;
objAdapterComp.Update(companies); objAdapterComp.Fill(companies);
OleDbDataAdapter objAdapterPos = new OleDbDataAdapter("Select * from positions", objCon); OleDbCommandBuilder objCBPos = new OleDbCommandBuilder(objAdapterPos); DataTable positions = new DataTable(); objAdapterPos.Fill(positions);
positions.PrimaryKey = new DataColumn[] { positions.Columns["compID"], positions.Columns["jahr"] };
for (int j = 1; j < parsedData.Count; j++) { Object[] PkeyComp = new object[] { parsedData[j][0].Replace("'", ""), parsedData[j][1].Replace("'", "") }; DataRow findRowComp = companies.Rows.Find(PkeyComp); string error = ""; try { if (findRowComp != null) { int CompID = Convert.ToInt32(findRowComp["ID"]); error = "positions : " + CompID;
Object[] PkeyPos = new object[] { CompID, parsedData[j][5] };
DataRow findRowPos = positions.Rows.Find(PkeyPos);
DataRow row = null; if (findRowPos == null) { row = positions.NewRow(); error += " Jahr" + parsedData[j][5];
row["compID"] = CompID; row["jahr"] = parsedData[j][5];
} else { row = findRowPos; }
row["FIAS"] = parsedData[j][6]; row["CUAS"] = parsedData[j][7]; row["STOK"] = parsedData[j][8]; row["RETO"] = parsedData[j][9]; row["REOP"] = parsedData[j][10]; row["SECU"] = parsedData[j][11]; row["CASH"] = parsedData[j][12]; row["TOAS"] = parsedData[j][13]; row["SHEQ"] = parsedData[j][14]; row["REEA"] = parsedData[j][15]; row["SOPO"] = parsedData[j][16]; row["RSTO"] = parsedData[j][17]; row["RELT"] = parsedData[j][18]; row["LITO"] = parsedData[j][19]; row["LISH"] = parsedData[j][20]; row["LIOP"] = parsedData[j][21]; row["TUOV"] = parsedData[j][22]; row["MACO"] = parsedData[j][23]; row["DEPR"] = parsedData[j][24]; row["INPA"] = parsedData[j][25]; row["EAOP"] = parsedData[j][26]; row["EXRE"] = parsedData[j][27]; row["REBT"] = parsedData[j][28]; row["REAT"] = parsedData[j][29];
if (findRowPos == null) positions.Rows.Add(row);
} } catch (Exception ee) { errorfile.Add(error); }
if (j == co) { OnThreadEventSetProgress(); co += (co2 * 2); } } objAdapterPos.Update(positions);
string path = Application.StartupPath + "\\error.txt"; if(errorfile.Count > 0) File.WriteAllLines(path, errorfile.ToArray()); |
Ich verstehe nicht wie ich 1000 DS selektieren soll und bearbeiten.
Gruß Kosta
|
|
JüTho
      
Beiträge: 2021
Erhaltene Danke: 6
Win XP Prof
C# 2.0 (#D für NET 2.0, dazu Firebird); früher Delphi 5 und Delphi 2005 Pro
|
Verfasst: So 28.03.10 10:46
Tut mir leid, dein Code ist so umfangreich und für mich unübersichtlich, da will ich mich nicht durchwühlen. Ich glaube aber, du tust dir selbst einen Gefallen, wenn du ihn neu gliederst und einzelne Teile in einzelne Methoden auslagerst:
1. Einlesen der csv-Datei komplett
2. Einlesen der csv-Datei ab Zeile n für m Zeilen. Dazu kannst du vielleicht die Stream.Position merken oder beim ReadLine n Zeilen übergehen.
3. Einlesen der Vergleichsdaten; dabei weiß ich aber nicht, wie die WHERE-Bedingung aussehen kann (das verschweigst du noch, obwohl Daniel und ich eine Vermutung geäußert hatten)
4. Vergleich aller Daten mit einer Schleife und Auslagerung in
5. Vergleich einer Zeile mit einem Datensatz
6. Kopie oder so für einen vorhandenen Datensatz
7. Kopie oder so für einen nicht vorhandenen Datensatz
8. Kopie oder so für einen fehlerhaften Datensatz
9. Speichern der aktuellen Datensätze
Wenn du in dieser Weise den Arbeitsablauf besser gliederst, kommen dir vielleicht selbst Ideen zur Verbesserung.
Gruß Jürgen
PS. Für deine allererste if-Abfrage gibt es String.IsNullOrEmpty.
|
|
KostaK 
      
Beiträge: 34
|
Verfasst: Mo 29.03.10 09:01
Hi Jürgen,
das ist eine Methode.
Zu der WHERE Bedingung bzw. wie ich feststelle ob DS schon vorhanden mit firmname und hrnummer (in der companies),
und mit ID bzw compID in positions:
C#-Quelltext 1:
| companies.PrimaryKey = new DataColumn[] { companies.Columns["firmname"], companies.Columns["hrnummer"] }; |
Wegen den Transactions hab ich mal geschaut es gibt OleDbTransactions aber ich kann damit nichts anfangen und hab auch keine Beispiele gefunden.
Hab alles gelesen und werde mein bestes versuchen -> Dankeschön.
|
|
|