Skip to content

Introduction of Progress Reporting with MySqlBackup.NET in WinForms

adriancs edited this page Aug 5, 2025 · 18 revisions

Part 1-1: Introduction of Progress Reporting with MySqlBackup.NET in WinForms

Main Content:


This article begins the series of doing progressing reporting with MySqlBackup.NET by introducing the basic walkthrough, concepts and core working mechanism.

The core concept is, there is a intermediary progress status caching layer between MySqlBackup.NET and the UI application.

An overview of a backup process in web application:

Example of MySqlBackup Progress Reporting UI

After you have understood the pattern and core mechanism of the progress reporting, these patterns can easily be replicated to any other forms of application, such as WinForms/WPF/Console, ASP.NET Web Forms, ASP.NET MVC, .NET Core Web App, etc.

In this series, we will be documenting the making of progress reporting in various application environment with various method:

Application Method Initiation of
Communication
Direction
Frontend to
Backend
Backend to
Frontend
Desktop Application
(WinForms/WPF/Console)
(Part 1: This article)
Multi-Threading Bi-Direction Yes Yes
Web Application (Part 2) HTTP Request One Way Yes
(Active Initiates)
Yes
(Passive Response)
Web Application (Part 3) Web Socket Bi-Direction Yes Yes
Web Application (Part 4) Server Sent Event (SSE) One Way No Yes

In desktop application, the concept of frontend and backend is a little obscure, as it is not as obvious as web application where there is a distinct identification of 2 separate layers of operations and presentations.

For the web application development, we'll be using Vanilla ASP.NET Web Forms as the UI Application to demonstrate the concept and idea of performing progress reporting with MySqlBackup.NET.

Vanilla ASP.NET Web Forms, refers to an architecture using pure HTML, JavaScript and CSS. (Zero ViewState, No Server Control, No User Control, No UpdatePanel)

Now, let us begin with the introduction of the fundamental mechanism of the process.

The Basics

MySqlBackup.NET provides the following progress values during the process:

Backup/Export

  • Total Rows
  • Current Rows (Current Processing)
  • Total Tables
  • Current Tables (Current Processing)
  • Current Table Name
  • Total Rows in Current Table
  • Current Rows in Current Table

Restore/Import

  • Total Bytes
  • Current Bytes

These values can be accessed/obtained through event subscriptions of

  • ExportProgressChanged (For Backup/Export)
  • ImportProgressChanged (For Restore/Import)

Example:

Backup/Export

void Backup()
{
    // optional, used for tracing task history
    int taskId = GetNewTaskId();
    
    string sqlFile = Server.MapPath("~/backup.sql");
    
    using (var conn = config.GetNewConnection())
    using (var cmd = conn.CreateCommand())
    using (var mb = new MySqlBackup(cmd))
    {
        conn.Open();
        
        // 200ms - report progress status 5 times per second
        mb.ExportInfo.IntervalForProgressReport = 200;
        
        // subscribe the event (without tracing task id)
        mb.ExportProgressChanged += Mb_ExportProgressChanged;
        
        // subscribe the event with tracing the task id
        mb.ExportProgressChanged += (sender, e) => Mb_ExportProgressChanged(sender, e, taskId);
        
        mb.ExportToFile(sqlFile);
    }
}

void Mb_ExportProgressChanged(object sender, ExportProgressArgs e)
{
    string CurrentTableName = e.CurrentTableName;
    long TotalRowsInCurrentTable = e.TotalRowsInCurrentTable;
    long TotalRowsInAllTables = e.TotalRowsInAllTables;
    long CurrentRowIndexInCurrentTable = e.CurrentRowIndexInCurrentTable;
    long CurrentRowIndexInAllTables = e.CurrentRowIndexInAllTables;
    int TotalTables = e.TotalTables;
    int CurrentTableIndex = e.CurrentTableIndex;
    
    // Percentage completed, this value requires manual calculation
    int PercentCompleted = CalculatePercent(TotalRowsInAllTables, CurrentRowIndexInAllTables);
}

void Mb_ExportProgressChanged(object sender, ExportProgressArgs e, int taskId)
{
    ...
}

