Building Dynamic Queries

Posts   
 
    
DannyGreen
User
Posts: 12
Joined: 25-Jul-2008
# Posted on: 25-Jul-2008 20:21:38   

I am trying to build an elegant design to interpret a UI with several criteria options for searching for records in a table. I would like to implement something along the lines of the PredicateBuilder by Josheph Albhari, http://www.albahari.com/nutshell/predicatebuilder.html

Is there any way with Linq to LLBLGEN to pre-define Expression Predicates and then combine them together? I know that building a library of Predicates is a commen practice with the standard LLBLGEN API, I expect there must be some way to accomplish this using Linq, but have been unable to come up with it myself. Any help would be greatly appreciated.

Thanks! Danny

DannyGreen
User
Posts: 12
Joined: 25-Jul-2008
# Posted on: 25-Jul-2008 20:26:21   

I forgot to mention that of course I did try to use PredicateBuilder, but it does not work with Link to LLBLGEN as it uses Invoke, which it is documented that it is not supported in LLBLGEN.

Thanks! Danny

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 26-Jul-2008 07:48:15   

Hi Danny, LLBLGenPro LinqSupportClasses already do that. Is this what you are trying to achieve?:

Building IQueryable

public IQueryable<ProductsEntity> SearchProducts(LinqMetaData metaData, params string[] keywords)
{
    // query to return
    IQueryable<ProductsEntity> query = metaData.Products;

    // ANY productthat match the criteria
    query = query.Where(p => keywords.Contains( p.Description));

    // done
    return query;
}

Usage

// this represent the UI input
string[] keywords = new string[] { "Konbu", "Tofu" };

// fetch results
using (DataAccessAdapter adapter = new DataAccessAdapter())
{
    // retrieve the query
    LinqMetaData metadata = new LinqMetaData(adapter);
    var q = SearchProducts(metadata, keywords);

    // execute the query
    EntityCollection<ProductsEntity> toReturn  = ((ILLBLGenProQuery) q).Execute<EntityCollection<ProductsEntity>>();
}
David Elizondo | LLBLGen Support Team
DannyGreen
User
Posts: 12
Joined: 25-Jul-2008
# Posted on: 26-Jul-2008 13:26:29   

Hello,

