Как уведомить пользовательский интерфейс при чтении записей с 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 от пользовательского интерфейса, вы можете взглянуть на эту статью.