HOWTO: Linq enable our Query Pattern Implementation – and impress our colleagues

In the last post I showed you my simple query pattern implementation, for abstracting searching away from my Business Logic layer, and to enable a descent layered architecture in my “Applications.” A colleague of mine argued that, though he liked the abstraction, “it doesn’t support Linq” like classes created through SPMetal – I argued “No, but it supports descent layering,” and after half an hour throwing arguments at each other I told him: “Give me a couple of hours, and it will Linq enabled.”

Linq enabling your data model requires you to implement the IQueryable<T> interface, the IOrderedQueryable<T> and IQueryProvider, and there is a lot of wiring and parsing involved in doing so. Luckily most of the wiring and parsing have been boiled down into a Codeplex toolkit, which I will be using.

There’s absolutely no need to tell my colleague – since he was thoroughly impressed at the speed which I came up with this solution….. let’s just keep this little secret between us, right? The toolkit I’m using is the LinqExtender project found at https://linqextender.codeplex.com/

Notice that it has moved to: https://mehfuzh.github.io/LinqExtender/

 

Also notice, that to be able use it in your SharePoint package, you will need to sign and recompile LinqExtender – otherwise you will be unable to place the solution in your Global Assembly Cache (GAC).

 

What I will be doing is, with LinqExtender, to create a IQueryable<T> implementation, that translates into my Query pattern implementation – this way I will still have abstracted the exact data source away from my layered model, though in my example it will be SharePoint – or more precisely my IQuerySource<Target,Source> interface, shown in the previous post.

4

This will be a fairly simple implementation of a Linq expression parser, so I’ll be using a stack to keep track of which part of the Linq expressing I’m currently parsing.

The actual invoking of the query will go on in the “Execute” function of the ExpressionVisitor of the LinqExtender project, so let us start with implementing that.

    public class SPQueryContext<T> : ExpressionVisitor, IQueryContext<T>
    {
        private DataQuery _query = new DataQuery();
        private readonly ICaseData<T> _dataInterface = null;
        private Stack<CriteriaBase> _currentCriteria = new Stack<CriteriaBase>();
        public SPQueryContext(ICaseData<T> dataInterface)
        {
            _dataInterface = dataInterface;
        }

        public IEnumerable<T> Execute(LinqExtender.Ast.Expression expression)
        {
            _currentCriteria.Push(new AndOperator());
            this.Visit(expression);
            _query.Where(_currentCriteria.Pop());
            return (IEnumerable<T>)_dataInterface.GetItems (_query);
        }
    }

 

As you can see, I’m passing on a ICaseData<T> interface to the SPQueryContext, for doing the actual searching, so my layering are still intact, and I still don’t know exactly on which platform the search is happening.

The ICaseData<T> interface *might* have been named a bit wrong, since the interface now are more a general searching interface. Refactoring this I might today have called it something like “IEntityData<T>”

 

