动态选择投影表达式

时间:2014-04-29 18:19:30

标签: c# reflection lambda

我有以下课程,其用途并不重要。重要的是方法SetCacheItemSelector,它接受一个参数,一个将Account实体投影到AccountCacheDTO的select表达式:

public class AccountRepositoryCache : RepositoryCache<Account, AccountCacheDTO>
{
    public AccountRepositoryCache()
    {
        SetCacheItemSelector(x => new AccountCacheDTO
        {
            Id = x.Id,
            Login = x.Login
        });
    }
}

这种方法的签名是:

public void SetCacheItemSelector(Expression<Func<TEntity, TCacheItem>> selector)

在这种情况下,TEntity是Account类,TCacheItem是AccountCacheDTO类。

有没有办法使用反射为所有匹配Account类和AccountCacheDTO类的属性动态构建select表达式?

目标是让方法看起来像这样:

public Expression<Func<TEntity, TCacheItem>> BuildSelector<TEntity, TCacheItem>()
{
... // implementation with reflection goes here
}

修改

这是最终实施(与接受的答案几乎相同):

public static Expression<Func<TSource, TTarget>> BuildSelector<TSource, TTarget>()
        {
            Type targetType = typeof(TTarget);
            Type sourceType = typeof(TSource);
            ParameterExpression parameterExpression = Expression.Parameter(sourceType, "source");
            List<MemberBinding> bindings = new List<MemberBinding>();
            foreach (PropertyInfo sourceProperty in sourceType.GetProperties().Where(x => x.CanRead))
            {
                PropertyInfo targetProperty = targetType.GetProperty(sourceProperty.Name);
                if (targetProperty != null && targetProperty.CanWrite && targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
                {
                    MemberExpression propertyExpression = Expression.Property(parameterExpression, sourceProperty);
                    bindings.Add(Expression.Bind(targetProperty, propertyExpression));
                }
            }
            NewExpression newExpression = Expression.New(targetType);
            Expression initializer = Expression.MemberInit(newExpression, bindings);
            return Expression.Lambda<Func<TSource, TTarget>>(initializer, parameterExpression);
        }

3 个答案:

答案 0 :(得分:2)

我没有对它进行测试,但你应该能够做到这样的事情:这只是为了传达一个大致的想法,你应该能够根据你的要求进行调整。

    public Expression<Func<TEntity, TCacheItem>> BuildSelector<TEntity, TCacheItem>(TEntity entity)
    {
        List<MemberBinding> memberBindings = new List<MemberBinding>();
        MemberInitExpression body = null;

        foreach (var entityPropertyInfo in typeof(TEntity).GetProperties())
        {
            foreach (var cachePropertyInfo in typeof(TCacheItem).GetProperties())
            {
                if (entityPropertyInfo.PropertyType == cachePropertyInfo.PropertyType && entityPropertyInfo.Name == cachePropertyInfo.Name)
                {
                    var fieldExpressoin = Expression.Field(Expression.Constant(entity), entityPropertyInfo.Name);
                    memberBindings.Add(Expression.Bind(cachePropertyInfo, fieldExpressoin));
                }
            }
        }

        var parameterExpression = Expression.Parameter(typeof(TEntity), "x");
        var newExpr = Expression.New(typeof(TCacheItem));
        body = Expression.MemberInit(newExpr, memberBindings);
        return Expression.Lambda<Func<TEntity, TCacheItem>>(body, parameterExpression);
    }

答案 1 :(得分:1)

当然,@ Aravol的答案可能有意义,但OP中需要的有点不同。这是一个更适合OP要求的解决方案。

public Expression<Func<TEntity, TCacheItem>> BuildSelector<TEntity, TCacheItem>()
{
    Type type = typeof(TEntity);
    Type typeDto = typeof(TCacheItem);
    var ctor = Expression.New(typeDto);
    ParameterExpression parameter = Expression.Parameter(type, "p");
    var propertiesDto = typeDto.GetProperties(BindingFlags.Public | BindingFlags.Instance);
    var memberAssignments = propertiesDto.Select(p =>
    {
        PropertyInfo propertyInfo = type.GetProperty(p.Name, BindingFlags.Public | BindingFlags.Instance);
        MemberExpression memberExpression = Expression.Property(parameter, propertyInfo);
        return Expression.Bind(p, memberExpression);
    });
    var memberInit = Expression.MemberInit(ctor, memberAssignments);
    return Expression.Lambda<Func<TEntity, TCacheItem>>(memberInit, parameter);
}

答案 2 :(得分:0)

您最好的选择是使用System.Linq.Expressions命名空间,该命名空间包含动态元方法调用方法调用并将其编译为委托所需的所有方法。特别参见Expression.Call和Lambda.Compile方法。请注意,使用Lambda.Compile,您还可以拥有一个真正的已编译的Delegate,而不是将调用包装到所需方法的表达式树(Expression)。 (注意:如果你真的想要这个表达式树,你也可以放弃编译步骤)

至于构建你的集合,这是程序集扫描,并且将是迭代你的程序集中的所有类的问题。我强烈建议您至少使用装配体或未来装配体上的自定义属性来标记它们以进行此扫描,以免此过程的成本更高。最多,您应该考虑使用自定义属性来标记要为此表达式构建扫描的属性。

此实际代码往往以

开头
AppDomain.CurrentDomain // Necessary to get all available Assemblies
    .GetAssemblies()    // Gets all the assemblies currently loaded in memory that this code can work with
    .AsParallel()       // Highly recommended to make the attribute-checking steps run asynchronously
                        // Also gives you a handy .ForAll Method at the end
    // TODO: .Where Assembly contains Attribute
    .SelectMany(assembly => assembly.GetTypes())
    // TODO: .Where Type contains Attribute
    .SelectMany(type => type.GetProperties)
    // TODO: Make sure Property has the right data...
    .Select(CompileFromProperty)

其中CompileFromProperty是一个获取PropertyInfo并返回所需表达式的方法。

在此之后查看ToList()和ToDictionary,因为一旦开始将值推送到缓存,您可能需要打破并行化

附录:你还在Type类上有.MakeGenericType,它允许你从其他Type变量中指定Generic参数,这在构建表达式时将证明是无价的。在定义泛型类型时,不要忘记Contravariance