Get a generic method without using GetMethods - c#

I want to get the method System.Linq.Queryable.OrderyBy<T, TKey>(the IQueryable<T> source, Expression<Func<T,TKey>> keySelector) method, but I keep coming up with nulls.
var type = typeof(T);
var propertyInfo = type.GetProperty(group.PropertyName);
var propertyType = propertyInfo.PropertyType;
var sorterType = typeof(Func<,>).MakeGenericType(type, propertyType);
var expressionType = typeof(Expression<>).MakeGenericType(sorterType);
var queryType = typeof(IQueryable<T>);
var orderBy = typeof(System.Linq.Queryable).GetMethod("OrderBy", new[] { queryType, expressionType }); /// is always null.
Does anyone have any insight? I would prefer to not loop through the GetMethods result.

Solved (by hacking LINQ)!
I saw your question while researching the same problem. After finding no good solution, I had the idea to look at the LINQ expression tree. Here's what I came up with:
public static MethodInfo GetOrderByMethod<TElement, TSortKey>()
{
Func<TElement, TSortKey> fakeKeySelector = element => default(TSortKey);
Expression<Func<IEnumerable<TElement>, IOrderedEnumerable<TElement>>> lamda
= list => list.OrderBy(fakeKeySelector);
return (lamda.Body as MethodCallExpression).Method;
}
static void Main(string[] args)
{
List<int> ints = new List<int>() { 9, 10, 3 };
MethodInfo mi = GetOrderByMethod<int, string>();
Func<int,string> keySelector = i => i.ToString();
IEnumerable<int> sortedList = mi.Invoke(null, new object[] { ints,
keySelector }
) as IEnumerable<int>;
foreach (int i in sortedList)
{
Console.WriteLine(i);
}
}
output: 10 3 9
EDIT: Here is how to get the method if you don't know the type at compile-time:
public static MethodInfo GetOrderByMethod(Type elementType, Type sortKeyType)
{
MethodInfo mi = typeof(Program).GetMethod("GetOrderByMethod", Type.EmptyTypes);
var getOrderByMethod = mi.MakeGenericMethod(new Type[] { elementType,
sortKeyType });
return getOrderByMethod.Invoke(null, new object[] { }) as MethodInfo;
}
Be sure to replace typeof(Program) with typeof(WhateverClassYouDeclareTheseMethodsIn).

A variant of your solution, as an extension method:
public static class TypeExtensions
{
private static readonly Func<MethodInfo, IEnumerable<Type>> ParameterTypeProjection =
method => method.GetParameters()
.Select(p => p.ParameterType.GetGenericTypeDefinition());
public static MethodInfo GetGenericMethod(this Type type, string name, params Type[] parameterTypes)
{
return (from method in type.GetMethods()
where method.Name == name
where parameterTypes.SequenceEqual(ParameterTypeProjection(method))
select method).SingleOrDefault();
}
}

I don't believe there's an easy way of doing this - it's basically a missing feature from reflection, IIRC. You have to loop through the methods to find the one you want :(

I think the following extension method would be a solution to the problem:
public static MethodInfo GetGenericMethod(
this Type type, string name, Type[] generic_type_args, Type[] param_types, bool complain = true)
{
foreach (MethodInfo m in type.GetMethods())
if (m.Name == name)
{
ParameterInfo[] pa = m.GetParameters();
if (pa.Length == param_types.Length)
{
MethodInfo c = m.MakeGenericMethod(generic_type_args);
if (c.GetParameters().Select(p => p.ParameterType).SequenceEqual(param_types))
return c;
}
}
if (complain)
throw new Exception("Could not find a method matching the signature " + type + "." + name +
"<" + String.Join(", ", generic_type_args.AsEnumerable()) + ">" +
"(" + String.Join(", ", param_types.AsEnumerable()) + ").");
return null;
}
The call would be something like (just changing the last line of your original code):
var type = typeof(T);
var propertyInfo = type.GetProperty(group.PropertyName);
var propertyType = propertyInfo.PropertyType;
var sorterType = typeof(Func<,>).MakeGenericType(type, propertyType);
var expressionType = typeof(Expression<>).MakeGenericType(sorterType);
var queryType = typeof(IQueryable<T>);
var orderBy = typeof(Queryable).GetGenericMethod("OrderBy",
new Type[] { type, propertyType },
new[] { queryType, expressionType });
What is different to the other solutions: the resulting method matches the parameter types exactly, not only their generic base types.

