动态 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);
如果您将多次使用它,请考虑将其也放在扩展方法中
我正在寻找动态创建 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);
如果您将多次使用它,请考虑将其也放在扩展方法中