Better handling of multiple where clauses

Dec 17, 2012 at 9:32 PM

So, I tried for hours and scratched my head why dynamic queries wouldn't work:

 

                var updates = (from entry in context.Status
                               where entry.Type == StatusType.Home &&
                                     entry.Count == MyApp.Environment.MaxItemsToRetrievePerCall + 1
                               select entry);

                if (query.HasIds)
                {
                    if (query.MinimalId.HasValue)
                    {
                        updates = updates.Where(x => x.SinceID == query.MinimalId.Value);
                    }

                    if (query.MaximalId.HasValue)
                    {
                        updates = updates.Where(x => x.MaxID == query.MaximalId.Value);
                    }
                }

 

And what seems, the software only checks for the first where clause :-(. Though I have no idea why this is, here is a fix:

1) TwitterContext::GetRequestParameters

            var parameters = new Dictionary<string, string>();

            // GHK FIX: Handle all wheres
            var whereExpressions = new FirstWhereClauseFinder().GetAllWheres(expression);
            foreach (var whereExpression in whereExpressions)
            {
                var lambdaExpression = (LambdaExpression)((UnaryExpression)(whereExpression.Arguments[1])).Operand;

                // translate variable references in expression into constants
                lambdaExpression = (LambdaExpression)Evaluator.PartialEval(lambdaExpression);

                var newParameters = reqProc.GetParameters(lambdaExpression);
                foreach (var newParameter in newParameters)
                {
                    if (!parameters.ContainsKey(newParameter.Key))
                    {
                        parameters.Add(newParameter.Key, newParameter.Value);
                    }
                }
            }

2) FirstWhereClauseFinder:

        private readonly List<MethodCallExpression> _whereExpressions = new List<MethodCallExpression>();

        public MethodCallExpression[] GetAllWheres(Expression expression)
        {
            Visit(expression);
            return _whereExpressions.ToArray();
        }
3) FirstWhereClauseFinder::VisitMethodCall:
           if (expression.Method.Name == "Where")
            {
                firstWhereExpression = expression;
                _whereExpressions.Add(expression);
            }

First tests look very promising :)

 

Coordinator
Dec 17, 2012 at 11:40 PM

Hi,

Thanks! This is something I've been wanting to do for a while. :)

@JoeMayo

Coordinator
Dec 18, 2012 at 1:34 AM

That works pretty well. I just made the update and checked it in. There's a demo in SearchDemos too.

Thanks again,

Joe

Dec 18, 2012 at 7:41 AM

Cool, great to hear that it's integrated now :)

Feb 24, 2013 at 6:35 AM
Edited Feb 24, 2013 at 6:53 AM
I just spent a long time missing a very small but important detail with this same multiple-where-clauses thing. I have a method like this:
public static User GetUser(Func<User, bool> criteria)
It's a little utility method that lets the ID / ScreenName be passed in as a pre-formed anonymous method where it's combined with the Type and IncludeEntities clauses that are always on. Usage would be like this:
User user = TwitterHelper.GetUser(u => u.ScreenName == screenName);
Here's the body of that method:
{
    User user = null;

    using (TwitterContext context = GetContext())
    {
        try
        {
            user = context.User
                .Where(u =>
                    u.Type == UserType.Show &&
                    u.IncludeEntities == false
                )
                .Where(criteria)
                .FirstOrDefault();
        }
        catch (TwitterQueryException tqe)
        {
            // 34 is does-not-exist; that means the user wasn't found
            if (tqe.ErrorCode != 34)
            {
                throw;
            }
        }               
    }

    return user;
}
See the problem? I didn't. I was passing the criteria in as a method group which apparently prevents that Where from being picked up at all when the Enumerator is being created. Once I changed it to...
.Where(u => criteria(u))
...everything was fine. Sigh. I thought I'd add this as a reminder to anyone else who might search for "where clause" here and find this thread.

P.S. Why is it only code coloring half of my code blocks? They're all marked as C# with "``` c#".

EDIT : Well, somehow after changing it to that it worked and then didn't work again which is a little bit disconcerting. So, I just bit the bullet and made the method...
public static User GetUser(Expression<Func<User, bool>> criteria)
...and now I can just pass the criteria to the Where, wholesale. Ugh.
Coordinator
Feb 25, 2013 at 9:00 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Coordinator
Feb 25, 2013 at 9:03 AM
shibumi,

Thanks, I copied to an issue.

@JoeMayo