Linq to llblgen Expression tree for Contains limitation

Posts   
 
    
MisterBK
User
Posts: 11
Joined: 18-Oct-2013
# Posted on: 10-Jun-2014 12:33:09   

Hiya,

a bit of a weird request for help/support/suggestions this time, basically asking if a solution, made my Marc Gravell, for Linq to sql, can be easily adjusted so it will do the same for linq to llblgen.

The problem : we use ODP.NET, and unfortunately, from time to time, have to match big sets of integers (representing ids) to match with our database tables, and for which Contains(), works pretty well. However, Oracle has a limitation, that such a set can not contain over 1000 elements, otherwise the database will throw an exception. (argument limitation)

In some cases we can circumvent the problem, but alas, not always.

Of course we can rewrite those queries so they are (if needed) run in batches of (in our case) 1000 at the most, but some are deeply nested as subqueries, which makes rewriting them a pain in the, well, you know :-)

Marc Gravell came up with a rather elegant solution for linq to sql, in which the IQuerable is parsed for Contains, and then the necessary Expression modifications are made to make it run in chuncks, so the query itself (for us merely developers) hardly has to be changed.

Alas, naively applying his code doesn't work in llblgen, but part of me hopes that the actual necessary modification to make the magic work isn't that different. As I am not (yet) capable enough to work confidently with Expressions, and lack the necesary insight in how llblgen translates its IQuerables, I hopefully turn to you! :-)

So, in short, is there a small modification possible to make this :

DataSource<SomeEntity> data; List<long> ids; var res = data.Where(x => ids.Contains(x.SomeId).ToList();

work without ORA-01795 if ids > 1000 elements ?

So that the only rewrite would have to be :

var res = data.InRange(x => x.SomeId, 1000, ids);

Thank you very much in advance, cheers, Bart

Marc's solution is : (or http://stackoverflow.com/questions/567963/linq-expression-to-return-property-value/568771#568771)

or :


public static class QueryableChunked
{
    public static IEnumerable<T> InRange<T, TValue>(this IQueryable<T> source, Expression<Func<T, TValue>> selector, int blockSize, IEnumerable<TValue> values)
    {
        MethodInfo method = null;
        foreach (MethodInfo tmp in typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static))
        {
            if (tmp.Name == "Contains" && tmp.IsGenericMethodDefinition && tmp.GetParameters().Length == 2)
            {
                method = tmp.MakeGenericMethod(typeof(TValue));
                break;
            }
        }

        if (method == null) throw new InvalidOperationException("Unable to locate Contains");

        foreach (TValue[] block in values.GetBlocks(blockSize))
        {
            var row = System.Linq.Expressions.Expression.Parameter(typeof(T), "row");
            var member = System.Linq.Expressions.Expression.Invoke(selector, row);
            var keys = System.Linq.Expressions.Expression.Constant(block, typeof(TValue[]));
            var predicate = System.Linq.Expressions.Expression.Call(method, keys, member);
            var lambda = System.Linq.Expressions.Expression.Lambda<Func<T, bool>>(predicate, row);

            foreach (T record in source.Where(lambda))
            {
                yield return record;
            }
        }
    }

    public static IEnumerable<T[]> GetBlocks<T>(this IEnumerable<T> source, int blockSize)
    {
        List<T> list = new List<T>(blockSize);
        foreach (T item in source)
        {
            list.Add(item);
            if (list.Count == blockSize)
            {
                yield return list.ToArray();
                list.Clear();
            }
        }

        if (list.Count > 0)
        {
            yield return list.ToArray();
        }
    }
}

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 10-Jun-2014 17:11:30   

The code seems to create multiple <query>.Contains() calls, so I don't see what the problem would be with the code for linq to sql, as it seems to create normal linq code, but I haven't ran it so I don't know what's going wrong. What exactly goes wrong?

Otherwise it's problematic and it simply comes down to creating multiple queries and merge the results in memory. That is: the original query is executed multiple times with different parameters. If the 'Contains' is nested deeply, it is of course not doable as the query has to be chopped up at the Contains.

In general with these kind of problems, it might be helpful to look into inserting the values in a temporary table and join with that, or look into obtaining the values from the database in other ways.

Frans Bouma | Lead developer LLBLGen Pro
MisterBK
User
Posts: 11
Joined: 18-Oct-2013
# Posted on: 11-Jun-2014 09:18:08   

Hi Frans,

