Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic LINQ on a collection?

I've a project which ask me to do such a BIG search engine but which is all dynamic. I mean I can have about 0 to 9 main "group" which have inside something like an infinite possibility of "where" with "OR" or "AND". First thing we think was to use Dynamic Linq which provide a good alternative to build dynamic query. All this using EF with an homemade wrapper.

Probleme : I'm not able to access to a "Collection". I mean, I can easly access to a referenced object (Like Customer.State.StateName = "New-York" OR Custoemr.State.StateName = "Quebec" ) but I can't find a way to acces to something like : "Customer.Orders.OrderID = 2 OR Customer.Orders.OrderID = 3". I can easly figure out this its because its a collection, but how can I do this?

Please help me out!!

** Sorry for my english !!


Update

I'm not clear enought I think, sorry its because im french...

My problem its because nothing is static. Its a candidat search engine for a recruting compagny that place candidats into an enterprise. In a page where manager can search candidat, he can "parse" by : Domain(s) (Jobs), City(ies) or many other that user have filled up when he register. All this in format (if it were in SQL) :

[...] WHERE (domaine.domainID = 3 OR domaine.domainID = 5 OR domaine.domainID = 23) AND (cities.cityID = 4, cities.city = 32) [...]

So i can't do this with a normal LINQ format like :

Candidat.Domaines.Where(domain => domain.DomainID == 3 || domain.DomainID == 5 || domain.DomainID == 23);

Even the operator in the paretheses are dynamic ("AND" or "OR")! That why we trying to use Dynamic Linq because its a lot more flexible.

Hope its more easy to understand my problem ...


Update 2 Here's my method

private string BuildDomainsWhereClause() {
        StringBuilder theWhere = new StringBuilder();

        if (this.Domaines.NumberOfIDs > 0) {
            theWhere.Append("( ");

            theWhere.Append(string.Format("Domaines.Where( "));
            foreach (int i in this.Domaines.ListOfIDs) {
                if (this.Domaines.ListOfIDs.IndexOf(i) > 0) {
                    theWhere.Append(string.Format(" {0} ", this.DispoJours.AndOr == AndOrEnum.And ? "&&" : "||"));
                }
                theWhere.Append(string.Format("DomaineId == {0}", i));
            }
            theWhere.Append(" ))");
        }

        return theWhere.ToString();
    }

It works great instead that it "Not return a boolean". So how should I? Error : "Expression of type 'Boolean' expected".

At the end, it returns something like : "( Domaines.Where( DomaineId == 2 && DomaineId == 3 && DomaineId == 4 && DomaineId == 5 ))." which is added to my LINQ Query :

var queryWithWhere = from c in m_context.Candidats.Where(WHERE)
                                     select c;

Dont forget that there's like 7 or 8 more "possible" added things to search in ... Any ideas?

like image 765
Simon Dugré Avatar asked Aug 20 '09 13:08

Simon Dugré


4 Answers

What you need to do here, is build a LambdaExpression (more specifically an Expression<Func<T, bool>>). You cannot use a string. You can build a simple expression like this:

ParameterExpression p = Expression.Parameter(typeof(Domaine), "domaine");
Expression<Func<Domaine, bool>> wherePredicate = 
  Expression.Lambda<Func<Domaine, bool>>(
    Expression.Or(
      Expression.Equal(
        Expression.Property(p, "DomainID"),
        Expression.Constant(10)),
      Expression.Equal(
        Expression.Property(p, "DomainID"),
        Expression.Constant(11))
      ), p);

i.e.,

domaine.DomainID = 10 || domaine.DomainID = 11

Not very readable if you need to do this by hand.

There's a sample of a fully operational expression parser that will actually do this for you based on a string in C# Samples for Visual Studio 2008 at MSDN Code Gallery, under DynamicQuery. (The LinqDataSource control uses a slightly modified version of this sample internally.)

like image 77
Ruben Avatar answered Nov 08 '22 13:11

Ruben


Finaly i've got it exactly the way I want.

private string BuildDomainsWhereClause() {
        StringBuilder theWhere = new StringBuilder();

        if (this.Domains.NumberOfIDs > 0) {
            theWhere.Append("( ");

            foreach (int i in this.Domains.ListOfIDs) {
                if (this.Domains.ListOfIDs.IndexOf(i) > 0) {
                    theWhere.Append(string.Format(" {0} ", this.Domains.AndOr == AndOrEnum.And ? "&&" : "||"));
                }
                theWhere.Append(string.Format("Domains.Any(IdDomaine== {0})", i));
            }
            theWhere.Append(" )");
        }

        return theWhere.ToString();
    }

Which produce something like : "( DispoJours.Any(IdDispo == 3) && DispoJours.Any(IdDispo == 5) )".

All my other "Where builder" will do the same things with a "&&" between which give the correct result.

And later :

var queryWithWhere = from c in m_context.Candidats.Where(WHERE)
                     select c;

WHOOOHOOO !! Thanks folks. Were very usefull! Love this website!


Update

Don't forget that i use Dynamic Linq on this query. It's not a normal LINQ query.

like image 42
Simon Dugré Avatar answered Nov 08 '22 13:11

Simon Dugré


Assuming that Customer.Orders returns a collection, this is exactly why you can't just call a property of it.

In order to use LINQ to get the Order you're looking for, you'd need to know the OrderID (Or some other property) in which case you can do:

Customer.Orders.Find(order => order.OrderID == 2);

Edit: To add the expression to find id 2 or 3 in this way:

Customer.Orders.FindAll(order => order.OrderID == 2 || order.OrderID == 3);
like image 1
Jimmeh Avatar answered Nov 08 '22 12:11

Jimmeh


Do I understand that right, that both Customers is a collection and Orders is a collection while State (obviously) is a Property?

var q = from a in Customer
    from b in a.Orders
    where b.ID == 2
              || b.ID == 3
    select b;

Would work I guess.

Edit:

I did partly something like that. It's been too long to be exactly sure how I did it, but I can tell you, that I was using

public static IQueryable<T> Where<T>(this IQueryable<T> source, string predicate, params object[] values);

From DynamicQueryable class.

this.CountrySitesObject.Sites.AsQueryable().Where(w.WhereQuery, w.WhereParameters) 

(copied from my code).

like image 1
StampedeXV Avatar answered Nov 08 '22 12:11

StampedeXV