简体   繁体   中英

linq query async/await review

I'm upgrading a working linq pipeline to async and I'm struggling a bit with the synthaxe/logic (where to task.run, async/await, Tolist ...). At the moment it won't compile as the last method returns an IEnumerable of Task products instead of IEnumerable of products

here is the call :

public async Task<IEnumerable<ProductDto>> GetProducts(bool isLogged)
{
    return await _mapper.GetStripeProductsDto(isLogged, false);
}

one of the repos :

public async Task<IEnumerable<StripeProduct>> GetAllStripeProductsAsync()
{
    return (await _productService.ListAsync())
        .Where(x => x.Type == "good" && x.Active == true);
}

and where I build my dto and am struggling with :

    public async Task<IEnumerable<ProductDto>> GetStripeProductsDto(bool isLogged, bool isSubscriber)
    {
        var productList = (await _productRepo.GetAllStripeProductsAsync()).ToList();

        return await Task.Run(async () => 
                GetSkuOffers(productList)
                    .Concat(await GetSubsciptionOffers(productList))
                    .GroupBy(product => product.Name)
                    .Select(productGroup => new ProductDto
                    {
                        Name = productGroup.Key,
                        Id = productGroup.Select(product => product.Id).First(),
                        Description = productGroup.Select(product => product.Description).First(),
                        Image = productGroup.Select(product => product.Image).First(),
                        CurrentUserProfile = isSubscriber
                            ? OfferTypeEnum.Pro.ToString()
                            : isLogged
                                ? OfferTypeEnum.Registered.ToString()
                                : OfferTypeEnum.Basic.ToString(),
                        Prices = productGroup.Select(product => new
                        {
                            Offer = product.OfferType.ToString(),
                            Price = product.Price.ToString()
                        })
                        .ToDictionary(p => p.Offer, p => p.Price)
                    })
                    .ToList())
            .ConfigureAwait(false);
    }

    private IEnumerable<Product> GetSkuOffers(IEnumerable<StripeProduct> productList)
    {
        return productList
            .SelectMany(sku => sku.Skus.Data, (product, sku) => new Product
            {
                Name = product.Name,
                Id = product.Id,
                Image = new Uri(product.Images.First()),
                Description = product.Description,
                OfferType = sku.Id.Contains("Basic") ? OfferTypeEnum.Basic : OfferTypeEnum.Registered,
                Price = sku.Price
            });
    }

    private IEnumerable<Product> GetSubsciptionOffers(IEnumerable<StripeProduct> productList)
    {
        return 
            productList
            .Select(async product => new Product
            {
                Name = product.Name,
                Id = product.Id,
                Image = new Uri(product.Images.First()),
                Description = product.Description,
                OfferType = OfferTypeEnum.Pro,
                Price = (await _planRepo.GetPlanByIdAsync(product.Metadata.First().Value)).Amount.GetValueOrDefault()
            });
    }

EDIT : compiling version, ToListAsync seems to work only for DBs, my repo makes calls to an api, so i gave up on that; I removed the Task.Run(...), and got rid of the IEnumerable of Task products in a probably nasty way

    public async Task<IEnumerable<ProductDto>> GetStripeProductsDto(bool isLogged, bool isSubscriber)
    {
        var productList = (await _productRepo.GetAllStripeProductsAsync()).ToList();
        var skuOffers = GetSkuOffers(productList);
        var subsciptionOffers = GetSubsciptionOffers(productList);

        return skuOffers
            .Concat(subsciptionOffers)
            .GroupBy(product => product.Name)
            .Select(productGroup => new ProductDto
            {
                Name = productGroup.Key,
                Id = productGroup.Select(product => product.Id).First(),
                Description = productGroup.Select(product => product.Description).First(),
                Image = productGroup.Select(product => product.Image).First(),
                CurrentUserProfile = isSubscriber
                    ? OfferTypeEnum.Pro.ToString()
                    : isLogged
                        ? OfferTypeEnum.Registered.ToString()
                        : OfferTypeEnum.Basic.ToString(),
                Prices = productGroup.Select(product => new
                {
                    Offer = product.OfferType.ToString(),
                    Price = product.Price.ToString()
                })
                .ToDictionary(p => p.Offer, p => p.Price)
            })
            .ToList();
    }

    private IEnumerable<Product> GetSkuOffers(IEnumerable<StripeProduct> productList)
    {
        return productList
            .SelectMany(sku => sku.Skus.Data, (product, sku) => new Product
            {
                Name = product.Name,
                Id = product.Id,
                Image = new Uri(product.Images.First()),
                Description = product.Description,
                OfferType = sku.Id.Contains("Basic") ? OfferTypeEnum.Basic : OfferTypeEnum.Registered,
                Price = sku.Price
            });
    }

    private IEnumerable<Product> GetSubsciptionOffers(IEnumerable<StripeProduct> productList)
    {
        return productList
            .Select(product => new Product
            {
                Name = product.Name,
                Id = product.Id,
                Image = new Uri(product.Images.First()),
                Description = product.Description,
                OfferType = OfferTypeEnum.Pro,
                //Price = _planRepo.GetPlanById(product.Metadata.First().Value).Amount.GetValueOrDefault()
                Price = GetSubscriptionPrice(product.Metadata.First().Value)
            }).ToList();
    }

    private int GetSubscriptionPrice(string str)
    {
        var plan = Task.Run(() => _planRepo.GetPlanByIdAsync(str)).GetAwaiter().GetResult();
        return plan.Amount.GetValueOrDefault();
    }

