Func, Expression et CSharpCodeProvider
Bonjour à tous,
Je suis en train de réaliser un moteur de règle pour les besoins d'un projet et je rencontre 2 soucis.
1)Le premier est la création de règle, celles-ci sont stockées dans une base de données SQL puis récupérer dans un service windows (au démarrage et lors de la modification d'une des règles via le back office).
Les règles sont de la forme :
Name |
Operator |
Target |
Nom |
NotEqual |
toto |
Code.Length |
Equal |
5 |
DateNaissance |
GreaterThan |
01/01/1900 |
Name, Code et DateNaissance correspondent aux attributs de ma classe.
Afin de créer mes règles côtés C# voilà les 2 procédures que j'utilise :
Code:
1 2 3 4 5 6 7
|
public static Func<T, bool> CompileRule<T>(Rule r)
{
var paramUser = Expression.Parameter(typeof(T));
Expression expr = BuildExpr<T>(r, paramUser);
return Expression.Lambda<Func<T, bool>>(expr, paramUser).Compile();
} |
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
static Expression BuildExpr<T>(Rule r, ParameterExpression param)
{
var left = MemberExpression.Property(param, r.MemberName);
var tProp = typeof(T).GetProperty(r.MemberName).PropertyType;
ExpressionType tBinary;
if (ExpressionType.TryParse(r.Operator, out tBinary))
{
var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp));
return Expression.MakeBinary(tBinary, left, right);
}
else
{
var method = tProp.GetMethod(r.Operator);
var tParam = method.GetParameters()[0].ParameterType;
var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tParam));
return Expression.Call(left, method, right);
}
} |
Lorsque je viens pour créer mes règles 1 et 3, pas de soucis cela fonctionne mais pour la règle 2 il y a une erreur qui dit que Code.Length n'est pas un membre de ma classe. Cela est normal car Length est une propriété de la classe string pour retourner la longueur de la chaîne. J'ai regardé sur le net pour pouvoir retourner les propriétés imbriqués pour une classe mais je ne trouve pas de solution. L'erreur se produit dans la 2ème procédure sur la ligne
Code:
var left = MemberExpression.Property(param, r.MemberName);
Pour le moment j'ai contourné le problème en ajoutant un attribut Code_Length dans ma classe, mais cela n'est pas propre si je dois créer un attribut pour chaque propriété. De plus, mes classes sont créer dynamiquement via la classe CSharpCodeProvider afin de pouvoir les recompiler en cas de modification depuis le back office.
2) Le second problème vient justement de la gestion dynamique de mes classes pour créer mes règles. La fonction
Code:
Func<T, bool> CompileRule<T>(Rule r)
attendant un type T qui correspond à une classe or je ne peux pas le préciser physiquement puisque je gère plusieurs classes en mémoire.
Code:
Func<Personne, bool> compiledRule = CompileRule<Personne>(rule);
Mes classes sont créer comme ceci :
Code:
1 2 3 4 5 6 7 8 9 10 11 12
|
CSharpCodeProvider cl = new CSharpCodeProvider();
var cp = new CompilerParameters();
cp.GenerateInMemory = true;
var res = cl.CompileAssemblyFromSource(
cp,
"using System; public class Personne \t{ public string Nom {get; set;} public string Code {get; set;} public DateTime DateNaissance {get; set;} public string Hello() { return \"Bonjour \" + Nom + \" !\"; } }"
);
var type = res.CompiledAssembly.GetType("Personne");
var obj = Activator.CreateInstance(type); |
La seule solution que je vois, serait de gérer dynamiquement dans une classe statique, autant de "Func" que j'ai de classe mais cela va vite devenir compliquer si tout est dynamique.
La solution n'est pas définitive si quelqu'un m'en propose une meilleure.