IQueryable 使用 Where 子句抛出 OutOfMemory 异常

IQueryable throws OutOfMemory exception with Where clause

更新:2015 年 9 月 3 日

好的,我尝试改为执行 .Join:

    [System.Web.Http.HttpGet]
    public IQueryable<RunOfferPublish> RunOfferPublishes()
    {
        var showIds = _showSecurityCache.GetRunIdsForCurrentUser().AsEnumerable();
        return Context.RunOfferPublishes.Join(showIds, r => r.RunOffer.Run.showId, i => i, (r, i) => r);
    }

我收到另一个异常,说我的查询嵌套很深。我做了一个方法来减少嵌套并改为获取 runIds。然而,还有更多的ids..

    [System.Web.Http.HttpGet]
    public IQueryable<RunOfferPublish> RunOfferPublishes()
    {
        var runIds = _showSecurityCache.GetRunIdsForCurrentUser().AsEnumerable();
        return Context.RunOfferPublishes.Join(runIds, r => r.RunOffer.RunId, i => i, (r, i) => r);
    }

我收到 Whosebug 异常,运行次数过多。

然后我尝试删除获取所有实体的列表,并在内存中执行 where。像这样:

    [System.Web.Http.HttpGet]
    public IQueryable<RunOfferPublish> RunOfferPublishes()
    {
        var showIds = _showSecurityCache.GetShowIdsForCurrentUser();
        var list = Context.RunOfferPublishes.ToList();
        return list.Where(i => showIds.Contains(i.RunOffer.Run.ShowId)).AsQueryable();
    }

这个 "works",它 returns 实体列表。但是,我遇到了 Breeze

的问题

'System.Linq.EnumerableQuery' does not contain a definition for 'Include'

