2017-08-17 3 views
0

Je tente de générer dynamiquement l'instruction select d'une requête Linq. J'ai une fonction comme ceci:Combinaison d'une expression multiple pour créer une expression de sélection Linq

public Task<List<T>> RunQuery<T>(
    IQueryable<T> query, 
    FieldSelector<T> fields, 
    int pageNumber, int pageSize) 
{ 
    var skip = (pageNumber-1) * pageSize; 
    var query = query.Skip(skip).Take(pageSize); 
    var selector = fields.GetSelector(); 
    return query.Select(selector).ToListAsync(); 
} 

Voici la classe FieldSelector: (I mon code, j'ai des propriétés supplémentaires par champ)

public class FieldSelector<T> 
{ 
    private List<LambdaExpression> expressions; 

    public FieldSelector() 
    { 
     expressions = new List<LambdaExpression>(); 
    } 

    public void Add(Expression<Func<T, object>> expr) 
    { 
     expressions.Add(expr); 
    } 

    public Expression<Func<T, object>> GetSelector() 
    { 
     //Build an expression like e => new {e.Name, e.Street} 
    } 
} 

Comment implémenter la fonction GetSelector? C'est possible? (sans devenir trop complexe).
Voilà comment je voudrais l'utiliser:

var fields = new FieldSelector<Employee>(); 
fields.Add(e => e.Name); 
fields.Add(e => e.Street); 
RunQuery<Employee>(query, fields, 1, 100); 

Répondre

3

Vous devez générer un type personnalisé qui fait comment le compilateur pour Anonymous Type, parce que vous ne pouvez pas générer vraiment Anonymous Type avec des propriétés dynamiques comptent. Après la génération de ce type, vous pouvez facilement définir les affectations d'expressions que vous avez passées à FieldSelector et les associer au type personnalisé.

public class FieldSelector<T> 
    { 
     private List<LambdaExpression> expressions; 

     public FieldSelector() 
     { 
      expressions = new List<LambdaExpression>(); 
     } 

     public void Add(Expression<Func<T, object>> expr) 
     { 
      expressions.Add(expr); 
     } 

     public Expression<Func<T, object>> GetSelector() 
     { 
      // We will create a new type in runtime that looks like a AnonymousType 
      var str = $"<>f__AnonymousType0`{expressions.Count}"; 

      // Create type builder 
      var assemblyName = Assembly.GetExecutingAssembly().GetName(); 
      var modelBuilder = AppDomain.CurrentDomain 
             .DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect) 
             .DefineDynamicModule("module"); 
      var typeBuilder = modelBuilder.DefineType(str, TypeAttributes.Public | TypeAttributes.Class); 

      var types = new Type[expressions.Count]; 
      var names = new List<string>[expressions.Count]; 

      for (int i = 0; i < expressions.Count; i++) 
      { 
       // Retrive passed properties 
       var unExpr = expressions[i].Body as UnaryExpression; 
       var exp = unExpr == null ? expressions[i].Body as MemberExpression : unExpr.Operand as MemberExpression; 
       types[i] = exp.Type; 
       // Retrive a nested properties 
       names[i] = GetAllNestedMembersName(exp); 
      } 

      // Defined generic parameters for custom type 
      var genericParams = typeBuilder.DefineGenericParameters(types.Select((_, i) => $"PropType{i}").ToArray()); 
      for (int i = 0; i < types.Length; i++) 
      { 
       typeBuilder.DefineField($"{string.Join("_", names[i])}", genericParams[i], FieldAttributes.Public); 
      } 

      // Create generic type by passed properties 
      var type = typeBuilder.CreateType(); 
      var genericType = type.MakeGenericType(types); 

      ParameterExpression parameter = Expression.Parameter(typeof(T), "MyItem"); 

      // Create nested properties 
      var assignments = genericType.GetFields().Select((prop, i) => Expression.Bind(prop, GetAllNestedMembers(parameter, names[i]))); 
      return Expression.Lambda<Func<T, object>>(Expression.MemberInit(Expression.New(genericType.GetConstructors()[0]), assignments), parameter); 
     } 

     private Expression GetAllNestedMembers(Expression parameter, List<string> properties) 
     { 
      Expression expression = parameter; 
      for (int i = 0; i < properties.Count; ++i) 
      { 
       expression = Expression.Property(expression, properties[i]); 
      } 
      return expression; 
     } 

     private List<string> GetAllNestedMembersName(Expression arg) 
     { 
      var result = new List<string>(); 
      var expression = arg as MemberExpression; 
      while (expression != null && expression.NodeType != ExpressionType.Parameter) 
      { 
       result.Insert(0, expression.Member.Name); 
       expression = expression.Expression as MemberExpression; 
      } 
      return result; 
     } 
    } 

Par ailleurs, comme vous l'avez vu dans le code ci-dessus solution actuelle ne fonctionne pas pour les cas lorsque vous essayez de récupérer la propriété avec plus de 1 niveau d'imbrication class1->class2->class3->Propery_1. Ce n'est pas difficile de le réparer.

EDIT: cas ci-dessus a été fixé. Maintenant, vous pouvez retrive class1->class2->class3->Propery_1

Et il est l'utilisation de celui-ci:

private class TestClass 
{ 
    public string Arg2 { get; set; } 

    public TestClass Nested { get; set; } 

    public int Id { get; set; } 
} 

var field = new FieldSelector<TestClass>(); 
field.Add(e => e.Arg2); 
field.Add(e => e.Id); 
field.Add(e => e.Nested.Id); 
dynamic cusObj = field.GetSelector().Compile()(new TestClass { Arg2 = "asd", Id = 6, Nested = new TestClass { Id = 79 } }); 
Console.WriteLine(cusObj.Arg2); 
Console.WriteLine(cusObj.Id); 
Console.WriteLine(cusObj.Nested_Id); 

Malheureusement, cusObj sera object au type de compilation et vous ne pouvez pas récupérer son propertis si vous ne marque pas comme dynamic.

Par ailleurs, votre public Task<List<T>> RunQuery<T> ne serait pas compiler, car field.GetSelector() retours Func<T, object> et vous obtiendrez un Task<List<object>> lorsque vous appellerez le retour return query.Select(field.GetSelector()).ToListAsync()

Hope, cela est utile.

+0

Merci! Cela fonctionne bien! J'ai seulement dû utiliser ceci parce que j'utilise le coeur de dotnet: AssemblyBuilder.DefineDynamicAssembly – r03