Create an Expression> using reflection

2020-02-17 05:30发布

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

3条回答
beautiful°
2楼-- · 2020-02-17 05:53

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;
    }
查看更多
Animai°情兽
3楼-- · 2020-02-17 05:54

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);
查看更多
劳资没心,怎么记你
4楼-- · 2020-02-17 06:05

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; }
    }
}
查看更多
登录 后发表回答