为什么我的IEnumerable <t>在异步调用时在using块内未执行迭代器的地方?

时间:2018-10-31 14:42:41

标签: c# asynchronous async-await dapper

我确定这不是Dapper的问题,但是在以下代码段中,我发现从未执行提供给Where函数的谓词。

private async Task<IEnumerable<Product>> GetProducts()
{
    using (var connection = await _connectionFactory.Create())
    {
        var products = await connection.QueryAsync<Product>("select * from Products");
        return products.Where(p => p.Active);
    }
}

但是,如果我将操作移至using之外,则会执行该操作。

private async Task<IEnumerable<Product>> GetProducts()
{
    var products = Enumerable.Empty<Product>();

    using (var connection = await _connectionFactory.Create())
    {
        products = await connection.QueryAsync<Product>("select * from Products");
    }

    return products.Where(p => p.Active);
}

是否存在某种延迟执行?

2 个答案:

答案 0 :(得分:1)

在第一个示例中,如果可以在return语句中进行以下修改:

return products.Where(p => p.Active).ToList();然后它将按预期运行

情况1:

这里的问题是Where上应用的IEnumerable<Product>子句被推迟执行,它以Task的形式包装在Task<IEnumerable<Product>>中返回,但是现在您需要运行Task(也将执行谓词),不确定您如何执行Task或以这种方式包装延迟执行会出现问题,但是最终结果是谓词未如预期那样生效,即使将其应用于Dapper结果,默认情况下也会缓冲(无流)

情况2:

它在第二种情况下有效,因为您完全摆脱了延迟执行,Enumerable.Empty<Product>()确保首先分配了内存,因此谓词在应用后立即执行,没有延迟执行。实际上,谓词是在using块之外应用的任何方式


在Async方法中,您要使用using块来处理连接,这主要是因为Dapper内部分配了内存,这就是为什么所有数据都通过其发送的原因,因此连接是disposed,而谓词永远不会被执行。我有类似的示例,它不依赖于数据库连接,并且可以按预期工作,因此我们可以推断出连接处置在谓词不执行中起了作用。在第二种情况下,谓词在using块外部应用,因此连接处理没有任何作用,并且已经分配了内存。


示例代码(使用LinqPad):

async Task Main()
{
    var result = await GetTest();   
    result.Dump();
}

public async Task<IEnumerable<Test>> GetTest()
{
    var value = await GetTestDb();
    return value.Where(x => x.Id == 1);
}

public async Task<IEnumerable<Test>> GetTestDb()
{
    return await Task.FromResult(
    new List<Test>
    {
        new Test{Id = 1, Name = "M"},
        new Test{Id = 2, Name = "S"}
    }
    );
}

public class Test
{
    public int Id { get; set; }

    public string Name { get; set; }
}

结果:

enter image description here

答案 1 :(得分:0)

您的谓词实际上并不充当谓词。这只是一个LINQ调用。

return products.Where(p => p.Active);

执行以上行时,根据您的查询,表中的所有行已经填充products,并在上一行中调用QueryAsync

关于Dapper的好处是,它可以完全控制对的查询写操作。因此,如果要过滤记录,为什么不以这种方式编写查询?

using(var connection = ....)
{
    var param = new DynamicParameters();
    param.Add("@Active", 1);
    var products = await connection.QueryAsync<Product>("select * from Products where Active = @Active", param);
    return products;
}

然后应删除products.Where行。


关于您所问的实际问题:

我无法重现该问题。当我运行以下代码以读取控制台应用程序中的输出时,它将返回预期的结果。

DbDataReader dbDataReader = new DbDataReader();
IEnumerable<Product> activeProducts = dbDataReader.GetProducts().Result;
Console.WriteLine(activeProducts.Count());

您的方法几乎没有如下修改:

public class DbDataReader
{
    string connectionString = @"....";
    public async Task<IEnumerable<Product>> GetProducts()
    {
        using(var connection = await GetOpenConnection())
        {
            var products = await connection.QueryAsync<Product>("select * from Products;WAITFOR DELAY '00:00:05'");
            return products.Where(p => p.Active);
        }
    }

    private async Task<SqlConnection> GetOpenConnection()
    {
        SqlConnection sqlConnection = new SqlConnection(connectionString);
        await sqlConnection.OpenAsync();
        return sqlConnection;
    }
}

请注意,我故意推迟了QueryAsync的{​​{1}}通话。