I have a generic method in which I want to sort an IQueryable<T>
by its key field (it is safe to assume there is only one). Thus:
void DoStuff<T>(...)
{
IQueryable<T> queryable = ... // given
PropertyInfo keyField = ... // given
var sortedQueryable = queryable.OrderBy(<some expression here>);
...
}
How do I define an Expression
that will return the keyField
property of T
so that this will work?
This isn't too difficult, but you need to invoke the OrderBy
with reflection as you don't know the type of the key field ahead of time. So given the code you already show, you would do something like this:
// Build up the property expression to pass into the OrderBy method
var parameterExp = Expression.Parameter(typeof(T), "x");
var propertyExp = Expression.Property(parameterExp, keyField);
var orderByExp = Expression.Lambda(propertyExp, parameterExp);
// Note here you can output "orderByExp.ToString()" which will give you this:
// x => x.NameOfProperty
// Now call the OrderBy via reflection, you can decide here if you want
// "OrderBy" or "OrderByDescending"
var orderByMethodGeneric = typeof(Queryable)
.GetMethods()
.Single(mi => mi.Name == "OrderBy" && mi.GetParameters().Length == 2);
var orderByMethod = orderByMethodGeneric.MakeGenericMethod(typeof(T), propertyExp.Type);
// Get the result
var sortedQueryable = (IQueryable<T>)orderByMethod
.Invoke(null, new object[] { queryable, orderByExp });
Same idea as in DavidG answer but different approach
// build lambda expression (T t) => t.KeyField
var type = typeof(T);
var parameter = Expression.Parameter(type, "k");
var lambda = Expression.Lambda(Expression.Property(parameter, keyField), parameter);
// get source expression
var baseExpression = queryable.Expression;
// call to OrderBy
var orderByCall = Expression.Call(
typeof(Queryable),
"OrderBy",
new[] {type, keyField.PropertyType},
baseExpression, lambda
);
// sorted result
var sorted = queryable.Provider.CreateQuery<T>(orderByCall);
I like to use an interface IBaseEntity that has an Id property of type T. That would make your query:
void DoStuff<IBaseEntity>(...)
{
IQueryable<IBaseEntity> queryable = ... // given
var sortedQueryable = queryable.OrderById(e=> e.Id); //extension method
...
}
Extension Method:
public static IQueryable<IBaseEntity<T>> OrderById<T>(this IQueryable<IBaseEntity<T>> query)
{
return query.OrderBy(e => e.Id);
}
Each entity would implement IBaseEntity and have something like
public partial class MyEntity : IBaseEntity<long>
{
[Required]
public override long Id
{
get { return base.Id;}
set { base.Id = value;}
}
}
Then in the context
modelBuilder
.Entity<MyEntity>()
.ToTable("DatabaseTable", "DatabaseSchema")
.HasKey(e => e.Id)
.Property(e => e.Id)
.HasColumnName("DatabasePrimaryKey")
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
Note: This requires some setup in the context and entities. The database can have whatever keys you want, you're mapping them individually to the property Id in the OnModelCreating method of the context.