Как уведомить пользовательский интерфейс при чтении записей с SqlDatareader

У меня есть следующий метод

    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;
    }

Он будет использоваться в приложении WPF, и я хочу иметь возможность информировать пользовательский интерфейс о ходе чтения. Должен ли я поднять простое событие? что-то вроде OnProgress(новый MyProgressEventHandler(recordcounter)); Я точно знаю, что такой метод замораживает пользовательский интерфейс во время выполнения. Есть ли что-то лучшее, что можно сделать, например, используя асинхронные методы, чтобы все еще ждать выполнения метода, но иметь возможность информировать пользователя о том, что он делает?

2 ответа

Вы можете пройти в IProgress<T> и использовать это Report(T) метод. Если объект, поддерживающий интерфейс, является Progress<T> он автоматически выполняет обратный вызов в пользовательском интерфейсе, если объект был создан в потоке пользовательского интерфейса.

//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;
}

Вы можете позвонить GetAll в фоновом потоке (просто обязательно позвоните new Progress<GetAllProgress>() в потоке пользовательского интерфейса) или переписать функцию, которая будет асинхронной.

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 Чемберлена великолепен, и я предложу использовать решение async/await.

Здесь я просто добавляю некоторые части для WPF.

В WPF вы можете использовать <ProgressBar>и просто установите значение, чтобы указать прогресс.

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

Когда вы звоните GetAllAsyncВы можете написать код так просто, как показано ниже:

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

Кстати, лучше использовать шаблон MVVM и отделить логику ADO от пользовательского интерфейса, вы можете взглянуть на эту статью.

Другие вопросы по тегам