Vijay
Vijay

Reputation: 815

How to select certain properties in a list

Currently, I am working on a requirement that uses a List and retrieves the data. But there's change that I need to incorporate which I'm not getting.

Now I'm having a list of elements and on user selection, I would receive the column names(randomly) which are part of that list.
How do I implement because in run-time I don't know how many column names I would receive.

Below is the sample code:

class Program
    {
        static void Main(string[] args)
        {
            var students = new List<Student>()
            {
                new Student { Name = "Vijay", Age = 21 , Gender = "M" , Address = "Address_1"},
                new Student { Name = "Ajay", Age = 26 , Gender = "M" , Address = "Address_2"},
                new Student { Name = "John", Age = 21 , Gender = "M" , Address = "Address_3"},
                new Student { Name = "Rob", Age = 42 , Gender = "M" , Address = "Address_4"},
                new Student { Name = "Kohli", Age = 32 , Gender = "M" , Address = "Address_5"}
            };

            var result = students.Select(x => x.Name);  
            // I get result with Name column. That's fine.
            
        }
    }


    public class Student
    {
        public string Name;
        public int Age;
        public string Gender;
        public string Address;
    }

The above code is just a sample of what I'm trying to explain. Since students.Select(x => x.Name); would give me the result which has a List of Names. But some times I might receive comma separated column names like Name, Gender or Age, Gender or Name, Address or Address or all column names.

Expected results:

// When user sends Name & Gender as selected columns
Name, Gender

vijay 26
Ajay  21
Rob   42
...

// When user sends only Address as selected columns
Address

Address_1
Address_2
....

Can anyone please help me how do I build runtime select using the available list.

Thanks in advance

Upvotes: 1

Views: 1467

Answers (2)

Alexander Petrov
Alexander Petrov

Reputation: 14231

Dynamic Linq may help you.

using System.Linq.Dynamic.Core;
var values = new List<string> { "Age", "Gender" };

var columns = "new {" + string.Join(",", values) + "}";

var result = students.AsQueryable().Select(columns);

foreach (var x in result)
    Console.WriteLine(x);

Output:

{ Age = 21, Gender = M }
{ Age = 26, Gender = M }
{ Age = 21, Gender = M }
{ Age = 42, Gender = M }
{ Age = 32, Gender = M }

Be aware that you are getting an anonymous type. And how you will work with it further is still a question.

Upvotes: 2

user4182984
user4182984

Reputation: 222

I actually did this some time back for a web API. Basically, you can use Newtonsoft.Json to serialize your type while keeping only the fields\properties you want.

First, you'll need a custom ContractResolver:

    public class SelectiveContractResolver : DefaultContractResolver
    {
        private readonly HashSet<string> properties;

        public SelectiveContractResolver(HashSet<string> selectedProperties)
        {
            properties = selectedProperties;
        }

        protected override JsonProperty CreateProperty
            (
                MemberInfo member,
                MemberSerialization memberSerialization
            )
        {
            var property = base.CreateProperty(member, memberSerialization);

            property.ShouldSerialize = _ =>
                properties
                    .Contains(
                        $"{member.ReflectedType.FullName.ToString()}.{member.Name}"
                        .ToLower());

            return property;
        }
    }

Here I specifically form my strings like "[full path goes here].Student.Name" or "[same here].Student.Gender" and check if property was selected for. This could be useful if you are going to serialize complex types and will need to feed selections for different types to your Resolver.

Second thing to do, and this is purely for optimization, create a factory that caches your resolvers, so you don't have to rebuild them every time:

    public class SelectiveContractResolverFactory
    {
        private ConcurrentDictionary<HashSet<string>, IContractResolver>
            cachedResolvers;

        public SelectiveContractResolverFactory()
        {
            cachedResolvers =
                new ConcurrentDictionary<HashSet<string>, IContractResolver>
                    (HashSet<string>.CreateSetComparer());
        }

        public IContractResolver GetResolver
            (
                IEnumerable<string> selectedProperties
            )
        {
            var selectedSet = selectedProperties.ToHashSet();

            if (cachedResolvers.ContainsKey(selectedSet))
                return cachedResolvers[selectedSet];

            var newResolver = new SelectiveContractResolver(selectedSet);
            cachedResolvers[selectedSet] = newResolver;

            return newResolver;
        }
    }

Finally, lets apply that thing to an object:

var student = new Student { Name = "Vijay", Age = 21 , Gender = "M" , Address = "Address_1"};

var selectedProps = new List<string> {"Student.Name","Student.Age"};

// Obviously, this should be injected as a singleton:
var resolverFactory = new SelectiveContractResolverFactory();

var resolver = resolverFactory.GetResolver(selectedProps);

var selectedResult = JsonConvert.SerializeObject
                     (
                         student,
                         Formatting.None,
                         new JsonSerializerSettings
                         {
                             ContractResolver = resolver
                         }
                     );

And just like that, you get a neat json object with the properties you wanted.

But, if you do have to get an actual C# object out of this, then you can deserialize into an ExpandoObject using ExpandoObjectConverter:

dynamic filteredStudent = JsonConvert
    .DeserializeObject<ExpandoObject>
    (
        selectedResult,
        new ExpandoObjectConverter()
    );

Though, the way the question is phrased, I feel like you are going to be returning this data from an API of sorts, so json would already be perfect for this.

Upvotes: 0

Related Questions