J'ai créé un générateur d'expression générique qui crée un prédicat basé sur la collecte de conditions. Je passe le prédicat à une méthode générique dans le référentiel. Je pense que le générateur d'expression fonctionne correctement et crée le prédicat souhaité, bien que le script SQL généré par Entity Framework ne soit pas conforme à mes attentes. J'ai lu beaucoup de questions et d'articles concernant la requête dynamique ou LinqKit et le constructeur d'expression à ce problème et le plus pertinent était this comment. J'apprécie vraiment si vous pouvez regarder ce que j'ai fait et laissez-moi savoir si j'ai fait une erreur?Création d'une expression dynamique pour l'infrastructure d'entité
Voici le code pour la classe ExpressionBuilder:
public static class ExpressionBuilder
{
private static MethodInfo containsMethod = typeof(string).GetMethod("Contains");
private static MethodInfo startsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
private static MethodInfo endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) });
public static Expression<Func<T, bool>> GetExpression<T>(IList<ExpressionModel> filters)
{
if (filters == null)
return null;
IList<ExpressionModel> nullFreeCollection = filters.OfType<ExpressionModel>().ToList();
if (nullFreeCollection.Count == 0)
return null;
ParameterExpression param = Expression.Parameter(typeof(T), "item");
Expression exp = null;
if (nullFreeCollection.Count == 1)
exp = GetExpression<T>(param, nullFreeCollection[0]);
else if (nullFreeCollection.Count == 2)
exp = GetExpression<T>(param, nullFreeCollection[0], nullFreeCollection[1]);
else
{
while (nullFreeCollection.Count > 0)
{
var f1 = nullFreeCollection[0];
var f2 = nullFreeCollection[1];
if (exp == null)
exp = GetExpression<T>(param, nullFreeCollection[0], nullFreeCollection[1]);
else
exp = Expression.AndAlso(exp, GetExpression<T>(param, nullFreeCollection[0], nullFreeCollection[1]));
nullFreeCollection.Remove(f1);
nullFreeCollection.Remove(f2);
if (nullFreeCollection.Count == 1)
{
exp = Expression.AndAlso(exp, GetExpression<T>(param, nullFreeCollection[0]));
nullFreeCollection.RemoveAt(0);
}
}
}
return Expression.Lambda<Func<T, bool>>(exp, param);
}
private static Expression GetExpression<T>(ParameterExpression param, ExpressionModel filter)
{
MemberExpression member = Expression.Property(param, filter.PropertyName);
ConstantExpression constant = Expression.Constant(filter.Value);
switch (filter.Operator)
{
case ExpressionOperators.Equals:
return Expression.Equal(member, constant);
case ExpressionOperators.GreaterThan:
return Expression.GreaterThan(member, constant);
case ExpressionOperators.LessThan:
return Expression.LessThan(member, constant);
case ExpressionOperators.GreaterThanOrEqual:
return Expression.GreaterThanOrEqual(member, constant);
case ExpressionOperators.LessThanOrEqual:
return Expression.LessThanOrEqual(member, constant);
case ExpressionOperators.Contains:
return Expression.Call(member, containsMethod, constant);
case ExpressionOperators.StartsWith:
return Expression.Call(member, startsWithMethod, constant);
case ExpressionOperators.EndsWith:
return Expression.Call(member, endsWithMethod, constant);
}
return null;
}
private static BinaryExpression GetExpression<T>(ParameterExpression param, ExpressionModel filter1, ExpressionModel filter2)
{
Expression bin1 = GetExpression<T>(param, filter1);
Expression bin2 = GetExpression<T>(param, filter2);
return Expression.AndAlso(bin1, bin2);
}
public enum ExpressionOperators
{
Equals,
GreaterThan,
LessThan,
GreaterThanOrEqual,
LessThanOrEqual,
Contains,
StartsWith,
EndsWith
}
}
Et voici la méthode de dépôt générique:
public IEnumerable<TEntity> RetrieveCollectionAsync(Expression<Func<TEntity, bool>> predicate)
{
try
{
return DataContext.Set<TEntity>().Where(predicate);
}
catch (Exception ex)
{
Logger.Error(ex);
throw ex;
}
}
et script généré par Entity Framework pour Sql (j'attends une requête de sélection avec un peu where):
SELECT
CAST(NULL AS uniqueidentifier) AS [C1],
CAST(NULL AS uniqueidentifier) AS [C2],
CAST(NULL AS varchar(1)) AS [C3],
CAST(NULL AS uniqueidentifier) AS [C4],
CAST(NULL AS uniqueidentifier) AS [C5],
CAST(NULL AS uniqueidentifier) AS [C6],
CAST(NULL AS datetime2) AS [C7],
CAST(NULL AS datetime2) AS [C8],
CAST(NULL AS varchar(1)) AS [C9],
CAST(NULL AS uniqueidentifier) AS [C10],
CAST(NULL AS varchar(1)) AS [C11],
CAST(NULL AS uniqueidentifier) AS [C12],
CAST(NULL AS uniqueidentifier) AS [C13],
CAST(NULL AS uniqueidentifier) AS [C14],
CAST(NULL AS uniqueidentifier) AS [C15],
CAST(NULL AS datetime2) AS [C16],
CAST(NULL AS varchar(1)) AS [C17],
CAST(NULL AS datetime2) AS [C18],
CAST(NULL AS varchar(1)) AS [C19],
CAST(NULL AS tinyint) AS [C20]
FROM (SELECT 1 AS X) AS [SingleRowTable1]
WHERE 1 = 0
J'utilise
- EntityFramework 6.0
- Net Framework 4.6
- ASP.NET MVC 5
Mise à jour le modèle d'expression:
public class ExpressionModel
{
public string PropertyName { get; set; }
public ExpressionOperators Operator { get; set; }
public object Value { get; set; }
}
Une autre partie manquante est un mappeur générique qui mappe un critère de recherche donné à un nouveau ExpressionModel qui, je crois, n'est pas pertinent pour ce problème.
Regarde trop compliqué. Pouvez-vous inclure les parties manquantes - la classe 'ExpressionModel' et d'autres fonctions' GetExpression' que vous appelez depuis la méthode affichée. Juste en regardant le code, il n'est pas clair pourquoi vous avez une fonction avec 2 arguments de modèle. –
@IvanStoev J'ai mis à jour la question selon la demande. – DeveloperX
@IvanStoev [this] (https://msdn.microsoft.com/en-us/library/bb882637.aspx) article suggère également une approche similaire. – DeveloperX