Разбираюсь с динамическим построением лямбда-запросов.
Вот мой исходный пример, неработающий:
void Main()
{
var data = Sample();
var filter = new Filter { Text = "ov", CityId = 1, };
var result = ExecuteQuery(data.AsQueryable(), filter);
result.Dump();
}
// Define other methods and classes here
public class Contact
{
public int Id { get; set; }
public string Title { get; set; }
public int? CityId { get; set; }
}
public class Filter
{
public string Text { get; set; }
public int? CityId { get; set; }
}
public List<Contact> Sample()
{
return new List<Contact>
{
new Contact { Id = 1, Title = "Ivanov", CityId = null, },
new Contact { Id = 2, Title = "Petrov", CityId = null, },
new Contact { Id = 3, Title = "Sidorov", CityId = 1, },
new Contact { Id = 4, Title = "Twain", CityId = 2, },
new Contact { Id = 5, Title = "Smith", CityId = 2, },
};
}
public IEnumerable<Contact> ExecuteQuery(IQueryable<Contact> data, Filter filter)
{
Expression<Func<Contact, bool>> expr = x => true;
if(!string.IsNullOrWhiteSpace(filter.Text))
{
Expression<Func<Contact, bool>> exprText = x => x.Title.Contains(filter.Text);
var body = Expression.AndAlso(expr.Body, exprText.Body);
expr = Expression.Lambda<Func<Contact, bool>>(body, expr.Parameters[0]);
}
if(filter.CityId != null)
{
Expression<Func<Contact, bool>> exprCity = x => x.CityId == filter.CityId;
var body = Expression.AndAlso(expr.Body, exprCity.Body);
expr = Expression.Lambda<Func<Contact, bool>>(body, expr.Parameters[0]);
}
return data.Where(expr);
}
Когда я формировал этот код (на основании этого ответа) я почему-то думал, что параметры у меня всегда совпадают.
Я в отладчике смотрел первые две лямбды:
ParameterExpression param1 = expr.Parameters[0];
ParameterExpression param2 = exprText.Parameters[0];
if (ReferenceEquals(param1, param2))
{
Но так и не понял, почему они различаются:
Кто-нибудь может пояснить этот момент?
PS Как переделать код к рабочему виду я уже сам разобрался:
public IEnumerable<Contact> ExecuteQuery(IQueryable<Contact> data, Filter filter)
{
Expression<Func<Contact, bool>> expr = x => true;
if(!string.IsNullOrWhiteSpace(filter.Text))
{
Expression<Func<Contact, bool>> exprText = x => x.Title.Contains(filter.Text);
expr = expr.AndAlso<Contact>(exprText);
}
if(filter.CityId != null)
{
Expression<Func<Contact, bool>> exprCity = x => x.CityId == filter.CityId;
expr = expr.AndAlso<Contact>(exprCity);
}
return data.Where(expr);
}
public static class MyExt
{
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
ParameterExpression param = expr1.Parameters[0];
if (ReferenceEquals(param, expr2.Parameters[0]))
{
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2.Body), param);
}
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, Expression.Invoke(expr2, param)), param);
}
}
Они различаются по той простой причине что все Expression всегда сравниваются по ссылке. Каждый вызов Expression.Parameter
создает уникальный параметр.
Эти параметры могут оказаться равными только в том случае когда кто-то скопирует их, явно или в составе выражения. В вашем случае это возможно, например, при вызове MyExt.AndAlso(exprText, exprText)
.
Если же вы спросите "зачем так сделано" - то вот простой ответ: то, как вы пытаетесь объединить два выражения - не единственный способ это сделать. И другим конструкциям нужны именно уникальные параметры:
Expression<Func<Foo, int>> foo = x => x.Y;
Expression<Func<Bar, int>> bar = x => x.Z;
var expr = Expression.Lambda<Func<Foo, Bar, int>>(
Expression.Add(foo.Body, bar.Body),
foo.Parameters[0],
bar.Parameters[0]);
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Работаю над приложением для Mixed RealityЗадача загружать сцены из интернета и загружать их соответственно
Использую winForms для построения графиковМетод для сохранения изображения :