Почему мой IEnumerable <T>. Где итератор не выполняется внутри блока using во время асинхронного вызова? - PullRequest
0 голосов
/ 31 октября 2018

Я уверен, что это не проблема 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 голосов
/ 07 ноября 2018

В первом примере, если вы можете внести следующие изменения в оператор возврата:

return products.Where(p => p.Active).ToList();, тогда будет работать как положено .

Дело 1:

Проблема здесь в том, что Where предложение, применённое к IEnumerable<Product>, является отложенным выполнением, которое возвращается в виде Task в виде Task<IEnumerable<Product>>, но теперь вам нужно запустить Task, который должен выполнить предикат тоже. Не уверен, как вы выполняете задание, или, возможно, есть проблема с переносом отложенного выполнения таким образом, но конечный результат - предикат не вступает в силу, как ожидалось, даже когда он применяется к результату Dapper, что буферизируется по умолчанию (без потоковой передачи)

Дело 2:

Это работает во втором случае, так как вы полностью избавляетесь от отложенного выполнения, Enumerable.Empty<Product>() гарантирует, что память будет выделена первой, поэтому предикат исполняется в момент его применения, когда отложенное выполнение не выполняется. Фактически предикат применяется любым способом вне блока using

.

В асинхронном методе вы удаляете соединение с помощью блока 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

0 голосов
/ 01 ноября 2018

Ваш предикат фактически не работает как предикат. Это просто вызов LINQ.

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

Когда вышеуказанная строка выполняется, products уже заполнена всеми строками таблицы на основе вашего запроса и QueryAsync вызовом в предыдущей строке.

Хорошая особенность Dapper в том, что он обеспечивает полный контроль над написанием запросов you . Итак, если вы хотите отфильтровать записи, почему бы не написать запрос таким образом?

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 с помощью WAITFOR.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...