Laden...

DB1 zu DB2 einzelne Felder kopieren mit Progressbar

Erstellt von PreAlpha vor 11 Jahren Letzter Beitrag vor 11 Jahren 5.907 Views
P
PreAlpha Themenstarter:in
23 Beiträge seit 2012
vor 11 Jahren
DB1 zu DB2 einzelne Felder kopieren mit Progressbar

Hallo,

ich bin gerade dabei von einer Firebird DB (db1) zu einer anderen Firebird DB (db2) zu kopieren, da ich neue Tabellen+Feldnamen brauche.

Nun hätte ich gerne den Progress des Kopiervorgangs angezeigt. Der Kopiervorgang an sich wird vom Backgroundworker abgearbeitet.

Ich reporte den Progress in meiner Kopier-Schleife:


foreach (System.Data.DataRow db_datarow in db_ds.Tables["Inter"].Rows)
                {
                    db2_datarow["Nachname"] = db1_datarow["IName"];
                    i++;
                    backgroundWorker1.ReportProgress((i * 100) /(anzahl_db1_reihen));
                 }

Außerdem:


        private void backgroundWorker1_ProgressChanged(object sender,ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;
        }

Nun zeigt meine Progressbar zwar etwas an, jedoch füllt sich diese innerhalb von 2-3sec obwohl der Kopiervorgang noch nicht abgeschlossen ist (dauert insgesamt momentan etwa 2-3min).

Ich weis momentan nicht so recht an was das liegen könnte. Vllt. hat von euch jemand eine Idee ?

LG

verwendetes Datenbanksystem: Firebird

16.842 Beiträge seit 2008
vor 11 Jahren

Hast mal mit dem Debugger durchgesteppt, was überhaupt berechnet wird?

F
10.010 Beiträge seit 2004
vor 11 Jahren

Vorallem ob anzahl_db1_reihen gesetzt ist.

P
PreAlpha Themenstarter:in
23 Beiträge seit 2012
vor 11 Jahren

Hast mal mit dem Debugger durchgesteppt, was überhaupt berechnet wird?

Ja, wenn ich durchsteppe zählt er mein e.ProgressPercentage langsam hoch.

Vorallem ob anzahl_db1_reihen gesetzt ist.

Mein anzahl_db1_reihen ist gesetzt. Das habe ich bereits überprüft.

LG

16.842 Beiträge seit 2008
vor 11 Jahren

Dann stimmt an der Gesagtlogik was nicht. Aber das sieht man nicht, weil Du hier nicht alles an Code zeigst, was hier relevant ist.
Könnte auch nen Zugriffsfehler durch die Threads sein. Aber kann man mit dem CodeSchnippsel nur raten.

C
258 Beiträge seit 2011
vor 11 Jahren

Wie hast die limits deiner Progressbar gesetzt
bzw geht sie auch von 0- 100 und dein ProgressPercentage auch ?

P
PreAlpha Themenstarter:in
23 Beiträge seit 2012
vor 11 Jahren

Hallo,


            backgroundWorker1.WorkerSupportsCancellation = true;
            backgroundWorker1.WorkerReportsProgress = true;
            progressBar1.Maximum = 100;
            progressBar1.Minimum = 0;
            progressBar1.Step = 1;

Meine ProgressPercentage geht auch von 0-100

P
PreAlpha Themenstarter:in
23 Beiträge seit 2012
vor 11 Jahren

Mir fällt da gerade ein:

Nach der foreach-Schleife update ich die DB mit:
db2.Update(db2_ds, "Kontakte");

Ist es möglich, das die foreach-Schleife in der progressBar korrekt dargestellt wird, jedoch das Update jenes ist, welches soviel Zeit beansprucht ? Wie könnte ich dieses Updaten der DB in den progress mit einschließen ?

//Edit: habe gerade mal das update mit in die foreach schleife gepackt. So funktioniert es. Wollte zwar alles in einem Rutsch updaten, aber so habe ich wenigstens meine ProgressBar.

16.842 Beiträge seit 2008
vor 11 Jahren

Wenn Du es genau und unperformant willst, Du musst Du das Update bei jedem Element abschicken.
Wie lang ein Gesamt-Update braucht kann man nie sagen. In so einem Fall schätzt man.

M
184 Beiträge seit 2012
vor 11 Jahren

Zur Performance:
Solche clientseitigen Batch-Inserts würde ich immer über DataReader, Prepared Statements und Parametern lösen...
Bist du auf DataSets angewiesen?

3.825 Beiträge seit 2006
vor 11 Jahren

Dataset ist gleichschnell wie eigene Statements, wenn man nicht jede Zeile speichert sondern Blöcke in der Größe von 1 - 500 MB.

Bei mir hat ein Datenimport von einigen Hundertausend Datensätze einzeln 36 h gedauert, in Blöcken von max. 500 MB nur 1 h.

Grüße Bernd

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

C
258 Beiträge seit 2011
vor 11 Jahren

Wenn du das Ganze Performanter machen willst aber die Progress bar nicht verlieren willst kannst du auch einfach nach rowCount / 100 updaten (oder noch genauer pro balken in der Bar)

Wie viel das bringt hängt stark davon ab wieviele Zeilen du schreibst.

P
PreAlpha Themenstarter:in
23 Beiträge seit 2012
vor 11 Jahren

Hallo,

