2016-02-11 2 views
1

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.

+1

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. –

+0

@IvanStoev J'ai mis à jour la question selon la demande. – DeveloperX

+0

@IvanStoev [this] (https://msdn.microsoft.com/en-us/library/bb882637.aspx) article suggère également une approche similaire. – DeveloperX

Répondre

3

Comme je l'ai mentionné dans les commentaires, la mise en œuvre est trop compliquée.

D'abord, cette méthode

private static BinaryExpression GetExpression<T>(ParameterExpression param, ExpressionModel filter1, ExpressionModel filter2) 

et toute la logique pour vérifier les filtres comptent, la suppression d'éléments traités, etc. est redondant. AND conditions peuvent facilement être enchaînées comme celui-ci

((Condition1 AND Condition2) AND Condition3) AND Condition4 ... 

donc simplement supprimer cette fonction.

En second lieu, cette fonction

private static Expression GetExpression<T>(ParameterExpression param, ExpressionModel filter) 

est mal nommé et n'a pas besoin d'un T générique car il est pas utilisé à l'intérieur.

Au lieu de cela, changer la signature

private static Expression MakePredicate(ParameterExpression item, ExpressionModel filter) 
{ 
    // implementation (same as posted) 
} 

Enfin, la méthode publique est plus simple:

public static Expression<Func<T, bool>> MakePredicate<T>(IEnumerable<ExpressionModel> filters) 
{ 
    if (filters == null) return null; 
    filters = filters.Where(filter => filter != null); 
    if (!filters.Any()) return null; 
    var item = Expression.Parameter(typeof(T), "item"); 
    var body = filters.Select(filter => MakePredicate(item, filter)).Aggregate(Expression.AndAlso); 
    var predicate = Expression.Lambda<Func<T, bool>>(body, item); 
    return predicate; 
} 

post-scriptumEt ne pas oublier de faire null vérifier l'utilisation:

// should not be called Async 
public IEnumerable<TEntity> RetrieveCollectionAsync(Expression<Func<TEntity, bool>> predicate) 
{ 
    try 
    { 
     var query = DataContext.Set<TEntity>().AsQueryable(); 
     if (predicate != null) 
      query = query.Where(predicate); 
     return query; 
    } 
    catch (Exception ex) 
    { 
     Logger.Error(ex); 
     throw ex; // should be: throw; 
    } 
} 
+0

Merci pour votre réponse Ivan. Si je ne me trompe pas dans le référentiel, vous récupérez la table entière et utilisez Linq To Object pour exécuter un filtre par le prédicat. Je crois que vous avez négligé la partie que j'ai mentionnée, je m'attends à ce que le framework d'entité génère un script avec une clause where. Je cherche Linq to Entity solution pas Linq to Object. – DeveloperX

+0

@DeveloperX Lisez attentivement la réponse. Il utilise LINQ to Object ** seulement ** pour créer facilement les expressions. Mais les expressions sont compatibles EF. Fondamentalement, c'est une implémentation simplifiée de votre code. La méthode publique 'MakePredicate' correspond à votre' GetExpression' public. Vous pouvez l'appeler à votre façon si vous le souhaitez. –

+0

Si vous voulez dire cette ligne 'var query = DataContext.Set () .AsQueryable();', il n'exécute pas la requête. C'est juste une bonne pratique de vérifier l'argument 'null'. Même votre méthode de construction d'expression a des branches qui renvoient 'null'. –