Today there is a good alternative with the method Type.MakeGenericMethodParameter. The following snippet retrieve the Queryable.OrderBy method:
var TSource = Type.MakeGenericMethodParameter(0);
var TKey = Type.MakeGenericMethodParameter(1);
var orderBy = typeof(Queryable).GetMethod(nameof(Queryable.OrderBy), 2, BindingFlags.Static | BindingFlags.Public, null, CallingConventions.Standard
, new[] { typeof(IQueryable<>).MakeGenericType(TSource), typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(TSource, TKey)) }
, null);
Assert.NotNull(orderBy);

var orderBy =
(from methodInfo in typeof(System.Linq.Queryable).GetMethods()
where methodInfo.Name == "OrderBy"
let parameterInfo = methodInfo.GetParameters()
where parameterInfo.Length == 2
&& parameterInfo[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& parameterInfo[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>)
select
methodInfo
).Single();

If you do know the types at compile time, you can do this with less code without using the Expression type, or depending on Linq at all, like so:
public static MethodInfo GetOrderByMethod<TElement, TSortKey>() {
IEnumerable<TElement> col = null;
return new Func<Func<TElement, TSortKey>, IOrderedEnumerable<TElement>>(col.OrderBy).Method;
}

Using lambda expressions you can get the generic method easily
var method = type.GetGenericMethod
(c => c.Validate((IValidator<object>)this, o, action));
Read more about it here:
http://www.nerdington.com/2010/08/calling-generic-method-without-magic.html
http://web.archive.org/web/20100911074123/http://www.nerdington.com/2010/08/calling-generic-method-without-magic.html

I think that it mabe be made with class like so:
public static class SortingUtilities<T, TProperty>
{
public static IOrderedQueryable<T> ApplyOrderBy(IQueryable<T> query, Expression<Func<T, TProperty>> selector)
{
return query.OrderBy(selector);
}
public static IOrderedQueryable<T> ApplyOrderByDescending(IQueryable<T> query, Expression<Func<T, TProperty>> selector)
{
return query.OrderByDescending(selector);
}
public static IQueryable<T> Preload(IQueryable<T> query, Expression<Func<T, TProperty>> selector)
{
return query.Include(selector);
}
}
And you can use this even like so:
public class SortingOption<T> where T: class
{
private MethodInfo ascendingMethod;
private MethodInfo descendingMethod;
private LambdaExpression lambda;
public string Name { get; private set; }
public SortDirection DefaultDirection { get; private set; }
public bool ApplyByDefault { get; private set; }
public SortingOption(PropertyInfo targetProperty, SortableAttribute options)
{
Name = targetProperty.Name;
DefaultDirection = options.Direction;
ApplyByDefault = options.IsDefault;
var utilitiesClass = typeof(SortingUtilities<,>).MakeGenericType(typeof(T), targetProperty.PropertyType);
ascendingMethod = utilitiesClass.GetMethod("ApplyOrderBy", BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase);
descendingMethod = utilitiesClass.GetMethod("ApplyOrderByDescending", BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase);
var param = Expression.Parameter(typeof(T));
var getter = Expression.MakeMemberAccess(param, targetProperty);
lambda = Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(T), targetProperty.PropertyType), getter, param);
}
public IQueryable<T> Apply(IQueryable<T> query, SortDirection? direction = null)
{
var dir = direction.HasValue ? direction.Value : DefaultDirection;
var method = dir == SortDirection.Ascending ? ascendingMethod : descendingMethod;
return (IQueryable<T>)method.Invoke(null, new object[] { query, lambda });
}
}
with attribute like this:
public class SortableAttribute : Attribute
{
public SortDirection Direction { get; set; }
public bool IsDefault { get; set; }
}
and this enum:
public enum SortDirection
{
Ascending,
Descending
}

