Skip to content

A small utility library that can be used to rewrite expression trees using methods as marker points for the rewriting process

License

Notifications You must be signed in to change notification settings

daniel-chambers/ExpressionTreeRewriter

Repository files navigation

Expression Tree Rewriter

This is small utility library that can be used to rewrite expression trees using methods as marker points for the rewriting process.

NuGet

Install-Package DigitallyCreated.ExpressionTreeRewriter

Example Usage

InlineContains

Let's say you want to write a LINQ query that filters a list of people and returns only those whose first name is in a small set of names.

var names = new[] { "Leia", "Chewbacca", "Luke" };

var results = _data.AsQueryable()
    .Where(person => names.Contains(person.FirstName))
    .ToList();

However, you then discover your LINQ provider doesn't support .Contains. Using the rewriter, you rewrite that .Contains call into a boolean expression by doing this:

var names = new[] { "Leia", "Chewbacca", "Luke" };

var results = _data.AsQueryable()
    .Where(person => names.InlineContains(person.FirstName))
    .Rewrite()
    .ToList();

The rewrite process effectively rewrites that .InlineContains method (known as a marker method) into the following boolean expression:

.Where(person => person.FirstName == "Leia" || person.FirstName == "Chewbacca" || person.FirstName == "Luke")

Lambda Inlining

Sometimes you wish you could extract some relatively complex logic out into another method when writing against IQueryable, but you can't because the Queryable provider wouldn't understand your method. Using the rewriter, you can extract that logic out into one place and then inline it into the expression tree at runtime. This keeps your code clean and your Queryable provider happy.

To do this, we first create an expression tree that represents our common logic and return it from a public static property on a class:

public class MyClass
{
  public static Expression<Func<Person, bool>> IsLukeExpr
  {
      get { return person => person.FirstName == "Luke" && person.LastName == "Skywalker"; }
  }
  
  ...
}

Then, we create a marker method that we can call in our query expressions. We annotate it with the RewriteUsingLambdaProperty attribute, telling the rewriter to replace all calls to this marker method with the lambda expression returned by the specified property (in this case MyClass.IsLukeExpr).

[RewriteUsingLambdaProperty(typeof(MyClass), "IsLukeExpr")]
public static bool IsLuke(Person person)
{
    throw new NotImplementedException("Should not be executed. Should be rewritten out of the expression tree.");
}

We can then use this marker method in our queries:

var results = _data.AsQueryable()
  .Where(person => IsLuke(person))
  .Rewrite()
  .ToList();

This is effectively rewritten into:

.Where(person => person.FirstName == "Luke" && person.LastName == "Skywalker")

InlineMaybe

You may want to test for nulls in your queries, but unfortunately your queries quickly start getting polluted with verbose ternary expressions like this:

var results = _data.AsQueryable()
  .Select(x => new {
    Name = x.Name,
    ParentName = x.Parent != null 
      ? x.Parent.Name 
      : null
  })
  .ToList();

Now you can use the InlineMaybe method instead, which will be inlined to the above ternary form when you rewrite the query.

var results = _data.AsQueryable()
  .Select(x => new {
    Name = x.Name,
    ParentName = x.Parent.InlineMaybe(p => p.Name)
  })
  .Rewrite()
  .ToList();

More Information

This library is based on the concepts and classes discussed in this blog post.

About

A small utility library that can be used to rewrite expression trees using methods as marker points for the rewriting process

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages