我想创建一个基于嵌套属性的Linq-Where。
假设这是我的项目:
public class Car {
public Engine Engine { get; set; }
}
public class Engine {
public int HorsePower { get; set; }
}
var myCar = new Car() {
Engine = new Engine() {
HorsePower = 400
}
};
我使用在某个地方找到的这段代码,它允许创建表达式,
private Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>( PropertyInfo property, TValue value ) {
var param = Expression.Parameter( typeof( TItem ) );
var memberExp = Expression.Property( param, property );
BinaryExpression body;
//If nullable, Expression.Equal won't work even if the value is not null. So we convert to non nullable (the compared expression)
Type typeIfNullable = Nullable.GetUnderlyingType( memberExp.Type );
if ( typeIfNullable != null ) {
var convertedExp = Expression.Convert( memberExp, Expression.Constant( value ).Type );
body = Expression.Equal( convertedExp, Expression.Constant( value ) );
} else {
body = Expression.Equal( memberExp, Expression.Constant( value ) );
}
return Expression.Lambda<Func<TItem, bool>>( body, param );
}
现在,我想要一个等价的方法,调用我的方法PropertyEquals
var engine = myCar.Select( c => c.Engine.HorsePower == 400 );
类似的东西
var property = GetPropertyForDotSequence( typeof( Car ), "Engine.HorsePower" );
.Select( PropertyEquals<TEntity, int>( property , 400 ) );
我找到了一个方法,它允许基于点格式查找属性(GetPropertyForDotSequence),它工作正常,但返回HorsePower属性信息,而不是Engine.HorsePower,所以我收到一个错误,说Car没有名为HorsePower的int32属性。
private PropertyInfo GetPropertyForDotSequence ( Type baseType, string propertyName ) {
var parts = propertyName.Split( '.' );
return ( parts.Length > 1 )
? GetPropertyForDotSequence( baseType.GetProperty( parts[ 0 ] ).PropertyType, parts.Skip( 1 ).Aggregate( ( a, i ) => a + "." + i ) )
: baseType.GetProperty( propertyName );
}
为了实现您的目标,与其使用单独的辅助函数从属性路径中提取最后一个属性信息,然后将属性信息传递给您的函数,所有这些都应该在函数本身内部完成,即它应该接收包含属性路径的string
,如
public static partial class Utils
{
public static Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>(string propertyPath, TValue value)
{
var source = Expression.Parameter(typeof(TItem), "source");
var propertyNames = propertyPath.Split('.');
var member = Expression.Property(source, propertyNames[0]);
for (int i = 1; i < propertyNames.Length; i++)
member = Expression.Property(member, propertyNames[i]);
Expression left = member, right = Expression.Constant(value, typeof(TValue));
if (left.Type != right.Type)
{
var nullableType = Nullable.GetUnderlyingType(left.Type);
if (nullableType != null)
right = Expression.Convert(right, left.Type);
else
left = Expression.Convert(left, right.Type);
}
var body = Expression.Equal(left, right);
var expr = Expression.Lambda<Func<TItem, bool>>(body, source);
return expr;
}
}
我不太确定它怎么会有用,因为签名不允许推断泛型类型,所以它需要像这样的东西
var predicate = Utils.PropertyEquals<Car, int>("Engine.HorsePower", 400);
bool result = predicate.Compile().Invoke(myCar);
IMO,如果与以下扩展方法结合使用,这将是有用的
public static partial class Utils
{
public static IQueryable<T> WherePropertyEquals<T, TValue>(this IQueryable<T> source, string propertyPath, TValue value)
{
return source.Where(PropertyEquals<T, TValue>(propertyPath, value));
}
public static IEnumerable<T> WherePropertyEquals<T, TValue>(this IEnumerable<T> source, string propertyPath, TValue value)
{
return source.Where(PropertyEquals<T, TValue>(propertyPath, value).Compile());
}
}
这样你就可以写这样的
List<Car> cars = new List<Car> { myCar };
var cars400 = cars.WherePropertyEquals("Engine.HorsePower", 400).ToList();
您可以使用此方法从嵌套属性名称为string
的object
中获取property
值
public static object GetNestedPropertyValue(object obj, string nestedDottedPropertyName)
{
foreach (String part in nestedDottedPropertyName.Split('.'))
{
if (obj == null)
return null;
PropertyInfo info = obj.GetType().GetProperty(part);
if (info == null)
return null;
obj = info.GetValue(obj, null);
}
return obj;
}
但这不是有效的Linq
语句
var engine = myCar.Select( c => c.Engine.HorsePower == 400 );
相反,你可以做的是,如果你有一个像这样的汽车物体
var myCar = new Car()
{
Engine = new Engine()
{
HorsePower = 400
}
};
你可以得到Engine.HorsePower
的值作为
var horsePower = (int)GetNestedPropertyValue(myCar, "Engine.HorsePower");
编辑
对于Linq
示例,如果您有一个类似于的List<Car>
var myCar2 = new Car()
{
Engine = new Engine()
{
HorsePower = 800
}
};
var cars = new List<Car> { myCar, myCar2 }; //myCar defined above
您可以使用Linq
作为
var car400 = cars.FirstOrDefault(c => (int)GetNestedPropertyValue(c, "Engine.HorsePower") == 400); //=> myCar
var horsePowers = cars.Select(c => (int)GetNestedPropertyValue(c, "Engine.HorsePower")); //=> 400, 800