Create an Expression<Func<,>> using reflection
Asked Answered
S

3

32

Im using Moq to create mocks of a data set.

I have created a little helper class that allows me to have an in memory storage instead of a database that makes unit testing a breeze. That way I can add and remove items from my mock data set, this allows me to test my insert and delete service calls.

During the setup of the mock I have a line that looks like the following

this.Setup(i => i.AcademicCycles).Returns(mockStore.GetList<AcademicCycle>());

My mock has a lot of properties so I would like to perform this setup step using reflection. I have managed to the Returns part of the process working via reflection but I am stuck on the lambda method to Setup.

Setup takes an

Expression<Func<GoalsModelUnitOfWork, IQueryable<AcademicCycle>>> that corresponds to the i => i.AcademicCycles

and I would like to create this dynamically. Using reflection I have the following:

The name of the property: "AcademicCycles"

The type IQueryable<AcademicCycle>

The type AcademicCycle

I also have the instance of the i in the lambda statement which is a GoalsModelUnitOfWork

Sulfide answered 23/5, 2012 at 2:32 Comment(0)
S
33

The code to create the expression dynamically would be like this:

ParameterExpression parameter = Expression.Parameter(typeof (GoalsModelUnitOfWork), "i");
MemberExpression property = Expression.Property(parameter, "AcademicCycles");

var queryableType = typeof (IQueryable<>).MakeGenericType(typeof (AcademicCycle));
var delegateType = typeof (Func<,>).MakeGenericType(typeof (GoalsModelUnitOfWork), queryableType);

var yourExpression = Expression.Lambda(delegateType, property, parameter);

The result will have the desired type, but the problem is that the return type of Expression.Lambda() is LambdaExpression and you can't perform a type cast to Expression<Func<...>> to pass it as parameter to your setup function because you don't know the generic type parameters for the Func. So you have to invoke the Setup method by reflection, too:

this.GetType().GetMethod("Setup", yourExpression.GetType()).Invoke(this, yourExpression);
Simpson answered 23/5, 2012 at 8:16 Comment(2)
Actually the result of Expression.Lambda can be cast to Expression<Func<...>> if you statically know the parameter and return types. Internally Expression.Lambda does construct an instance of the appropriate Expression<Func<...>> type, even though the return type of Expression.Lambda is weakly typed.Chivy
Also I don't think you need the middle two lines. From testing in a simpler case, var lambda = Expression.Lambda(parameter, property) should work (Expression.Lambda will work out the delegate type from the types and properties). However my test code was slightly different to yours and used simpler types, so your mileage may vary...!Chivy
G
2

I decided to take a crack at it and ended up with this god awful piece of code.

I am no reflection expert and this is just a first attempt to get something working. I'd be very interested in what other approaches people have, or whether any of the relfection wrapper libraries can make this nicer.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Moq;
using Xunit;

namespace MyExample
{
    public class Tests
    {
        [Fact]
        public void Test()
        {
            Dictionary<Type, object> data = new Dictionary<Type, object>
            {
                { typeof(IQueryable<Cycle>), new List<Cycle> { new Cycle { Name = "Test" } }.AsQueryable() },
                { typeof(IQueryable<Rider>), new List<Rider> { new Rider { Name = "1"}, new Rider { Name = "2" } }.AsQueryable() }
            };

            var mock = new Mock<IDataContext>();
            var setup = mock.GetType().GetMethods().Single(d => d.Name == "Setup" && d.ContainsGenericParameters);
            var param = Expression.Parameter(typeof(IDataContext), "i");
            foreach (var property in typeof(IDataContext).GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                // Build lambda
                var ex = Expression.Lambda(Expression.MakeMemberAccess(param, property), param);

                // Get generic version of the Setup method
                var typedSetup = setup.MakeGenericMethod(property.PropertyType);

                // Run the Setup method
                var returnedSetup = typedSetup.Invoke(mock, new[] { ex });

                // Get generic version of IReturns interface
                var iReturns = typedSetup.ReturnType.GetInterfaces().Single(d => d.Name.StartsWith("IReturns`"));

                // Get the generic Returns method
                var returns = iReturns.GetMethod("Returns", new Type[] { property.PropertyType });

                // Run the returns method passing in our data
                returns.Invoke(returnedSetup, new[] { data[property.PropertyType] });
            }

            Assert.Equal(1, mock.Object.Cycles.Count());
        }
    }

    public class Cycle
    {
        public string Name { get; set; }
    }

    public class Rider
    {
        public string Name { get; set; }
    }

    public interface IDataContext
    {
        IQueryable<Cycle> Cycles { get; set; }

        IQueryable<Rider> Riders { get; set; }
    }
}
Goldofpleasure answered 23/5, 2012 at 9:15 Comment(0)
B
2

This method ought to construct the lambda expression. Since you are invoking the Setup method by reflection, you do not need a strongly-typed lambda expression; you are going to pass it as part of an object array when you call Invoke:

    public LambdaExpression PropertyGetLambda(string parameterName, Type parameterType, string propertyName, Type propertyType)
    {
        var parameter = Expression.Parameter(parameterType, parameterName);
        var memberExpression = Expression.Property(parameter, propertyName);
        var lambdaExpression = Expression.Lambda(memberExpression, parameter);
        return lambdaExpression;
    }

I don't think you actually need the parameter name. If I'm right about that, you could simplify a bit:

    public LambdaExpression PropertyGetLambda(Type parameterType, string propertyName, Type propertyType)
    {
        var parameter = Expression.Parameter(parameterType);
        var memberExpression = Expression.Property(parameter, propertyName);
        var lambdaExpression = Expression.Lambda(memberExpression, parameter);
        return lambdaExpression;
    }
Beggs answered 23/5, 2012 at 17:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.