3

I have a property whose name is in a variable (userFilters.property), which can be of type string/enum/number.

I want to select the distinct values of that property dynamically. how do I do it.

        var query = CreatePincodeQuery(useFilters);

        //userFilters.property contains the property to be selected
        Expression<Func<PincodeData, string>> selectExpr = null;

        IList<string> list = query.Select(selectExpr)
            .Distinct()
            .OrderBy(selectExpr)
            .ToList();
        return list.;

I should be creating the expression of type Expression<Func<PincodeData, string>> selectExpr to use as part of select & orderBy block.

How do I do it?

I looked at the solution provided here, here and here, but not able to understand how to I modify those to fit my need.

EDIT

came up with below solution, as expected its not working, and how do i convert the value to string.

    Func<TEntity, string> CreateNewStatement<TEntity>(string field)
    {
        //https://stackoverflow.com/questions/16516971/linq-dynamic-select
        var xParameter = Expression.Parameter(typeof(TEntity), "o");
        var property = Expression.Property(xParameter, typeof(TEntity).GetProperty(field));
        var lambda = Expression.Lambda<Func<TEntity, string>>(property, xParameter);
        return lambda.Compile();
    }

EDIT

I changed the type from string to object, but its still failing while creating the lambda for enum types, what could be the reason

Community
  • 1
  • 1
Kapil Gandhi
  • 181
  • 1
  • 1
  • 8
  • Your edit is the right approach: build the expression dynamically. To convert to string: all types (usable here) derive ultimately from object and can override `ToString`…. – Richard Jan 06 '15 at 12:42
  • You need to get the property (as you are doing) which will give you a result that is the type of the property; and then call `ToString` on that: ie. a property expression *and* a method call expression. – Richard Jan 06 '15 at 13:01
  • @Richard I tried your approach, it works for string/number types, but I am still getting the error from `enum` types, even after converting to `object`, little strange, dont understand why – Kapil Gandhi Jan 06 '15 at 13:02
  • can you provide example of method call expression – Kapil Gandhi Jan 06 '15 at 13:03
  • Have you looked at this: http://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-aquery-library – zaitsman Jan 06 '15 at 13:06
  • @zaitsman that was the first thing I tried, but I am getting some string VS issue, I added the nuget, and when I add `using System.Linq.Dynamic` I get compile time error, hence I did not continue with that :(, dont know why that namespace is not available – Kapil Gandhi Jan 06 '15 at 13:14
  • Sorry don't have code to hand; but you should start at [`Expression.Call`](http://msdn.microsoft.com/en-us/library/bb349020%28v=vs.110%29.aspx) passing a `MethodInfo` instance for the property's type's `ToString` override. – Richard Jan 06 '15 at 13:22
  • how you try use `CreateNewStatement`? – Grundy Apr 20 '15 at 18:25

1 Answers1

0

In the last case, when you changed the return type to object, you didn't convert the value type to it. That mistake is rather common because we, C# developers, writing our own C# code, get used to relying on the C# compiler to make the value type to object conversion automatically. But, that's not the case for expressions, so you need to do the conversion explicitly.

Your particular case is solved below, but! It is very, very easy. Since you are on the way to dynamically building queries based on the end-user input your next steps might become complicated very fast (for instance, filtering with logical conditions). Once that happens, consider using Dynamic LINQ, which is very powerful for such tasks.

 public class Entity
    {
        public string StringProp { get; }

        public int IntProp { get; }

        public DayOfWeek EnumProp { get; }

        public Entity(string stringProp, int intProp, DayOfWeek enumProp)
        {
            StringProp = stringProp;
            IntProp = intProp;
            EnumProp = enumProp;
        }
    }

    [Test]
    public void Expressions()
    {
        var entities = new List<Entity>
        {
            new("Prop3", 3, DayOfWeek.Wednesday),
            new("Prop2", 2, DayOfWeek.Tuesday),
            new("Prop1", 1, DayOfWeek.Monday),
        };

        const string stringPropName = nameof(Entity.StringProp);
        var stringStatement = CreateNewStatement<Entity>(stringPropName);

        const string intPropName = nameof(Entity.IntProp);
        var intStatement = CreateNewStatement<Entity>(intPropName);

        const string enumPropName = nameof(Entity.EnumProp);
        var enumStatement = CreateNewStatement<Entity>(enumPropName);


        IList<object> listOfStrings = entities.Select(stringStatement).Distinct().OrderBy(i => i).ToList();
        TestContext.WriteLine(string.Join(",", listOfStrings));

        IList<object> listOfInts = entities.Select(intStatement).Distinct().OrderBy(i => i).ToList();
        TestContext.WriteLine(string.Join(",", listOfInts));

        IList<object> listOfEnums = entities.Select(enumStatement).Distinct().OrderBy(i => i).ToList();
        TestContext.WriteLine(string.Join(",", listOfEnums));
    }

    private static Func<TEntity, object> CreateNewStatement<TEntity>(string fieldName)
    {
        var parameter = Expression.Parameter(typeof(TEntity), "o");
        var propertyInfo = typeof(TEntity).GetProperty(fieldName);
        if (propertyInfo == null)
            throw new InvalidOperationException($"Property '{fieldName}' not found");
        
        Expression body = Expression.Property(parameter, propertyInfo);

        if (propertyInfo.PropertyType.IsValueType)
            body = Expression.Convert(body, typeof(object));

        var lambda = Expression.Lambda<Func<TEntity, object>>(body, parameter);
        return lambda.Compile();
    }
ademchenko
  • 585
  • 5
  • 18