Restore/Import

void Restore()
{
    // optional, used for tracing task history
    int taskId = GetNewTaskId();
    
    string sqlFile = Server.MapPath("~/backup.sql");
    
    using (var conn = config.GetNewConnection())
    using (var cmd = conn.CreateCommand())
    using (var mb = new MySqlBackup(cmd))
    {
        conn.Open();
        
        // for better real-time accuracy
        mb.ImportInfo.EnableParallelProcessing = false;

        // 200ms - report progress status 5 times per second
        mb.ImportInfo.IntervalForProgressReport = 200;
        
        // subscribe the event (without tracing task id)
        mb.ImportProgressChanged += Mb_ImportProgressChanged;
        
        // subscribe the event with tracing the task id
        mb.ImportProgressChanged += (sender, e) => Mb_ImportProgressChanged(sender, e, taskId);
        
        mb.ImportFromFile(filePathSql);
    }
}

void Mb_ImportProgressChanged(object sender, ExportProgressArgs e)
{
    long TotalBytes = e.TotalBytes;
    long CurrentBytes = e.CurrentBytes;
    
    // Percentage completed, this value requires manual calculation
    int PercentCompleted = CalculatePercent(TotalRowsInAllTables, CurrentRowIndexInAllTables);
}

void Mb_ImportProgressChanged(object sender, ExportProgressArgs e, int taskId)
{
    ...
}

If you are doing a simple one time progress report in desktop application (WinForms/WPF/Console), (more of this at the following), most probably you don't need task id tracing. Task ID is used by your application internally to trace and keep the history record of each backup or restore task.

Task Info (associates with task id) can be stored in another MySQL database or in an external 3rd party storage medium, such as SQLite.

Now, let's us move the next chapter, by looking at the progress reporting in action in Desktop Application: WinForms.

Progress Reporting in WinForms

As mentioned earlier, in between MySqlBackup.NET and your application UI, there is a layer or section of "Intermediary" progress status caching, refers as "The Control Centre".

MySqlBackup.NET will send progress status to the "Control Centre", then your application UI processing logic will retrieve values from it.

In desktop application (WinForms/WPF/Console), due to the nature of the app environment, the intermediary caching can be as simple as just cache it as the class level global variable.

Enabling multi-threading:

using System.Threading.Tasks;

The code behind of the Form:

public partial class Form1 : Form
{
    // MySql Connection String
    string constr = "server=localhost;user=root;pwd=pwd;database=db;convertzerodatetime=true;";

    // The global cached values (class level)
    string CurrentTableName = "";
    long TotalRowsInCurrentTable = 0L;
    long TotalRowsInAllTables = 0L;
    long CurrentRowIndexInCurrentTable = 0L;
    long CurrentRowIndexInAllTables = 0L;
    int TotalTables = 0;
    int CurrentTableIndex = 0;
    int PercentCompleted = 0;

    // ---------------------------
    // The Control Centre
    // task related message control centre
    // ---------------------------
    bool task_completed = false;
    bool has_error = false;
    string err_msg = "";

    public Form1()
    {
        InitializeComponent();
    }

    // The main button clicked event, initiating the backup process
    private void btBackup_Click(object sender, EventArgs e)
    {
        // 1st thread, this is the main thread that control the UI

        // reset the control centre
        task_completed = false;
        has_error = false;
        err_msg = "";

        // multi-threading
        // 2nd thread, run the backup on another thread for not locking the main UI thread
        _ = Task.Run(() => { BeginBackup(); });
    }

    // running in 2nd thread, starting the main backup process
    public void BeginBackup()
    {
        try
        {
            string sqlFile = @"D:\backup.sql";

            using (var conn = new MySqlConnection(constr))
            using (var cmd = conn.CreateCommand())
            using (var mb = new MySqlBackup(cmd))
            {
                conn.Open();

                // report the progress 5 times per second
                mb.ExportInfo.IntervalForProgressReport = 200;

                // subscribe the progress change event
                mb.ExportProgressChanged += Mb_ExportProgressChanged;

                // run the backup process
                mb.ExportToFile(sqlFile);
            }
        }
        catch (Exception ex)
        {
            // tell the control centre, there is an error
            has_error = true;
            err_msg = ex.Message;
        }

        // passing this point indicates the task is completed/stopped
        // mark the boolean flag to tell the UI for the completion
        
        task_completed = true;
    }