Just another comment (it should be, but since its too long, i have to post it as an answer) following up #NeilWhitaker -s answer (here using Enumerable.Count), since we are in the middle of clearing the strings out :)
why not use the Expression trees in your bytype method too?
Something like :
#region Count
/// <summary>
/// gets the
/// public static int Count<TSource>(this IEnumerable<TSource> source);
/// methodinfo
/// </summary>
/// <typeparam name="TSource">type of the elements</typeparam>
/// <returns></returns>
public static MethodInfo GetCountMethod<TSource>()
{
Expression<Func<IEnumerable<TSource>, int>> lamda = list => list.Count();
return (lamda.Body as MethodCallExpression).Method;
}
/// <summary>
/// gets the
/// public static int Count<TSource>(this IEnumerable<TSource> source);
/// methodinfo
/// </summary>
/// <param name="elementType">type of the elements</param>
/// <returns></returns>
public static MethodInfo GetCountMethodByType(Type elementType)
{
// to get the method name, we use lambdas too
Expression<Action> methodNamer = () => GetCountMethod<object>();
var gmi = ((MethodCallExpression)methodNamer.Body).Method.GetGenericMethodDefinition();
var mi = gmi.MakeGenericMethod(new Type[] { elementType });
return mi.Invoke(null, new object[] { }) as MethodInfo;
}
#endregion Disctinct

Related

Is it possible to create in a lambda-function a dynamical field?

I have a function like this:
public CountryDto FindCountryByName(string name)
{
Country country = _countryRepository.GetAll().Where(g => g.Name.ToLower().Trim() == name.ToLower().Trim()).FirstOrDefault();
var dto = _mapper.Map<Country, CountryDto>(country);
return dto;
}
and it's referred to GetAll-function in the GenericRepository
public IEnumerable<T> GetAll()
{
return table.ToList();
}
Is it possible creating a function like this (in the GenericRepository)?
public IEnumerable<T> FindByName(string objname, string name)
{
return table.Where(t => t.GetType(objname) == name);
}
By example
Country country = _countryRepository.FindByName("CountryName", name);
and
AlbumTrack track = _albumtrackRepository.FindByName("SongTitle", songTitle);
I don't remember where I found this function, I'm using it to generate LINQ queries based on given property name, please note that you can change the search function "Equals" to "Contains" or "StartsWith" :
///<summary>
/// Create query that search in given property of class T for matching results with value
/// </summary>
public static IQueryable<T> CreateSearchQuery<T>(IQueryable<T> queryable, string PropertyName, string value) where T : class
{
IQueryable<T> query = queryable;
List<Expression> expressions = new List<Expression>();
ParameterExpression parameter = Expression.Parameter(typeof(T), "p");
MethodInfo Equals_Method = typeof(string).GetMethod("Equals", new[] { typeof(string) });
MethodInfo ToString_Method = typeof(object).GetMethod("ToString");
//Iterate through all properties Except inherited ones
foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.PropertyType == typeof(string) //properties of type string
|| x.Name == PropertyName))
{
ConstantExpression value_expression = Expression.Constant(value, typeof(string));
MemberExpression member_expression = Expression.PropertyOrField(parameter, prop.Name);
//Combine ToString() and Equals() methods
MethodCallExpression callChain = Expression.Call(Expression.Call(member_expression, ToString_Method), Equals_Method, value_expression);
expressions.Add(callChain);
}
if (expressions.Count == 0)
return query;
Expression or_expression = expressions[0];
for (int i = 1; i < expressions.Count; i++)
{
or_expression = Expression.OrElse(or_expression, expressions[i]);
}
Expression<Func<T, bool>> expression = Expression.Lambda<Func<T, bool>>(or_expression, parameter);
return query.Where(expression);
}

No generic method 'ThenBy'

