【问题标题】:Net Core: Async method with ThenInclude Filter and WhereNet Core:带有 ThenInclude 过滤器和 Where 的异步方法
【发布时间】:2019-08-12 00:15:39
【问题描述】:

我正在尝试使用带有 ThenInclude 过滤器和 Where 的 Async 方法。

它抛出这个错误。认为它与这一行有关:

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

并在我的应用服务中跳过以下这些行:如何使此 ThenInclude 过滤器异步并修复跳过行?

   //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;

错误:

    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

存储库:

    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();
    }

应用服务:

    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;
    }

控制器:

    [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

【问题讨论】:

  • 该错误确实告诉您 IAsyncEnumerable&lt;LkDocumentTypeProduct&gt; 不能转换为 IEnumerable&lt;LkDocumentTypeProduct&gt;。这没有帮助吗?
  • 由于某种原因,在转换为异步枚举或列表异步时,我也遇到了错误,调试了几个小时,一个月前才开始学习 linq
  • public interface IAsyncEnumerator&lt;out T&gt; : IAsyncDisposablepublic interface IEnumerable&lt;out T&gt; : System.Collections.IEnumerable 无关,因此您不能从一个转换到另一个。

标签: c# entity-framework asp.net-core .net-core


【解决方案1】:

这不是“跳过”行,而是在这一行出错: IEnumerable ProductModel = mapper.Map>(Products);

Automapper 是一个助手,而不是魔术师。 :)

据我猜测,您有一个返回 Product 实体的方法,您被告知要切换到 DTO。 Automapper 可以以异步方式帮助您:

首先,忽略您正在使用异步集合这一事实。 Automapper 非常好地将对象相互映射,而不是集合,它在 in 集合中工作。

在您的情况下,鉴于您的存储库返回 IEnumerable&lt;Product&gt;,要将其映射到 IEnumerable&lt;ProductDto&gt;,请在您的服务中使用:

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;
}

这应该能让你工作,但并不理想。原因是存储库返回了一个实体集合,这意味着我们将实现每个返回的实体中的所有字段,无论我们是否需要它们。如果 Automapper “接触”任何尚未预先加载的相关实体,这也会打开大门,因为这将触发对数据库的延迟加载调用。您可以使用 Include 来满足此需求,但随着代码的成熟,这些延迟加载命中可能会潜入,或者您对不再需要的字段有急切的加载成本。

Automapper 提供了一种出色的方法来解决这个问题,ProjectTo,但它需要代码来利用 EF 的 IQueryable 实现,而不是返回 IEnumerable

例如,如果我们将存储库方法更改为:

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

    return query;
}

然后在服务中:

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

这样做是更改存储库以返回一个 IQueryable,基本上返回一个承诺,即检索我们的消费者可以查询的一组已知实体。对于该属性返回的内容,我会对“全部”属性保持警惕。我见过一些类似的方法执行类似return _context.Products.ToList(); 的情况,这是一个真正的性能/资源陷阱。

我们将 Queryable 提供给 Automapper 提供的扩展方法 ProjectTo,然后它只查询满足 ProductDto 所需的列。这种方法的优点是相当大的。我们不需要显式地Include 相关表或担心触发延迟加载的引用,并且构建的查询只会拉回我们 DTO 关心的字段。

【讨论】:

    猜你喜欢
    • 2021-04-21
    • 1970-01-01
    • 2021-10-01
    • 2018-04-21
    • 2022-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多