2011-12-28 14 views
8

me sale el error:Un ciclo se detectó en una excepción de expresión LINQ

Se detectó un ciclo en una expresión LINQ.

en ToList() al tratar de hacer lo siguiente:

private IEnumerable<int> FilterIdsByClient(IEnumerable<int> entityIds) 
{ 
    entityIds = 
     MyObjectContext.CreateObjectSet<TEntity>() 
      .Where(x => x.ClientId == _clientId) 
      .Where(x => entityIds.Contains(x.Id)) 
      .Select(x => x.Id); 

    return entityIds.ToList(); 
} 

Sin embargo, esto no lanza ninguna excepción y funciona bien:

private IEnumerable<int> FilterIdsByClient(IEnumerable<int> entityIds) 
{ 
    entityIds = 
     MyObjectContext.CreateObjectSet<TEntity>() 
      .Where(x => x.ClientId == _clientId) 
      .Where(x => entityIds.Contains(x.Id)) 
      .Select(x => x.Id) 
      .ToList(); 

    return entityIds; 
} 

(Esta es una versión Simplfied por supuesto) .

¿Alguien tiene una idea de por qué ocurre este extraño comportamiento?

Editar:

Este es el seguimiento de la pila:

at System.Data.Objects.ELinq.Funcletizer.FuncletizingVisitor.Visit(Expression exp) 
    at System.Data.Objects.ELinq.Funcletizer.Funcletize(Expression expression, Func`1& recompileRequired) 
    at System.Data.Objects.ELinq.Funcletizer.FuncletizingVisitor.InlineExpression(Expression exp) 
    at System.Data.Objects.ELinq.Funcletizer.FuncletizingVisitor.InlineObjectQuery(ObjectQuery inlineQuery, Type expressionType) 
    at System.Data.Objects.ELinq.Funcletizer.FuncletizingVisitor.InlineValue(Expression expression, Boolean recompileOnChange) 
    at System.Data.Objects.ELinq.Funcletizer.FuncletizingVisitor.Visit(Expression exp) 
    at System.Linq.Expressions.EntityExpressionVisitor.VisitExpressionList(ReadOnlyCollection`1 original) 
    at System.Linq.Expressions.EntityExpressionVisitor.VisitMethodCall(MethodCallExpression m) 
    at System.Linq.Expressions.EntityExpressionVisitor.Visit(Expression exp) 
    at System.Data.Objects.ELinq.Funcletizer.FuncletizingVisitor.Visit(Expression exp) 
    at System.Linq.Expressions.EntityExpressionVisitor.VisitLambda(LambdaExpression lambda) 
    at System.Linq.Expressions.EntityExpressionVisitor.Visit(Expression exp) 
    at System.Data.Objects.ELinq.Funcletizer.FuncletizingVisitor.Visit(Expression exp) 
    at System.Linq.Expressions.EntityExpressionVisitor.VisitUnary(UnaryExpression u) 
    at System.Linq.Expressions.EntityExpressionVisitor.Visit(Expression exp) 
    at System.Data.Objects.ELinq.Funcletizer.FuncletizingVisitor.Visit(Expression exp) 
    at System.Linq.Expressions.EntityExpressionVisitor.VisitExpressionList(ReadOnlyCollection`1 original) 
    at System.Linq.Expressions.EntityExpressionVisitor.VisitMethodCall(MethodCallExpression m) 
    at System.Linq.Expressions.EntityExpressionVisitor.Visit(Expression exp) 
    at System.Data.Objects.ELinq.Funcletizer.FuncletizingVisitor.Visit(Expression exp) 
    at System.Linq.Expressions.EntityExpressionVisitor.VisitExpressionList(ReadOnlyCollection`1 original) 
    at System.Linq.Expressions.EntityExpressionVisitor.VisitMethodCall(MethodCallExpression m) 
    at System.Linq.Expressions.EntityExpressionVisitor.Visit(Expression exp) 
    at System.Data.Objects.ELinq.Funcletizer.FuncletizingVisitor.Visit(Expression exp) 
    at System.Data.Objects.ELinq.Funcletizer.Funcletize(Expression expression, Func`1& recompileRequired) 
    at System.Data.Objects.ELinq.ExpressionConverter..ctor(Funcletizer funcletizer, Expression expression) 
    at System.Data.Objects.ELinq.ELinqQueryState.CreateExpressionConverter() 
    at System.Data.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption) 
    at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption) 
    at System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() 
    at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) 
    at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source) 
    at ...FilterIdsByClient... 

Edit2:

deben tener en cuenta que en este caso, IEnumerable<int> entityIds es una lista que vienen de una petición AJAX y no una consulta desde algun lado.

+0

Se puede tratar de resolver los resultados 'MyObjectContext.CreateObjectSet ()' a una variable y luego usarlo en la consulta LINQ? – sll

+0

Pero esto devolverá la tabla de la base de datos, ¿no? Y también, ¿cómo va a ayudar esto? –

Respuesta

9

El comportamiento parece extraño, ya que no está considerando la semántica de cierre correctamente. Ver los comentarios a continuación:

private IEnumerable<int> FilterIdsByClient(IEnumerable<int> entityIds) 
{ 
    // The variable entityIds points to whatever was passed in: A List, according to the edited question. 

    entityIds =         //this is an assignment, changing the referent of entityIds 
     MyObjectContext.CreateObjectSet<TEntity>() 
      .Where(x => x.ClientId == _clientId) 
      .Where(x => entityIds.Contains(x.Id)) //this lambda closes over the variable entityIds 
      .Select(x => x.Id); 

    // The query now has a reference to the *variable* entityIds, not to the object that entityIds pointed to originally. 
    // The value of entityIds has been changed; it now points to the query itself! 
    // The query is therefore operating on itself; this causes the "cycle detected" message. 
    // Because of delayed execution, the query is not executed until the next line of code: 

    return entityIds.ToList(); 
} 
+0

+1 Gracias por evitarme explicar esto. – Mzn

4

La respuesta es no asignar la consulta LINQ a entityIds. Vea la respuesta de @ Stu para una solución.

+0

Tenga en cuenta que entityIds es una lista y no una consulta. ¿Qué quieres decir con reescribirlo? Cómo ? –

+0

@gil: en realidad, entityIds es una variable. En el ejemplo del código de generación de excepciones, en la llamada .ToList(), esa variable ha sido reasignada y apunta a la consulta, no a la lista. – phoog

+0

En su primer ejemplo, no es una lista, sino una consulta LINQ. Esto es que el error explica. Sin embargo, en el segundo ejemplo, la salida de 'ToList' se asigna a' entityIds' ** después de que ** la consulta se ejecuta, por lo que 'entityIds' será' null'. En cuanto a cómo reescribirlo: no pude ayudarte con esto porque esto depende de lo que estás tratando de lograr. –

6

¿Por qué está asignando a su parámetro? ¿Por qué no?

private IEnumerable<int> FilterIdsByClient(IEnumerable<int> entityIds) 
{ 
    return 
     MyObjectContext.CreateObjectSet<TEntity>() 
      .Where(x => x.ClientId == _clientId) 
      .Where(x => entityIds.Contains(x.Id)) 
      .Select(x => x.Id) 
      .ToList(); 
} 
+0

Esta no era mi pregunta. Sé que puedo hacerlo y estoy de acuerdo en que esto es mejor, pero quería saber POR QUÉ este comportamiento ocurre. –

+0

Claro, solo quería señalar que asignar la forma en que lo hace es pedir problemas, punto. La explicación es bastante simple: está asignando un enumerador que usa el valor original del que está asignando como explica phoog. – Stu

3

Por supuesto hay un ciclo. Está utilizando entityIds en el método Where Linq Extension y es la consulta que se está construyendo. En lugar de modificar el IEnumerable introducido, devolver una nueva consulta de la siguiente manera:

private IEnumerable<int> FilterIdsByClient(IEnumerable<int> entityIds) 
{ 
    var query = 
     MyObjectContext.CreateObjectSet<TEntity>() 
      .Where(x => x.ClientId == _clientId) 
      .Where(x => entityIds.Contains(x.Id)) 
      .Select(x => x.Id); 

    return query.ToList(); 
} 
+0

Pero entityIds es solo una lista. NO es una consulta. –

+1

@gil: en realidad, entityIds es una * variable *. En el ejemplo del código de generación de excepciones, en la llamada '.ToList()', esa variable * ha sido reasignada * y apunta a la consulta, no a la lista. – phoog

+0

@gil Ver la respuesta de phoog. Te beneficiarás mucho si entiendes que las expresiones de Linq usan lambdas que tienen cierres y, por lo tanto, hay intrincadas implicaciones. – Mzn

Cuestiones relacionadas