I would do something like this:

public async Task<IEnumerable<ProductDto>> GetProductsAsync(bool isLogged)
{
    return await _mapper.GetStripeProductsDto(isLogged, false);
}

public async Task<IEnumerable<StripeProduct>> GetAllStripeProductsAsync()
{
    var list = await _productService.ListAsync();

    return await list.Where(x => x.Type == "good" && x.Active == true).ToListAsync();
}

public async Task<IEnumerable<ProductDto>> GetStripeProductsDtoAsync(bool isLogged, bool isSubscriber)
{
    // here you're making async call and when task is done, you are making .ToList() on results
    var productList = await _productRepo.GetAllStripeProductsAsync();

    // this will be executed when productList has results from database (task).
    // Because GetSkuOffers() method is sync you can execute this like below.
    var skuOffersResult = GetSkuOffers(productList);

    // get asynchronously subscription offers
    var getSubscriptionOffers = await GetSubsciptionOffers(productList);

    // this is done also synchronously so you don't need to use async/await statements
    skuOffersResult.Concat(getSubscriptionOffers)
                .GroupBy(product => product.Name)
                .Select(productGroup => new ProductDto
                {
                    Name = productGroup.Key,
                    Id = productGroup.Select(product => product.Id).First(),
                    Description = productGroup.Select(product => product.Description).First(),
                    Image = productGroup.Select(product => product.Image).First(),
                    CurrentUserProfile = isSubscriber
                        ? OfferTypeEnum.Pro.ToString()
                        : isLogged
                            ? OfferTypeEnum.Registered.ToString()
                            : OfferTypeEnum.Basic.ToString(),
                    Prices = productGroup.Select(product => new
                    {
                        Offer = product.OfferType.ToString(),
                        Price = product.Price.ToString()
                    })
                    .ToDictionary(p => p.Offer, p => p.Price)
                })
                .ToList());
}

private IEnumerable<Product> GetSkuOffers(IEnumerable<StripeProduct> productList)
{
    return productList
        .SelectMany(sku => sku.Skus.Data, (product, sku) => new Product
        {
            Name = product.Name,
            Id = product.Id,
            Image = new Uri(product.Images.First()),
            Description = product.Description,
            OfferType = sku.Id.Contains("Basic") ? OfferTypeEnum.Basic : OfferTypeEnum.Registered,
            Price = sku.Price
        });
}

private async Task<IEnumerable<Product>> GetSubsciptionOffers(IEnumerable<StripeProduct> productList)
{
    return
        productList
        .Select(async product => new Product
        {
            Name = product.Name,
            Id = product.Id,
            Image = new Uri(product.Images.First()),
            Description = product.Description,
            OfferType = OfferTypeEnum.Pro,
            Price = (await _planRepo.GetPlanByIdAsync(product.Metadata.First().Value)).Amount.GetValueOrDefault()
        });
}

Simple example how to use async/await: Let say that you have service which uses repository to get product list - all made synchronously for now:

public class Repository : IRepository
{
    public List<Product> GetProducts()
    {
        return _dbContext.Products.ToList();
    }
}

public class Service : IService
{
    private readonly IRepository _repository;

    public Service(IRepository repository)
    {
        _repository = repository;
    }

    public List<Product> GetProductsFromDb()
    {
        return _repository.GetProducts();
    }
}

Now you want to change this code to make it work asynchronously:

public class Repository : IRepository
{
    public async Task<List<Product>> GetProductsAsync()
    {
        return await _dbContext.Products.ToListAsync();
    }
}

public class Service : IService
{
    private readonly IRepository _repository;

    public Service(IRepository repository)
    {
        _repository = repository;
    }

    public async Task<List<Product>> GetProductsFromDbAsync()
    {
        return await _repository.GetProductsAsync();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM