动态选择投影表达式



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

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类匹配的所有属性动态构建选择表达式?

目标是有这样的方法:

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

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

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

当然,@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);
}

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

至于构建集合,这是Assembly扫描,需要迭代Assembly中的所有类。我强烈建议您至少在程序集或将来的程序集上使用自定义属性来标记这些程序集以进行此扫描,以免此过程的成本高得多。最多,您应该考虑使用自定义属性来标记要为此表达式生成扫描的属性。

实际的代码倾向于从开始

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参数,这将在构建表达式时证明是非常宝贵的。当您定义泛型类型时,不要忘记方差!

最新更新