/Expressionify

Use extension methods in Entity Framework Core queries

Primary LanguageC#MIT LicenseMIT

Expressionify

Nuget Nuget Build Status Azure DevOps tests

Use extension methods in Entity Framework Core queries

Installing

Install these two nuget packages:

  • Clave.Expressionify
  • Clave.Expressionify.Tasks

How to use

  1. Mark the public static extension method with the [Expressionify] attribute.
  2. Call .Expressionify() in your Entity Framework query chain, before using any extension method.
  3. Use the extension method in the query

Example

Lets say you have this code:

var users = await db.Users
    .Where(user => user.DateOfBirth < DateTime.Now.AddYears(-18))
    .ToListAsync();

That second line is a bit long, so it would be nice to pull it out as a reusable extension method:

public static Extensions
{
    public static bool IsOver18(this User user)
        => user.DateOfBirth < DateTime.Now.AddYears(-18);
}

// ...

var users = await db.Users
    .Where(user => user.IsOver18())
    .ToListAsync();

Unfortunately this forces Entity Framework to run the query in memory, rather than in the database. That's not very efficient...

But, with just two additional lines of code we can get Entity Framework to understand how translate our extension method to SQL

public static Extensions
{
+   [Expressionify]
    public static bool IsOver18(this User user)
        => user.DateOfBirth < DateTime.Now.AddYears(-18);
}

// ...

// create a query
var users = await db.Users
+   .Expressionify()
    .Where(user => user.IsOver18())
    .ToListAsync();

Limitations

Expressionify uses the Roslyn code analyzer and generator to look for public static methods with expression bodies tagged with the [Expressionify] attribute.

// ✔ OK
[Expressionify]
public static int ToInt(this string value) => Convert.ToInt32(value);

// ❌ Not ok (it's not static)
[Expressionify]
public int ToInt(this string value) => Convert.ToInt32(value);

// ❌ Not ok (it's missing the attribute)
public static int ToInt(this string value) => Convert.ToInt32(value);

// ❌ Not ok (it's not public)
[Expressionify]
private static int ToInt(this string value) => Convert.ToInt32(value);

// ❌ Not ok (it doesn't have an expression body)
[Expressionify]
public static int ToInt(this string value)
{
    return Convert.ToInt32(value);
}

Inspiration and help

The first part of this project relies heavily on the work done by Luke McGregor in his LinqExpander project, as described in his article on composable repositories - nesting expressions, and on the updated code by Ben Cull in his article Expression and Projection Magic for Entity Framework Core .

The second part of this project uses Roslyn to analyze and generate code, and part of it is built directly on code by Carlos Mendible from his article Create a class with .NET Core and Roslyn.

The rest is stitched together from various Stack Overflow answers and code snippets found on GitHub.