简体   繁体   English

创建表达式 <Func<,> &gt;使用反射

[英]Create an Expression<Func<,>> using reflection

Im using Moq to create mocks of a data set. 我使用Moq来创建数据集的模拟。

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 . 我已经设法通过反射工作的Returns部分,但我坚持使用lambda方法进行Setup

Setup takes an Setup需要一个

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

and I would like to create this dynamically. 我想动态创建它。 Using reflection I have the following: 使用反射我有以下内容:

The name of the property: "AcademicCycles" 物业名称:“AcademicCycles”

The type IQueryable<AcademicCycle> 类型IQueryable<AcademicCycle>

The type AcademicCycle 类型AcademicCycle

I also have the instance of the i in the lambda statement which is a GoalsModelUnitOfWork 我还在lambda语句中有一个i的实例,它是一个GoalsModelUnitOfWork

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 . 结果将具有所需的类型,但问题是Expression.Lambda()的返回类型是LambdaExpression并且您无法对Expression<Func<...>>执行类型转换以将其作为参数传递给您设置函数,因为您不知道Func的泛型类型参数。 So you have to invoke the Setup method by reflection, too: 所以你必须通过反射来调用Setup方法:

this.GetType().GetMethod("Setup", yourExpression.GetType()).Invoke(this, yourExpression);

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. 我会对人们有什么其他方法感兴趣,或者是否有任何一个relfection包装库可以使这个更好。

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; }
    }
}

This method ought to construct the lambda expression. 该方法应该构造lambda表达式。 Since you are invoking the Setup method by reflection, you do not need a strongly-typed lambda expression; 由于您通过反射调用Setup方法,因此您不需要强类型的lambda表达式; you are going to pass it as part of an object array when you call Invoke : 当您调用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;
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM