简体   繁体   中英

Howto Notify the UI while reading records with a SqlDatareader

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?

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'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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM