C# Expression to sort generic query by key field

2020-07-18 10:53发布

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?

3条回答
smile是对你的礼貌
2楼-- · 2020-07-18 11:23

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.

查看更多
何必那么认真
3楼-- · 2020-07-18 11:34

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 });
查看更多
Juvenile、少年°
4楼-- · 2020-07-18 11:35

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);
查看更多
登录 后发表回答