简体   繁体   中英

Net Core: Async method with ThenInclude Filter and Where

I am trying to use an Async method with ThenInclude filter and Where.

Its throwing this error. Think has it has something to do with this line:

LkDocumentTypeProduct = new Collection<LkDocumentTypeProduct>(dept.LkDocumentTypeProduct.Where(c => c.LkDocumentTypeId == lkDocumentTypeId).ToList())

and also skipping these lines below in my App Service: How would I make this ThenInclude Filter Async and fix the skipping of lines?

   //ITS SKIPPING THESE THREE LINES BELOW
    IEnumerable<Product> ProductModel = mapper.Map<IEnumerable<Product>>(Products);
    var ProductDto = mapper.Map<IEnumerable<Product>, IEnumerable<ProductDto>>(ProductModel);
    return ProductDto;

Error:

    ArgumentException: Expression of type  'System.Collections.Generic.IAsyncEnumerable`1[Data.Entities.LkDocumentTypeProduct]' cannot be used for parameter of type 
'System.Collections.Generic.IEnumerable`1[Data.Entities.LkDocumentTypeProduct]' of method 'System.Collections.Generic.List`1[Data.Entities.LkDocumentTypeProduct] ToList[LkDocumentTypeProduct]
(System.Collections.Generic.IEnumerable`1[Data.Entities.LkDocumentTypeProduct])'
Parameter name: arg0

Repository:

    public async Task<IEnumerable<Product>> GetProductsByDocumentType(int lkDocumentTypeId)
    {
        var ProductsByDocumentType = All
            .Include(dept => dept.LkDocumentTypeProduct)
            .Select(dept => new Product
            {
                ProductId = dept.ProductId,
                ProductName = dept.ProductName,
                ProductCode = dept.ProductCode,
                LkDocumentTypeProduct = new Collection<LkDocumentTypeProduct>(dept.LkDocumentTypeProduct.Where(c => c.LkDocumentTypeId == lkDocumentTypeId).ToList())
            }).Where(dept=>dept.LkDocumentTypeProduct.Any());

        return await ProductsByDocumentType.ToListAsync();
    }

AppService:

    public async Task<IEnumerable<ProductDto>> GetProductsByDocumentTypeId(int id)
    {
        var Products = await ProductRepository.GetProductsByDocumentType(id);

       //ITS SKIPPING THESE THREE LINES BELOW !
        IEnumerable<Product> ProductModel = mapper.Map<IEnumerable<Product>>(Products);
        var ProductDto = mapper.Map<IEnumerable<Product>, IEnumerable<ProductDto>>(ProductModel);
        return ProductDto;
    }

Controller:

    [HttpGet("[Action]/{id}")]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<IEnumerable<ProductDto>>> GetByDocumentType(int id)
    {
        IEnumerable<ProductDto> Product = await ProductAppService.GetProductsByDocumentTypeId(id);
        if (Product == null)
        {
            return NotFound();
        }

How to add where clause to ThenInclude

It's not "skipping" lines, it's erroring on this line: IEnumerable ProductModel = mapper.Map>(Products);

Automapper is a helper, not a magician. :)

From what I'm guessing you had a method that was returning Product entities that you've been told to change over to a DTO. Automapper can help you, in an Async way:

Firstly, ignore the fact that you're working with async collections. Automapper maps objects to one another really well, not collections, it works within collections.

In your case given your repository is returning IEnumerable<Product> , to map this to an IEnumerable<ProductDto> use this in your Service:

public async Task<IEnumerable<ProductDto>> GetProductsByDocumentTypeId(int id)
{
    var products = await ProductRepository.GetProductsByDocumentType(id);
    var dtos = await products.Select(x => Mapper.Map<ProductDto>(x)).ToListAsync();
    return dtos;
}

That should get you working, but isn't ideal. The reason is that the repository is returning back a collection of Entities which means that we'd be materializing all fields in each of those entities returned, whether we need them or not. This also opens the door if Automapper "touches" any related entities that haven't been eager loaded as this will trigger lazy-load calls to the database. You may cater for this with Include , but as code matures these lazy load hits can sneak in or you have eager load costs for fields you no longer need.

Automapper offers a brilliant method to address this, ProjectTo but it requires code to leverage EF's IQueryable implementation rather than returning IEnumerable .

For instance if we change the repository method to:

public IQueryable<Product> GetProductsByDocumentType(int lkDocumentTypeId)
{
    var query = _dbContext.Products
       .Where(p => p.LkDocumentTypeProduct.Any(c => c.LkDocumentTypeId == lkDocumentTypeId));

    return query;
}

Then in the service:

public async Task<IEnumerable<ProductDto>> GetProductsByDocumentTypeId(int id)
{
    var dtos = await ProductRepository.GetProductsByDocumentType(id)
       .ProjectTo<ProductDto>().ToListAsync();
    return dtos;
}

What this does is change the repository to return an IQueryable, basically return a promise of retrieving a known set of entities that our consumer can query against. I would be a bit wary of the "All" property as to what that property returns. I've seen a few cases where methods like that do something like return _context.Products.ToList(); which is a real performance / resource trap.

We feed that Queryable to Automapper's provided extension method ProjectTo which then queries just the columns needed to satisfy the ProductDto. The advantages of this approach are considerable. We don't need to explicitly Include related tables or worry about tripping lazy loaded references, and the built query only pulls back the fields our DTO cares about.

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