at CallSite.Target(Closure , CallSite , Object , String )
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at CallSite.Target(Closure , CallSite , Object , String )
at Breeze.WebApi.QueryHelper.<>c__DisplayClass14.<ApplyExpand>b__11(String expand)
at System.Collections.Generic.List`1.ForEach(Action`1 action)
at Breeze.WebApi.QueryHelper.ApplyExpand(IQueryable queryable, String expandsQueryString)
at Breeze.WebApi.QueryHelper.ApplySelectAndExpand(IQueryable queryable, NameValueCollection map)
at Breeze.WebApi.BreezeQueryableAttribute.OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
at System.Web.Http.Filters.ActionFilterAttribute.CallOnActionExecuted(HttpActionContext actionContext, HttpResponseMessage response, Exception exception)
at System.Web.Http.Filters.ActionFilterAttribute.<>c__DisplayClass2.<System.Web.Http.Filters.IActionFilter.ExecuteActionFilterAsync>b__0(HttpResponseMessage response)
at System.Threading.Tasks.TaskHelpersExtensions.<>c__DisplayClass41`2.<Then>b__40(Task`1 t)
at System.Threading.Tasks.TaskHelpersExtensions.ThenImpl[TTask,TOuterResult](TTask task, Func`2 continuation, CancellationToken cancellationToken, Boolean runSynchronously)

所以,我还是卡住了。

----- 上面的问题更新----

我正在尝试调查此控制器方法的问题:

        [System.Web.Http.HttpGet]
        public IQueryable<RunOfferPublish> RunOfferPublishes()
        {
        var showIds = _showSecurityCache.GetShowIdsForCurrentUser();
        var query = Context.RunOfferPublishes.Where(rop => showIds.Contains(rop.RunOffer.Run.ShowId)).AsQueryable();
        return query;
    }

showIds 数组是 212 个小整数(0 到 212)的列表。 RunOfferPublishes 是 DBContext(Context)table

如果我删除了 .Where 查询正在运行,但它会引发内存不足异常。我试图查看生成的 SQL 是什么,但我相信它没有达到这一点,因为我没有通过 SQL Profiler 看到它。

异常:

System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException'  was thrown.
at System.Collections.Generic.List`1.set_Capacity(Int32 value)
at System.Collections.Generic.List`1.EnsureCapacity(Int32 min)
at System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection)
at System.Collections.Generic.List`1.AddRange(IEnumerable`1 collection)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitBinaryJoin(Node joinNode, DbExpressionKind joinKind)
at System.Data.Query.PlanCompiler.CTreeGenerator.Visit(LeftOuterJoinOp op, Node n)
at System.Data.Query.InternalTrees.LeftOuterJoinOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n)
at System.Data.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitAsRelOp(Node inputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitJoinInput(Node joinInputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitBinaryJoin(Node joinNode, DbExpressionKind joinKind)
at System.Data.Query.PlanCompiler.CTreeGenerator.Visit(LeftOuterJoinOp op, Node n)
at System.Data.Query.InternalTrees.LeftOuterJoinOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n)
at System.Data.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitAsRelOp(Node inputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitJoinInput(Node joinInputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitBinaryJoin(Node joinNode, DbExpressionKind joinKind)
at System.Data.Query.PlanCompiler.CTreeGenerator.Visit(LeftOuterJoinOp op, Node n)
at System.Data.Query.InternalTrees.LeftOuterJoinOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n)
at System.Data.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitAsRelOp(Node inputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitJoinInput(Node joinInputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitBinaryJoin(Node joinNode, DbExpressionKind joinKind)
at System.Data.Query.PlanCompiler.CTreeGenerator.Visit(LeftOuterJoinOp op, Node n)
at System.Data.Query.InternalTrees.LeftOuterJoinOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n)
at System.Data.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitAsRelOp(Node inputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitJoinInput(Node joinInputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitBinaryJoin(Node joinNode, DbExpressionKind joinKind)
at System.Data.Query.PlanCompiler.CTreeGenerator.Visit(LeftOuterJoinOp op, Node n)
at System.Data.Query.InternalTrees.LeftOuterJoinOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n)
at System.Data.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitAsRelOp(Node inputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitJoinInput(Node joinInputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitBinaryJoin(Node joinNode, DbExpressionKind joinKind)
at System.Data.Query.PlanCompiler.CTreeGenerator.Visit(LeftOuterJoinOp op, Node n)
at System.Data.Query.InternalTrees.LeftOuterJoinOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n)
at System.Data.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitAsRelOp(Node inputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitJoinInput(Node joinInputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitBinaryJoin(Node joinNode, DbExpressionKind joinKind)
at System.Data.Query.PlanCompiler.CTreeGenerator.Visit(LeftOuterJoinOp op, Node n)
at System.Data.Query.InternalTrees.LeftOuterJoinOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n)
at System.Data.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitAsRelOp(Node inputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.Visit(FilterOp op, Node n)
at System.Data.Query.InternalTrees.FilterOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n)
at System.Data.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n)
at System.Data.Query.PlanCompiler.CTreeGenerator.VisitAsRelOp(Node inputNode)
at System.Data.Query.PlanCompiler.CTreeGenerator.BuildProjection(Node relOpNode, IEnumerable`1 projectionVars)
at System.Data.Query.PlanCompiler.CTreeGenerator.Visit(PhysicalProjectOp op, Node n)
at System.Data.Query.InternalTrees.PhysicalProjectOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n)
at System.Data.Query.InternalTrees.BasicOpVisitorOfT`1.VisitNode(Node n)
at System.Data.Query.PlanCompiler.CTreeGenerator..ctor(Command itree, Node toConvert)
at System.Data.Query.PlanCompiler.ProviderCommandInfoUtils.Create(Command command, Node node, List`1 children)
at System.Data.Query.PlanCompiler.CodeGen.Process(List`1& childCommands, ColumnMap& resultColumnMap, Int32& columnCount)
at System.Data.Query.PlanCompiler.PlanCompiler.Compile(List`1& providerCommands, ColumnMap& resultColumnMap, Int32& columnCount, Set`1& entitySets)
at System.Data.EntityClient.EntityCommandDefinition..ctor(DbProviderFactory storeProviderFactory, DbCommandTree commandTree)
at System.Data.EntityClient.EntityProviderServices.CreateCommandDefinition(DbProviderFactory storeProviderFactory, DbCommandTree commandTree)
at System.Data.EntityClient.EntityProviderServices.CreateDbCommandDefinition(DbProviderManifest providerManifest, DbCommandTree commandTree)
 at System.Data.Common.DbProviderServices.CreateCommandDefinition(DbCommandTree commandTree)
 at  System.Data.Objects.Internal.ObjectQueryExecutionPlan.Prepare(ObjectContext context, DbQueryCommandTree tree, Type elementType, MergeOption mergeOption, Span span, ReadOnlyCollection`1 compiledQueryParameters, AliasGenerator aliasGenerator)
 at System.Data.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
 at System.Data.Objects.ObjectQuery.ToTraceString()
 at System.Data.Entity.Internal.Linq.InternalQuery`1.ToString()
 at System.Data.Entity.Infrastructure.DbQuery`1.ToString()
 at ******************MainController.RunOfferPublishes() in 

