【问题标题】:Whether to use AsEnumerable() in LINQ or not?是否在 LINQ 中使用 AsEnumerable()?
【发布时间】:2015-09-16 17:32:15
【问题描述】:

我了解AsEnumerable() 用于从“LINQ to SQL”切换到“LINQ to Object”,因此我们可以在 LINQ 查询中使用一些额外的(主要是用户定义的)方法。但是根据我的经验,使用AsEnumerable() 会使查询速度变慢。在这种情况下,我可以稍后枚举列表以应用我自己的方法,但结果仍然很慢。

谁能提出更好的方法?

这是我正在尝试做的一些代码示例?

使用 AsEnumerable():

var Data = (from r in _context.PRD_ChemProdReq.AsEnumerable()
                    //where r.RecordStatus == "NCF"
                    orderby r.RequisitionNo descending
                    select new PRDChemProdReq
                    {
                        RequisitionID = r.RequisitionID,
                        RequisitionNo = r.RequisitionNo,
                        RequisitionCategory = DalCommon.ReturnRequisitionCategory(r.RequisitionCategory),
                        RequisitionType = DalCommon.ReturnOrderType(r.RequisitionType),
                        ReqRaisedOn = (Convert.ToDateTime(r.ReqRaisedOn)).ToString("dd'/'MM'/'yyyy"),
                        RecordStatus= DalCommon.ReturnRecordStatus(r.RecordStatus),
                        RequisitionFromName = DalCommon.GetStoreName(r.RequisitionFrom),
                        RequisitionToName = DalCommon.GetStoreName(r.RequisitionTo)
                    }).ToList();

没有 AsEnumerable():

var Data = (from r in _context.PRD_ChemProdReq
                    //where r.RecordStatus == "NCF"
                    orderby r.RequisitionNo descending
                    select new PRDChemProdReq
                    {
                        RequisitionID = r.RequisitionID,
                        RequisitionNo = r.RequisitionNo,
                        RequisitionCategory = r.RequisitionCategory,
                        RequisitionType = (r.RequisitionType),
                        ReqRaisedOnTemp = (r.ReqRaisedOn),
                        RecordStatus= (r.RecordStatus),
                        RequisitionFrom = (r.RequisitionFrom),
                        RequisitionTo = (r.RequisitionTo)
                    }).ToList();

        foreach (var item in Data)
        {
            item.RequisitionCategory = DalCommon.ReturnRequisitionCategory(item.RequisitionCategory);
            item.RequisitionType = DalCommon.ReturnOrderType(item.RequisitionType);
            item.ReqRaisedOn = (Convert.ToDateTime(item.ReqRaisedOnTemp)).ToString("dd'/'MM'/'yyyy");
            item.RecordStatus = DalCommon.ReturnRecordStatus(item.RecordStatus);
            item.RequisitionFromName = DalCommon.GetStoreName(item.RequisitionFrom);
            item.RequisitionToName = DalCommon.GetStoreName(item.RequisitionTo);
        }

【问题讨论】:

  • XY 问题。您真正的问题在于您将显示逻辑与数据访问逻辑混合在一起。
  • 两个代码 sn-ps 之间没有太大区别,您总是将数据带入内存,一次使用 AsEnumerable,然后使用 ToList(),在其他情况下使用 ToList()。只有单独使用 IEnumerable 和 IQueryable 才能发现区别。 IQueryable 将帮助通过数据提供者执行查询

标签: c# linq linq-to-sql entity-framework-5 linq-to-objects


【解决方案1】:

看起来您将这两个接口混淆为两个完全不同的东西。事实上IQueryable 是从IEnumerable 继承而来的,所以无论你使用后者,都可以使用前者,所以不需要使用AsEnumerable

在幕后,尽管这些接口的实现方式截然不同 - IEnumerable 将在内存中处理您的集合,而 IQueryable 会将查询传递给底层数据提供者。您可以想象,如果一个数据库表包含数百万条记录并且您尝试对其进行排序,DB 服务器可以非常快速地完成它(使用索引),因此 Queryable 会大放异彩。对于 IEnumerable,所有数据都需要加载到您的计算机内存中并在那里进行排序。

在 SO 上搜索 “IEnumerable IQueryable 差异” 以获得更长的答案,您将看到大量详细信息:

Random Link 1 Random Link 2

更新:如果您从第二个示例中删除 call .ToList,则结果不会自动加载到内存中。此时,您需要决定要将哪些项目存储在内存中,并仅为它们调用您的函数。

var Data = (from r in _context.PRD_ChemProdReq
           orderby r.RequisitionNo descending
           select new PRDChemProdReq
           {
               // do your initialization
           });

