Print

Adding Enum Support to Entity Framework LINQ queries

Roger Alsing wrote an interesting post yesterday about adding support for enums to LINQ queries. It is nice to see what Roger is doing with the .NET 4.0 ExpressionVisitor to change Expression trees that allow this behavior. What I dislike however, is that for this solution you need to reimplement all Queryable extension methods. So I thought about this and found a more pleasant way of intercepting query calls.

You should definitely start by read Roger's post. What Roger does is creating a new Expression object based on a supplied one. As I said, he abuses extension methods to get this work. It would be nicer if we could just intercept calls to the IQueryProvider and replace expressions before they get executed.

The trick is to wrap an IQueryable<T> and a IQueryProvider object in decorators that allow you to intercept these calls. Then, instead of returning the repositories of your LINQ provider (ObjectQuery<T> for Entity Framework and Table<T> when using LINQ to SQL) you wrap them in a decorator. Downside of this is that you can't use Table<T> or ObjectQuery<T> directly in your code. However, I doubt that you really want this. You want your code to be testable and this would lead you to a design such as described in this post of mine.

Please note that LINQ to SQL actually supports Enum values, but still these transformation can be useful to create support for a large range of other interesting features. It would be nice to have a community site were developers can share extensions for rewriting Expression trees to add all sorts of new behavior to LINQ queries. The popularity of snippets could trigger Microsoft to add native support for these features in a next version of Entity Framework and LINQ to SQL.

I created an InterceptingQueryable<T> that you can return in your code. The creation of the repository could look like this:

protected override IQueryable<TEntity> GetRepository<TEntity>()
where TEntity : class
{
Table<TEntity> table = this.db.GetTable<TEntity>();

var interceptor = new InterceptingQueryable<TEntity>(table);

interceptor.InterceptingProvider.ExecutingQuery +=
TransformExpression;

return interceptor;
}

void TransformExpression(object sender, ExecutingQueryEventArgs e)
{
// replace the expression with a new one
e.Expression = [Use Rogers code here];

// or you could even use a DI fx to get multiple transformations
var transformers = ServiceLocator.Current
.GetAllInstances<ExpressionTransformer>();

foreach (var transformer in transformers)
{
e.Expression = transformer.Transform(e.Expression);
}
}

Here is the code for the InterceptingQueryable<T>:

public class InterceptingQueryable<T> : IQueryable<T>
{
private readonly IQueryable<T> wrapped;
private InterceptingQueryProvider queryProvider;

public InterceptingQueryable(IQueryable<T> wrapped)
{
this.wrapped = wrapped;
}

public Type ElementType
{
get { return this.wrapped.ElementType; }
}

public Expression Expression
{
get { return this.wrapped.Expression; }
}

public IQueryProvider Provider
{
get { return this.InterceptingProvider; }
}

public IEnumerator<T> GetEnumerator()
{
return this.wrapped.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)this.wrapped).GetEnumerator();
}

public InterceptingQueryProvider InterceptingProvider
{
get
{
if (this.queryProvider == null)
{
this.queryProvider = new InterceptingQueryProvider(
this.wrapped.Provider);
}

return this.queryProvider;
}
}
}

The InterceptingQueryable<T> uses a InterceptingQueryProvider that allows you to hook on to:

public class ExecutingQueryEventArgs : EventArgs
{
public Expression Expression { get; set; }
}

public class InterceptingQueryProvider : IQueryProvider
{
private readonly IQueryProvider wrapped;

public InterceptingQueryProvider(IQueryProvider wrapped)
{
this.wrapped = wrapped;
}

public event EventHandler<ExecutingQueryEventArgs> ExecutingQuery;

public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return this.wrapped.CreateQuery<TElement>(this.Transform(expression));
}

public IQueryable CreateQuery(Expression expression)
{
return this.wrapped.CreateQuery(this.Transform(expression));
}

public TResult Execute<TResult>(Expression expression)
{
return this.wrapped.Execute<TResult>(this.Transform(expression));
}

public object Execute(Expression expression)
{
return this.wrapped.Execute(this.Transform(expression));
}

protected virtual void OnExecutingQuery(ExecutingQueryEventArgs e)
{
if (this.ExecutingQuery != null)
{
this.ExecutingQuery(this, e);
}
}

private Expression Transform(Expression expression)
{
var e = new ExecutingQueryEventArgs() { Expression = expression };

this.OnExecutingQuery(e);

return e.Expression;
}
}

Cool! Happy querying!

- .NET General, C#, Databases, Entity Framework, LINQ, LINQ to SQL, O/RM, SQL - one comment / No trackbacks - §

The code samples on my weblog are colorized using javascript, but you disabled javascript (for my website) on your browser. If you're interested in viewing the posted code snippets in color, please enable javascript.

one comment:

Hi,

I think your method is really interesting. I tried to implement it and got problems when nested queries comes into play:

var context = new PROGRAMMINGEFDB1Entities();

//Works
var var1 =
    from x in context.Addresses
    where x.StateProvince == "Ontario"
    select x;

var var2 = (
    from x in var1
    select new
    {
        x,
        contact = (
            from y in context.Contacts
            where y.ContactID == x.ContactID
            select y)
    }).ToList();

//Works not
var addresses = new InterceptingQueryable(context.Addresses);

var var3 =
    from oa in addresses
    where oa.StateProvince == "Ontario"
    select oa;

var contacts = (new InterceptingQueryable(context.Contacts));

//Throws Exception
var var4 = (
    from oa in var3
    select new
    {
        oa,
        contact = (
            from y in contacts
            where y.ContactID == oa.ContactID
            select y)
    }).ToList();

Why is this happening and what can we do to avoid this? Seems EF is behaving differently when not using pure ObjectSets...
Dresel - 04 01 11 - 22:42


No trackbacks:

Trackback link:

Please enable javascript to generate a trackback url


  
Remember personal info?

/

Before sending a comment, you have to answer correctly a simple question everyone knows the answer to. This completely baffles automated spam bots.
 

  (Register your username / Log in)

Notify:
Hide email:

Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.