【问题标题】:C# IQueryable - How to query a list inside another listC# IQueryable - 如何在另一个列表中查询一个列表
【发布时间】:2020-03-04 10:12:14
【问题描述】:

我正在一个项目中工作,该项目显示一个包含 ListDTO 的 IQueryable 的 BoardDto。每个 ListDTO 都包含一个 CardDTO 的 IQueryable。

如何在加载所有 ListDto 的情况下获取我的 BoardDto 对象,但只能使用他们的卡片 where CardType = 1

public class BoardDTO
{
        public int BoardId { get; set; }
        public string Nome { get; set; }
        public IQueryable<BoardListDTO> Lists { get; set; }
}

public class BoardListDTO
{
        public int BoardListId { get; set; }
        public string Name { get; set; }
        public IQueryable<CardDTO> Cards { get; set; }
}

public class CardDTO
{
        public long CardId { get; set; }
        public CardType Type { get; set; }
}

编辑

下图说明了项目必须如何组成 DTO。

解决方案

这解决了我的问题:

var query = _context.Boards
                    .AsNoTracking()
                    .ProjectTo<BoardDto>(_mapper)
                    .Where(x => x.BoardId == id)
                    .Select(x => new BoardDto
                             {
                                BoardId = x.BoardId,
                                Name = x.Name,
                                Lists = x.Lists.Select(list => new BoardListSearchDTO
                                                {
                                                    Cards = list.Cards.Where(x => x.Type == 1),
                                                }).FilterLists(filterListsBy, listSearchValue)
                                            });

【问题讨论】:

  • 更好的标题可能是 ef-core 延迟加载和根据条件提前加载某些数据

标签: c# asp.net entity-framework linq iqueryable


【解决方案1】:

分离您的顾虑。实体代表领域模型。 DTO 代表视图或消费者模型。虽然 EF 广泛处理 IQueryable,但您的消费者通常不需要 IQueryable,大多数时候他们只需要能够枚举结果,因此 IEnumerable 就足够了。即使在实体中,我也不用IQueryable 表示实体之间的关系,而是使用ICollection

IQueryable 是一个结构,表示应该将 Linq 表达式传递到数据源。对于允许 Linq 表达式向下转换为 SQL 的 EF Linq 操作。在处理已返回的消费数据时,如果您需要进一步过滤或翻译结果,您可以使用 Linq 针对IEnumerable 等。实体永远不应真正传递到其 DbContext 范围之外,因为延迟加载等功能需要 DbContext 处于活动状态,并且您不能可靠地假设延迟加载永远不会被消费视图或序列化程序等触发。实体是数据状态,视图状态应该分开。

给定一个由 Boards、BoardLists 和 Cards 组成的实体域模型,我的实体看起来像:

public class Board
{
    public int BoardId { get; set; }
    public string Name { get; set; }
    public virtual ICollection<BoardList> Lists { get; set; } = new List<BoardList>();
}

public class BoardList
{
    public int BoardListId { get; set; }
    public string Name { get; set; }
    public Virtual ICollection<Card> Cards { get; set; } = new List<Card>();
}

public class Card
{
    public long CardId { get; set; }
    public CardType Type { get; set; }
}

实体通常会反映基础表中的所有数据以及实体之间的关系。子集合被声明为virtual 以适应需要时的延迟加载,并初始化为新集合。这适用于我们想要创建一个新实体并准备好它的子集合的情况。

DTO 仅代表消费者需要的模型中的数据。这也是我们考虑过滤数据视图以适应视图的地方。我们可以使用IEnumerable,而不是ICollection。您可以使用ICollectionIList,但通常建议使用IEnumerable,因为它对消费者说“这是一组您可以读取的数据,但不要尝试添加等等”。

public class BoardDTO
{
    public int BoardId { get; set; }
    public string Name { get; set; }
    public IEnumerable<BoardListDTO> Lists { get; set; }
}

public class BoardListDTO
{
    public int BoardListId { get; set; }
    public string Name { get; set; }
    public IEnumerabe<CardDTO> Cards { get; set; }
}

public class CardDTO
{
    public long CardId { get; set; }
    public CardType Type { get; set; }
}

在此示例中,DTO 看起来与实体相同。但通常情况下,实体和基础表将包含许多字段,而我们的视图模型或 DTO 只需要几个字段或关系。

在为视图填充 DTO 集合时,您可以使用 Select 来利用投影。因此,如果您想列出所有至少有 1 张 cardType 1 卡的棋盘,然后返回那些只有 CardType 1 卡的棋盘:

这是假设 CardType 被映射为 Enum。如果 CardType 被映射到一个表,那么它也需要一个实体/DTO。

var boards = context.Boards
   .Where(b => b.Lists
      .Any(bl => bl.Cards.Any(c => c.CardType == CardTypes.Type1)))
   .Select(b => new BoardDTO
   {
      BoardId = b.BoardId,
      Name = b.Name,
      Lists = b.Lists.Select(bl => new BoardListDTO
      {
         BoardListId = bl.BoardListId,
         Name = bl.Name,
         Cards = bl.Cards.Where(c => c.CardType == CardTypes.Type1)
            .Select(c => new CardDTO
            {
               CardId = c.CardId,
               CardType = c.CardType
            }).ToList()
      }).ToList()
   }).ToList()

这个例子在实体和 DTO 之间做了很多一对一的映射。通常 DTO 被展平以表示消费视图可以使用的结构。这可能会将消费者模型合并到仅董事会和卡列表,其中董事会列表名称出现在相关卡 DTO 中。 (所以板 DTO 只会显示包含 BoardList 名称的卡片列表。)

像这样的 DTO/ViewModel 对过滤数据很有用,因为实体应该始终反映真实的数据状态,因此您不能只返回一些相关卡片的子集的实体。董事会将始终与与其关联的所有董事会列表以及与其关联的所有卡片相关联。使用 DTO 和 Select,您可以自定义视图/消费者实际需要的数据。

【讨论】:

  • 嗨@Steve Py,感谢您的回复。你对这些担忧是完全正确的。在我的项目中,实体与 DTO 是分开的,但是您对在 DTO 中使用 IQueryable 的解释非常清楚,谢谢。但我真正想要的是加载所有列表的板(例如:BoardId = 1),但每个列表必须只包含其自己的类型 = 1 的卡片。我刚刚用图像编辑了我的问题以说明什么我想要。提前谢谢你。
  • 在这种情况下,只需从查询的初始 context.Boards 部分中删除 Where 子句,并保留 Cards 部分的 Where 子句。这将加载所有板,无论它们是否具有类型 1 卡的列表,并且对于列表,它只会为类型 1 的卡创建 DTO。
  • 嘿@Steve Py,我用一段代码编辑了我的问题,解决了我的问题。
猜你喜欢
  • 2013-05-04
  • 2023-02-22
  • 2016-02-28
  • 1970-01-01
  • 1970-01-01
  • 2012-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多