LINQ : Dynamic select

前端 未结 10 607
北荒
北荒 2020-11-22 05:26

Consider we have this class :

    public  class Data
{
    public string Field1 { get; set; }
    public string Field2 { get; set; }
    public string Field         


        
相关标签:
10条回答
  • 2020-11-22 05:32

    In addition for Nicholas Butler and the hint in comment of Matt(that use T for type of input class), I put an improve to Nicholas answer that generate the property of entity dynamically and the function does not need to send field as parameter.

    For Use add class as below:

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

    The DynamicSelectGenerator method get entity with type T, this method have optional input parameter Fields that if you want to select special field from entity send as a string such as "Field1, Field2" and if you don't send anything to method, it returns all of the fields of entity, you could use this method as below:

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

    (Assume that you have a DbContext with name AppDbContext and the context have an entity with name SampleEntity)

    0 讨论(0)
  • 2020-11-22 05:42

    You must use reflection to get and set property value with it's name.

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

    And here is GetPropValue() method

    public static object GetPropValue(object src, string propName)
    {
       return src.GetType().GetProperty(propName).GetValue(src, null);
    }
    
    0 讨论(0)
  • 2020-11-22 05:46

    Using ExpandoObject you can build a dynamic objects or return the full object from the example below.

    public object CreateShappedObject(object obj, List<string> lstFields)
    {
        if (!lstFields.Any())
        {
            return obj;
        }
        else
        {
            ExpandoObject objectToReturn = new ExpandoObject();
            foreach (var field in lstFields)
            {
                var fieldValue = obj.GetType()
                    .GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
                    .GetValue(obj, null);
    
                ((IDictionary<string, object>)objectToReturn).Add(field, fieldValue);
            }
    
            return objectToReturn;
        }
    }
    

    The following is an example of how to use this from your controller.

    http://localhost:12345/api/yourapi?fields=field1,field2

    public IHttpActionResult Get(string fields = null)
    {
        try
        {
            List<string> lstFields = new List<string>();
            if (fields != null)
            {
                lstFields = fields.ToLower().Split(',').ToList();
            }
       
            // Custom query
            var result = db.data.Select(i => CreateShappedObject(new Data()
            , lstFields)).ToList();
    
            return Ok(result);
    
        }
        catch(Exception)
        {
            return InternalServerError();
        }
    }
    
    0 讨论(0)
  • 2020-11-22 05:49

    The OP mentioned Dynamic Linq library, so I'd like to lay out an explanation on its usage.

    1. Dynamic Linq Built-In Select

    Dynamic Linq has a built-in Select method, which can be used as follows:

    var numbers = new List<int> { 1, 2, 3 };
    var wrapped = numbers.Select(num => new { Value = num }).ToList();
    
    // the "it" keyword functions as the lambda parameter,
    // so essentialy it's like calling: numbers.Select(num => num)
    var selectedNumbers = numbers.Select("it"); 
    
    // the following is the equivalent of calling: wrapped.Select(num => num.Value)
    var selectedValues = wrapped.Select("Value");
    
    // the following is the equivalent of calling: numbers.Select(num => new { Value = num })
    var selectedObjects = numbers.Select("new(it as Value)"); 
    
    foreach (int num in selectedNumbers) Console.WriteLine(num);
    foreach (int val in selectedValues) Console.WriteLine(val);
    foreach (dynamic obj in selectedObjects) Console.WriteLine(obj.Value);
    

    The Downside

    There's somewhat a downside using the built-in Select:

    Since it's an IQueryable - not IQueryable<T> - extension method, with IQueryable as its return type, common materialization methods - like ToList or FirstOrDefault - can't be used. This is why the above example uses foreach - it's simply the only convenient way of materializing the results.

    So to make things more convenient, let's support these methods.

    2. Supporting Select<T> in Dynamic Linq (to enable using ToList and alike)

    To support Select<T>, it needs to be added into the Dynamic Linq file. The simple steps for doing that are explained in this answer and in my comment on it.

    After doing so, it can be used in the following way:

    var numbers = new List<int> { 1, 2, 3 };
    var wrapped = numbers.Select(num => new { Value = num }).ToList();
    
    // the following is the equivalent of calling: numbers.Select(num => num).ToList()
    var selectedNumbers = numbers.Select<int>("it").ToList(); 
    
    // the following is the equivalent of calling: wrapped.Select(num => num.Value).ToList()
    var selectedValues = wrapped.Select<int>("Value").ToList();
    
    // the following is the equivalent of calling: numbers.Select(num => new { Value = num }).ToList()
    var selectedObjects = numbers.Select<object>("new(it as Value)").ToList(); 
    

    The Downside

    Arguably, this implementation introduces yet another kind of downside: By having to explicitly parameterize the Select<T> call (e.g., having to call Select<int>), we're losing the dynamic nature of the library.

    Nevertheless, since we can now call any materialization Linq method, this usage may still be quite useful.

    0 讨论(0)
  • 2020-11-22 05:50
    var result = from g in list.AsEnumerable()
                    select new {F1 = g.Field1,F2  = g.Field2};
    
    0 讨论(0)
  • 2020-11-22 05:52

    You can do this by dynamically creating the lambda you pass to 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();
    }
    

    Then you can use it like this:

    var result = list.Select( CreateNewStatement( "Field1, Field2" ) );
    
    0 讨论(0)
提交回复
热议问题