LINQ: selección dinámica

Resuelto Unforgiven asked hace 11 años • 12 respuestas

Considere que tenemos esta clase:

    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; }

}

¿Cómo selecciono dinámicamente columnas específicas? algo como esto :

  var list = new List<Data>();

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

¿Es esta la única solución => LINQ dinámico ?
Los campos seleccionados no se conocen en el momento de la compilación. Se especificarían en tiempo de ejecución.

Unforgiven avatar May 13 '13 14:05 Unforgiven
Aceptado

Puede hacer esto creando dinámicamente la lambda a la que pasaSelect:

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();
}

Entonces puedes usarlo así:

var result = list.Select( CreateNewStatement( "Field1, Field2" ) );
Nick Butler avatar May 13 '2013 08:05 Nick Butler

Además de Nicholas Butler y la sugerencia en el comentario de Matt (que se usa Tpara el tipo de clase de entrada), puse una mejora en la respuesta de Nicholas que genera la propiedad de la entidad dinámicamente y la función no necesita enviarse fieldcomo parámetro.

Para uso, agregue la clase como se muestra a continuación:

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();
    }
}

El DynamicSelectGeneratormétodo obtiene entidad con tipo T, este método tiene un parámetro de entrada opcional Fieldsque si desea seleccionar un campo especial de la entidad, envíelo como una cadena, como "Field1, Field2"y si no envía nada al método, devuelve todos los campos de la entidad. Podría utilizar este método de la siguiente manera:

 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();
            }

(Supongamos que tiene un DbContextnombre AppDbContexty el contexto tiene una entidad con nombre SampleEntity)

Ali avatar Jul 20 '2017 04:07 Ali

Debe utilizar la reflexión para obtener y establecer el valor de la propiedad con su nombre.

  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);
         }
      }
  }

Y aquí está el método GetPropValue()

public static object GetPropValue(object src, string propName)
{
   return src.GetType().GetProperty(propName).GetValue(src, null);
}
Azadeh Radkianpour avatar May 13 '2013 08:05 Azadeh Radkianpour

Usando Reflexión y Expresión bulid puedes hacer lo que dices. Ejemplo:

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();
Jarvan avatar May 13 '2013 08:05 Jarvan