I am trying to add the ThenById() method which will be launched after a call to OrderBy() on IOrderedQueryable:
public static IOrderedQueryable<TEntity> ThenById<TEntity>(this IQueryable<TEntity> source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var command = "ThenBy";
var thenByProperty = "Id";
var type = typeof(TEntity);
if (type.GetProperty(thenByProperty) == null)
{
throw new MissingFieldException(nameof(thenByProperty));
}
var param = Expression.Parameter(type, "p");
var property = type.GetProperty(thenByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var propertyAccess = Expression.MakeMemberAccess(param, property);
var orderByExpression = Expression.Lambda(propertyAccess, param);
var resultExpression = Expression.Call(
typeof(IOrderedQueryable),
command,
new Type[] { type, property.PropertyType },
source.Expression,
Expression.Quote(orderByExpression));
return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}
I receive the following error message:
No generic method 'ThenBy' on type 'System.Linq.IOrderedQueryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
The ThenBy extension method is in the System.Linq.Queryable class, not in IOrderedQueryable. You simply need to replace that in your code:
public static IOrderedQueryable<TEntity> ThenById<TEntity>(
this IOrderedQueryable<TEntity> source)
{
//snip
var resultExpression = Expression.Call(
typeof(System.Linq.Queryable),
command,
new Type[] { type, property.PropertyType },
source.Expression,
Expression.Quote(orderByExpression));
return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}
Note that the method should be extending IOrderedQueryable, not just IQueryable.
However, this will fail at runtime if TEntity doesn't have an Id property. My preference would be to give all entities with the Id property an interface and use a generic constraint here. That way you avoid expressions completely and get compile time safety. For example:
public interface IHasId
{
int Id { get; set; }
}
public class SomeEntity : IHasId
{
public int Id { get; set; }
public string Name { get; set; }
//etc
}
Which simplifies your extension method:
public static IOrderedQueryable<TEntity> ThenById<TEntity>(
this IOrderedQueryable<TEntity> source)
where TEntity : IHasId
{
return source.ThenBy(e => e.Id);
}

How to query column name compare with a string?

I want to sort my columns on the base of column name which is a string but unfortunately I couldn't achieve this because OrderByDescending compares something else, but I am sending a string.
public List<DOlead> sortLead(DOuser user, string Item)
{
List<DOlead> ObjLead = new List<DOlead>();
ObjLead = _Context.leads.Where(x => x.is_converted == false).OrderByDescending(Item).ToList();
return ObjLead;
}
Kindly Help me out?
Use reflection
public List<DOlead> sortLead(DOuser user, string Item)
{
var propertyInfo = typeof(DOlead).GetProperty(Item);
List<DOlead> ObjLead = new List<DOlead>();
ObjLead = _Context.leads.Where(x => x.is_converted == false).OrderByDescending(x => propertyInfo.GetValue(x, null)).ToList();
return ObjLead;
}
Edit
After getting comment by SO, and after some research, I came across this answer and decided to modify it for user.
You have to create lambda expression first then pass it to order by clause.
Creating lambda expression.
public static class QueryableHelper
{
public static IQueryable<TModel> OrderBy<TModel>(this IQueryable<TModel> q, string name)
{
Type entityType = typeof(TModel);
PropertyInfo p = entityType.GetProperty(name);
MethodInfo m = typeof(QueryableHelper).GetMethod("OrderByProperty").MakeGenericMethod(entityType, p.PropertyType);
return(IQueryable<TModel>) m.Invoke(null, new object[] { q, p });
}
public static IQueryable<TModel> OrderByDescending<TModel>(this IQueryable<TModel> q, string name)
{
Type entityType = typeof(TModel);
PropertyInfo p = entityType.GetProperty(name);
MethodInfo m = typeof(QueryableHelper).GetMethod("OrderByPropertyDescending").MakeGenericMethod(entityType, p.PropertyType);
return (IQueryable<TModel>)m.Invoke(null, new object[] { q, p });
}
public static IQueryable<TModel> OrderByPropertyDescending<TModel, TRet>(IQueryable<TModel> q, PropertyInfo p)
{
ParameterExpression pe = Expression.Parameter(typeof(TModel));
Expression se = Expression.Convert(Expression.Property(pe, p), typeof(object));
return q.OrderByDescending(Expression.Lambda<Func<TModel, TRet>>(se, pe));
}
public static IQueryable<TModel> OrderByProperty<TModel, TRet>(IQueryable<TModel> q, PropertyInfo p)
{
ParameterExpression pe = Expression.Parameter(typeof(TModel));
Expression se = Expression.Convert(Expression.Property(pe, p), typeof(object));
return q.OrderBy(Expression.Lambda<Func<TModel, TRet>>(se, pe));
}
}
Your modified method
public List<DOlead> sortLead(DOuser user, string Item)
{
List<DOlead> ObjLead = new List<DOlead>();
ObjLead = _Context.leads.Where(x => x.is_converted == false).OrderByDescending(Item).ToList();
return ObjLead;
}
From Stack , I guess you could use reflection:
from x in db.TableName
where (x.GetType().GetProperty(stringCOLUMN_1_or2).GetValue(x, null)) == " 8"
select x;
Not sure there's any easy Linqish way to do that though...
Assuming dynamic Linq will work it would just be:
from x in objets
.Where(stringCOLUMN_1_or2 + " = ' " + 8 + "'")
select x
There is some more info about dynamic Linq with SQL here: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
You should use Expression builder to achieve this example simple with string properties:
public class OrderByData
{
public string PropertyName { get; set; }
}
public static class ExpressionBuilder
{
public static Expression<Func<T, string>> GetExpression<T>(OrderByData filter)
{
ParameterExpression param = Expression.Parameter(typeof(T), "t");
Expression exp = GetExpression<T>(param, filter);
return Expression.Lambda<Func<T, string>>(exp, param);
}
private static Expression GetExpression<T>(ParameterExpression param, OrderByData filter)
{
MemberExpression member = Expression.Property(param, filter.PropertyName);
return member;
}
}
and then call like this:
public List<DOlead> sortLead(DOuser user, string Item)
{
List<DOlead> ObjLead = new List<DOlead>();
new OrderByData { PropertyName = Item };
var deleg = ExpressionBuilder.GetExpression<lead>(filter).Compile();
ObjLead = _Context.leads.Where(x => x.is_converted == false).OrderByDescending(deleg).ToList();
return ObjLead;
}
also you can extend to use also other types not only strings.
You need to pass delegate to OrderByDescending method.
As you just have the property name so you need to access to the property dynamically, you can use dynamic expression to do so:
public List<DOlead> sortLead(DOuser user, string Item)
{
List<DOlead> ObjLead = new List<DOlead>();
ObjLead = _Context.leads.Where(x => x.is_converted == false).OrderByDescending((d) =>
Expression.Lambda(Expression.Property(Expression.Constant(d), Item)).Compile()()).ToList();
return ObjLead;
}

C# Turning magic string into lambda expression

I have a set of extension methods that allow for using magic strings in the LINQ OrderBy() methods. I know the first question will be why, but it's part of a generic repository and is there for flexibility so that strings can be sent from the UI and used directly.
I have it working if you pass in a magic string that represents a property on the main entity you are querying, but I'm having trouble making it more generic so it can handle multiple levels deep magic string. For example:
IQueryable<Contact> contacts = GetContacts();
contacts.OrderByProperty("Name"); // works great
// can't figure out how to handle this
contacts.OrderByProperty("ContactType.Name");
Here is the code that I have so far:
public static class LinqHelpers
{
private static readonly MethodInfo OrderByMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "OrderBy" && method.GetParameters().Length == 2);
private static readonly MethodInfo OrderByDescendingMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "OrderByDescending" && method.GetParameters().Length == 2);
private static readonly MethodInfo ThenByMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "ThenBy" && method.GetParameters().Length == 2);
private static readonly MethodInfo ThenByDescendingMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "ThenByDescending" && method.GetParameters().Length == 2);
public static IOrderedQueryable<TSource> ApplyOrdering<TSource>(IQueryable<TSource> source, string propertyName, MethodInfo orderingMethod)
{
var parameter = Expression.Parameter(typeof(TSource), "x");
var orderByProperty = Expression.Property(parameter, propertyName);
var lambda = Expression.Lambda(orderByProperty, new[] { parameter });
var genericMethod = orderingMethod.MakeGenericMethod(new[] { typeof(TSource), orderByProperty.Type });
return (IOrderedQueryable<TSource>)genericMethod.Invoke(null, new object[] { source, lambda });
}
public static IOrderedQueryable<TSource> OrderByProperty<TSource>(this IQueryable<TSource> source, string propertyName)
{
return ApplyOrdering(source, propertyName, OrderByMethod);
}
public static IOrderedQueryable<TSource> OrderByDescendingProperty<TSource>(this IQueryable<TSource> source, string propertyName)
{
return ApplyOrdering(source, propertyName, OrderByDescendingMethod);
}
public static IOrderedQueryable<TSource> ThenByProperty<TSource>(this IOrderedQueryable<TSource> source, string propertyName)
{
return ApplyOrdering(source, propertyName, ThenByMethod);
}
public static IOrderedQueryable<TSource> ThenByDescendingProperty<TSource>(this IOrderedQueryable<TSource> source, string propertyName)
{
return ApplyOrdering(source, propertyName, ThenByDescendingMethod);
}
}
I'm pretty sure I need to split the propertyName on the period and then use those parts to build up a more complicated Expression that involves a MemberExpression and then a Property but I'm struggling. Any help or pointing in the right direction would be appreciated.
I wrote my own predicate builder type thing a while back. I attempted to adapt the code for posting here. This returns an expression to access a property, and can be used to build up more complicated expressions - just make sure that all the components of the expression use the exact same param object instance.
This won't work as a drop in for your code. It'll will require some slight adaptations to make it work for your use I think.
This outputs param => (param.Child.IntProperty == 42).
You could use the predicate variable in a where clause. Let's say you had a List<Parent> called parents, you could call parents.Where(predicate).
public class Parent {
public string StringProperty { get; set; }
public Child Child { get; set; }
}
public class Child {
public int IntProperty { get; set; }
}
internal class Program {
private static void Main(string[] args) {
var param = Expression.Parameter(typeof(Parent), "param");
var accessExpression = GetAccessExpression(param, "Child.IntProperty", typeof(Parent));
var constantExpression = Expression.Constant(42);
var condition = Expression.Equal(accessExpression, constantExpression);
var predicate = Expression.Lambda<Func<Parent, bool>>(condition, param);
Console.WriteLine(predicate.ToString());
}
/// <summary>
/// Returns an Expression that represents member access for the specified property on the specified type. Uses recursion
/// to find the full expression.
/// </summary>
/// <param name="property">The property path.</param>
/// <param name="type">The type that contains the first part of the property path.</param>
/// <returns></returns>
private static Expression GetAccessExpression(Expression param, string property, Type type) {
if (property == null)
throw new ArgumentNullException("property");
if (type == null)
throw new ArgumentNullException("type");
string[] propPath = property.Split('.');
var propInfo = type.GetProperty(propPath[0]);
if (propInfo == null)
throw new Exception(String.Format("Could not find property '{0}' on type {1}.", propPath[0], type.FullName));
var propAccess = Expression.MakeMemberAccess(param, type.GetProperty(propPath[0]));
if (propPath.Length > 1)
return GetAccessExpression(propAccess, string.Join(".", propPath, 1, propPath.Length - 1), type.GetProperty(propPath[0]).PropertyType);
else
return propAccess;
}
}

