Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert anonymous object to expression delegate

Tags:

c#

I have an third party library that has the following API:

 Update<TReport>(object updateOnly, Expression<Func<TReport,bool>> where)

What I want to do is call this method but using anonymous objects such as :

Update(new {Name = "test"}, new {Id = id})

Is it possible to take the second anonymous object and convert it to something like :

x => x.Id == id.

So what I want is to convert the new {Id = id} to a function that takes TReport and returns bool?

like image 691
JD. Avatar asked Nov 03 '22 19:11

JD.


1 Answers

Even if I agree with Daniel A. White on the fact that it's complicating things, I tried a bit.

But it's not safe, cause you're losing strong typing. (You can put whatever you want in an anonymous object : it's not linked to the object's "real" properties... So no refactoring, no check...)

It's not really tested, so not sure if that's what you want. You can have (if it works) different objects in the "predicate object" :

new {Name="test"}, new{Id=1, Name="test2"})

So, you could have something like that :

public static class MyHelpers
{
        public static Expression<Func<TReport, bool>> CreatePredicate<TReport>(this object predicateObject)
        {
            var parameterExpression = Expression.Parameter(typeof(TReport), "item");
            Expression memberExpression = parameterExpression;
            var objectDictionary = MakeDictionary(predicateObject);
            foreach (var entry in objectDictionary.Where(entry => typeof(TReport).GetProperty(entry.Key) == null))
            {
               throw new ArgumentException(string.Format("Type {0} has no property {1}", typeof(TReport).Name, entry.Key));
            }
            var equalityExpressions = GetBinaryExpressions(objectDictionary, memberExpression).ToList();
            var body = equalityExpressions.First();
            body = equalityExpressions.Skip(1).Aggregate(body, Expression.And);

            return Expression.Lambda<Func<TReport, bool>>(body, new[] { parameterExpression });
        }
        private static IDictionary<string, object> MakeDictionary(object withProperties)
        {
            var properties = TypeDescriptor.GetProperties(withProperties);
            return properties.Cast<PropertyDescriptor>().ToDictionary(property => property.Name, property => property.GetValue(withProperties));
        }

        private static IEnumerable<BinaryExpression> GetBinaryExpressions(IDictionary<string, object> dic, Expression expression)
        {
            return dic.Select(m => Expression.Equal(Expression.Property(expression, m.Key), Expression.Constant(m.Value)));
        }
}

usage, for example

public void Update<TReport>(object updateOnly, object predicateObject) {
   var predicate = predicateObject.CreatePredicate<TReport>();
   yourGenericApi.Update(updateOnly, predicate);
}

EDIT : As you're losing strong typing security, you should add something like

foreach (var entry in objectDictionary.Where(entry => typeof(TReport).GetProperty(entry.Key) == null))
{
    throw new ArgumentException(string.Format("Type {0} has no property {1}", typeof(TReport).Name, entry.Key));
}

after

var objectDictionary = MakeDictionary(predicateObject);
like image 50
Raphaël Althaus Avatar answered Nov 10 '22 01:11

Raphaël Althaus