为 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);
}
}
}
我有一个数据访问 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);
}
}
}