4
public IEnumerable<Table1> GetMatchingTable1(string param, double[] Thicknesses)
{
    return DBContext.Table1.Where(c => c.Field1 == param
                                    && Thicknesses.Any(Thickness => Thickness >= c.MinThickness && Thickness <= c.MaxThickness))
                           .ToList();
}

Above query return the following exception. "Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries."

So far, all my research on the web for this error pointed toward replacing "ANY" with "CONTAINS". Here is one site where they fix the problem using this solution : http://blog.hompus.nl/2010/08/26/joining-an-iqueryable-with-an-ienumerable/
But in my case, "CONTAINS" doesn't seem usable since I check a RANGE with Min and Max.

How should this query be written to have a proper SQL Statement generated by LinqToEntity?

Thanks

AXMIM
  • 2,424
  • 1
  • 20
  • 38
  • Looks like your collection is just too large; you'll need to find some way of breaking it down into multiple smaller collections to check. – Servy Mar 16 '15 at 14:59
  • When local array have few elements in it. It works as the nested query limit in SQL isn't reached. But with forty and more, the limit is reached and exception raised. Since local array "Thicknesses" come from userinput, I can't guarantee it will be small. – AXMIM Mar 16 '15 at 15:06

1 Answers1

2

You could try to build the query dynamically:

public IEnumerable<Table1> GetAllCoilLengthSettingsWithChilds(string param, double[] Thicknesses)
{
    // Base query
    var query = LinqKit.Extensions.AsExpandable(DBContext.Table1.Where(c => c.Field1 == param));

    // All the various || between the Thickness ranges
    var predicate = LinqKit.PredicateBuilder.False<Table1>();

    foreach (double th in Thicknesses)
    {
        // Don't want a closure around th
        double th2 = th;
        predicate = predicate.Or(c => th2 >= c.MinThickness && th2 <= c.MaxThickness);
    }

    // This is implicitly in && with the other Where
    query = query.Where(predicate);

    return query.ToList();
}

The PredicateBuilder helps you build an || query. Take it from the LinqKit (source available) I've tested it with 1000 parameters (but they where DateTime, and I didn't have other query pieces), and it seems to work. Note that the program uses another extension of LinqPad, AsExpandable, used to make the PredicateBuilder "trick" work. Note that I'm using EF 6.1.3, so your mileage may vary.

If you don't want to use LinqKit, I'm appending my version of PredicateBuilder. It doesn't require the use of AsExpandable(), but its syntax is slightly different:

public class PredicateBuilder<T>
{
    // We share a single parameter for all the PredicatBuilder<T>
    // istances. This isn't a proble, because Expressions are immutable
    protected static readonly ParameterExpression Parameter = Expression.Parameter(typeof(T), "x");

    protected Expression Current { get; set; }

    // Returns an empty PredicateBuilder that, if used, is true
    public PredicateBuilder()
    {
    }

    // Use it like this: .Where(predicate) or .Any(predicate) or 
    // .First(predicate) or...
    public static implicit operator Expression<Func<T, bool>>(PredicateBuilder<T> predicate)
    {
        if (object.ReferenceEquals(predicate, null))
        {
            return null;
        }

        // Handling of empty PredicateBuilder
        Expression current = predicate.Current ?? Expression.Constant(true);

        Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(current, Parameter);
        return lambda;
    }

    public static implicit operator PredicateBuilder<T>(Expression<Func<T, bool>> expression)
    {
        var predicate = new PredicateBuilder<T>();

        if (expression != null)
        {
            // Equivalent to predicate.Or(expression)
            predicate.And(expression);
        }

        return predicate;
    }

    public void And(Expression<Func<T, bool>> expression)
    {
        if (expression == null)
        {
            throw new ArgumentNullException("expression");
        }

        var expression2 = new ParameterConverter(expression.Parameters[0], Parameter).Visit(expression.Body);
        this.Current = this.Current != null ? Expression.AndAlso(this.Current, expression2) : expression2;
    }

    public void Or(Expression<Func<T, bool>> expression)
    {
        if (expression == null)
        {
            throw new ArgumentNullException("expression");
        }

        var expression2 = new ParameterConverter(expression.Parameters[0], Parameter).Visit(expression.Body);
        this.Current = this.Current != null ? Expression.OrElse(this.Current, expression2) : expression2;
    }

    public override string ToString()
    {
        // We reuse the .ToString() of Expression<Func<T, bool>>
        // Implicit cast here :-)
        Expression<Func<T, bool>> expression = this;
        return expression.ToString();
    }

    // Small ExpressionVisitor that replaces the ParameterExpression of
    // an Expression with another ParameterExpression (to make two
    // Expressions "compatible")
    protected class ParameterConverter : ExpressionVisitor
    {
        public readonly ParameterExpression From;
        public readonly ParameterExpression To;

