0

I want to cancel querying data from my Oracle database, when the user clicks on the "Cancel" button.

Below the simplified code of existing application:

Database connector

public interface IDatabaseConnector
{
    string Query { get; set; }

    void Fetch(Action<DataTable> action);
}

class DatabaseConnector : IDatabaseConnector
{
    private string connectionString = "";

    public string Query { get; set; }

    private OracleConnection connection { get; set; }
    private OracleCommand command { get; set; }

    public DatabaseConnector(string connectionString)
    {
        this.connectionString = connectionString;
    }

    public bool ConnectionOpened
    {
        get => connection.State == ConnectionState.Open;
    }

    public OracleConnection Connection { get => connection;  }

    public void Fetch(Action<DataTable> action)
    {
        try
        {
            connection = new OracleConnection(connectionString);
            command = new OracleCommand(Query, connection);

            connection.Open();

            using (var reader = command.ExecuteReader())
            {
                if (!reader.HasRows)
                {
                    throw new Exception("Empty result!");
                }

                // maybe I should stop querying here?
                var dataTable = new DataTable();
                dataTable.Load(reader);

                // run callback processor
                action(dataTable);
            }
        }
        finally
        {
            if (connection != null)
            {
                connection.Dispose();
            }

            if (command != null)
            {
                command.Dispose();
            }
        }
    }
    
    public void Close()
    {
        command.Cancel();
        command.Dispose();

        connection.Close();
        connection.Dispose();
    }
}

Form:

public class ProductsForm : Form
{
    public EntryForm EntryForm { get; set; }
    public ApplicationSettings Settings { get; set; }

    private BackgroundWorker backgroundWorker;
    private DatabaseConnector DbConnector;
    
    public ProductsForm()
    {
        InitializeComponent();
    }
    
    public ProductsForm(EntryForm parentForm, ApplicationSettings appSettings) : this()
    {
        EntryForm = parentForm;
        Settings = appSettings;

        backgroundWorker = new BackgroundWorker()
        {
            WorkerSupportsCancellation = true
        };

        backgroundWorker.DoWork += DoWork;
        backgroundWorker.RunWorkerCompleted += WorkCompleted;
    }
    
    public void DoWork(object sender, DoWorkEventArgs e)
    {
        Invoke(new Action(() => {
            ExecuteButton.Text = "Querying...";
            ExecuteButton.Enabled = false;
            StopButton.Enabled = true;
        }));

        DbConnector = new DatabaseConnector(Settings.DwhConnection.ToString());

        var repo = new ProductsRepository(DbConnector)
        {
            startDate = StartDateTimePicker.Value.ToShortDateString(),
            endDate = EndDateTimePicker.Value.ToShortDateString(),
            byPattern = ByPattrenCheckBox.Checked
        };

        try
        {
            // POINT 1
            repo.Query((datatable) =>
            {
                if (backgroundWorker.CancellationPending)
                {
                    MessageBox.Show("BackgroundWorker Canceled");

                    if (DbConnector.ConnectionOpened)
                    {
                        DbConnector.Close();
                    }

                    e.Cancel = true;
                    return;
                }

                Invoke(new Action(() =>
                {
                    QueryResultDataGridView.DataSource = datatable;
                    QueryResultDataGridView.SetColumntDateTimeFormat("Start time");

                    ExecuteButton.Enabled = true;
                    StopButton.Enabled = false;
                }));
            });
        }
        catch (Exception exception)
        {
            MessageBox.Show(exception.Message, Text);

            Invoke(new Action(() => {
                ExecuteButton.Enabled = true;
                StopButton.Enabled = false;
            }));
        }
    }
    
    public void WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
            if(DbConnector.ConnectionOpened)
            {
                DbConnector.Close();
            }
        }
        else if (e.Error != null)
        {
            MessageBox.Show(e.Error.StackTrace, "Error!");
        }
    }

    private void ExecuteButton_Click(object sender, EventArgs e)
    {
        backgroundWorker.RunWorkerAsync();
    }

    private void StopButton_Click(object sender, EventArgs e)
    {
        backgroundWorker.CancelAsync();
    }
}

Repo

public abstract class AbstractRepository
{
    public abstract void Query(Action<DataTable> callback);
}

public class ProductsRepository : AbstractRepository
{
    private IDatabaseConnector connector;
    public string startDate;
    public string endDate;
    public bool byPattern = false;

    public ProductsRepository(IDatabaseConnector connector)
    {
        this.connector = connector;
    }

    public override void Query(Action<DataTable> callback)
    {
        var query = QueriesSQL.GetAllProdutcs(startDate, endDate, byPattern);

        connector.Query = query;
        connector.Fetch(data => callback(data));
    }
}

When I cancel operation program continues querying for data and only when data is completely retrieved, it stops.

I found out that this happens because program call callback function that passed to DatabaseConnector.Fetch after data is retrieved.

So, how I can reorganazie code to stop querying for the data when cancel button has been clicked?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • What's your environment? Windows Forms? ASP.NET Webforms? Something else (if so: **what?**) ? – marc_s Jun 25 '21 at 12:43
  • My environment is Windows Forms – KantDevelop Jun 25 '21 at 13:09
  • A common usage of catch and finally together is to obtain and use resources in a try block, deal with exceptional circumstances in a catch block, and release the resources in the finally block. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/try-catch-finally -- In your Fetch code block Try Finally you are closing if (connection != null) connection.Dispose(); – Manik Jun 25 '21 at 13:44
  • 1
    Rather than calling `dataTable.Load(reader);`, instead fill a table in your own loop. In the loop, check if stop is requested, then call `command.Cancel();` followed by `reader.Close();`. If this doesn't work because of something related to Oracle, then check out this thread: https://stackoverflow.com/questions/889102/how-to-cancel-a-long-running-database-operation – Loathing Jun 25 '21 at 17:46

0 Answers0