var subsetOfData = Data.Take(100).ToList(); // Now it's loaded to memory
foreach (var item in subsetOfData)
{
    item.RequisitionCategory = DalCommon.ReturnRequisitionCategory(item.RequisitionCategory);
    item.RequisitionType = DalCommon.ReturnOrderType(item.RequisitionType);
    item.ReqRaisedOn = (Convert.ToDateTime(item.ReqRaisedOnTemp)).ToString("dd'/'MM'/'yyyy");
    item.RecordStatus = DalCommon.ReturnRecordStatus(item.RecordStatus);
    item.RequisitionFromName = DalCommon.GetStoreName(item.RequisitionFrom);
    item.RequisitionToName = DalCommon.GetStoreName(item.RequisitionTo);
}

现在,如果您确实需要为所有您的数据分配这些属性,并且数据可以是任意大的,您需要制定一个策略来做到这一点。非常简单的选择是将它们保存到数据库中的新表中,然后处理数据的大小将仅受数据库容量的限制。

【讨论】:

  • 我能理解你的观点,但是在我的情况下,什么是让事情变得更快的正确方法,如果我不使用 AsEnumerable(),我不能在 select 中使用用户定义的方法调用块,所以我必须稍后枚举,如果我使用 AsEnumerable(),整个记录都会进入内存,这也不好。
  • @Jain 通常使用的技术是您对所需字段执行select,然后在foreach 中“格式化”它们。这就是您在第二个示例中所做的。
  • @xanatos 所以,你是说这比使用 .AsEnumerable() 更好,对吧?我会努力遵循它。谢谢。
【解决方案2】:

AsEnumerable() 后面添加查询元素会变慢。

即使AsEnumerable() 不直接执行查询,应用whereorderby AsEnumerable() 之后意味着Sql 将得到all 项目,然后将过滤排序应用到内存中的集合。

简而言之:

  • 没有AsEnumerable() = 在 SQL 中完成过滤和排序
  • 使用AsEnumerable() 然后Whereorderby = 应用于整个集合到内存中。

您只能在内存中的集合上运行用户定义的函数(因为您的 Linq to SQL 将无法将您的函数解释为 SQL 代码)。所以你的第二个代码 sn-p(没有AsEnumerable())可能是最好的。

唯一的另一种选择是在 SQL 本身中应用您的用户定义函数

【讨论】:

  • AsEnumerable() 将所有记录带到内存中,然后应用过滤器,这就是为什么我们可以在选择块中使用用户定义的函数,而不使用 AsEnumerable(),在选择中使用任何用户定义的方法调用块会抛出错误。
  • @Jain,你完全正确,我很抱歉,AsEnumerable() 不会执行将结果集存储到内存中的查询,但是任何进一步的语句或过滤器都将应用于结果集在记忆中。我已经相应地更新了答案。
【解决方案3】:

好的,伙计们,我注意到了你们所有人的不同观点并想出了这个:

var Data = (from r in _context.PRD_ChemProdReq.AsEnumerable()
                    //where r.RecordStatus == "NCF"

                    join rf in _context.SYS_Store on (r.RequisitionFrom==null?0: r.RequisitionFrom) equals rf.StoreID into requisitionfrom
                    from rf in requisitionfrom.DefaultIfEmpty()

                    join rt in _context.SYS_Store on (r.RequisitionTo == null ? 0 : r.RequisitionTo) equals rt.StoreID into requisitionto
                    from rt in requisitionto.DefaultIfEmpty()

                    orderby r.RequisitionNo descending
                    select new PRDChemProdReq
                    {
                        RequisitionID = r.RequisitionID,
                        RequisitionNo = r.RequisitionNo,
                        RequisitionCategory = DalCommon.ReturnRequisitionCategory(r.RequisitionCategory),
                        RequisitionType = r.RequisitionType == "UR" ? "Urgent" : "Normal",
                        ReqRaisedOn = (Convert.ToDateTime(r.ReqRaisedOn)).ToString("dd'/'MM'/'yyyy"),
                        RecordStatus = (r.RecordStatus=="NCF"? "Not Confirmed": "Approved"),
                        RequisitionFromName = (rf==null? null: rf.StoreName),
                        RequisitionToName = (rt == null ? null : rt.StoreName)
                    });

首先,我删除了我的 ToList(),它什么都不做,只是执行在我调用 AsEnumerable() 时已经完成的查询。两次执行相同的查询没有任何意义。此外,我在 select 块中的自定义方法调用也在减慢速度方面发挥了重要作用。我尝试减少方法调用,而是尽可能使用 join 。它使事情变得更快。谢谢大家。

【讨论】:

  • 您仍在将 DAO 与 Display 逻辑混合使用。您选择的所有代码都应该在您的 UI 中!这真是糟糕的软件设计。
  • @Jain,您需要问自己一个问题 - 我是否担心每次运行此查询时都会加载一些疯狂的记录?如果是,那么这种方法无论如何都无法解决您的问题。如果您不担心,那么您可以使用.AsEnumerable
  • @Aron 我知道。但是项目已经按照这种方式开发了,所以我的选择很少。但我们肯定会在未来的更新中努力让事情变得更好。
  • @Alexey 你是对的。我们将不得不在某个时候实现服务器分页,目前我将使用 .AsEnumerable。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多