while this trick works for linq to sql and linq to entity, in linq to llblgen, in an example that a table on field Id (long) is matched with an IEnumerable<long>, the runtime throws an exception saying "Expression of type 'System.Int64' cannot be invoked" in the inner foreach in method InRange.

(full stack trace at the end of the mail)

Hearing you say that you are surprised it doesn't work as well in llblgen, as indeed it does nothing more then creating multiple contains, is hopeful and I hope will trigger your curiosity why it currently doesn't. (So I do urge you to try it for yourself :-)

Your other suggestions are appreciated, and valid of course, but also the reason why I hope to get this trick to work in llblgen as well, as they are far from optimal.

Lets see if the stacktrace enlightens you, thank you in advance again of course. cheers, Bart


System.ArgumentException was unhandled
  HResult=-2147024809
  Message=Expression of type 'System.Int64' cannot be invoked
  Source=System.Core
  StackTrace:
       at System.Linq.Expressions.Expression.GetInvokeMethod(Expression expression)
       at System.Linq.Expressions.Expression.Invoke(Expression expression, IEnumerable`1 arguments)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleInvocationExpression(InvocationExpression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleInClauseExpression(InClauseExpression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleLambdaExpression(LambdaExpression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleWhereExpression(WhereExpression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.GenericExpressionHandler.HandleExpression(Expression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.ExpressionHandlers.QueryExpressionBuilder.HandleExpression(Expression expressionToHandle)
       at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.HandleExpressionTree(Expression expression)
       at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.Execute(Expression expression)
       at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.System.Linq.IQueryProvider.Execute(Expression expression)
       at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProQuery`1.Execute()
       at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
       at GenerateDataClass.QueryableChunked.<InRange>d__0`2.MoveNext() in c:\Temp\code\GenerateDataClass\GenerateDataClass\Program.cs:line 126
       at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
       at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
       at GenerateDataClass.Program.Main(String[] args) in c:\Temp\code\GenerateDataClass\GenerateDataClass\Program.cs:line 89
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 


Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 12-Jun-2014 10:26:39   

It dies on the Invoke, which isn't surprising: it has to be interpreted, and we don't do that, we simply invoke it.

The thing it has to do is:

from: var q = metaData.Order.InRange(o => o.OrderId, 5, ids);
to:  var q = metaData.Order.Where(o => block.Contains(o.OrderId));

I'll mess around with the code a bit to see whether I can change Gravell's code a bit.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 12-Jun-2014 10:48:02   

Change:

var row = System.Linq.Expressions.Expression.Parameter(typeof(T), "row");
var member = System.Linq.Expressions.Expression.Invoke(selector, row);
var keys = System.Linq.Expressions.Expression.Constant(block, typeof(TValue[]));
var predicate = System.Linq.Expressions.Expression.Call(method, keys, member);
var lambda = System.Linq.Expressions.Expression.Lambda<Func<T, bool>>(predicate, row);

into

var keys = System.Linq.Expressions.Expression.Constant(block, typeof(TValue[]));
var predicate = System.Linq.Expressions.Expression.Call(method, keys, selector.Body);
var lambda = System.Linq.Expressions.Expression.Lambda<Func<T, bool>>(predicate, selector.Parameters[0]);

and it will work simple_smile

Is it ok to move this thread to the Linq forums so more people can see it?

I now understand what the Invoke() should result in (simply the lambda it gets passed in) so I can add a handler for it to the runtime. The original code will then also work, just a sec.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 12-Jun-2014 11:15:05   

I've updated the runtime, it can now deal with the Invoke as well properly (replace parameter of lambda with first argument, return body of processed lambda). This runtime is attached and will deal with the original code.

Frans Bouma | Lead developer LLBLGen Pro
MisterBK
User
Posts: 11
Joined: 18-Oct-2013
# Posted on: 12-Jun-2014 11:15:47   

Hee hee hee Frans, congratulations!

Wonderful news, this is exactly what I had hoped for. Tested it, spot on!

Of course, by all means, make this public, because as I usually say at work, "if we have this problem, then someone else has (had) it as well". (and then investigate with hope that they managed to find a solution...)

Can't say I fully understand what you mean by "so I can add a handler for it to the runtime", but you seem enthusiastic, and that's always good :-)

Thank you so much! cheers, Bart

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 12-Jun-2014 11:24:08   

See my last post simple_smile I added the handler, works now with the original code simple_smile

Great that it solves your problem! simple_smile

Frans Bouma | Lead developer LLBLGen Pro