65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Async/Await 任务方法与 SQL 查询 .NET 4.5

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (30投票s)

2016 年 9 月 2 日

CPOL

5分钟阅读

viewsIcon

153075

downloadIcon

3073

Microsoft .NET 4.5 引入了新的 "async 和 await" 方法,使用 .NET 任务对象轻松实现异步。

引言

Microsoft .NET 4.5 引入了新的 "asyncawait" 方法,使用 .NET "Task" 对象轻松实现异步。

这使得开发人员可以更灵活地进行 async 调用,而不是使用传统的线程/回调方法。

在本文中,我构建了一个演示,展示如何将此应用于构建易于使用的 SQL 函数,这些函数可以 async 调用。

完整的演示和源代码可从本文获取。

Github

代码也已在 GitHub 上提供。欢迎您创建 issue 和 pull request。

入门

在本文中,我们将使用 .NET SQL Client 库连接到 Microsoft SQL Server 数据库,但是使用任务包装代码的相同方法可用于任何其他类型的连接。

在本例中,我们将把连接数据库以获取数据的代码包装到一个函数中,该函数根据所需的数据类型返回一个 "Task" 对象。

构建异步任务函数

我们首先构建返回任务对象的初始包装器。在本例中,我们将返回一个数据集。

题外话:任何数据类型都可以与任务对象一起返回,因此您可以返回 string、整数或自定义类。

以下是我们函数的が基本包装器。这段代码将根据数据类型返回一个任务对象,并包含一个用于添加我们自己的代码来构建和返回数据集的部分。

public Task<DataSet> GetDataSetAsync(string sConnectionString, string sSQL)
{
    return Task.Run(() =>
    {
        //YOUR CODE HERE
    });
}

现在,我们可以将连接服务器以获取数据的代码插入到此函数中。

示例 1:根据 SQL 查询填充数据集的函数

在本例中,代码已添加到函数体中,该函数使用标准的 SQL 连接 stringSystem.Data.SQLClient .NET 库中的对象来连接数据库并根据查询填充数据集。

数据集填充后,代码将使用标准的 "return" 调用返回 dataset

public Task<DataSet> GetDataSetAsync
(string sConnectionString, string sSQL, params SqlParameter[] parameters)
{
    return Task.Run(() =>
    {
        using (var newConnection = new SqlConnection(sConnectionString))
        using (var mySQLAdapter = new SqlDataAdapter(sSQL, newConnection))
        {
            mySQLAdapter.SelectCommand.CommandType = CommandType.Text;
            if (parameters != null) mySQLAdapter.SelectCommand.Parameters.AddRange(parameters);

            DataSet myDataSet = new DataSet();
            mySQLAdapter.Fill(myDataSet);
            return myDataSet;
        }
    });
}

我们现在可以 异步调用此函数,以根据我们的查询返回一个数据集。

示例 2:执行命令并返回受影响行数的函数

在本例中,我们将数据类型更改为整数,因为我们只想返回此函数结果的受影响行数。

public Task<int> ExecuteAsync(string sConnectionString, string sSQL, params SqlParameter[] parameters)
{
    return Task.Run(() =>
    {
        using (var newConnection = new SqlConnection(sConnectionString))
        using (var newCommand = new SqlCommand(sSQL, newConnection))
        {
            newCommand.CommandType = CommandType.Text;
            if (parameters != null) newCommand.Parameters.AddRange(parameters);

            newConnection.Open();
            return newCommand.ExecuteNonQuery();
        }
    });
}

此外,SqlCommand 对象上已存在异步任务函数,可用于进一步简化函数。

public async Task<int> ExecuteAsync(string sConnectionString, 
string sSQL, params SqlParameter[] parameters)
{
    using (var newConnection = new SqlConnection(sConnectionString))
    using (var newCommand = new SqlCommand(sSQL, newConnection))
    {
        newCommand.CommandType = CommandType.Text;
        if (parameters != null) newCommand.Parameters.AddRange(parameters);

        await newConnection.OpenAsync().ConfigureAwait(false);
        return await newCommand.ExecuteNonQueryAsync().ConfigureAwait(false);
    }
}

我们可以使用此函数执行 SQL 并获取受影响的行数。

调用异步任务函数

现在我们有了一个 async 函数来从 SQL 获取数据,我们可以使用 async/await 子句从任何方法异步调用它。

