Создание выражения OrderBy Lambda на основе свойства дочернего объекта
Я пытаюсь создать предложение LINQ OrderBy
, используя лямбда-выражения с вводом имени столбца объекта в виде строки (в переменной "sortOn" ниже).
Приведенный ниже код отлично работает для значения sortOn, такого как "Code", генерирующего lambda
p => p.Code
Но я также хотел бы сортировать дочерний объект, где лямбда может быть
p => p.Category.Description
Итак, в этом случае я бы просто хотел установить sortOn = "Category.Description" и создать правильное выражение lamdba.
Возможно ли это? Любые предложения относительно наилучшего способа сделать это будут приветствоваться.
Этот код отлично подходит для простого случая:
var param = Expression.Parameter(typeof (Product), "p");
var sortExpression = Expression.Lambda<Func<Product, object>>(
Expression.Property(param, sortOn), param);
if (sortAscending ?? true)
{
products = products.OrderBy(sortExpression);
}
else
{
products = products.OrderByDescending(sortExpression);
}
Пример использования этой проблемы - отображение сетки данных и возможность сортировки данных, просто передав имя столбца для сортировки на сервере. Я хотел бы сделать решение общим, но начал использовать определенный тип (Продукт в примере) на данный момент.
Ответы
Ответ 1
Это будет генерировать правильное лямбда-выражение:
var sortOn = "Category.Description";
var param = Expression.Parameter(typeof(Product), "p");
var parts = sortOn.Split('.');
Expression parent = param;
foreach (var part in parts)
{
parent = Expression.Property(parent, part);
}
var sortExpression = Expression.Lambda<Func<Product, object>>(parent, param);
Ответ 2
Вы можете использовать динамическую библиотеку запросов LINQ, чтобы сделать это легко. Предполагая, что вы IQueryable<T>
реализация Product
, вы можете легко сделать:
IQueryable<Product> products = ...;
// Order by dynamically.
products = products.OrderBy("Category.Description");
В блоге есть ссылка на libary, и вам придется самостоятельно создавать проект/проект в своем решении, но он работает очень хорошо, и синтаксический анализ очень надежный. Это мешает вам самому писать код разбора; даже для чего-то такого простого, если требования расширяются, библиотека вас покрыла, тогда как в домашнем решении это не так.
Он также имеет ряд других динамических операторов (Select
, Where
и т.д.), чтобы вы могли выполнять другие динамические операции.
Там нет волшебства под капотом, он просто анализирует строки, которые вы передаете, а затем создает лямбда-выражения на основе результатов синтаксического анализа.
Ответ 3
Если вам не нужны выражения, как насчет:
products = products.Orderby(p1 => p1.Code).ThenBy(p2 => p2.Category.Description)
Ответ 4
Привет, вы также можете создать метод расширения, который может сортировать любую глубину, а не только дочерний
public static IEnumerable<TSource> CustomOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
List<string> list=new List<string>();
List<TSource> returnList=new List<TSource>();
List<int> indexList = new List<int>();
if (source == null)
return null;
if (source.Count() <= 0)
return source;
source.ToList().ForEach(sc=>list.Add(keySelector(sc).ToString())); //Extract the strings of property to be ordered
list.Sort(); //sort the list of strings
foreach (string l in list) // extract the list of indexes of source according to the order
{
int i=0;
//list.ForEach(l =>
foreach (var s in source.ToList())
{
if (keySelector(s).ToString() == l)
break;
i++;
}
indexList.Add(i);
}
indexList.ForEach(i=>returnList.Add(source.ElementAt(i))); //rearrange the source according to the above extracted indexes
return returnList;
}
}
public class Name
{
public string FName { get; set; }
public string LName { get; set; }
}
public class Category
{
public Name Name { get; set; }
}
public class SortChild
{
public void SortOn()
{
List<Category> category = new List<Category>{new Category(){Name=new Name(){FName="sahil",LName="chauhan"}},
new Category(){Name=new Name(){FName="pankaj",LName="chauhan"}},
new Category(){Name=new Name(){FName="harish",LName="thakur"}},
new Category(){Name=new Name(){FName="deepak",LName="bakseth"}},
new Category(){Name=new Name(){FName="manish",LName="dhamaka"}},
new Category(){Name=new Name(){FName="arev",LName="raghaka"}}
};
var a = category.CustomOrderBy(s => s.Name.FName);
}
}
Свой собственный метод и прямо сейчас он работает только для свойства string, однако его можно реактивировать с помощью дженериков для работы с любым примитивным типом. Надеюсь, это поможет.
Ответ 5
Вот метод расширения OrderBy, который работает для любого количества вложенных параметров.
public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string key, bool asc = true)
{
try
{
string orderMethodName = asc ? "OrderBy" : "OrderByDescending";
Type type = typeof(T);
Type propertyType = type.GetProperty(key)?.PropertyType; ;
var param = Expression.Parameter(type, "x");
Expression parent = param;
var keyParts = key.Split('.');
for (int i = 0; i < keyParts.Length; i++)
{
var keyPart = keyParts[i];
parent = Expression.Property(parent, keyPart);
if (keyParts.Length > 1)
{
if (i == 0)
{
propertyType = type.GetProperty(keyPart).PropertyType;
}
else
{
propertyType = propertyType.GetProperty(keyPart).PropertyType;
}
}
}
MethodCallExpression orderByExpression = Expression.Call(
typeof(Queryable),
orderMethodName,
new Type[] { type, propertyType },
query.Expression,
CreateExpression(type, key)
);
return query.Provider.CreateQuery<T>(orderByExpression);
}
catch (Exception e)
{
return query;
}
}
Метод CreateExpression
, который используется в моем решении, определяется в этом сообщении.
Использование метода расширения OrderBy
заключается в следующем.
IQueryable<Foo> q = [Your database context].Foos.AsQueryable();
IQueryable<Foo> p = null;
p = q.OrderBy("myBar.name"); // Ascending sort
// Or
p = q.OrderBy("myBar.name", false); // Descending sort
// Materialize
var result = p.ToList();
Тип Foo
и его свойства также берутся из того же поста, что и метод CreateExpression
.
Надеюсь, что вы найдете эту статью полезной.