Entity Framework Core Pitfalls: Calling DB Functions in LINQ Queries as Extension Methods

Introduction

Another one for my series on Entity Framework Core Pitfalls. Sometimes, even when using EF Core, we need to perform some operations, like calling a database (DB) function, that is not mapped to LINQ. We've been able to do this for a long time, and it is certainly the case with EF Core. Let's see how.

Calling DB Functions in LINQ Queries

So, some standard DB functions have been mapped to EF.Functions, as part of the DbFunctions class, but there are a big number of them that haven’t. Some examples include SQL Server's REVERSESOUNDEX, DIFFERENCE, and many others. Now, there's a way to call them in LINQ queries, but there is a (minor) caveat.

One way to do this is to have a method in your DbContext-derived class, which can be static or instance, such as:

public string Reverse(string property) => throw new NotImplementedException("Only to be called in an EF Core LINQ query.");
};

Now, we need to tell EF Core to use this function, and there are two ways:

  1. Applying an attribute
  2. Using code mapping

For option #1, it's as simple as:

[DbFunction(Name = "REVERSE", IsBuiltIn = true, IsNullable = true)]
public string Reverse(string column) ...

The key is the [DbFunction] attribute, we use it to tell EF Core that this method should map to a built-in function (IsBuiltIn) called (Name) "REVERSE" which can return nulls (IsNullable). If we weren't considering a built-in function, we should also specify a schema for it (Schema).

Option #2 involves adding some extra code to OnModelCreating:

modelBuilder.HasDbFunction(GetType().GetMethod(nameof(Reverse), [ typeof(string) ]))
    .IsBuiltIn(true)
    .IsNullable()
    .HasName("REVERSE");

I think this code is straightforward and can be easily mapped to the first example. The parameters to HasDbFunction are just a method pointer, everything else (return type, arguments) is inferred from it.

Using either approach, we can now call it as this:

var names = ctx.MyEntity.Select(x => ctx.Reverse(x.Name)).ToList();

But, used as we are to extension methods, we can do even better!

Consider this static class containing an extension method:

public static class DbContextExtensions
{
    public static string Reverse(this string property) => throw new NotImplementedException("Only to be called in an EF Core LINQ query.");

}

Don't worry about the exception being thrown, we're not going to call the method directly, it here just as a proxy to the database function we actually want to call, and we need to tell EF Core about it. However, you may find it weird, but the [DbFunction] approach won't work, we need to use HasDbFunction instead:

modelBuilder.HasDbFunction(typeof(DbContextExtensions).GetMethod(nameof(DbContextExtensions.Reverse), [ typeof(string) ]))

    .IsBuiltIn(true)
    .IsNullable()
    .HasName("REVERSE");

So, in a nutshell, for some reason, the extension method approach requires that we map the DB function using code! I wish Microsoft would fix this soon.

Conclusion

The ability to call DB functions from a LINQ query is quite powerful, we just need to remember a trick or two. Stay tuned for more!

Comments

Popular posts from this blog

Audit Trails in EF Core

.NET Cancellation Tokens

ASP.NET Core Middleware