web-dev-qa-db-fra.com

LINQ: sélection dynamique

Considérons que nous avons cette classe: 

    public  class Data
{
    public string Field1 { get; set; }
    public string Field2 { get; set; }
    public string Field3 { get; set; }
    public string Field4 { get; set; }
    public string Field5 { get; set; }

}

Comment sélectionner dynamiquement des colonnes spécifiées? quelque chose comme ça :

  var list = new List<Data>();

  var result= list.Select("Field1,Field2"); // How ?

Est-ce la seule solution => Dynamic LINQ ?
Les champs sélectionnés ne sont pas connus au moment de la compilation. Ils seraient spécifiés à l'exécution

40
Unforgiven

Vous pouvez le faire en créant dynamiquement le lambda que vous passez à Select:

Func<Data,Data> CreateNewStatement( string fields )
{
    // input parameter "o"
    var xParameter = Expression.Parameter( typeof( Data ), "o" );

    // new statement "new Data()"
    var xNew = Expression.New( typeof( Data ) );

    // create initializers
    var bindings = fields.Split( ',' ).Select( o => o.Trim() )
        .Select( o => {

            // property "Field1"
            var mi = typeof( Data ).GetProperty( o );

            // original value "o.Field1"
            var xOriginal = Expression.Property( xParameter, mi );

            // set value "Field1 = o.Field1"
            return Expression.Bind( mi, xOriginal );
        }
    );

    // initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
    var xInit = Expression.MemberInit( xNew, bindings );

    // expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
    var lambda = Expression.Lambda<Func<Data,Data>>( xInit, xParameter );

    // compile to Func<Data, Data>
    return lambda.Compile();
}

Ensuite, vous pouvez l'utiliser comme ceci:

var result = list.Select( CreateNewStatement( "Field1, Field2" ) );
58
Nicholas Butler

En utilisant Reflection and Expression, Bulid peut faire ce que vous dites. Exemple:

var list = new List<Data>();
//bulid a expression tree to create a paramter
ParameterExpression param = Expression.Parameter(typeof(Data), "d");
//bulid expression tree:data.Field1
Expression selector = Expression.Property(param,typeof(Data).GetProperty("Field1"));
Expression pred = Expression.Lambda(selector, param);
//bulid expression tree:Select(d=>d.Field1)
Expression expr = Expression.Call(typeof(Queryable), "Select",
    new Type[] { typeof(Data), typeof(string) },
    Expression.Constant(list.AsQueryable()), pred);
//create dynamic query
IQueryable<string> query = list.AsQueryable().Provider.CreateQuery<string>(expr);
var result=query.ToList();
4
Jarvan

En plus de Nicholas Butler et de la remarque dans le commentaire de Matt (qui utilise T pour le type de classe d'entrée), j'ai mis une amélioration à la réponse de Nicholas qui génère la propriété d'entité de manière dynamique et la fonction n'a pas besoin d'envoyer field en tant que paramètre.

Pour utilisation, ajoutez la classe ci-dessous:

public static class Helpers
{
    public static Func<T, T> DynamicSelectGenerator<T>(string Fields = "")
    {
        string[] EntityFields;
        if (Fields == "")
            // get Properties of the T
            EntityFields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();
        else
            EntityFields = Fields.Split(',');

        // input parameter "o"
        var xParameter = Expression.Parameter(typeof(T), "o");

        // new statement "new Data()"
        var xNew = Expression.New(typeof(T));

        // create initializers
        var bindings = EntityFields.Select(o => o.Trim())
            .Select(o =>
            {

                // property "Field1"
                var mi = typeof(T).GetProperty(o);

                // original value "o.Field1"
                var xOriginal = Expression.Property(xParameter, mi);

                // set value "Field1 = o.Field1"
                return Expression.Bind(mi, xOriginal);
            }
        );

        // initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
        var xInit = Expression.MemberInit(xNew, bindings);

        // expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
        var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);

        // compile to Func<Data, Data>
        return lambda.Compile();
    }
}

La méthode DynamicSelectGenerator obtient l'entité de type T. Cette méthode a le paramètre d'entrée optionnel Fields que si vous voulez sélectionner un champ spécial à partir d'une entité, envoyez-le sous forme de chaîne telle que "Field1, Field2" et si vous n'envoyez rien à methid, elle renvoie tous les champs de l'entité. , vous pouvez utiliser cette méthode comme ci-dessous:

 using (AppDbContext db = new AppDbContext())
            {
                //select "Field1, Field2" from entity
                var result = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>("Field1, Field2")).ToList();

                //select all field from entity
                var result1 = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>()).ToList();
            }

(Supposons que vous avez une DbContext avec le nom AppDbContext et que le contexte a une entité avec le nom SampleEntity)

4
combo_ci

Vous devez utiliser la réflexion pour obtenir et définir la valeur de la propriété avec son nom. 

  var result = new List<Data>();
  var data = new Data();
  var type = data.GetType();
  var fieldName = "Something";

  for (var i = 0; i < list.Count; i++)
  {
      foreach (var property in data.GetType().GetProperties())
      {
         if (property.Name == fieldName)
         {
            type.GetProperties().FirstOrDefault(n => n.Name == property.Name).SetValue(data, GetPropValue(list[i], property.Name), null);
            result.Add(data);
         }
      }
  }