I think this could be adapted to work for my current needs, but it seems that a more flexible approach would be nice. Here is what I have done using the standard API:


        public static LB.IPredicateExpression PreferredLifeStage(ILifeStageCriteria criteria)
        {
            LB.IPredicateExpression retVal = new LB.PredicateExpression();
            
            if (criteria.Married) {retVal.AddWithOr(SmallGroupFields.LifeStageCode == "M");}
            if (criteria.Single) { retVal.AddWithOr(SmallGroupFields.LifeStageCode == "S"); }
            if (criteria.MarriedAndSingle) { retVal.AddWithOr(SmallGroupFields.LifeStageCode == "B"); }
            return retVal;
        }

        public static LB.IPredicateExpression GroupAge(NewExistingAllGroups selectedGroupAge)
        {
            LB.IPredicateExpression retVal = new LB.PredicateExpression();
            switch (selectedGroupAge)
            {
                case NewExistingAllGroups.NewGroups:
                    retVal.Add(SmallGroupFields.DateCreated >= Utility.MiscUtilities.GetCurrentTime().Subtract(new TimeSpan(60, 0, 0, 0)));
                    break;
                case NewExistingAllGroups.ExistingGroups:
                    retVal.Add(SmallGroupFields.DateCreated <= Utility.MiscUtilities.GetCurrentTime().Subtract(new TimeSpan(60, 0, 0, 0)));
                    break;
                case NewExistingAllGroups.AllGroups:
                    break;
                default:
                    break;
            }
            return retVal;
        }


                using (var lbAdapter = Factory.CreateAdapter())
                {
                    LB.RelationPredicateBucket predBucket = new SD.LLBLGen.Pro.ORMSupportClasses.RelationPredicateBucket();
                    predBucket.PredicateExpression.Add(GroupAge(criteria.GroupAge));
                    predBucket.PredicateExpression.AddWithAnd(PreferredLifeStage(criteria.LifeStages));



Using Albhari's PredicateBuilder I think it would have looked something like this:


        public static Expression<Func<SmallGroupEntity, bool>> GroupAgeLinq(NewExistingAllGroups selectedGroupAge)
        {
            var retVal = PredicateBuilder.True<SmallGroupEntity>();

            switch (selectedGroupAge)
                {
                case NewExistingAllGroups.NewGroups:
                    retVal = retVal.And(sg => sg.DateCreated >= DateTime.Now.Subtract(new TimeSpan(60, 0, 0, 0)));
                    break;
                case NewExistingAllGroups.ExistingGroups:
                    retVal = retVal.And(sg => sg.DateCreated <= DateTime.Now.Subtract(new TimeSpan(60, 0, 0, 0)));
                    break;
                }
            return retVal;
        }

        public static Expression<Func<SmallGroupEntity, bool>> PreferredLifeStageDD(ILifeStageCriteria lifeStage)
        {
            var retVal = PredicateBuilder.False<SmallGroupEntity>();
            if (lifeStage.Married) { retVal.Or(p => p.LifeStageCode == "M"); }
            if (lifeStage.Single) { retVal.Or(p => p.LifeStageCode == "S"); }
            if (lifeStage.MarriedAndSingle) retVal.Or(p => p.LifeStageCode == "B");
            return retVal;
        }


            using (var dataAccess = Factory.CreateAdapter())
                {
                var meta = new LinqMetaData(dataAccess);
                var myPred = GroupAgeLinq(criteria.GroupAge);
                myPred = myPred.And(PreferredLifeStageLinq());
                var q = from g in meta.SmallGroup.Where(myPred) select g;
                var retVal = ((LBL.ILLBLGenProQuery)q).Execute<EntityCollection<SmallGroupEntity>>;
                }


Is there any way with Linq to create a 'Library' of predicates that individualy could contain a combination of 'AND' and 'OR' operaterers that could then be combined together in any combination of AND's and OR's?

Thanks! Danny

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39866
Joined: 17-Aug-2003
# Posted on: 28-Jul-2008 14:54:23   

I think that should be done like this: you pass the queryable object to the routines which build the where clauses. In there, you simply call Where on that passed in queryable and return the result. So the GroupAgeLinq routine doesn't return an Expression, but does return a Queryable<SmallGroupEntity>

Combining the predicates can be a bit problematic indeed, because you need to group and-ed predicates for example.

Two boolean expressions which are anded are stored as a binary expression with two subbranches. So it's key to first know what to pack together, then to call the Where extension methods. You could build this with Expression<Func<T, U>> but it's key that the input for the Func is the total query, so Queryable<T>.

You posted some example code, but did it or didn't it work? Or was your question more geared towards what is possible with Linq?

Frans Bouma | Lead developer LLBLGen Pro
DannyGreen
User
Posts: 12
Joined: 25-Jul-2008
# Posted on: 28-Jul-2008 15:53:28   

Hello,

The code posted using the LLBLGEN 'Standard' API works, the second block using Albhari's PredicateBuilder does not because that code uses the 'Invoke' method. I am basically trying to see if there is a way using LINQ to do what is being done in the first block of 'Standard' code. I've seen that you can use Where multiple times, but doesn't this limit you to Anding only? How could I conditionally append OR clauses, as is being done in the PreferredLifeStage examples?

Thanks! Danny

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39866
Joined: 17-Aug-2003
# Posted on: 29-Jul-2008 10:50:54   

DannyGreen wrote:

Hello,

The code posted using the LLBLGEN 'Standard' API works, the second block using Albhari's PredicateBuilder does not because that code uses the 'Invoke' method.

strange that it does that, Invoke is of no use for expression trees...

I am basically trying to see if there is a way using LINQ to do what is being done in the first block of 'Standard' code. I've seen that you can use Where multiple times, but doesn't this limit you to Anding only? How could I conditionally append OR clauses, as is being done in the PreferredLifeStage examples?

It's indeed doing 'And' additions with multiple Where clauses attached to the query.

What you should create is a BinaryExpression which works on the Queryable<T>. I'll work on some examples for you.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39866
Joined: 17-Aug-2003
# Posted on: 29-Jul-2008 12:35:54   

Hmm... I think I know where the Invoke comes from: http://tomasp.net/blog/dynamic-linq-queries.aspx

this simply expands the data and runs linq to objects queries over it... not what you want.

When looking at the expression tree of an OR-ed where, e.g.: var q = from e in metaData.Employee where e.LastName=="Foo" || e.FirstName=="Foo" select e;

you'll see that the where lambda is actually a BinaryExpression of type OrElse and two sides which both are a binary expression of type Equal.

The problem is that the parameters passed into the lambda's have to be of the same type as the source element, here EmployeeEntity (as metaData.Employee is DataSource<EmployeeEntity>, or better: IQueryable<EmployeeEntity> )

So you can't easily formulate a lambda in code and use it as a db filter predicate by transforming it, you've to build it from scratch using Parameter objects and Expression.Equal and Expression.OrElse. You then have to add a method call to Queryable.Where and pass the build expression as the second parameter. (first is the source for the where call).

I.o.w.: a pile of code which isn't worth the effort IMHO.

Frans Bouma | Lead developer LLBLGen Pro