1

Consider I have this IQueryble database collection which is similar to this list List and linq:

var lstData = new List<IDictionary<string, object>>()
{
   new Dictionary<string, object>() {
        { "Name" , "John"},
        { "Age", 20 },
        { "School", "Waterloo" }
    },
   new Dictionary<string, object>() {
        { "Name" , "Goli"},
        { "Age", 23 },
        { "School", "Mazandaran" }
    },
};    

var result = lstData.Select(x => new { Name= x["Name"], School= x["School"] });

However, I do not know the name of properties at compilation. How do I dynamically select specific columns in the Linq select at runtime? something like this:

var result= lstData .Select("Name,School");
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Amir
  • 1,722
  • 22
  • 20
  • @Sweeper My Data is coming from a Json and I do not know what are the property names at compilation. Therefore I can not create a class at time of comilation. – Amir May 23 '21 at 05:01
  • 1
    Why you need to convert dictionaries to anonymous types? I think dictionary type is enough to use. – Epic Chen May 23 '21 at 05:08
  • @Sweeper,To be accurate, The Data is coming from a MongoDb collection. The records are big. I want to limit the number of properties returning from the database. The name of properties are dynamic and user selects it. – Amir May 23 '21 at 05:10
  • Yes, It is reading from a database collection which is IQueryable. – Amir May 23 '21 at 05:14
  • @EpicChen I need to limit the number of properties returning from a database collection query to optimize it. – Amir May 23 '21 at 05:16
  • I think you mean this?https://stackoverflow.com/questions/16516971/linq-dynamic-select – Epic Chen May 23 '21 at 07:31
  • @EpicChen Thanks for sharing the link. Yes. I am looking for something like that like. However, the answer for that is not working when the T is IDictionary – Amir May 23 '21 at 11:04
  • Maybe you could consider that using the max set of columns for forming a model and replace it with IDictionary. It may reduce the complexity. Do you have any reason to use IDictionary? – Epic Chen May 23 '21 at 14:11
  • @EpicChen, I can not. My users define their fields. Each user has their own data model. At runtime I absolutely do not have any idea of their data model. and it is totally different than any other user. – Amir May 23 '21 at 18:25
  • Have you tried using tuples to get a similar effect? Such as `IDictionary` the columns can always be accessed with the property `.itemn` – DekuDesu May 23 '21 at 23:35
  • Note that your variable `result` will be problematic since it won't have a static type in C# (one place where `DataTable` can be helpful). I think you are looking for [Dynamic LINQ](https://github.com/StefH/System.Linq.Dynamic.Core). – NetMage May 27 '21 at 01:01
  • Have you try the answer? – Epic Chen Jun 04 '21 at 02:32

2 Answers2

0

It would be more efficient and more maintainable if you define a class that you could store your data for each person rather than storing it in an object which in that case it would need unnecessary boxing and unboxing every time you search through your list. having that said you could search in your data structure like this:

        var result = lstData.Find(x => x["Name"] == (object)"John" && x["School"] == (object)"Waterloo");
        var age = (int)result["Age"]; //20

Edit

If the goal is to get properties as an anonymous list without knowing the property names at compilation time it's not possible. the anonymous type in C# needs the property name first hand or at least property access when you define it. an alternative way to solve this problem would be to use another dictionary so you could map the original one into:

  var summayList = lstData.Select(x => new Dictionary<string, object>()
        {
            { "Name", x["Name"] },
            { "School", x["School"] }
        });
  var john = summayList.Single(x => x["Name"] == (object)"John");

if the number of properties is unknown in addition to their names, you could use a method that return a list of dictionaries given unknown number of property names:

public static IEnumerable<IDictionary<string, object>> GetProps(List<IDictionary<string, object>> list, params string[] props)
    {
        return list.Select(x => new Dictionary<string, object>(
            props.Select(p => new KeyValuePair<string, object>(p, x[p]))));
    }

Usage

var result = GetProps(lstData, "Name", "School");
var goli = result.Single(x => x["Name"] == (object)"Goli");
Nima Ghomri
  • 98
  • 1
  • 8
  • Re strongly typed: My users define their fields. Each user has their own data model. At compilation I absolutely do not have any idea of their data model. and it is totally different than any other user. So your solution does not work for me. – Amir May 23 '21 at 18:27
  • Anonymous types can't be defined without property name in compilation, you should use another dictionary – Nima Ghomri May 24 '21 at 04:48
0

I have finished the code, try the following code.

Func<IDictionary<string, object>, dynamic> CreateDynamicFromDict(string fields)
{
     // input parameter "x"
     var xParameter = Expression.Parameter(typeof(IDictionary<string, object>), "x");
     // output object
     var result = Expression.Parameter(typeof(IDictionary<string, object>), "result");

     var add = typeof(IDictionary<string, object>).GetMethod("Add");

     var body = new List<Expression>();
     //initial output object
     body.Add(Expression.Assign(result, Expression.New(typeof(ExpandoObject))));

     // set value "FieldN = x.FieldN"
     var bindings = fields.Split(',').Select(o => o.Trim())
     .Select(o =>
     {
           var key = Expression.Constant(o);
           return Expression.Call(result, add, key, Expression.Property(xParameter, "Item", key));
     });

     body.AddRange(bindings);
     // return value
     body.Add(result);

     var block = Expression.Block(new[] { result }, body);

     var lambda = Expression.Lambda<Func<IDictionary<string, object>, dynamic>>(block, xParameter);

     // compile to Func<IDictionary<string, object>, dynamic>
     return lambda.Compile();
}

The usage is

var result = lstData.Select(CreateDynamicFromDict("Field1, Field2"));

References:

LINQ : Dynamic select

Expression tree create dictionary with property values for class

Expression Trees (C#)

Expression Tree

Epic Chen
  • 1,282
  • 12
  • 17