Loupe

User Defined Functions SQL Server, Entity Framework : usages et optimisations (partie 1)

Les User Defined Functions, ou plus simplement UDFs, sont un moyen très pratique de mutualiser du code auquel on souhaite faire appel dans une requête SQL. On peut l'employer pour appliquer du traitement sur les données requêtées, effectuer des vérifications, simplifier une requête, etc. On peut aussi s'en servir pour camoufler l'usage d'une syntaxe SQL qui ne serait pas supportée par Entity Framework (Entity Framework 6 en l’occurrence). 

Nous allons donc tacher de voir ensemble comment faire appel à une UDF via Entity Framework.

Contexte

Dernièrement, j'ai été amené à utiliser le mot clef OPENJSON de SQL Server, afin de parser un champ JSON d'une table et d'en traiter le résultat dans une clause WHERE. Plus concrètement, l'une des colonnes de type nvarchar contient un tableau JSON et la requête SQL tente de vérifier si certaines valeurs sont présentes dans ce tableau JSON.

Prenons la table suivante : 

CREATE TABLE [dbo].[Persons](
	[Id] [uniqueidentifier] NOT NULL,
	[Kinds] [nvarchar](max) NULL)

Si l'on admet donc que le champ Kinds peut contenir un tableau JSON de string, une façon d'obtenir toutes les lignes avec la valeur 'test' serait :

select * from dbo.Persons
where 'test' in (select [value] from openjson (Kinds))

Toutefois, il n'est pas possible de traduire cette requête avec Linq to Entity Framework, en cause le mot clef OPENJSON.

Création d'une Scalar Valued Function

Dans ces conditions, il peut être pertinent de masquer l'usage de la syntaxe invalide pour Entity Framework, en créant une UDF :

CREATE FUNCTION [dbo].[json_array_contains]
(
	@json nvarchar(max),
	@value nvarchar(max)
)
RETURNS BIT
AS
BEGIN
	declare @res bit
	declare @count int
	select @count = count([key]) from openjson(@json) where [value] = @value
	select @res = case when @count > 0 then 1 else 0
END
	return @res
END

On peut alors simplifier la requête de filtrage précédente comme suit :

select * from dbo.Persons
where [dbo].[json_array_contains](Kinds, 'kind1') = 1

Appel d'une Scalar Valued Function via Entity Framework

Il peut maintenant être judicieux d'intégrer cette UDF dans un modèle Entity Framework et composer les appels à celle-ci intuitivement, en C# ! Selon le projet, l'usage d'un EDMX ou non, on trouvera plein de solutions via une simple recherche Google. Si, comme moi, vous préférez Entity Framework Code-First, un package nuget très utile permet l'usage de nos UDFs très simplement, dont vous trouverez le GitHub ici. Il suffit ensuite de définir une méthode statique, dans le contexte par exemple :

[Function(FunctionType.ComposableScalarValuedFunction, "json_array_contains", Schema = "dbo")]
public static bool JsonArrayContains(string json, string value)
{
    return Function.CallNotSupported<bool>();
}

Pour ajouter cette méthode au model, le package nuget évoqué précédemment offre une méthode de scan très pratique qui ajoutera toutes les fonctions qu'elle découvrira automatiquement :

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.AddFunctions<MyDbContext>();
}

Pour utiliser l'UDF, il n'y a maintenant rien de plus simple :

using (var db = new MyDbContext())
{
    db.Persons.Where(p => MyDbContext.JsonArrayContains(p.Kinds, "test"));
}

Jusqu'ici, tout est assez simple, mais nous verrons dans un prochain article que les choses sérieuses commencent alors ! Nous aborderons notamment les problèmes de performances rencontrées avec l'usage de cette fonction et comment nous avons pu y remédier :)

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus