带有嵌套特性值反射的LINQ



我想创建一个基于嵌套属性的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();

您可以使用此方法从嵌套属性名称为stringobject中获取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

相关内容

  • 没有找到相关文章

最新更新