Let’s then start by doing the actual Linq parsing, and building up the Query structure:

        public override LinqExtender.Ast.Expression VisitBinaryExpression(
                                LinqExtender.Ast.BinaryExpression expression)
        {
            System.Diagnostics.Debug.WriteLine(expression.ToString());
            CombineOperator combine = _currentCriteria.Peek() as CombineOperator;
            if (combine != null)
            {
                switch (expression.Operator)
                {
                    case BinaryOperator.Contains:
                        {
                            Criteria c = new Criteria("", OperatorEnum.Like, "");
                            _currentCriteria.Push(c);
                            break;
                        }
                    case BinaryOperator.Equal:
                        {
                            Criteria c = new Criteria("", OperatorEnum.Equals, "");
                            _currentCriteria.Push(c);
                            break;
                        }
                    case BinaryOperator.GreaterThan:
                        {
                            Criteria c = new Criteria("", OperatorEnum.BiggerThan, "");
                            _currentCriteria.Push(c);

                            break;
                        }
                    case BinaryOperator.GreaterThanEqual:
                        {
                            Criteria c = new Criteria("", OperatorEnum.BiggerThanOrEqual, "");
                            _currentCriteria.Push(c);

                            break;
                        }
                    case BinaryOperator.LessThan:
                        {
                            Criteria c = new Criteria("", OperatorEnum.LessThan, "");
                            _currentCriteria.Push(c);
                            break;
                        }
                    case BinaryOperator.LessThanEqual:
                        {
                            Criteria c = new Criteria("", OperatorEnum.LessThanOrEqual, "");
                            _currentCriteria.Push(c);
                            break;
                        }
                    case BinaryOperator.NotApplicable:
                        {
                            break;
                        }
                    case BinaryOperator.NotEqual:
                        {
                            Criteria c = new Criteria("", OperatorEnum.NotEqual, "");
                            _currentCriteria.Push(c);
                            break;
                        }
                }
                Visit(expression.Left);
                Visit(expression.Right);
                combine.Where(_currentCriteria.Pop());
                return expression;
            }
            return base.VisitBinaryExpression(expression);
        }
        public override LinqExtender.Ast.Expression VisitLiteralExpression(
                                               LinqExtender.Ast.LiteralExpression expression)
        {
            System.Diagnostics.Debug.WriteLine(expression.ToString());
            Criteria c = _currentCriteria.Peek() as Criteria;
            if (c != null)
            {
                c.Value = expression.Value.ToString();
                return expression;
            }
            else
            {

                return base.VisitLiteralExpression(expression);
            }
        }
        public override LinqExtender.Ast.Expression VisitLogicalExpression(
                               LinqExtender.Ast.LogicalExpression expression)
        {
            System.Diagnostics.Debug.WriteLine(expression.ToString());
            switch (expression.Operator)
            {
                case LogicalOperator.None:
                case LogicalOperator.And:
                    {
                        _currentCriteria.Push(new AndOperator());
                        break;
                    }
                case LogicalOperator.Or:
                    {
                        _currentCriteria.Push(new OrOperator());
                        break;
                    }
            }
            this.Visit(expression.Left);
            this.Visit(expression.Right);
            _query.Where(_currentCriteria.Pop());
            return expression;
            //return base.VisitLogicalExpression(expression);
        }
        public override LinqExtender.Ast.Expression VisitMemberExpression(
                               LinqExtender.Ast.MemberExpression expression)
        {
            System.Diagnostics.Debug.WriteLine(expression.ToString());
            Criteria c = _currentCriteria.Peek() as Criteria;
            if (c != null)
            {
                c.Field = expression.Name;
                return expression;
            }
            else
            {

                return base.VisitMemberExpression(expression);
            }
        }

 

In the Query classes, I’ll be changing the following:

    public enum OperatorEnum{Like,

BeginsWith,

Equals,

BiggerThan,

LessThan,

BiggerThanOrEqual,

LessThanOrEqual,

NotEqual

}

public abstract class  CombineOperator : CriteriaBase

{

private List<CriteriaBase> _subCriterias = new List<CriteriaBase>();

public override Query Parent

{

get

{

return base.Parent;

}

set

{

base.Parent = value;

foreach (Criteria c in _subCriterias)

{

c.Parent = value;

}

}

}

internal virtual CombineOperator Where(CriteriaBase criteria)

{

criteria.Parent = base.Parent;

_subCriterias.Add(criteria);

return this;

}

public abstract string OperatorName { get; }

public List<CriteriaBase> Items

{

get

{

return _subCriterias;

}

}

}

 

 

Now I need to make my Case repository “Linq-aware,” so I will add a property named “Cases” that will return and instance of my new “SPQueryContext.” In my repository, as you saw in my last post, I already have a ICaseData<T> instantiated object, that I can pass to my SPQueryContext.

        public IQueryContext<Entities.Case> Cases{get

{

IQueryContext<Entities.Case> returns = new

SPQueryContext<Entities.Case>(_caseDataInterface);

return returns;

}

}

 

 

But now I have a serious problem: My entity property social security number is called “Cpr”, and in my underlying datamodel it’s called “CPR”. Luckily this is not that big a problem, since in the LinqExtender library we have a “NameAttribute” attribute, that we can use to set the datamodel name of our property.

I’m not entirely sure that I like this solution, since I now in my entities have bound some information to the underlying datamodel – which sort of breaks the idea that I should be able to replace the datamodel, without anyone noticing. Instead I might actually opt for the idea, of doing the query field mapping somewhere in the “SPCaseData” class, or equivalent.

 

 

So in my Entities Case I change the following:

        [LinqExtender.Attributes.Name(“CPR”)]public string Cpr{

get { return _cpr; }

set { _cpr = value; }

}

 

 

 

I should now be able to use Linq expressions on my repository like this:

        public List<Entities.Case> SearchByCaseHandlerLinq(string CaseHandlerName){var l = from c in Cases

where c.CaseHandler == CaseHandlerName

select c;

return l.ToList();

}

 

As you can see, I now in very few lines have implemented Linq awareness in my repository, and maintained my layering.

A word of warning: Doing this in an hour, will seriously impress your colleagues, and the might start bringing you coffee, and call you “Master.”

I have a piece of code you can get to test for yourself. Send me an email on kim.hermansen@visma.com and I’ll send it to you.