我正在创建一个Assembler接口,它有助于在我的应用程序中的不同层之间转换对象。
E.g。
public interface IAssembler<TObjectSource, TObjectTarget>
{
IEnumerable<Expression<Func<TObjectSource, object>>> GetIncludes();
TObjectTarget Project(TObjectSource source);
}
GetIncludes方法将通知较低级别层创建目标对象需要源对象的哪些部分,因此需要由该层加载哪些部分。
Book汇编程序的实现可以是:
public class BookAssembler : IAssembler<Book, BookDTO>
{
public BookAssembler(IAssembler<Publisher, PublisherDTO> publisherAssembler)
{
PublisherAssembler = publisherAssembler;
}
private IAssembler<Publisher, PublisherDTO> PublisherAssembler { get; }
public IEnumerable<Expression<Func<Book, object>>> GetIncludes() {
return Expression<Func<Book, object>>[]
{
include => include.CreatedBy,
include => include.DeletedBy,
include => include.Publishers.Select(
includePublisher => PublisherAssembler.GetIncludes()
),
...
}
}
public BookDTO Project(Book book) {
//Convert the book into the book DTO.
}
}
BookAssembler未知PublisherAssembler返回的包含列表,例如它可以是0到N.
我需要能够压缩返回的可枚举对象,例如如果PublisherAssembler.GetIncludes()返回一个包含2个项目的枚举,我需要BookAssembler.GetIncludes()的输出为:
return Expression<Func<Book, object>>[]
{
include => include.CreatedBy,
include => include.DeletedBy,
include => include.Publishers.Select(includePublisher => includePublisher.Item1),
include => include.Publishers.Select(includePublisher => includePublisher.Item2),
...
}
关于如何做到这一点的任何想法?
答案 0 :(得分:0)
以下内容将按照我的要求进行,但仍需要扩展以处理所有表达式类型并执行更多错误检查。
它基本上只是将表达式树格式化,因此它不包含任何带复合对象的Select调用。
public class ExpressionFlattener
{
public ExpressionFlattener(MethodInfo selectMethod)
{
SelectMethod = selectMethod;
}
/// <summary>
/// Flattens the specified expression.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <param name="expression">The expression.</param>
/// <returns>IEnumerable<Expression<Func<TEntity, System.Object>>>.</returns>
public IEnumerable<Expression<Func<TEntity, object>>> Flatten<TEntity>(Expression<Func<TEntity, object>> expression) => Flatten(expression, new List<Expression>()).Cast<Expression<Func<TEntity, object>>>();
/// <summary>
/// Gets the select method.
/// </summary>
/// <value>The select method.</value>
private MethodInfo SelectMethod { get; }
/// <summary>
/// Flattens the specified expression.
/// </summary>
/// <param name="expression">The expression.</param>
/// <param name="expressions">The expressions.</param>
/// <returns>List<Expression>.</returns>
/// <exception cref="ArgumentException">Invalid expression specified, the specified expression is of an unhandled node type.</exception>
private List<Expression> Flatten(Expression expression, List<Expression> expressions)
{
switch (expression.NodeType)
{
case ExpressionType.Call: return FlattenMethodCall(expression as MethodCallExpression, expressions);
case ExpressionType.Lambda: return FlattenLambda(expression as LambdaExpression, expressions);
case ExpressionType.MemberAccess: expressions.Add(expression); return expressions;
case ExpressionType.New: return FlattenNew(expression as NewExpression, expressions);
default: throw new ArgumentException("Invalid expression specified, the specified expression is of an unhandled node type.");
}
}
/// <summary>
/// Flattens the lambda expression.
/// </summary>
/// <param name="expression">The expression.</param>
/// <param name="expressions">The expressions.</param>
/// <returns>List<Expression<Func<TEntity, System.Object>>>.</returns>
/// <exception cref="ArgumentException">Invalid expression specified, the specified expression has a body with an unhandled node type.</exception>
private List<Expression> FlattenLambda(LambdaExpression expression, List<Expression> expressions)
{
//Flatten the body expression.
List<Expression> innerExpressions = Flatten(expression.Body, new List<Expression>());
//We should never be creating a lambda expression which returns more than one property as we should
//have flattened everything out with the call to the above. So it should be safe? to assume we're
//inputting the same parameter type but just getting out an object.
Type delegateType = typeof(Func<,>).MakeGenericType(expression.Parameters[0].Type, typeof(object));
//Rebuild the lambda expression for each expression returned.
foreach (Expression innerExpression in innerExpressions)
expressions.Add(Expression.Lambda(delegateType, innerExpression, expression.Parameters[0]));
return expressions;
}
/// <summary>
/// Flattens the method call expression.
/// </summary>
/// <param name="expression">The expression.</param>
/// <param name="expressions">The expressions.</param>
/// <param name="lambdaExpression">The lambda expression.</param>
/// <returns>List<Expression<Func<TEntity, System.Object>>>.</returns>
/// <exception cref="ArgumentException">Invalid method call expression specified, the method call specified expression is to an unhandled method.</exception>
private List<Expression> FlattenMethodCall(MethodCallExpression expression, List<Expression> expressions)
{
switch (expression.Method.Name)
{
case "Select":
{
//The second argument will be the right side of the select statement e.g. property.Select(argument0 => argument1);
List<Expression> innerExpressions = Flatten(expression.Arguments[1], new List<Expression>());
//Create the select method.
//The first argument should always be the object we're passing in, so it will be the same.
//The second argument has been normailised into an object, e.g. from a single property or an anonyamous object containing multiple properties.
MethodInfo method = SelectMethod.MakeGenericMethod(expression.Method.GetGenericArguments().First(), typeof(object));
//Rebuild the select expression for each expression returned.
foreach (Expression innerExpression in innerExpressions)
expressions.Add(Expression.Call(method, expression.Arguments[0], innerExpression));
return expressions;
}
default: throw new ArgumentException("Invalid method call expression specified, the method call expression is to an unknown method.");
}
}
/// <summary>
/// Flattens the new expression.
/// </summary>
/// <param name="expression">The expression.</param>
/// <param name="expressions">The expressions.</param>
/// <returns>List<Expression>.</returns>
private List<Expression> FlattenNew(NewExpression expression, List<Expression> expressions)
{
//TODO: Make sure we're an anonyamous type.
foreach (Expression argument in expression.Arguments)
Flatten(argument, expressions);
return expressions;
}
}
传递给构造函数的select方法是:
Enumerable.Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
我已经编写了测试,按预期检查了以下工作:
单一财产:
输入:
book => book.CreatedBy
输出:
book => book.CreatedBy
集合的单一属性:
输入:
book => book.Publishers.Select(publisher => publisher.CreatedBy)
输出:
book => book.Publishers.Select(publisher => publisher.CreatedBy)
集合的多个属性:
输入:
book => book.Publishers.Select(publisher => new
{
publisher.CreatedBy,
publisher.DeletedBy
});
输出:
book => book.Publishers.Select(publisher => publisher.CreatedBy);
book => book.Publishers.Select(publisher => publisher.DeletedBy);
我需要写更多测试,但我很高兴我现在正走在正确的轨道上。