views:

84

answers:

2

Forgive my clumsy explanation, but I have a class that contains a List:

public class Document
    {
        public int OwnerId { get; set; }
        public List<User> Users { get; set; }

        public Document() { }
    }

    public class User
    {
        public string UserName { get; set; }
        public string Department { get; set; }
    }

Currently I use PredicateBuilder to perform dynamic queries on my objects. How can I turn the following LINQ statement into an Expression Tree:

var predicate= PredicateBuilder.True<User>();
predicate= predicate.And<User>(user => user.Deparment == "HR"); 

var deptDocs = documents.AsQueryable()
          .Where(doc => doc.Users
          .AsQueryable().Count(predicate) > 0)
          .ToList();

In other words var deptDocs = documents.HasUserAttributes("Department", "HR").ToList();. From what I can tell, my issue is not being able to evaluate the user => user.Deparment == "HR" row by row if I were to issue an Expression.Invoke.

UPDATE:

I think I have made some progress on this:

var predicate = PredicateBuilder.True<User>();
predicate = predicate.And<User>(user => user.Department == "FIN");

Expression<Func<Document, bool>> userSelector =
            doc => doc.Users
            .AsQueryable()
            .Any(predicate);

var docParm = Expression.Parameter(typeof(Document), "appDoc");
var body = Expression.Invoke(userSelector, docParm);
var lambda = Expression.Lambda<Func<Document, bool>>(body, docParm);

var allPredicate = PredicateBuilder.True<Document>();            
allPredicate = allPredicate.And<Document>(doc => doc.OwnerId == 1);
allPredicate = allPredicate.And<Document>(lambda);

var hrDocs = this.docs.AsQueryable().Where(allPredicate).ToList();

Is there a more effective way to do this? How can I make an expression that creates the predicate - user => user.Department - with generics?

A: 

Sounds like you want this toolkit?

Craig Stuntz
Interesting - if I understand the post, you register your queries ahead of time? Have you used this approach?
David Robbins
Right; you build the queries before compiling. I've used the approach in spirit, but not with this specific toolkit.
Craig Stuntz
A: 

I've come up with a somewhat satisfactory solution where I can perform the query with syntax like below:

var predicate = PredicateBuilder.True<Document>();
predicate = predicate.And<Document>(User.SubQuery("UserName", "DAVER"));
predicate = predicate.And<Document>(AdHoc<Document>("OwnerId", 1));

var finDocs = docs.AsQueryable().Where(predicate).ToList();

I have an extension class with this method:

public static Expression<Func<T, bool>> AdHoc<T>
            (string columnName, object compValue)
{
    //  Determine type of parameter
    ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
    //  Target to compare to
    Expression property = Expression.Property(parameter, columnName);
    //  The value to match
    Expression constant = Expression.Constant(compValue, compValue.GetType());

    Expression equality = Expression.Equal(property, constant);
    Expression<Func<T, bool>> predicate =
    Expression.Lambda<Func<T, bool>>(equality, parameter);

    return predicate;
}

And on my User class I have a static method:

public static Expression<Func<Document, bool>> SubQuery(string property, 
                                                         string targetValue)
        {
            var predicate = PredicateBuilder.True<User>();
            predicate = predicate.And<User>(Extensions.AdHoc<User>(property, targetValue));

            Expression<Func<Document, bool>> userSelector =
                                    doc => doc.Users
                                        .AsQueryable()
                                        .Any(predicate);

            var docParm = Expression.Parameter(typeof(Document), "appDoc");
            var body = Expression.Invoke(userSelector, docParm);

            var docPredicate = PredicateBuilder.True<Document>();
            docPredicate = docPredicate.And<Document>(Expression.Lambda<Func<Document, bool>>(body, docParm));

            return docPredicate;
        }

The drawback is that I've included the Subquery functionality on the User class itself. It gets the job done, but if anyone has any suggestions or a better way to use generics so I don't have to include this static method on my User class I'm eager to hear what you have to say.

David Robbins