动态 lambda 表达式 - 比较 List<T> 位置 0 中的属性与位置 1 中的属性

Dynamic lambda expression - compare properties in a List<T> postion 0 with properties in position 1

我正在寻找动态创建 lambda 表达式的帮助。

下面是我的 类 的示例。我希望能够传入一个字符串,该字符串将成为我要比较的 属性。

为了这个 post,我简化了我的模型。原始模型有超过 300 列(不是我的设计)并定期添加。

列表中只有 2 条记录。我想比较 List[0].属性 != List[1].属性.

中的 属性
public class DataRecord
{
    public string Id { set; get; }
    public IList<Record> Records { set; get; }
}

public class Record
{
    public string Name {set; get;}
    public string Address {set; get;}
    public string Postcode {set; get;}
}

这是数据示例:

DataRecord
        {
            Id = "ID",                
            Records = new List<Record>
            {
                new Record {Name = "name1", Address = "someAddress1", Postcode = "postcode1"},
                new Record {Name = "name2", Address = "someAddress2", Postcode = "postcode2"}
            }

        };

我想说return如果第一个地址与第二个地址不同或名字与第二个名字不同等等

我想动态创建这个:

ListOfDataRecords.Where(dataRecord => dataRecord.Records[0].Property != dataRecord.Records[1].Property)

这是我目前的代码:

    private static Expression<Func<DataRecord, bool>> CreateFilterExpression(string propertyName)
    {
        var parameter = Expression.Parameter(typeof(DataRecord), "dataRecord");

        var member = Expression.Property(parameter, "Records");
        var member2 = Expression.Property(parameter, "Records");


        var arType = Array.CreateInstance(member.Type.GetGenericArguments().Single(), 0).GetType();
        var arType2 = Array.CreateInstance(member2.Type.GetGenericArguments().Single(), 0).GetType();


        var indexedProperty1 = Expression.ArrayIndex(member, Expression.Constant(0));
        var indexedProperty2 = Expression.ArrayIndex(member2, Expression.Constant(1));

        var body = Expression.Equal(member, member2);
        return Expression.Lambda<Func<DataRecord, bool>>(body, parameter);
    }   

这不起作用,这是我收到的错误:

Argument must be array (Parameter 'array')

谁能帮我解决这个问题?

在此先感谢您提供的任何帮助。

目前,您的表情似乎正在生成 dataRecord.Record[0] == dataRecord.Record[1]。我觉得“Record”打错了,应该是“Records”。

编辑

重新阅读异常消息后,我意识到问题在于数组索引运算符 适用于数组。您要访问的不是数组,而是定义了索引器的 class 。索引器实际上作为 class 上的 get 和 set 方法公开,可以通过 Expression.Call.

调用
private static Expression<Func<DataRecord, bool>> CreateFilterExpression(string propertyName)
{
    var parameter = Expression.Parameter(typeof(DataRecord), "dataRecord");
    var records = Expression.Property(parameter, "Records");
    var indexer = typeof(IList<Record>).GetMethod("get_Item");
    var record0 = Expression.Call(records, indexer, Expression.Constant(0));
    var record1 = Expression.Call(records, indexer, Expression.Constant(1));
    var property0 = Expression.Property(record0, propertyName);
    var property1 = Expression.Property(record1, propertyName);
    var body = Expression.NotEqual(property0, property1);
    return Expression.Lambda<Func<DataRecord, bool>>(body, parameter);
}

我的建议是不要对表达式使用反射和 fiddle,而是创建扩展方法,这样您就可以像使用 LINQ 方法一样使用它。 参见 extension methods demystified

使用 Lambda 表达式

将属性名称转换为属性选择器,并使用属性选择器调用过滤器方法:

static IQueryable<DataRecord> FilterByName(
    this IQueryable<DataRecord> dataRecords,
    string propertyName)
{
    // TODO: what if propertyName null or invalid?

    switch (propertyName)
    {
        case nameof(Record.Address):
            return dataRecords.FilterByProperty(record => record.Address);
        case nameof(Record.Postcode):
            return dataRecords.FilterByProperty(record => record.Postcode);
        case nameof(Record.Name):
            return dataRecords.FilteryByProperty(record => record.Name);
        default:
            throw new ArgumentException(nameof(propertyName));
    }

使用属性选择器的过滤方法:

static IQueryable<DataRecord> FilterByProperty<TProperty>(
    this IQueryable<DataRecord> dataRecords,
    Expression<Func<Record, TProperty>> propertySelector)
{
    // TODO: handle null dataRecords, null propertySelector

    return dataRecords.Where(dataRecord => !propertySelector(dataRecord.Records[0])
                                    .Equals(propertySelector(dataRecord.Records[1]);

用法:

string propertyName = ...
IQueryable<DataRecord> originalDataRecords = ...
IQueryable<DataRecord> filteredDataRecords = originalDataRecords.FilterByProperty(propertyName);

如果你真的想使用表达式

将您的问题拆分为子问题:

  • 将字符串转换为 PropertySelector:
  • 使用 属性选择器,return Expression<Func<DataRecord, bool>>

字符串到 PropertySelector:作为扩展方法:

static Expression<Func<Record, string>> ToPropertySelector(this string propertyName)
{
    switch (propertyName)
    {
        case nameof(Record.Address):
            return (record) => record.Address;
        case nameof(Record.Postcode):
            return (record) => record.Postcode;
        case nameof(Record.Name):
            return (record) => record.Name;
        default:
            throw new ArgumentException(nameof(propertyName));
    }
}

PropertySelector 到 FilterExpression:

static Expression<Func<DataRecord, bool>> ToFilterExpression(
    Func<Record, string> propertySelector)
{
    return (dataRecord) => !propertySelector(dataRecord.Records[0])
                    .Equals(propertySelector(dataRecord.Records[1]));
}

用法:

string propertyName = ...
Expression<Func<DataRecord, bool>> filterExpression = propertyName
    .ToPropertySelector()
    .ToFilterExpression();

var validDataRecords = dbContext.DataRecords.Where(filterExpression);

如果您将多次使用它,请考虑将其也放在扩展方法中