Using given properties as strings

I would like to use a single, general method to retrieve an ordered list for a given string representing a property inside a lambda expression.
I know people requested this before but it didn't work for me. I tried this and it threw error:
db.Books.OrderByDescending(x => x.GetType().GetProperty("Discount").GetValue(x,null))
.Take(3);
I'm using this at the moment:
public IQueryable<Book> GetCheapestBooks()
{
return db.Books.OrderBy(x => x.Discount)
.Take(3);
}
maybe this is what you are looking for:
Dynamic Linq
With this you can write queries like:
var result = db.Books.OrderBy( "Discount" ).Take( 3 );
Simple console application.
class A
{
public int prop1 { get; set; }
public int prop2 { get; set; }
}
class Program
{
static IEnumerable<T> GenericOrderByDescending<T>(IEnumerable<T> arg, string property, int take)
{
return arg.OrderByDescending(x => x.GetType().GetProperty(property).GetValue(x, null)).Take(take);
}
static void Main(string[] args)
{
IEnumerable<A> arr = new List<A>()
{
new A(){ prop1 = 1, prop2 = 2},
new A(){prop1 = 2,prop2 =2},
new A(){prop1 = 3,prop2 =2},
new A(){prop1 = 441,prop2 =2},
new A(){prop1 = 2,prop2 =2}
};
foreach(var a1 in GenericOrderByDescending<A>(arr, "prop1", 3))
{
Console.WriteLine(a1.prop1);
}
}
}
U can pass your db.Boks.AsEnumerable() as parameter for GenericOrderByDescending<T>() method. Instead of T you should type the type of your db.Boks items. My example sorts an array of instances of class A and I've got no errors, it works fine. Did I understand you correctly?
You can try with this code
public IQueryable<Book> GetCheapestBooks()
{
db.Books.OrderBy(x => x.Discount).Take(3).AsQueryable<Book>();
}
You could create an extension method which creates the property expression:
private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "obj");
MemberExpression propExpr = Expression.Property(paramExpr, prop);
Type funcType = typeof(Func<,>).MakeGenericType(typeof(T), prop.PropertyType);
Type keySelectorType = typeof(Expression<>).MakeGenericType(funcType);
LambdaExpression keySelector = Expression.Lambda(funcType, propExpr, paramExpr);
MethodInfo orderByMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2).MakeGenericMethod(typeof(T), prop.PropertyType);
return (IOrderedQueryable<T>) orderByMethod.Invoke(null, new object[] { source, keySelector });
}

Categories