Создать выражение <Func <, >> используя отражение
Im, использующий Moq для создания mocks набора данных.
Я создал небольшой вспомогательный класс, который позволяет мне иметь память в памяти вместо базы данных, которая делает модульное тестирование бриз. Таким образом, я могу добавлять и удалять элементы из моего набора данных, это позволяет мне проверять мои вставки и удалять вызовы служб.
Во время настройки макета у меня есть строка, которая выглядит следующим образом
this.Setup(i => i.AcademicCycles).Returns(mockStore.GetList<AcademicCycle>());
Мой макет имеет много свойств, поэтому я хотел бы выполнить этот шаг настройки, используя отражение. Мне удалось выполнить Returns
часть процесса, работающего через отражение, но я привязан к методу лямбда к Setup
.
Setup
принимает
Expression<Func<GoalsModelUnitOfWork, IQueryable<AcademicCycle>>>
, что соответствует i => i.AcademicCycles
и я хотел бы создать это динамически. Используя отражение, я имею следующее:
Название свойства: "AcademicCycles"
Тип IQueryable<AcademicCycle>
Тип AcademicCycle
У меня также есть экземпляр i
в выражении лямбда, который является GoalsModelUnitOfWork
Ответы
Ответ 1
Код для создания выражения динамически будет выглядеть следующим образом:
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);
Результат будет иметь желаемый тип, но проблема в том, что возвращаемый тип Expression.Lambda()
равен LambdaExpression
, и вы не можете выполнить тип, отлитый от Expression<Func<...>>
, чтобы передать его как параметр вашей функции установки, потому что вы не знаете параметры типового типа для Func
. Поэтому вы также должны вызвать метод Setup
путем отражения:
this.GetType().GetMethod("Setup", yourExpression.GetType()).Invoke(this, yourExpression);
Ответ 2
Я решил взломать его и закончил с этим богом ужасный фрагмент кода.
Я не эксперт по размышлениям, и это всего лишь первая попытка заставить что-то работать. Мне было бы очень интересно, какие другие подходы у людей есть, или может ли какая-либо из библиотек обертки для фильтрации сделать это лучше.
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; }
}
}
Ответ 3
Этот метод должен построить лямбда-выражение. Поскольку вы вызываете метод установки методом отражения, вам не требуется строго типизированное лямбда-выражение; вы собираетесь передать его как часть массива объектов, когда вы вызываете 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;
}
Я не думаю, что вам действительно нужно имя параметра. Если я прав, вы можете немного упростить:
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;
}