EF 和 Contains 通常不能一起使用。也许可以在没有包含的情况下重写查询。我在猜测您的数据库模式,但也许是这样的:

var userId = _showSecurityCache.GetIdForCurrentUser();
var query = Context.RunOfferPublishes.Where(rop => Context.Users.Any(u => u.Id == userId && u.Shows.Any(s => s.Id == rop.RunOffer.Run.ShowId))).AsQueryable();
return query;

以这种方式进行连接,问题就解决了。这就像你必须准确地告诉 EF 要生成什么 SQL 否则它会崩溃。

    [System.Web.Http.HttpGet]
    public IQueryable<RunOfferPublish> RunOfferPublishes()
    {
        var showIds = _showSecurityCache.GetShowIdsForCurrentUser();
        return (from rop in Context.RunOfferPublishes
                   join ro in Context.RunOffers on rop.RunOfferId equals ro.RunOfferId
                   join r in Context.Runs on ro.RunId equals r.RunId
                   where showIds.Contains(r.ShowId)
                   select rop);
    }

这似乎是旧版 EF 的问题。我用 EF 4.1 和 6.1.3 试过这个。在4.1中,列表中只有3个项目(!),查询结构是这样的:

SELECT 
[Extent1].[AId] AS [AId], 
[Extent1].[BId] AS [BId], 
...
FROM         [dbo].[A] AS [Extent1]
INNER JOIN [dbo].[B] AS [Extent2] ON [Extent1].[BId] = [Extent2].[BId]
LEFT OUTER JOIN [dbo].[C] AS [Extent3] ON [Extent2].[CId] = [Extent3].[CId]
LEFT OUTER JOIN [dbo].[B] AS [Extent4] ON [Extent1].[BId] = [Extent4].[BId]
LEFT OUTER JOIN [dbo].[C] AS [Extent5] ON [Extent4].[CId] = [Extent5].[CId]
LEFT OUTER JOIN [dbo].[C] AS [Extent6] ON [Extent4].[CId] = [Extent6].[CId]
LEFT OUTER JOIN [dbo].[C] AS [Extent7] ON [Extent4].[CId] = [Extent7].[CId]
LEFT OUTER JOIN [dbo].[C] AS [Extent8] ON [Extent4].[CId] = [Extent8].[CId]
LEFT OUTER JOIN [dbo].[C] AS [Extent9] ON [Extent4].[CId] = [Extent9].[CId]
WHERE (1 = (CASE WHEN ([Extent3].[DId] IS NULL) THEN 0 ELSE [Extent5].[DId] END)) 
   OR (2 = (CASE WHEN ([Extent6].[DId] IS NULL) THEN 0 ELSE [Extent7].[DId] END)) 
   OR (3 = (CASE WHEN ([Extent8].[DId] IS NULL) THEN 0 ELSE [Extent9].[DId] END))

很明显,随着项目数量的增加,要生成的连接数量超出了可用资源。

您没有说明您使用的是哪个 EF 版本,但如果更深入的研究表明此查询形状是这样的,直到最近 查询生成改进[=26],我才不会感到惊讶=] 在 6.1 之后的版本中。在 6.1.3 中,查询形状符合预期:

SELECT 
    [Extent1].[AId] AS [AId], 
    [Extent1].[BId] AS [BId], 
    ...
    FROM   [dbo].[A] AS [Extent1]
    INNER JOIN [dbo].[B] AS [Extent2] ON [Extent1].[BId] = [Extent2].[BId]
    INNER JOIN [dbo].[C] AS [Extent3] ON [Extent2].[CId] = [Extent3].[CId]
    WHERE (CASE WHEN ([Extent3].[DId] IS NULL) THEN 0 
    ELSE [Extent3].[DId] END IN (1, 2, 3)) 
    AND (CASE WHEN ([Extent3].[DId] IS NULL) THEN 0 ELSE [Extent3].[DId] END IS NOT NULL)

而且 context.Configuration.UseDatabaseNullSemantics = true; 更苗条:

WHERE CASE WHEN ([Extent3].[DId] IS NULL) THEN 0 ELSE [Extent3].[DId] END IN (1, 2, 3)

因此,您可以使用设法找到的解决方法,或者升级到更高版本的 EF。