Et voici la méthode GetPropValue ()

public static object GetPropValue(object src, string propName)
{
   return src.GetType().GetProperty(propName).GetValue(src, null);
}
4
Azade

Une autre approche que j'ai utilisée est un opérateur ternaire imbriqué:

string col = "Column3";
var query = table.Select(i => col == "Column1" ? i.Column1 :
                              col == "Column2" ? i.Column2 :
                              col == "Column3" ? i.Column3 :
                              col == "Column4" ? i.Column4 :
                              null);

L'opérateur ternaire requiert que chaque champ soit du même type. Vous devez donc appeler .ToString () sur toutes les colonnes non-chaîne.

1
Mike Grasso

J'ai générer ma propre classe dans le même but d'utilisation.

github Gist: https://Gist.github.com/mstrYoda/663789375b0df23e2662a53bebaf2c7c

Il génère une sélection dynamique lambda pour une chaîne donnée et prend également en charge les propriétés imbriquées à deux niveaux.

Exemple d'utilisation:

class Shipment {
   // other fields...
   public Address Sender;
   public Address Recipient;
}

class Address {
    public string AddressText;
    public string CityName;
    public string CityId;
}

// in the service method
var shipmentDtos = _context.Shipments.Where(s => request.ShipmentIdList.Contains(s.Id))
                .Select(new SelectLambdaBuilder<Shipment>().CreateNewStatement(request.Fields)) // request.Fields = "Sender.CityName,Sender.CityId"
                .ToList();

Il compile le lambda comme ci-dessous:

s => new Shipment {
    Sender = new Address {
        CityId = s.Sender.CityId,
        CityName = s.Sender.CityName
    }
}

Vous pouvez également trouver ma question et répondre ici: c # - Générer dynamiquement linq select avec des propriétés imbriquées

public class SelectLambdaBuilder<T>
{
// as a performence consideration I cached already computed type-properties
private static Dictionary<Type, PropertyInfo[]> _typePropertyInfoMappings = new Dictionary<Type, PropertyInfo[]>();
private readonly Type _typeOfBaseClass = typeof(T);

private Dictionary<string, List<string>> GetFieldMapping(string fields)
{
    var selectedFieldsMap = new Dictionary<string, List<string>>();

    foreach (var s in fields.Split(','))
    {
        var nestedFields = s.Split('.').Select(f => f.Trim()).ToArray();
        var nestedValue = nestedFields.Length > 1 ? nestedFields[1] : null;

        if (selectedFieldsMap.Keys.Any(key => key == nestedFields[0]))
        {
            selectedFieldsMap[nestedFields[0]].Add(nestedValue);
        }
        else
        {
            selectedFieldsMap.Add(nestedFields[0], new List<string> { nestedValue });
        }
    }

    return selectedFieldsMap;
}

public Func<T, T> CreateNewStatement(string fields)
{
    ParameterExpression xParameter = Expression.Parameter(_typeOfBaseClass, "s");
    NewExpression xNew = Expression.New(_typeOfBaseClass);

    var selectFields = GetFieldMapping(fields);

    var shpNestedPropertyBindings = new List<MemberAssignment>();
    foreach (var keyValuePair in selectFields)
    {
        PropertyInfo[] propertyInfos;
        if (!_typePropertyInfoMappings.TryGetValue(_typeOfBaseClass, out propertyInfos))
        {
            var properties = _typeOfBaseClass.GetProperties();
            propertyInfos = properties;
            _typePropertyInfoMappings.Add(_typeOfBaseClass, properties);
        }

        var propertyType = propertyInfos
            .FirstOrDefault(p => p.Name.ToLowerInvariant().Equals(keyValuePair.Key.ToLowerInvariant()))
            .PropertyType;

        if (propertyType.IsClass)
        {
            PropertyInfo objClassPropInfo = _typeOfBaseClass.GetProperty(keyValuePair.Key);
            MemberExpression objNestedMemberExpression = Expression.Property(xParameter, objClassPropInfo);

            NewExpression innerObjNew = Expression.New(propertyType);

            var nestedBindings = keyValuePair.Value.Select(v =>
            {
                PropertyInfo nestedObjPropInfo = propertyType.GetProperty(v);

                MemberExpression nestedOrigin2 = Expression.Property(objNestedMemberExpression, nestedObjPropInfo);
                var binding2 = Expression.Bind(nestedObjPropInfo, nestedOrigin2);

                return binding2;
            });

            MemberInitExpression nestedInit = Expression.MemberInit(innerObjNew, nestedBindings);
            shpNestedPropertyBindings.Add(Expression.Bind(objClassPropInfo, nestedInit));
        }
        else
        {
            Expression mbr = xParameter;
            mbr = Expression.PropertyOrField(mbr, keyValuePair.Key);

            PropertyInfo mi = _typeOfBaseClass.GetProperty( ((MemberExpression)mbr).Member.Name );

            var xOriginal = Expression.Property(xParameter, mi);

            shpNestedPropertyBindings.Add(Expression.Bind(mi, xOriginal));
        }
    }

    var xInit = Expression.MemberInit(xNew, shpNestedPropertyBindings);
    var lambda = Expression.Lambda<Func<T,T>>( xInit, xParameter );

    return lambda.Compile();
}
0
Emre Savcı