Custom SqlFunctionExpression pour EFCore — Part 1

Vandenbussche Julien
Just-Tech-IT
Published in
3 min readJan 20, 2023

Cet article fait partie d’une suite d’articles expliquant pourquoi j’ai eu à créer une SqlFunctionCustom et les différentes approches que l’on peut avoir pour un même résultat.

Vous trouverez donc 3 articles

Pourquoi ai-je eu besoin de créer ma propre SQLFunctionExpression

Pour le besoin d’un projet, je me suis aventuré dans la création d’une expression Sql pour EntityFrameworkCore. Quelque chose qui n’est pas spécialement évident compte tenu de la documentation un peu pauvre.

Mon besoin, au niveau du projet, est de générer une requête Sql via EFCore à partir d’un filtre Ldap… Il me fallait donc “convertir” une expression Ldap en requete Sql… Et là, commence mon aventure ;-).

En effet, un des opérateurs Ldap le “~=” (approximation) permet de faire une recherche par sonorité. Je me dis très bien en Sql, il existe SOUNDEX.

Cependant, EFCore ne gère pas cette fonctionnalité. J’ai donc recherché sur le web, et j’ai trouvé l’approche la plus simple mais que je qualifierais d’intrusive dans mon code… J’ai donc testé cette approche que je détaillerai par la suite. Après avoir obtenu le résultat escompté, j’ai voulu rendre cette fonctionnalité la moins intrusive à mettre en place… Et j’ai donc dû me rendre dans le code source d’EFCore afin de comprendre comment fonctionnaient les DbFunctionsExtensions, cette seconde approche sera détaillée dans le prochain article.

SqlFunctionExpression: version code-less

Il s’agit de la méthode la plus populaire et facile à mettre en place. Il suffit d’ajouter une classe partielle de notre DbContext et de déclarer la fonction en question et de la flagger via l’attribut DbFunction

public partial class MyDbContext
{
[DbFunction(Name = nameof(MyDbContext.Soundex))]
public virtual string Soundex(string value)
{
throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(MyDbContext.Soundex)));
}
}

il est aussi possible de définir cette fonction au niveau du modelBuilder par le biais de la fonction OnModelCreatingPartial.

public partial class MyDbContext
{
public virtual string Soundex(string value)
{
throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(MyDbContext.Soundex)));
}

private partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
{
modelBuilder.HasDbFunction(typeof(ROIDbContext).GetMethod(nameof(MyDbContext.Soundex)))
.HasTranslation(arguments => new SqlFunctionExpression("SOUNDEX", arguments, false,
arguments.Select(a => false), typeof(string), null));
}
}

Afin de cacher la définition de l’objet SqlFunctionExpression, je suis plutôt parti pour écrire ma propre SqlFunctionExpression (qui vous en conviendrez n’est pas compliquée)

internal sealed class SoundexSqlFunctionExpression : SqlFunctionExpression
{
public SoundexSqlFunctionExpression(SqlExpression instance)
: this(new[] { instance })
{

}

private SoundexSqlFunctionExpression(SqlExpression[] arguments)
: base("SOUNDEX", arguments, false, arguments.Select(a => false), typeof(string), null)
{

}
}

Ce qui se traduit dans notre DbContext

public parital class MyDbContext
{
public virtual string Soundex(string value)
{
throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(MyDbContext.Soundex)));
}

private partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
{
modelBuilder.HasDbFunction(SqlDbFunctionsExtensions.SoundexMethod)
.HasTranslation(arguments => new SoundexSqlFunctionExpression(arguments.ToArray()));
}
}

L’inconvénient de cette approche est le fait de devoir définir une classe partielle pour un DbContext donné et de copier/coller notre code produit ci-dessus… Et si comme moi vous souhaitiez avoir des tests d’intégration cette approche ne nous le permettra pas… ce qui m’amène donc à mon prochain article une version plus “complexe” mais plus flexible ;-).

--

--

Vandenbussche Julien
Just-Tech-IT

Developper/TechLead chez AXA France, DotNet est mon terrain de jeu depuis plusieurs années. Craftman dans l’âme, j’adore échanger autour des sujets technologies