1

I have the following method

    public List<VatRate> GetAll( string cnString )
    {
        List<VatRate> result = new List<VatRate>();
        using (SqlConnection cn = new SqlConnection(cnString))
        {
            SqlCommand cmd = new SqlCommand();
            cmd.Connection = cn;
            cmd.CommandText = SQL_SELECT;
            cmd.CommandType = System.Data.CommandType.Text;
            cn.Open();
            SqlDataReader reader = cmd.ExecuteReader();
            if (reader.HasRows)
            {
                while (reader.Read())
                {
                    VatRate vr = new VatRate();
                    vr.IDVatRate = reader["IDVatRate"] == System.DBNull.Value ? Guid.Empty : (Guid)reader["IDVatRate"];
                    vr.Percent = reader["Percent"].XxNullDecimal();
                    vr.Type = reader["Type"].XxNullString();
                    vr.VatRateDescription = reader["VatRateDescription"].XxNullString();
                }
            }
            reader.Close();
            cn.Close();
        }
        return result;
    }

It will be used in a WPF application, and I want to be able to inform the UI of the reading progress. Do I have to raise a simple event? something like OnProgress(new MyProgressEventHandler(recordcounter)); I know for certain that a method like this will freeze the UI while executing, is there something better that can be done for example using the asyncronous methods to still wait for the method execution but be able to inform the user of what it is doing?

Sabrina_cs
  • 421
  • 3
  • 18
  • You should be able to make the method `async` and then just `await` the call. – itsme86 Jun 02 '16 at 22:25
  • Maybe, An idea of Progressbar with BackgroundWorker might be one of references.. – Kay Lee Jun 02 '16 at 23:02
  • The async await seems a nice way to proceed, but it does not solves my progress notification problem, do I have to set the async await and use an event to notify the UI ? and in this case are there problems of cross threads? Thank you in advance – Sabrina_cs Jun 05 '16 at 17:08

2 Answers2

2

you can pass in a IProgress<T> and use it's Report(T) method. If the object backing the interface is a Progress<T> it will automaticly marsal the callback on the UI if the object was created on the UI thread.

//elsewhere
public class GetAllProgress
{
    public GetAllProgress(int count, int total)
    {
        Count = count;
        Total = total;
    }

    public Count {get;}
    public Total {get;}
}

public List<VatRate> GetAll( string cnString, IProgress<GetAllProgress> progress )
{
    List<VatRate> result = new List<VatRate>();
    using (SqlConnection cn = new SqlConnection(cnString))
    {
        SqlCommand cmd = new SqlCommand();
        cmd.Connection = cn;
        cmd.CommandText = SQL_SELECT_COUNT;
        //You don't need to do set CommandType to text, it is the default value.
        cn.Open();

        var totalCount = (int)cmd.ExecuteScalar();
        progress.Report(new GetAllProgress(0, totalCount));

        cmd.CommandText = SQL_SELECT; 
        using(SqlDataReader reader = cmd.ExecuteReader())
        {
            //reader.HasRows is unnecessary, if there are no rows reader.Read() will be false the first call
            while (reader.Read())
            {
                VatRate vr = new VatRate();
                vr.IDVatRate = reader["IDVatRate"] == System.DBNull.Value ? Guid.Empty : (Guid)reader["IDVatRate"];
                vr.Percent = reader["Percent"].XxNullDecimal();
                vr.Type = reader["Type"].XxNullString();
                vr.VatRateDescription = reader["VatRateDescription"].XxNullString();
                result.Add(vr);
                progress.Report(new GetAllProgress(result.Count, TotalCount));
            }
            //I put reader in a using so you don't need to close it.
        }
        //You don't need to do cn.Close() inside a using
    }
    return result;
}

You can then call GetAll on a background thread (just be sure to call new Progress<GetAllProgress>() on the UI thread) or re-write the function to be async.

public async Task<List<VatRate>> GetAllAsync( string cnString, IProgress<GetAllProgress> progress )
{
    List<VatRate> result = new List<VatRate>();
    using (SqlConnection cn = new SqlConnection(cnString))
    {
        SqlCommand cmd = new SqlCommand();
        cmd.Connection = cn;
        cmd.CommandText = SQL_SELECT_COUNT;

        //The .ConfigureAwait(false) makes it so it does not need to wait for the UI thread to become available to continue with the code.
        await cn.OpenAsync().ConfigureAwait(false);

        var totalCount = (int)await cmd.ExecuteScalarAsync().ConfigureAwait(false);
        progress.Report(new GetAllProgress(0, totalCount));

        cmd.CommandText = SQL_SELECT; 
        using(SqlDataReader reader = await cmd.ExecuteReaderAsync().ConfigureAwait(false))
        {
            while (await reader.ReadAsync().ConfigureAwait(false))
            {
                VatRate vr = new VatRate();
                vr.IDVatRate = reader["IDVatRate"] == System.DBNull.Value ? Guid.Empty : (Guid)reader["IDVatRate"];
                vr.Percent = reader["Percent"].XxNullDecimal();
                vr.Type = reader["Type"].XxNullString();
                vr.VatRateDescription = reader["VatRateDescription"].XxNullString();
                result.Add(vr);
                progress.Report(new GetAllProgress(result.Count, TotalCount));
            }
        }
    }
    return result;
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • I find this code very interesting, but still it has a strong connection between the Data provider and the UI, what I'd like to do, if possible is detach the data class from the UI and just let it communicate it's progress. Maybe using an Event that is then intercepted by the business class that uses the data class and passed to the UI. I don't know if I'm clear here but if you know any example on this it would be great. – Sabrina_cs Jul 14 '16 at 08:04
0

@Scott Chamberlain's answer is great, and I'll suggest use the async/await solution.

Here, I just add some parts for WPF.

In WPF, you can make use of <ProgressBar>, and just set value to indicate the progress.

<Grid>
    <ProgressBar x:Name="progressBar" HorizontalAlignment="Left" Height="22" Margin="59,240,0,0" VerticalAlignment="Top" Width="383"/>
</Grid>

When you call GetAllAsync, you can actually write code as easy as below:

await GetAllAsync(new Progress<GetAllProgress>(progress=> { progressBar.Maximum = progress.Total; progressBar.Value = progress.Count; } ));

BTW, it's better to use MVVM pattern and decouple your ADO logic from UI, you can take a look at this article.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
Charlie
  • 2,141
  • 3
  • 19
  • 35
  • Since you where calling my GetAllPregress I changed your example to use the correct type of `Progress` to match what my function took in. – Scott Chamberlain Jul 12 '16 at 02:44
  • Thanks for the link to the article, I've to read it and download the code to understand exactly what the author does, it could be something in the direction of what I want to do. – Sabrina_cs Jul 14 '16 at 08:07