    void Mb_ExportProgressChanged(object sender, ExportProgressArgs e)
    {
        // caching all the values to global variables

        CurrentTableName = e.CurrentTableName;
        TotalRowsInCurrentTable = e.TotalRowsInCurrentTable;
        TotalRowsInAllTables = e.TotalRowsInAllTables;
        CurrentRowIndexInCurrentTable = e.CurrentRowIndexInCurrentTable;
        CurrentRowIndexInAllTables = e.CurrentRowIndexInAllTables;
        TotalTables = e.TotalTables;
        CurrentTableIndex = e.CurrentTableIndex;
        PercentCompleted = CalculatePercent(TotalRowsInAllTables, CurrentRowIndexInAllTables);
    }
    
    int CalculatePercent(long totalValue, long currentValue)
    {
        if (currentValue >= totalValue)
            return 100;

        if (totalValue == 0L || currentValue == 0L)
            return 0;

        return (int)((double)currentValue * 100.0 / (double)totalValue);
    }
}

Now, the values have been collected. Next, updating the UI.

Use a timer to update the UI to show the progress status every 200 milliseconds:

// a timer, serves as the 3rd thread for updating the UI
Timer timerUpdateUi = new Timer();

public Form1()
{
    InitializeComponent();

    timerUpdateUi.Tick += TimerUpdateUi_Tick;

    // update the UI 5 times per second
    timerUpdateUi.Interval = 200;
}

private void btBackup_Click(object sender, EventArgs e)
{
    user_request_cancel_task = false;
    task_completed = false;
    has_error = false;
    err_msg = "";

    _ = Task.Run(() => { BeginBackup(); });

    // 3rd thread, start the timer in background to intervally update the progress to the UI
    timerUpdateUi.Start();
}

// The timer ticked (elapsed) event will update the UI with global cached values
private void TimerUpdateUi_Tick(object sender, EventArgs e)
{
    // writing the values to the UI
    
    labelCurrentTableName.Text = CurrentTableName;
    labelTotalRowsInCurrentTable.Text = TotalRowsInCurrentTable.ToString();
    labelTotalRowsInAllTables.Text = TotalRowsInAllTables.ToString();
    labelCurrentRowIndexInCurrentTable.Text = CurrentRowIndexInCurrentTable.ToString();
    labelCurrentRowIndexInAllTables.Text = CurrentRowIndexInAllTables.ToString();
    labelTotalTables.Text = TotalTables.ToString();
    labelCurrentTableIndex.Text = CurrentTableIndex.ToString();
    labelPercentCompleted.Text = PercentCompleted.ToString();

    // task completion detection
    if (task_completed)
    {
        // task is completed (stopped)
        
        // stop the timer
        timerUpdateUi.Stop();

        // the task ended with error
        if (has_error)
        {
            MessageBox.Show($"Error: {err_msg}");
        }
        else
        {
            MessageBox.Show("Task completed successfully");
        }
    }
}

Handling task cancellation.

To be able to cancel the task before it's successful completion, simply put another button that trigger the "stop" signal.

private void btStop_Click(object sender, EventArgs e)
{
    // set the boolean flag to signal MySqlBackup.NET to stop
    user_request_cancel_task = true;
}

At the global variable (the contro centre), add a boolean flag for this:

// ---------------------------
// The Control Centre
// task related message control centre
// ---------------------------
bool user_request_cancel_task = false;
bool task_completed = false;
bool has_error = false;
string err_msg = "";

During the timer ticked event, handle the cancellation situation:

