Hi Guys,

I have the following model

class Assignment


[Field] bool Active [Field] DateTime Start [Field] DateTime? End Current

The implementation for Assignment.Current is:

get { return Active && Start <= DateTime.Now && (End == null || End.Value >= DateTime.Now); }

How can I write a custom LINQ expression rewriter for the Current property so that I can use the following LINQ query:

Query.All<assignment>.Where(assignment => assignment.Current)

I've tried to do it myself but the Expression stuff is new to me.

Thanks for your help!


Updated at 17.02.2010 23:38:06

Hi Alex,

Thanks for the help. This is the error I get with your code.

QualificationTest.AssignQualificationTest : FailedSystem.NullReferenceException: Object reference not set to an instance of an object.
at Xtensive.Core.Linq.ExpressionExtensions.StripCasts(Expression expression)
at Xtensive.Storage.Linq.Translator.GetMember(Expression expression, MemberInfo member, Expression sourceExpression)
at Xtensive.Storage.Linq.Translator.VisitMemberAccess(MemberExpression ma)
at Xtensive.Core.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.VisitBinary(BinaryExpression binaryExpression)
at Xtensive.Core.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.VisitBinary(BinaryExpression binaryExpression)
at Xtensive.Core.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.VisitBinary(BinaryExpression binaryExpression)
at Xtensive.Core.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.VisitMemberAccess(MemberExpression ma)
at Xtensive.Core.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.VisitBinary(BinaryExpression binaryExpression)
at Xtensive.Core.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.VisitLambda(LambdaExpression le)
at Xtensive.Storage.Linq.Translator.VisitWhere(Expression expression, LambdaExpression le)
at Xtensive.Storage.Linq.Translator.VisitQueryableMethod(MethodCallExpression mc, QueryableMethodKind methodKind)
at Xtensive.Core.Linq.QueryableVisitor.VisitMethodCall(MethodCallExpression mc)
at Xtensive.Storage.Linq.Translator.VisitMethodCall(MethodCallExpression mc)
at Xtensive.Core.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.VisitSequence(Expression sequenceExpression, Expression expressionPart)
at Xtensive.Storage.Linq.Translator.VisitRootExists(Expression source, LambdaExpression predicate, Boolean notExists)
at Xtensive.Storage.Linq.Translator.VisitAny(Expression source, LambdaExpression predicate, Boolean isRoot)
at Xtensive.Storage.Linq.Translator.VisitQueryableMethod(MethodCallExpression mc, QueryableMethodKind methodKind)
at Xtensive.Core.Linq.QueryableVisitor.VisitMethodCall(MethodCallExpression mc)
at Xtensive.Storage.Linq.Translator.VisitMethodCall(MethodCallExpression mc)
at Xtensive.Core.Linq.ExpressionVisitor`1.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.Visit(Expression e)
at Xtensive.Storage.Linq.Translator.Translate<TResult>()
at Xtensive.Storage.Linq.QueryProvider.Translate<TResult>(Expression expression) 
Xtensive.Storage.Linq.TranslationException: Unable to translate '$<Queryable<QualificationAssignment>>(Query.All<QualificationAssignment>()).Where(a => (((a.Assignee == $<Employee>(John Doe)) && (a.Qualification == @.qualification)) && a.Current)).Any()' expression. See inner exception for details.
at Xtensive.Storage.Linq.QueryProvider.Translate<TResult>(Expression expression)
at Xtensive.Storage.Linq.QueryProvider.Execute<TResult>(Expression expression)
at System.Linq.Queryable.Any<TSource>(IQueryable`1 source)
at Model.Employee.IsQualified(Qualification qualification) in Employee.cs: line 26
at ModelTest.QualificationTest.AssignQualificationTest() in QualificationTest.cs: line 27

Also, the manual says to register the rewriter using

config.CompilerContainers.Register(typeof (CustomLinqCompilerContainer)), but config does not expose CompilterContainers property. I'm assuming you autodetect them now.

