mai
2011
Nous travaillons sur un projet qui exploite :
- un modèle Entity Framework au sein d’une couche d’accès aux données,
- des CLR UDF (user defined functions) permettant l’agrégation de données (comme SUM, AVG, etc).
Pour information, ce sont des assemblies dot net injecté dans SQL Server permettant de disposer de fonctionnalités dot net appelable à partir de code SQL directement, comme si ces fonctions étaient natives à SQL Server),
Nous avons été confrontés à la nécessité de devoir utiliser ces CLR-UDF(disponibles en SQL mais, pas nativement en C#) d’agrégation dans la DAL de l’application, via les requêtes Linq sur le modèle EF.
Nativement… ce n’est pas possible.
Il faut modifier le cours des choses…
Recette :
- Modifier l’edmx pour ajouter la fonction
- Ajouter une classe particulière au sein du projet, pour permettre à Linq-EF de connaitre la fonction
- Utiliser la fonction dans les requêtes Linq-EF
Modification de l’EDMX
Voici les fonctions d’agrégations disponibles sous SQL Server :
Ouvrir l’edmx dans le designer, et appeler le menu contextuel pour mettre à jour le modèle, ajouter la fonction (située dans les procédures stoquées…), en jaune ci dessous…
Valider en cliquant sur Terminer.
Ouvrer l’Edmx à la main (éditeur XML), à la recherche des fonctions d’agrégation ajoutées (InterpWeightAvg et InterpWeightSum):
<Function Name="InterpWeightAvg" ReturnType="float" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo"> <Parameter Name="value" Type="float" Mode="In" /> </Function> <Function Name="InterpWeightSum" ReturnType="float" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo"> <Parameter Name="value" Type="float" Mode="In" /> </Function>
<Function Name="InterpWeightAvg" ReturnType="float" Aggregate="true" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo"> <Parameter Name="value" Type="Collection(float)" Mode="In" /> </Function> <Function Name="InterpWeightSum" ReturnType="float" Aggregate="true"
BuiltIn="false" NiladicFunction="false" IsComposable="true"
ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo"> <Parameter Name="value" Type="Collection(float)" Mode="In" /> </Function>
Ajout d’une classe de mapping sur la fonction
L’étape ultime consiste à créer une classe d’extension, sur les types manipulés pour permettre la liaison avec la fonction d’agrégation.
public static class LinqAggregatesMapper { [EdmFunction("MyEntityModel.Store", "InterpWeightAvg")] public static double? InterpWeightAvg(this IEnumerable<double?> source) { throw new NotImplementedException("Direct calls are not supported."); } [EdmFunction("MyEntityModel.Store", "InterpWeightAvg")] public static double? InterpWeightAvg(this IEnumerable<double> source) { throw new NotImplementedException("Direct calls are not supported."); }
[EdmFunction("MyEntityModel.Store", "InterpWeightSum")]
public static double? InterpWeightSum(this IEnumerable<double> source)
{
throw new NotImplementedException("Direct calls are not supported.");
}
[EdmFunction("MyEntityModel.Store", "InterpWeightSum")]
public static double? InterpWeightSum(this IEnumerable<double> source)
{
throw new NotImplementedException("Direct calls are not supported.");
}
}
J’attire votre attention sur les points suivants :
- La classe est statique (normal => classe d’extension),
- L’utilisation du mot clé “this” (normal => méthode d’extension),
- Chaque fonction est décorée par un attribut “EdmFunction”. Cet attribut précise en premier paramètre le chemin (namespace) de la fonction dans l’edmx (nom du contexte de données + Store => indique le SSDL), le second paramètre précise le nom de la fonction dans le SSDL,
- Chaque fonction est doublée : type nullable et non nullable (indispensable),
Utilisation de la fonction dans Linq-EF
Exemple d’utilisation dans notre code :
var result = context.DonneeDetaillees.Select(data => data.DureeFonctionnement).InterpolateAvg();
En ouvrant le SQL Profiler (mais on peut également utiliser l’intellitrace de Visual Studio), il devient possible de voir dans la requête SQL générée l’utilisation de la fonction.
Attention tout de même : nous avons noté que lors de mises à jour de l’Edmx par le designer de Visual Studio, il arrivait que notre modification manuelle de la fonction soit à nouveau ré écrite par le designer.
Bonne journée
Merci à l’équipe : Xavier et David.