【问题标题】:Howto Notify the UI while reading records with a SqlDatareader如何在使用 SqlDatareader 读取记录时通知 UI
【发布时间】:2016-07-20 22:11:36
【问题描述】:

我有以下方法

    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 应用程序中使用,我希望能够通知 UI 阅读进度。我必须提出一个简单的事件吗?就像是 OnProgress(new MyProgressEventHandler(recordcounter));我肯定知道这样的方法会在执行时冻结 UI,有没有更好的方法可以做,例如使用异步方法仍然等待方法执行但能够通知用户它在做什么?

【问题讨论】:

  • 您应该可以调用async 方法,然后只需调用await
  • 也许,一个带有 BackgroundWorker 的 Progressbar 的想法可能是参考之一..
  • 异步等待似乎是一个不错的方法,但它不能解决我的进度通知问题,我是否必须设置异步等待并使用事件来通知 UI?在这种情况下是否存在交叉线程问题?提前谢谢你

标签: c# wpf sqldatareader


【解决方案1】:

您可以传入IProgress&lt;T&gt; 并使用它的Report(T) 方法。如果支持接口的对象是Progress&lt;T&gt;,如果对象是在 UI 线程上创建的,它将自动在 UI 上编组回调。

//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(请务必在 UI 线程上调用 new Progress&lt;GetAllProgress&gt;())或将函数重写为异步。

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

【讨论】:

  • 我觉得这段代码很有趣,但它仍然在数据提供者和 UI 之间有很强的联系,如果可能的话,我想做的是将数据类从 UI 中分离出来,然后让它传达它的进展。也许使用一个事件,然后由使用数据类的业务类拦截并传递给 UI。我不知道我在这里是否清楚,但如果你知道这方面的任何例子,那就太好了。
【解决方案2】:

@Scott Chamberlain 的回答很棒,我建议使用 async/await 解决方案。

这里,我只是为 WPF 添加一些部分。

在WPF中,可以使用&lt;ProgressBar&gt;,只需设置值来表示进度。

<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 逻辑与 UI 解耦,你可以看看这个article

【讨论】:

  • 感谢文章的链接,我必须阅读并下载代码以准确了解作者所做的事情,这可能是我想做的事情。跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-10
相关资源
最近更新 更多