Also, if IQueryPreprocessor is a better option, where can I find more info?

This thread was imported from our support forum. The original discussion may contain more detailed answer.

asked Feb 17 '10 at 09:06

ara's gravatar image

ara
395878791


One Answer:

First of all, I just fixed a bug related to usage of IQueryPreprocessor - it simply didn't work, and, as we discovered, there were no real tests for it except in Localization sample (it seems our guys have occasionally moved the code from tests to it). This must be interesting for you, even although this isn't related to your question at all: query preprocessors provide even more flexible way of doing such things (e.g. they allow you to inject filtering condition automatically for any query).

So RC is updated once more, and localization sample works there now ;)

Secondly, you need nearly the following code:

[CompilerContainer(typeof (Expression))]
  public static class CustomLinqCompilerContainer
  {
    [Compiler(typeof (Assignment), "Current", TargetKind.PropertyGet)]
    public static Expression Current(Expression assignmentExpression)
    {
      Expression<Func<Assignment, bool>> ex = 
        a => a.Active && (a.Start <= DateTime.Now) && (a.End == null || a.End.Value >= DateTime.Now);
      return ex.BindParameters(assignmentExpression);
    }
  }

I didn't test this - I just adopted this code from sample from our Manual project (all the code provided in Manual is actually taken from it).


Btw... Do you know that currently any method of LINQ rewriter must return the same expression for the same input, i.e. its methods must be pure functions.

More precisely, if you' violate this, you'll definitely get issues with compiled queries. But it's ok to do this if you don't use rewritten expressions in compiled queries (i.e. use them just in regular LINQ queries).

In future we'll offer an API allowing to properly cache produced query versions for compilers that aren't pure functions.


> I'm assuming you autodetect them now.

It must be registered as any other type - i.e. any type DO4 is interested in now must be added to DomainConfiguration.Types collection (DomainTypeRegistry, the reference is here). This isn't reflected in Manual yet, but we'll fix this until release.

> Also, if IQueryPreprocessor is a better option, where can I find more info?

For now you can see just Localization sample. In your particular case LINQ rewriters is more precise solution, but the same can be achieved with LINQ preprocessor as well.

> This is the error I get with your code.

Could you try to simplify the ex expression to isolate the bug? E.g. leave just a.Active part there, and then add other pieces of it. Most likely there is some issue related to handling of Nullable<t>.Value in translator - we'll try to reproduce it today as well.

An example simplified expression can be e.g. this one:

Expression<Func<Assignment, bool>> ex = 
    a => a.Active && (a.Start <= DateTime.Now) && (a.End >= DateTime.Now);

The effect will be the same, taking into account null handling.


v4.2 RC installer containing the bugfix is here: http://code.google.com/p/dataobjectsdot ... loads/list

I did not publish this @ Xtensive, since I'm going to update it once more in the deep night today (Manual will contain few more sections, etc.).

answered Feb 17 '10 at 19:38

Alex%20Yakunin's gravatar image

Alex Yakunin
29714412

I used simply a => a.Active and it worked. If I use a => a.Start <= DateTime.Now, it does NOT work.

Apparently DateTime fields can't be used (and therefore Nullable<datetime>). But ints, bools, etc work fine.

(Feb 17 '10 at 19:38) ara ara's gravatar image

Hmm... We're testing the issue. Most likely it is related just to translation of casting of an expression of DateTime type to Nullable<datetime>, since there are tests involving both DateTime and Nullable<datetime> in LINQ.

(Feb 17 '10 at 19:38) Alex Yakunin Alex%20Yakunin's gravatar image

We've just fixed an issue - it really was there (conversion + static property access was broken). So I'll update RC shortly, for now the changes are already @ Google Code.

(Feb 17 '10 at 19:38) Alex Yakunin Alex%20Yakunin's gravatar image

Fix confirmed. Thank you!

DO4 is fantastic!

(Feb 17 '10 at 19:38) ara ara's gravatar image
Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!
toggle preview

powered by OSQA