        public ParameterConverter(ParameterExpression from, ParameterExpression to)
        {
            this.From = from;
            this.To = to;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (node == this.From)
            {
                node = this.To;
            }

            return base.VisitParameter(node);
        }
    }
}

public static class PredicateBuilder
{
    // The value of source isn't really necessary/interesting. Its type
    // is :-) By passing a query you are building to Create, the compiler
    // will give to Create the the of the object returned from the query
    // Use it like:
    // var predicate = PredicateBuilder.Create<MyType>();
    // or
    // var predicate = PredicateBuilder.Create(query);
    public static PredicateBuilder<T> Create<T>(IEnumerable<T> source = null)
    {
        return new PredicateBuilder<T>();
    }

    // Useful if you want to start with a query:
    // var predicate = PredicateBuilder.Create<MyType>(x => x.ID != 0);
    // Note that if expression == null, then a new PredicateBuilder<T>()
    // will be returned (that by default is "true")
    public static PredicateBuilder<T> Create<T>(Expression<Func<T, bool>> expression)
    {
        // Implicit cast to PredicateBuilder<T>
        return expression;
    }
}

Use it like:

var predicate = PredicateBuilder.Create(query);

and then everything is the same (but remove the LinqKit.Extensions.AsExpandable part)

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • 1
    I expect `Any` to product the exact same SQL in that case. – MarcinJuraszek Mar 16 '15 at 15:11
  • @MarcinJuraszek Probably, not 100% sure :-) I'll give this way a 50% chance of working. 50% is better than 0%. – xanatos Mar 16 '15 at 15:16
  • The OP's query is failing specifically because it's nesting ORs, and it goes too deep. This is doing the same thing, and will fail for the same reason. – Servy Mar 16 '15 at 15:17
  • @Servy Here I don't have an Entity Framework DB, no one replied in 30 minutes, so the chances of someone giving a "correct" "100% working" solution are becoming lower. If it doesn't work, I'll write **doesn't work** on it. It won't be the first time a non-working solution was posted on SO – xanatos Mar 16 '15 at 15:19
  • @xanatos And what, you don't care that it doesn't work? It's okay to post a solution that won't work because nobody has posted a working solution? – Servy Mar 16 '15 at 15:20
  • @Servy 50% is > 0% I began with `You could try to build the query dynamically`. But here we are speaking of the sex of angels. I don't know if it works, you don't know, no one knows. It is a schrodinger answer. – xanatos Mar 16 '15 at 15:23
  • It doesn't have a 50% chance of working. It won't work for exactly the same reason the OP's code won't work. It's doing exactly the same thing after all. Should I go and rename all of the variables in the OP's and then say that the code and then say it has a 50% chance of working and that nobody knows that it won't work? – Servy Mar 16 '15 at 15:24
  • 2
    `Any()` produces a query consisting of unions between `n` single row queries (where `n` is the number of items in `param`). This is a structure that quickly hits the nesting limit. A query with `OR`s can contain many more predicates. Still, for performance reasons, the number of items in `param` shouldn't get "too high", but now the limit is in the thousands, as opposed to ~50. – Gert Arnold Mar 16 '15 at 15:26
  • @GertArnold I suspected something similar by looking at the link provided by the OP, but it is a suspect. I can't prove it. `FROM ( SELECT 1 AS [C1] FROM (SELECT 1 AS X) AS [SingleRowTable1] **UNION ALL** SELECT 2 AS [C1] FROM (SELECT 1 AS X) AS [SingleRowTable2] ) AS [UnionAll1]` and even if it worked on my pc, the OP could use one of the other versions of EF, with different query building logic. – xanatos Mar 16 '15 at 15:31
  • @xanatos Yes, there it is. I didn't open the link because I bumped into this myself once and I was amazed to see this absurd query structure (although I understand the problem of translating a local sequence into a temporatry SQL table). – Gert Arnold Mar 16 '15 at 15:37
  • I'm trying your solution, but I'm not yet at testing with large array because I'm having unexpected record that doesn't fit criteria with small array. Trying to figure out why they are returned. Could it be because I'm missing the "AsExpandable" part you added recently? By the way, user will never input 1000 elements but 50 is really possible. So a solution that push the limit from 50 to 1000 is good for me. – AXMIM Mar 16 '15 at 16:07
  • @AXMIM Yes, you do need the `AsExpandable()`. And note that I had to do another correction: `query = query.Where(predicate); ` – xanatos Mar 16 '15 at 16:08
  • @AXMIM if you want, I've included the source of *my* version of `PredicateBuilder`, that doesn't require LinqKit nor it requires `AsExpandable()` – xanatos Mar 17 '15 at 13:13