Выражение C # для сортировки универсального запроса по ключевому полю - PullRequest
3 голосов
/ 25 марта 2019

У меня есть общий метод, в котором я хочу отсортировать IQueryable<T> по его ключевому полю (можно с уверенностью предположить, что есть только один).Таким образом:

void DoStuff<T>(...) 
{
   IQueryable<T> queryable = ... // given
   PropertyInfo keyField = ... // given
   var sortedQueryable = queryable.OrderBy(<some expression here>);
   ...
}

Как определить Expression, который будет возвращать свойство keyField T, чтобы это работало?

Ответы [ 3 ]

8 голосов
/ 25 марта 2019

Это не так уж сложно, но вам нужно вызвать OrderBy с отражением, поскольку вы заранее не знаете тип ключевого поля.Поэтому, учитывая код, который вы уже показываете, вы бы сделали что-то вроде этого:

// 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 });
1 голос
/ 25 марта 2019

Та же идея, что и в ответе DavidG, но другой подход

// 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);
1 голос
/ 25 марта 2019

Мне нравится использовать интерфейс IBaseEntity, который имеет свойство Id типа T. Это сделает ваш запрос:

void DoStuff<IBaseEntity>(...) 
{
   IQueryable<IBaseEntity> queryable = ... // given
   var sortedQueryable = queryable.OrderById(e=> e.Id); //extension method
   ...
}

Метод расширения:

public static IQueryable<IBaseEntity<T>> OrderById<T>(this IQueryable<IBaseEntity<T>> query)
{
   return query.OrderBy(e => e.Id);
}

Каждая сущность будет реализовывать IBaseEntity и иметь что-то вроде

public partial class MyEntity : IBaseEntity<long>
{
  [Required]
  public override long  Id 
  {
    get { return base.Id;}
    set { base.Id = value;}
  }
}

Тогда в контексте

modelBuilder
  .Entity<MyEntity>()
  .ToTable("DatabaseTable", "DatabaseSchema")
  .HasKey(e => e.Id)
  .Property(e => e.Id)
  .HasColumnName("DatabasePrimaryKey")                
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);

Примечание: это требует некоторой настройки в контексте и сущностях. База данных может иметь любые ключи, которые вы хотите, вы индивидуально сопоставляете их со свойством Id в методе OnModelCreating контекста.

...