要调用函数以获取数据,我们首先在方法声明中添加 "async"。

private async <type> MyMethod()

现在我们的方法是一个 "async" 方法,我们可以在从 SQL 函数返回的任务上使用 "await" 选项。

执行任务函数

要执行任务函数,需要在函数调用前添加 "await"。

这将执行函数,并将 "Result" 返回到指定的变量(如果指定了)。

private async Task GetSomeData(string sSQL)
{
    //Use Async method to get data
    DataSet results = await GetDataSetAsync(sConnectionString, sSQL, sqlParams);

    //Populate once data received
    grdResults.DataSource = results.Tables[0];
}

变量不需要指定。您可以使用 "await" 调用函数,这将运行任务而不检索结果。

private async Task ExecuteSomeData(string sSQL)
{
    //Use Async method to get data
    await ExecuteAsync(sConnectionString, sSQL, sqlParams);
}

如果您需要进行其他异步调用,可以多次指定 "await" 选项。

您可以在 "await" 命令之前和之后进行 GUI 更新,因为只有 await 命令是异步运行的。

使用 "async/await" 最简单的方法是使用 "await" 选项,然后继续执行您的代码。

然而,.NET 任务对象在异步任务完成后提供了其他延续方法。

运行多个任务/链接任务

如果您需要运行多个 SQL 操作,有几种方法可以实现。

将任务指定为变量然后一起运行

您可以将多个命令指定为变量,然后异步地一次性运行它们,然后根据需要获取结果。

这可以通过将您的任务设置为变量来实现。

设置完成后,您需要将它们放入一个数组,然后使用 "Task.WhenAll" 来异步运行所有任务。

完成后,您可以访问任务对象上的结果。

//Setup tasks
Task<int> ExecuteTask1 = database.ExecuteAsync(sConnectionString, sExecuteSQL1, sqlParams);
Task<int> ExecuteTask2 = database.ExecuteAsync(sConnectionString, sExecuteSQL2, sqlParams);
Task<int> ExecuteTask3 = database.ExecuteAsync(sConnectionString, sExecuteSQL3, sqlParams);
//Add to array
Task<int>[] Tasks = new Task<int>[] { ExecuteTask1, ExecuteTask2, ExecuteTask3 };
//Run all
await Task.WhenAll(Tasks);
//Get results
int iRowsAffected = Tasks[0].Result + Tasks[1].Result + Tasks[2].Result;

使用 "ContinueWith" 调用其他异步任务

链接任务的另一种方法是使用 SQL 函数返回的任务对象上的 "ContinueWith" 方法。

在本例中,我们首先运行一个执行任务函数,然后一旦完成,我们运行一个选择任务函数以将数据集获取到变量中。

DataSet results = await database.ExecuteAsync(sConnectionString, sExecuteSQL, sqlParams).ContinueWith
(t => database.GetDataSetAsync(sConnectionString, sGetSQL, sqlParams)).Result;

使用 "ContinueWith" 调用标准方法

您可以在 "ContinueWith" 方法上调用标准方法。

在本例中,我们使用任务对象的 "ContinueWith" 将数据传递给一个单独的方法。

private async Task RunSQLQuery(string sSQL)
{
    //Use await method to get data
    await GetDataSetAsync
         (sConnectionString, sSQL, sqlParams).ContinueWith(t => PopulateResults(t.Result));
}
private void PopulateResults(DataSet results)
{
    //Do something with results
}

警告:以这种方式使用 "ContinueWith" 时,它将继续 async 线程,这意味着如果您打算更新 GUI 组件(如 gridtextbox 控件),您可能会遇到错误:

跨线程操作无效:控件 MyControl 在非创建它的线程上被访问。

在这些情况下,您可以在 "ContinueWith" 内部调用该方法,这将允许 GUI 更新。

await database.GetDataSetAsync(sConnectionString, sSQL, sqlParams).ContinueWith
(t => this.Invoke((Action)(() => { PopulateResultsToScreen(t.Result); })));

演示项目

本文中添加了一个简单的演示项目来演示 async/await 选项。该应用程序将要求您输入服务器详细信息以连接到现有数据库。

您可以输入您的 SQL 查询,然后使用 "Execute" 按钮来执行 SQL 查询。

代码存储在 "frmTestForm" 窗体中,如果您想查看此代码的实际运行情况,可以从 "Execute" 方法开始。

© . All rights reserved.