danke für die vielen Beiträge. Die Perfomance stört mich hier nicht so sehr. Das Programm wird vermutlich eh nur mal alle paar Jahre aus dem Datensalat gewühlt. Viel mehr stört mich gerade das beim canceln des backgroundworker trotzdem in die "erfolgreich erledigt" Bedingung gesprungen wird (Race).


        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                MessageBox.Show("Vorgang abgebrochen!");
                unlock_textfelder();
            }
            else
            {
                MessageBox.Show("Done!");
                unlock_textfelder();
            }
        }

Die korrekte Meldung wäre mir jedoch schon wichtig. Gibt es da ein Patentrezept um soetwas zu vermeiden ?

LG

C
258 Beiträge seit 2011
vor 11 Jahren

Leider kann man anhand des Schnipsels nicht sagen wo dein Problem liegt jedoch bezweifle ich eine Race-Condition da du ja entweder das event mit Canceld auf true oder false feuerst.

Kannst du die Stelle in deinem BackgroundWorker Posten an denen die events sind / an der du dafür sorgst das der worker sauber terminiert?

P
PreAlpha Themenstarter:in
23 Beiträge seit 2012
vor 11 Jahren

Naja, einfach mein Abort-Button:


        private void iAbbrechenButton_Click(object sender, EventArgs e)
        {
            backgroundWorker1.CancelAsync();
        }

In meiner foreach-Schleife ist noch folgendes:


                    if (backgroundWorker1.CancellationPending == false)
                    {
                        backgroundWorker1.ReportProgress((db2.Tables["Kontakte"].Rows.Count * 100) / (anzahl_db1_reihen));
                    }
                    else
                    {
                        break;
                    }
C
258 Beiträge seit 2011
vor 11 Jahren

Laut MSDN sollte dein Verhalten nur auftreten wenn du das CancelAsync in der DoWork Methode aufrufst.

BackgroundWorker.CancelAsync()

Im notfall kannst du das ganze durch eine eigene Mechanik ersetzten auch wenn ich nicht weiß warum es zu deiner Race Condition kommt.

sowas wie:


while(this.cancelled || work finished)
{
    //work
}

und im button dann cancelled setzen
und in der WorkerCompleted auf das eigen cancelled abfragen

P
PreAlpha Themenstarter:in
23 Beiträge seit 2012
vor 11 Jahren

In meiner DoWork steht nur folgendes:


        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {


            if (backgroundWorker1.CancellationPending)
            {
                e.Cancel = true;
            }
            else
            {
                Copy_db2_to_db1();
            }
        }
3.430 Beiträge seit 2007
vor 11 Jahren

Hallo,

das bringt so leider nix.
Weil die Do-Work Methode wird nur einmal aufgerufen.
Beim Aufruf das Cancellation-Pending natürlich noch false.
Anschließend rufst du die Methode auf wo du die Daten kopierst.
Und diese Methode hat keine Ahnung von den CancellationPending.

Du musst in dieser besagten Methode abfragen ob das CancellationPending true ist und dann den Vorgang gegebenfalls abbrechen.

Grüße
Michael

P
PreAlpha Themenstarter:in
23 Beiträge seit 2012
vor 11 Jahren

Wenn ich das richtig verstanden habe, soll ich in meine foreach-Schleife welche die Daten kopiert sowas packen:


                    if (backgroundWorker1.CancellationPending == false)
                    {
                        backgroundWorker1.ReportProgress((pro_ds.Tables["Kontakte"].Rows.Count * 100) / (anzahl_imos_reihen));
                    }
                    else
                    {
                        backgroundWorker1.CancelAsync(); // <-- Das hier
                        break;
                    }

Das funktioniert aber ebenfalls nicht. Es wird immernoch die Meldung "Done!" ausgegeben obwohl "Abbruch" erscheinen sollte.

5.658 Beiträge seit 2006
vor 11 Jahren

Hi PreAlpha,

so kann das aber nicht funktionieren. Zuerst prüfst du das CancellationPending-Flag, das du aber erst mit CancelAsync() setzt, wenn es bereits gesetzt ist.

Der Vorgang soll vom Benutzer abgebrochen werden, deshalb mußt du auf ein Benutzer-Ereignis reagieren. Das CancellationPending-Property sollte also beim Klick auf die Schaltfläche gesetzt werden, und deine Routine sollte sich beenden, wenn das Flag gesetzt ist. Siehe auch das Beispiel in der Doku unter BackgroundWorker.

Christian

PS: [Tipp] Anfängerfehler == true / == false

Weeks of programming can save you hours of planning

P
PreAlpha Themenstarter:in
23 Beiträge seit 2012
vor 11 Jahren

Hi,

es IST ein Benutzer-Ereignis, siehe meinen früheren post weiter oben:

Naja, einfach mein Abort-Button:

  
        private void iAbbrechenButton_Click(object sender, EventArgs e)  
        {  
            backgroundWorker1.CancelAsync();  
        }  

In meiner foreach-Schleife ist noch folgendes:

  
                    if (backgroundWorker1.CancellationPending == false)  
                    {  
                        backgroundWorker1.ReportProgress((db2.Tables["Kontakte"].Rows.Count * 100) / (anzahl_db1_reihen));  
                    }  
                    else  
                    {  
                        break;  
                    }  
P
PreAlpha Themenstarter:in
23 Beiträge seit 2012
vor 11 Jahren

Ich habe jetzt einfach eine eigene bool variable "cancelled" eingeführt welche ich beim drücken auf den Abort Button auf true setze und die Methode CancelAsync() aufrufe. In RunWorkerCompleted prüfe ich nun einfach ob cancelled true ist und werfe dann die richtige Meldung aus.

Komischerweise funktioniert das einwandfrei. Wundert mich nur einwenig, da CancelAsync() ja eigentlich e.Cancelled auf true setzen sollte.