【问题标题】:Iterate over an IQueryable without calling ToList()在不调用 ToList() 的情况下迭代 IQueryable
【发布时间】:2018-04-17 05:33:52
【问题描述】:

我有一个用于生产线的数据库。它有一个 Orders 表、一个 Ordertracker 表、一个 Item 表和一个 Itemtracker 表。

订单和项目都与状态具有多对多关系。跟踪器表以这样一种方式解决这些关系,即一个项目可以在跟踪器中有多个条目 - 每个条目都有特定的状态。

我试图上传一张表格的图片以使事情更清楚,但可惜我的积分还不够:C

我需要查找 Itemtracker 表中最后状态满足条件的项目,“3”或“0”。

然后我需要得到这些项目中的第一个。

我用来完成此操作的步骤如下:

  1. 获取所有具有一定状态的订单。
  2. 获取该订单中的所有项目。
  3. 获取最后状态为 = 0 或 3 的所有项目。
  4. 获取这些项目中的第一个。

我的代码如下:

    public ITEM GetFirstItemFailedOrNotInProductionFromCurrentOrder()
    {

        var firstOrder = GetFirstOrderInProductionAndNotCompleted();

        var items = ERPContext.ITEM.Where(i => i.OrderID == firstOrder.OrderID) as IQueryable<ITEM>;

        if (CheckStatusOfItems(items) != null)
        {
            var nextItem = CheckStatusOfItems(items);

            return nextItem ;
        }
        else
        {
            return null;
        }
    }

    private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
    {
        List<ITEM> listOfItemsToProduce = new List<ITEM>();

        foreach (ITEM item in items.ToList())
        {

           var lastStatusOfItem = ERPContext.ITEMTRACKER.Where(it => it.ItemID == item.ItemID)
                                                        .OrderByDescending(it => it.ItemTrackerID).FirstOrDefault();

            if (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
            {
                listOfItemsToProduce.Add(item);
            }
        }
        return listOfItemsToProduce.FirstOrDefault();

    }

现在,这一切正常并返回了我需要的东西,但我知道这可能不是最好的方法。因为现在我的 IQueryable 项目集合永远不会包含超过 6 个项目 - 但如果它可以变得更大,那么在 IQueryable 上调用 ToList() 并迭代内存中的结果可能不是一个好主意。

有没有更好的方法来遍历 IQueryable 项目以获取具有特定状态作为其最新状态的项目,而无需调用 ToList() 并遍历结果?

任何建议将不胜感激。

【问题讨论】:

  • 如果你懒加载信息,遍历 IQueryable 会非常慢。然后每个循环成为一个新的查询。 ITEMTRACKER 是否与您的模型中的 ITEM 没有关联?
  • 顺便说一句,您应该将CheckStatusOfItems 的结果缓存到一个变量中,然后检查该变量是否为空。否则,您将调用它两次并执行两次查询。也就是说,您可以将整个第一个方法折叠到 var firstOrder=... ; var items=... ; return CheckStatusOfItems(items);,因为如果它不为 null,则返回结果,如果为 null,则返回 null,因此与返回 (null) 结果相同
  • 是的,我想是的,ITEMTRACKER 是将ITEM 表连接到ITEMSTATUS 表的表。 ItemID 和 OrderID 是 ITEM 中的 PK。 ItemID、OrderID 和 ItemStatus 是 ITEMTRACKER 中的 FK - PK 是 ItemTrackerID。任何建议如何解决这个问题?谢谢
  • 另外,更一般地说:如果您只想返回集合中的第一个匹配项,请不要遍历整个列表并将所有匹配项添加到另一个列表中,然后最后返回第一项那个集合...只需返回满足您条件的第一个项目遇到它时,然后在循环后返回return null
  • 注意!谢谢pinkfloydx33

标签: c# database linq foreach iqueryable


【解决方案1】:

使用 LINQ 查询语法,您可以以与编写命令式迭代的方式几乎相同的方式以声明方式构建单个查询。 foreach 转换为 fromvar 转换为 letif 转换为 where

private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
    var query =
        from item in items
        let lastStatusOfItem = ERPContext.ITEMTRACKER
            .Where(it => it.ItemID == item.ItemID)
            .OrderByDescending(it => it.ItemTrackerID)
            .FirstOrDefault()
        where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
        select item;

    return query.FirstOrDefault();
}

或者使用from代替letTake(1)代替FirstOrDefault()

private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
    var query =
        from item in items
        from lastStatusOfItem in ERPContext.ITEMTRACKER
            .Where(it => it.ItemID == item.ItemID)
            .OrderByDescending(it => it.ItemTrackerID)
            .Take(1)
        where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
        select item;

    return query.FirstOrDefault();
}

【讨论】:

  • Take(1) 是解决方案。 FirstOrDefault() 总是会出问题。
  • 太棒了!谢谢 - 所以如果我理解正确的话 - Take(1) 解决方案允许查询在数据库中作为表达式树执行,而不是 ToList() 方法需要首先返回项目集合?
  • 不客气。两种方式都会生成并执行单个 SQL 查询 (SELECT TOP 1 ...)。关键是尽可能保持整个IQueryable&lt;T&gt;(如上面query变量),并在最后执行一次。
  • 非常有帮助。已尝试支持您的回答,但我的投票似乎没有足够的积分来登记。
  • 完全没有问题,很高兴它有所帮助 :) 但如果您愿意,您可以随时接受并回答您自己的问题 - 不需要代表 :)
猜你喜欢
  • 2016-10-11
  • 1970-01-01
  • 1970-01-01
  • 2016-11-28
  • 2020-10-14
  • 1970-01-01
  • 2012-03-02
  • 2021-01-10
相关资源
最近更新 更多