générer EF orderby Expression by string

c# entity-framework expression-trees linq

Question

Je veux générer une expression par paramètre de chaîne, un code comme:

 private Expression<Func<Task, T>> Generate(string orderby)
    {
        switch (orderby)
        {
            case "Time":  
                return t => t.Time;
            case "Money":
                return t => t.RewardMoney;
            default:
                return t => t.Id;
        }
    }

puis appelez-le:

 private Expression<Func<Task, T>> Generate(string orderby)
    {
        switch (orderby)
        {
            case "Time":  
                return t => t.Time;
            case "Money":
                return t => t.RewardMoney;
            default:
                return t => t.Id;
        }
    }

Mais ça ne peut pas compiler! Je change T pour objecter.

 private Expression<Func<Task, T>> Generate(string orderby)
    {
        switch (orderby)
        {
            case "Time":  
                return t => t.Time;
            case "Money":
                return t => t.RewardMoney;
            default:
                return t => t.Id;
        }
    }

Ensuite, il peut compiler, mais cela ne fonctionne pas.

System.NotSupportedException: impossible de convertir le type 'System.Int32' en 'System.Object'. LINQ to Entities prend uniquement en charge le transtypage de types de primitive ou d'énumération EDM.

Réponse acceptée

À l'aide d' et d' vous pouvez fournir les paramètres, puis appeler la fonction OrderBy , au lieu de renvoyer Expression<Func<Task, T>> , puis d'appeler OrderBy .

Notez que OrderBy est une méthode d'extension mise en œuvre à la fois dans les classes System.Linq.Enumarable et System.Linq.Queryable . Le premier concerne et le dernier, . besoin de l'arborescence d'expression de la requête pour la traduire en commandes SQL. Nous utilisons donc l'implémentation Queryable .

Cela peut être fait par une méthode d'extension (explications ajoutées sous forme de commentaires):

public static IOrderedQueryable<TSource> OrderBy<TSource>(
       this IEnumerable<TSource> query, string propertyName)
{
    var entityType = typeof(TSource);

    //Create x=>x.PropName
    var propertyInfo = entityType.GetProperty(propertyName);
    ParameterExpression arg = Expression.Parameter(entityType, "x");
    MemberExpression property = Expression.Property(arg, propertyName);
    var selector = Expression.Lambda(property, new ParameterExpression[] { arg });

    //Get System.Linq.Queryable.OrderBy() method.
    var enumarableType = typeof(System.Linq.Queryable);
    var method = enumarableType.GetMethods()
         .Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition)
         .Where(m =>
         {
            var parameters = m.GetParameters().ToList();
            //Put more restriction here to ensure selecting the right overload                
            return parameters.Count == 2;//overload that has 2 parameters
         }).Single();
    //The linq's OrderBy<TSource, TKey> has two generic types, which provided here
    MethodInfo genericMethod = method
         .MakeGenericMethod(entityType, propertyInfo.PropertyType);

    /*Call query.OrderBy(selector), with query and selector: x=> x.PropName
      Note that we pass the selector as Expression to the method and we don't compile it.
      By doing so EF can extract "order by" columns and generate SQL for it.*/
    var newQuery = (IOrderedQueryable<TSource>)genericMethod
         .Invoke(genericMethod, new object[] { query, selector });
    return newQuery;
}

Vous pouvez maintenant appeler cette surcharge de OrderBy comme toute autre surcharge de celle-ci.
Par exemple:

public static IOrderedQueryable<TSource> OrderBy<TSource>(
       this IEnumerable<TSource> query, string propertyName)
{
    var entityType = typeof(TSource);

    //Create x=>x.PropName
    var propertyInfo = entityType.GetProperty(propertyName);
    ParameterExpression arg = Expression.Parameter(entityType, "x");
    MemberExpression property = Expression.Property(arg, propertyName);
    var selector = Expression.Lambda(property, new ParameterExpression[] { arg });

    //Get System.Linq.Queryable.OrderBy() method.
    var enumarableType = typeof(System.Linq.Queryable);
    var method = enumarableType.GetMethods()
         .Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition)
         .Where(m =>
         {
            var parameters = m.GetParameters().ToList();
            //Put more restriction here to ensure selecting the right overload                
            return parameters.Count == 2;//overload that has 2 parameters
         }).Single();
    //The linq's OrderBy<TSource, TKey> has two generic types, which provided here
    MethodInfo genericMethod = method
         .MakeGenericMethod(entityType, propertyInfo.PropertyType);

    /*Call query.OrderBy(selector), with query and selector: x=> x.PropName
      Note that we pass the selector as Expression to the method and we don't compile it.
      By doing so EF can extract "order by" columns and generate SQL for it.*/
    var newQuery = (IOrderedQueryable<TSource>)genericMethod
         .Invoke(genericMethod, new object[] { query, selector });
    return newQuery;
}

Ce qui se traduit par:

public static IOrderedQueryable<TSource> OrderBy<TSource>(
       this IEnumerable<TSource> query, string propertyName)
{
    var entityType = typeof(TSource);

    //Create x=>x.PropName
    var propertyInfo = entityType.GetProperty(propertyName);
    ParameterExpression arg = Expression.Parameter(entityType, "x");
    MemberExpression property = Expression.Property(arg, propertyName);
    var selector = Expression.Lambda(property, new ParameterExpression[] { arg });

    //Get System.Linq.Queryable.OrderBy() method.
    var enumarableType = typeof(System.Linq.Queryable);
    var method = enumarableType.GetMethods()
         .Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition)
         .Where(m =>
         {
            var parameters = m.GetParameters().ToList();
            //Put more restriction here to ensure selecting the right overload                
            return parameters.Count == 2;//overload that has 2 parameters
         }).Single();
    //The linq's OrderBy<TSource, TKey> has two generic types, which provided here
    MethodInfo genericMethod = method
         .MakeGenericMethod(entityType, propertyInfo.PropertyType);

    /*Call query.OrderBy(selector), with query and selector: x=> x.PropName
      Note that we pass the selector as Expression to the method and we don't compile it.
      By doing so EF can extract "order by" columns and generate SQL for it.*/
    var newQuery = (IOrderedQueryable<TSource>)genericMethod
         .Invoke(genericMethod, new object[] { query, selector });
    return newQuery;
}

Cette approche peut être utilisée pour définir toutes les surcharges des méthodes OrderBy et OrderByDescending afin d’avoir le sélecteur de propriété de string .


Réponse populaire

Vous pouvez essayer de convertir la méthode Generate en une méthode générique:

private Expression<Func<Task, TResult>> Generate<TResult>(string orderby)
{
     switch (orderby)
     {
        case "Time":  
          return t => t.Time;
        case "Money":
          return t => t.RewardMoney;
        default:
         return t => t.Id;
     }
}

Donc, si vous appelez cette méthode, vous devez spécifier le type de la propriété que vous souhaitez commander par:

private Expression<Func<Task, TResult>> Generate<TResult>(string orderby)
{
     switch (orderby)
     {
        case "Time":  
          return t => t.Time;
        case "Money":
          return t => t.RewardMoney;
        default:
         return t => t.Id;
     }
}

Rappelez-vous maintenant que TResult ne peut être qu'un type primitif ou un type énumération.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi