IEnumerable의 동적 LINQ OrderBy / IQueryable
나는 당신이 SQL과 같은 문자열을 사용할 수있게 해주는 동적 LINQ를위한 VS2008 예제 에서 예제를 찾았습니다 OrderBy("Name, Age DESC"))
. (예 를 들어 , 주문을 위해. 불행히도 포함 된 메서드는 IQueryable<T>
;. 에서만 작동합니다 .이 기능을 사용할 수있는 방법이 IEnumerable<T>
있습니까?
이 오래된 사람을 우연히 발견했습니다 ...
동적 LINQ 라이브러리없이이를 수행하려면 아래와 같은 코드가 필요합니다. 여기에는 중첩 된 속성을 포함한 가장 일반적인 시나리오가 포함됩니다.
작업을 수행하려면 IEnumerable<T>
통과하는 몇 가지 래퍼 메서드를 추가 할 수 AsQueryable
있지만 아래 코드는 Expression
필요한 핵심 논리입니다.
public static IOrderedQueryable<T> OrderBy<T>(
this IQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderBy");
}
public static IOrderedQueryable<T> OrderByDescending<T>(
this IQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderByDescending");
}
public static IOrderedQueryable<T> ThenBy<T>(
this IOrderedQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenBy");
}
public static IOrderedQueryable<T> ThenByDescending<T>(
this IOrderedQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenByDescending");
}
static IOrderedQueryable<T> ApplyOrder<T>(
IQueryable<T> source,
string property,
string methodName)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach(string prop in props) {
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] {source, lambda});
return (IOrderedQueryable<T>)result;
}
편집 : LINQ-to-Objects에만 적용 dynamic
되지만 dynamic
ORM 등의 표현식 트리는 dynamic
쿼리를 실제로 나타낼 수 MemberExpression
는 없지만이를 지원하지 않음 에도 불구 하고 혼합하려는 경우 더 재미 있습니다 . 그러나 여기에 LINQ-to-Objects로이를 수행하는 방법이 있습니다. 의 선택은 Hashtable
유리한 잠금 의미로 인한 것입니다.
using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Runtime.CompilerServices;
static class Program
{
private static class AccessorCache
{
private static readonly Hashtable accessors = new Hashtable();
private static readonly Hashtable callSites = new Hashtable();
private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(
string name)
{
var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name];
if(callSite == null)
{
callSites[name] = callSite = CallSite<Func<CallSite, object, object>>
.Create(Binder.GetMember(
CSharpBinderFlags.None,
name,
typeof(AccessorCache),
new CSharpArgumentInfo[] {
CSharpArgumentInfo.Create(
CSharpArgumentInfoFlags.None,
null)
}));
}
return callSite;
}
internal static Func<dynamic,object> GetAccessor(string name)
{
Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name];
if (accessor == null)
{
lock (accessors )
{
accessor = (Func<dynamic, object>)accessors[name];
if (accessor == null)
{
if(name.IndexOf('.') >= 0) {
string[] props = name.Split('.');
CallSite<Func<CallSite, object, object>>[] arr
= Array.ConvertAll(props, GetCallSiteLocked);
accessor = target =>
{
object val = (object)target;
for (int i = 0; i < arr.Length; i++)
{
var cs = arr[i];
val = cs.Target(cs, val);
}
return val;
};
} else {
var callSite = GetCallSiteLocked(name);
accessor = target =>
{
return callSite.Target(callSite, (object)target);
};
}
accessors[name] = accessor;
}
}
}
return accessor;
}
}
public static IOrderedEnumerable<dynamic> OrderBy(
this IEnumerable<dynamic> source,
string property)
{
return Enumerable.OrderBy<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> OrderByDescending(
this IEnumerable<dynamic> source,
string property)
{
return Enumerable.OrderByDescending<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenBy(
this IOrderedEnumerable<dynamic> source,
string property)
{
return Enumerable.ThenBy<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenByDescending(
this IOrderedEnumerable<dynamic> source,
string property)
{
return Enumerable.ThenByDescending<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
static void Main()
{
dynamic a = new ExpandoObject(),
b = new ExpandoObject(),
c = new ExpandoObject();
a.X = "abc";
b.X = "ghi";
c.X = "def";
dynamic[] data = new[] {
new { Y = a },
new { Y = b },
new { Y = c }
};
var ordered = data.OrderByDescending("Y.X").ToArray();
foreach (var obj in ordered)
{
Console.WriteLine(obj.Y.X);
}
}
}
복잡하지 않고 너무 쉬움 :
using System.Linq.Dynamic;
상단에 추가하십시오 .- 사용하다
vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
답을 찾았습니다. .AsQueryable<>()
확장 메서드를 사용하여 내 목록을 IQueryable로 변환 한 다음 이에 대해 동적 순서를 실행할 수 있습니다.
이 질문을 우연히 발견했습니다.
위에서 Marc의 ApplyOrder 구현을 사용하여 다음과 같은 SQL과 유사한 문자열을 처리하는 Extension 메서드를 함께 사용했습니다.
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
자세한 내용은 http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html 에서 확인할 수 있습니다.
정렬하려는 속성을 얻으려면 리플렉션을 사용하는 것이 좋습니다.
IEnumerable<T> myEnumerables
var query=from enumerable in myenumerables
where some criteria
orderby GetPropertyValue(enumerable,"SomeProperty")
select enumerable
private static object GetPropertyValue(object obj, string property)
{
System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
return propertyInfo.GetValue(obj, null);
}
리플렉션을 사용하는 것은 속성에 직접 액세스하는 것보다 상당히 느리므로 성능을 조사해야합니다.
다른 사람들의 말을 기반으로합니다. 나는 다음이 아주 잘 작동한다는 것을 알았다.
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> input, string queryString)
{
if (string.IsNullOrEmpty(queryString))
return input;
int i = 0;
foreach (string propname in queryString.Split(','))
{
var subContent = propname.Split('|');
if (Convert.ToInt32(subContent[1].Trim()) == 0)
{
if (i == 0)
input = input.OrderBy(x => GetPropertyValue(x, subContent[0].Trim()));
else
input = ((IOrderedEnumerable<T>)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim()));
}
else
{
if (i == 0)
input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
else
input = ((IOrderedEnumerable<T>)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
}
i++;
}
return input;
}
나는 Linq 다중 orderby 절을 찾고이 질문을 우연히 발견했으며 아마도 이것이 저자가 찾던 것입니다.
방법은 다음과 같습니다.
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
나는 이것을 시도했지만 인라인 linq 구문을 사용하지 않기 때문에 Kjetil Watnedal의 솔루션에 문제가 있습니다-나는 메소드 스타일 구문을 선호합니다. 내 특정 문제는 사용자 정의를 사용하여 동적 정렬을 시도하는 것 IComparer
입니다.
내 솔루션은 다음과 같이 끝났습니다.
다음과 같은 IQueryable 쿼리가 제공됩니다.
List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();
런타임 정렬 필드 인수가 주어지면 :
string SortField; // Set at run-time to "Name"
동적 OrderBy는 다음과 같습니다.
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
그리고 GetReflectedPropertyValue ()라는 작은 도우미 메서드를 사용하고 있습니다.
public static string GetReflectedPropertyValue(this object subject, string field)
{
object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
return reflectedValue != null ? reflectedValue.ToString() : "";
}
마지막으로 한 가지- OrderBy
사용자 지정을 사용 하고 싶다고 언급했습니다 . 자연 정렬IComparer
을하고 싶었 기 때문 입니다.
이를 위해 다음과 같이 변경합니다 OrderBy
.
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
에 대한 코드는 이 게시물 을 참조하십시오 NaturalSortComparer()
.
추가 할 수 있습니다.
public static IEnumerable<T> OrderBy( this IEnumerable<T> input, string queryString) {
//parse the string into property names
//Use reflection to get and sort by properties
//something like
foreach( string propname in queryString.Split(','))
input.OrderBy( x => GetPropertyValue( x, propname ) );
// I used Kjetil Watnedal's reflection example
}
이 GetPropertyValue
기능은 Kjetil Watnedal의 답변입니다.
왜 문제가 될까요? 그러한 정렬은 컴파일 타임이 아닌 런타임에 예외를 throw합니다 (D2VIANT의 답변과 같음).
If you're dealing with Linq to Sql and the orderby is an expression tree it will be converted into SQL for execution anyway.
Here's something else I found interesting. If your source is a DataTable, you can use dynamic sorting without using Dynamic Linq
DataTable orders = dataSet.Tables["SalesOrderHeader"];
EnumerableRowCollection<DataRow> query = from order in orders.AsEnumerable()
orderby order.Field<DateTime>("OrderDate")
select order;
DataView view = query.AsDataView();
bindingSource1.DataSource = view;
reference: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Using DataSetExtensions)
Here is one more way to do it by converting it to a DataView:
DataTable contacts = dataSet.Tables["Contact"];
DataView view = contacts.AsDataView();
view.Sort = "LastName desc, FirstName asc";
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
Thanks to Maarten (Query a collection using PropertyInfo object in LINQ) I got this solution:
myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList();
In my case I was working on a "ColumnHeaderMouseClick" (WindowsForm) so just found the specific Column pressed and its correspondent PropertyInfo:
foreach (PropertyInfo column in (new Process()).GetType().GetProperties())
{
if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name)
{}
}
OR
PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();
(be sure to have your column Names matching the object Properties)
Cheers
After a lot of searching this worked for me:
public static IEnumerable<TEntity> OrderBy<TEntity>(this IEnumerable<TEntity> source,
string orderByProperty, bool desc)
{
string command = desc ? "OrderByDescending" : "OrderBy";
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command,
new[] { type, property.PropertyType },
source.AsQueryable().Expression,
Expression.Quote(orderByExpression));
return source.AsQueryable().Provider.CreateQuery<TEntity>(resultExpression);
}
You can convert the IEnumerable to IQueryable.
items = items.AsQueryable().OrderBy("Name ASC");
An alternate solution uses the following class/interface. It's not truly dynamic, but it works.
public interface IID
{
int ID
{
get; set;
}
}
public static class Utils
{
public static int GetID<T>(ObjectQuery<T> items) where T:EntityObject, IID
{
if (items.Count() == 0) return 1;
return items.OrderByDescending(u => u.ID).FirstOrDefault().ID + 1;
}
}
This answer is a response to the comments that need an example for the solution provided by @John Sheehan - Runscope
Please provide an example for the rest of us.
in DAL (Data Access Layer),
The IEnumerable version:
public IEnumerable<Order> GetOrders()
{
// i use Dapper to return IEnumerable<T> using Query<T>
//.. do stuff
return orders // IEnumerable<Order>
}
The IQueryable version
public IQueryable<Order> GetOrdersAsQuerable()
{
IEnumerable<Order> qry= GetOrders();
//use the built-in extension method AsQueryable in System.Linq namespace
return qry.AsQueryable();
}
Now you can use the IQueryable version to bind, for example GridView in Asp.net and benefit for sorting (you can't sort using IEnumerable version)
I used Dapper as ORM and build IQueryable version and utilized sorting in GridView in asp.net so easy.
First Install Dynamic Tools --> NuGet Package Manager --> Package Manager Console
install-package System.Linq.Dynamic
Add Namespace using System.Linq.Dynamic;
Now you can use OrderBy("Name, Age DESC")
Use dynamic linq
just add using System.Linq.Dynamic;
And use it like this to order all your columns:
string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
Convert List to IEnumerable or Iquerable, add using System.LINQ.Dynamic namespace, then u can mention the property names in comma seperated string to OrderBy Method which comes by default from System.LINQ.Dynamic.
var result1 = lst.OrderBy(a=>a.Name);// for ascending order.
var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.
참고URL : https://stackoverflow.com/questions/41244/dynamic-linq-orderby-on-ienumerablet-iqueryablet
'Programing' 카테고리의 다른 글
glob ()을 사용하여 파일을 재귀 적으로 찾는 방법은 무엇입니까? (0) | 2020.10.02 |
---|---|
SQL은 SELECT * [columnA 제외] FROM tableA를 사용하여 열을 제외합니까? (0) | 2020.10.02 |
Java에서 현재 날짜 / 시간을 얻는 방법 (0) | 2020.10.02 |
PyPy가 6.3 배 빠르면 CPython을 통해 PyPy를 사용하지 않는 이유는 무엇입니까? (0) | 2020.10.02 |
HttpClient 요청에 대한 Content-Type 헤더를 어떻게 설정합니까? (0) | 2020.10.02 |