为 Expression<Func<T, U>> 访问的每个 属性 获取 Expression<Func<T, object>>

Get Expression<Func<T, object>> for each property accessed by Expression<Func<T, U>>

我有一个数据访问 class,它充当逻辑 classes 和底层数据源之间的中介,它是可互换的。 class 允许您使用 LINQ 风格的 lambda 查询数据源。与源无关的 class 提供由一些基本操作(添加、获取全部、更新、删除、提交)提供支持的高级功能,这些操作由小型适配器 classes 实现,每个源类型一个( SQL、SQlite、XML 序列化程序、WCF 客户端、REST 客户端等等)。

我的问题是某些关系数据源(尤其是 SQLite)不够智能,无法在我需要时加载关系属性;我必须明确要求将它们包括在内。这对我的 Get 方法来说很好;我可以传递一个 params 表达式数组来加载我需要的任何东西。然而,对于 .Any(),这感觉有点奇怪 - 如果我问是否有任何 Customer 记录的 Purchases 列表包含某个项目,我不应该告诉它加载 Purchases 列表;这似乎是它应该能够弄清楚的事情。

所以我的 Any() 方法采用 Expression<Func<T, bool>>,其中 T 显然是我正在操作的类型。在上面的例子中,它会像这样使用:

using (var db = _dataAccessProvider.NewTransaction())
{
    return db.Any<Customer>(c => c.Purchases.Contains(someProduct));
}

是否可以把代表操作c => c.Purchases.Contains(someProduct))Expression<Func<Customer, bool>>算出来它所指的属性是c => c.Purchases?我该怎么做呢?涉及多个属性的 lambda 怎么样?

使用 ExpressionVisitor 查找引用所需对象属性的所有 MemberExpression 表达式。

快速示例:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

class Program
{
    sealed class ReferencedPropertyFinder : ExpressionVisitor
    {
        private readonly Type _ownerType;
        private readonly List<PropertyInfo> _properties = new List<PropertyInfo>();

        public ReferencedPropertyFinder(Type ownerType)
        {
            _ownerType = ownerType;
        }

        public IReadOnlyList<PropertyInfo> Properties
        {
            get { return _properties; }
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            var propertyInfo = node.Member as PropertyInfo;
            if(propertyInfo != null && _ownerType.IsAssignableFrom(propertyInfo.DeclaringType))
            {
                // probably more filtering required
                _properties.Add(propertyInfo);
            }
            return base.VisitMember(node);
        }
    }

    private static IReadOnlyList<PropertyInfo> GetReferencedProperties<T, U>(Expression<Func<T, U>> expression)
    {
        var v = new ReferencedPropertyFinder(typeof(T));
        v.Visit(expression);
        return v.Properties;
    }

    sealed class TestEntity
    {
        public int PropertyA { get; set; }

        public int PropertyB { get; set; }

        public int PropertyC { get; set; }
    }

    static void Main(string[] args)
    {
        Expression<Func<TestEntity, int>> expression =
            e => e.PropertyA + e.PropertyB;

        foreach(var property in GetReferencedProperties(expression))
        {
            Console.WriteLine(property.Name);
        }
    }
}