private void TimerUpdateUi_Tick(object sender, EventArgs e)
{
    labelCurrentTableName.Text = CurrentTableName;
    labelTotalRowsInCurrentTable.Text = TotalRowsInCurrentTable.ToString();
    labelTotalRowsInAllTables.Text = TotalRowsInAllTables.ToString();
    labelCurrentRowIndexInCurrentTable.Text = CurrentRowIndexInCurrentTable.ToString();
    labelCurrentRowIndexInAllTables.Text = CurrentRowIndexInAllTables.ToString();
    labelTotalTables.Text = TotalTables.ToString();
    labelCurrentTableIndex.Text = CurrentTableIndex.ToString();
    labelPercentCompleted.Text = PercentCompleted.ToString();

    // task completion detection
    if (task_completed)
    {
        // stop the timer
        timerUpdateUi.Stop();

        if (has_error)
        {
            MessageBox.Show($"Error: {err_msg}");
        }
        else if (user_request_cancel_task)
        {
            // indicating and handling of task cancellation
            MessageBox.Show($"Task is cancelled.");
        }
        else
        {
            MessageBox.Show("Task completed successfully");
        }
    }
}

Here is the extension of C# code for the Restore process that can be patched to above WinForms:

string constr = "server=localhost;user=root;pwd=pwd;database=db;convertzerodatetime=true;treattinyasboolean=true;";

// ---------------------------
// The Control Centre
// task related message control centre
// ---------------------------
bool user_request_cancel_task = false;
bool task_completed = false;
bool has_error = false;
string err_msg = "";

// a timer, serves as the 3rd thread for updating the UI
Timer timerUpdateUi = new Timer();

long CurrentBytes = 0L;
long TotalBytes = 0L;

public Form1()
{
 InitializeComponent();

 timerUpdateUi.Tick += TimerUpdateUi_Tick;

 // update the UI 5 times per second
 timerUpdateUi.Interval = 200;
}

private void btRestore_Click(object sender, EventArgs e)
{
    // 1st thread, this is the main thread that control the UI

    // reset task status control centre
    user_request_cancel_task = false;
    task_completed = false;
    has_error = false;
    err_msg = "";

    // 2nd thread, run the restore on another thread for not locking the main UI thread
    _ = Task.Run(() => { BeginRestore(); });

    // 3rd thread, start the timer in background to intervally update the progress to the UI
    timerUpdateUi.Start();
}

void BeginRestore()
{
    try
    {
        string sqlFile = @"D:\backup.sql";

        using (var conn = new MySqlConnection(constr))
        using (var cmd = conn.CreateCommand())
        using (var mb = new MySqlBackup(cmd))
        {
            conn.Open();
            
            // turn off parallel procesing to have a more real time feel
            mb.EnableParallelProcessing = false;

            // report the progress 5 times per second
            mb.ImportInfo.IntervalForProgressReport = 200;

            // subscribe the progress change event
            mb.ImportProgressChanged += Mb_ImportProgressChanged;

            // run the import process
            mb.ImportFromFile(sqlFile);
        }
    }
    catch (Exception ex)
    {
        has_error = true;
        err_msg = ex.Message;
    }

    // passing this point indicates the task is completed/stopped
    // mark the boolean flag to tell the UI for the completion
    task_completed = true;
}

private void Mb_ImportProgressChanged(object sender, ImportProgressArgs e)
{
    CurrentBytes=e.CurrentBytes;
    TotalBytes =e.TotalBytes;

    // detecting the user cancellation request
    if (user_request_cancel_task)
    {
        // calling the MySqlBackup.NET to stop
        ((MySqlBackup)sender).StopAllProcess();
    }
}

private void TimerUpdateUi_Tick(object sender, EventArgs e)
{
    labelTotalBytes.Text = TotalBytes.ToString();
    labelCurrentBytes.Text = CurrentBytes.ToString();

    // task completion detection
    if (task_completed)
    {
        // stop the timer
        timerUpdateUi.Stop();

        if (has_error)
        {
            MessageBox.Show($"Error: {err_msg}");
        }
        else if (user_request_cancel_task)
        {
            MessageBox.Show($"Task is cancelled.");
        }
        else
        {
            MessageBox.Show("Task completed successfully");
        }
    }
}

Now, you have understood the fundamentals of the making of progress reporting in Desktop Application.

Next chapters, we'll implement the logic in Web Application.


Source Code - WinForms Full Demo

We have developed a full demo for WinForms, the source code available below:

Clone this wiki locally