Hallöle Leute,
ich sitze da vor ein Paar Tagen vor einem Problem.
Ich habe da eine Anwendung die meine Festplatte Systematisch durchläuft und von jeder Datei einen Hashcode (mittels MD5) bildet. Dieser wird in eine SQL-Compact Datenbank eingetragen.
Sollte dabei Duplikate festgestellt werden, wird dies über ei Dialog mitgeteilt.
Die Anwendung läuft soweit einwandfrei, aber wenn der Prozess die 180MB erreicht, dann bekomme ich diese Exception:
System.Data.SqlServerCe.SqlCeException ist aufgetreten.
HResult=-2146233087
Message=Für diesen Vorgang ist nicht genügend Speicher verfügbar.
Source=SQL Server Compact ADO.NET Data Provider
NativeError=0
StackTrace:
bei Storage.DataProvider.DataProvider.InsertData(FileInfo file, String hash)
bei Duplicate.UiContext.FileChecker_FileItemChecked(Object sender, FileItemCheckEventArgs e)
InnerException:
Ich verstehe nicht, warum hier ein Problem auftaucht!
In der Datenbank befinden sich derzeit 11852 Zeilen und sie ist 3.988 KB groß.
Beim Zugriff auf die Datenbank wird vor jeder Abfrage Connected und nach der Abfrage wieder Disconnected.
Die Verarbeitung der Dateien läuft über einen anderen Thread über den ThreadPool.
Ich hoffe hier kann jemand helfen.
Gruß
10110010
Könnten die 180 MB eine Obergrenze für SQL Compact sein? Oder es hängt an was anderem als an dieser Größe.
Machst du irgendwas nicht zu? Tritt es immer bei der selben Anzahl Dateien auf?
Ich würde hier verschiedenes versuchen. Zuerst nicht immer auf/zumachen, sondern Connection offen lassen und alles auf einmal reinschieben. Gehts dann?
Was ist die "Verarbeitung"? Ist das was separates? Dann lass das mal weg, vielleicht hat das noch einen Nebeneffekt.
Nein, 180MB ist nicht die Obergrenze, aber es kann im Gegenteil genau das sein was Du vorschlägst, nämlich das eine offene Connection oder eine Transaction zu soetwas führt.
Auch z.b. Firebird macht das selbe wenn man mehr als ca 100.000 am stück per ADO.NET einfügt.
@B2:
Wie machst du das Insert und das suchen?
Hallöle,
das Insert wird so durchgeführt:
public FileData InsertData(System.IO.FileInfo file, string hash)
{
if (this.GetFileData(file) == null)
{
Storage.DataProvider.DB dataProvider = new Storage.DataProvider.DB();
try
{
dataProvider.SQL = "insert into Data (Directory, Filename, LastUpdate, Hash, [Exist]) values (@Directory, @Filename, @LastUpdate, @Hash, @Exist)";
dataProvider.Parameters.Add(new DbParameterHelper("@Directory", System.Data.DbType.String, file.DirectoryName));
dataProvider.Parameters.Add(new DbParameterHelper("@Filename", System.Data.DbType.String, file.Name));
dataProvider.Parameters.Add(new DbParameterHelper("@LastUpdate", System.Data.DbType.DateTime, file.LastWriteTimeUtc));
dataProvider.Parameters.Add(new DbParameterHelper("@Hash", System.Data.DbType.String, hash));
dataProvider.Parameters.Add(new DbParameterHelper("@Exist", System.Data.DbType.Boolean, 1));
dataProvider.Connect(this.ConnectionString, this.Provider);
dataProvider.ExecuteNoneQuery();
}
catch (System.Exception ex)
{
throw ex;
}
finally
{
dataProvider.Disconnect();
}
}
return this.GetFileData(file);
}
Die Verarbeitung sieht so aus, dass ich mit
foreach (DirectoryInfo item in directory.Directory.GetDirectories("*", SearchOption.TopDirectoryOnly))
die Verzeichnisse durchlaufe und rekusif zum untersten gehe.
von dort aus werden alle Files innerhalb der Ordner geladen und der Hashwert gebildet. dieser wird dann in die Datenbank geschrieben.
Anschließend wird geprüft, ob dieser Hashwert mehrmals in der Datenbank auftaucht. Wenn ja, dann werden alle Infos dieser Dateien in einem Dialog angezeigt.
Da diese Verarbeitung in einem eigenen Thread abläuft, finden hier viele Invokes statt. Ich denke aber, dass das keine Probleme machen sollte.
Im Großen und Ganzen war es das.
Ich werde mal nachsehen, ob es immer die selbe Datei ist, und wie viele Connections insgesamt auf gemacht wurden.
Gruß
10110010
Hi,
der dataProvider verwendet doch bestimmt. IDisposable. Dann lass doch das try catch finally weg und nimm using().
Ansonsten mal mit einem MemoryProfiler nachschauen.
Hallöle,
habe das mit dem Dispose mal versucht.
Leider ohne erfolg.
Da auf die Datenbank Asynchron zugegriffen wird, kann ich nicht die Verbindung ein mal öffnen und erst beim Beenden der Anwendung schließen.
habe mal nachgesehen wieviele connections hergestellt werden.
es sind immer unterschiedliche.
mal bei 88501, dann bei 88561, 88454 und 88476.
es sind auch immer andere Files.
Noch jemand ideen?
gruß
10110010
Hi,
ich würde in's Blaue hinein einfach mal alle 1000 Inserts ein Commit machen.
http://msdn.microsoft.com/en-us/library/esdw1h9d(v=vs.100).aspx
Gruß
f_igy
...aber warum hast Du denn da 88000 Connections? Ich hätte jetzt an eine Consumer/Producer-Queue etc gedacht, wo einige Tasks ihre Dateisystem-Ergebnisse reinstellen, die dann von wenigen/einen Schreiber abgearbeitet werden, jeder mit einer dedizierten Connection?
Wir können Dir da nicht weiter helfen da wir zuwenig von der Architektur wissen.
Das ist der komplett falsche Ansatz.
Wenn man so etwas so "einfach" parallelisiert, wird es selten gut.
Erstelle dir eine Queue die die Daten entgegen nimmt und arbeite diese ab.
Der SqlServer Compact ist eh nicht für sowas wirklich ausgelegt.
Das was Du da machst ist doch nichts anderes als eine Metadatenbank.
Nicht nur, dass Dein Ansatz völlig Käse ist (jedes File eine Connection) ist auch die Umgebung meiner Meinung nach "völlig"/ziemlich ungeeignet.
Es gibt hier spezialisierte Umgebungen, wie MongoDB GridFS, die Datei und Metdaten aus Anwendungssicht gemeinsam, auf Dateiebene aber getrennt halten (verwende ich auch in zwei Projekten sehr erfolgreich).
Zur Not belass es bei SQLite / SqlCe, aber vergewaltige doch die Datenschicht nicht so.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Hallöle Leute,
erstmal vielen Dank für eure Einwände.
Habe jetzt mal einige Änderungen vorgenommen.
Alle Datenbankanfragen werden jetzt in einen Stapel abgelegt und dieser von einem eigenen Thread verarbeitet.
In diesem wird dann auch nur zu beginn die Connection aufgebaut und beim beenden erst geschlossen.
Das ist auch schon mal besser.
Mein Memory-Leak habe ich auch gefunden.
die Threads mussten aktualisierungen des UIs vornehmen.
Dafür habe ich in den Jeweiligen Klassen ein Control erzeugt, das nur für den Invoke zuständig war. Beim Dispose habe ich das Handle nicht zerstört. 😕
Jetzt ist das super.
Vielen Dank nochmal für eure Hilfe.
Gruß
10110010
Dafür habe ich in den Jeweiligen Klassen ein Control erzeugt, das nur für den Invoke zuständig war.
Das ist auch der falsche Ansatz.
Hallöle,
Welcher wäre den der richtige?
Möchte ja dazulernen.
Gruß
10110010
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code