【问题标题】:C# Expression to sort generic query by key fieldC# 表达式按关键字段对通用查询进行排序
【发布时间】:2019-03-26 07:16:07
【问题描述】:

我有一个通用方法,我想通过它的关键字段对IQueryable<T> 进行排序(假设只有一个是安全的)。因此:

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

如何定义一个Expression 来返回TkeyField 属性,这样才能正常工作?

【问题讨论】:

  • @Fabjan 当然可以使用表达式。
  • @DavidG 这里的问题不是表达式本身,而是试图将其转换为SQL 的引擎,例如Linq To SQL 或其他
  • @Fabjan 我知道,我是说可以构建一个表达式来做到这一点 - 我自己已经做过不止一次了。
  • @DavidG 如果你知道确切地怎么做,那么你最好为这个问题写一个好的答案
  • @Fabjan 好的,完成。

标签: c# linq linq-expressions


【解决方案1】:

这并不太难,但是您需要通过反射调用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 });

【讨论】:

  • 我们不能只构建Expression&lt;Func&lt;TEntity, TProp&gt;&gt; 并将其传递给“普通”OrderBy() 方法吗?
  • @haim770 如果您提前知道TProp 的类型,那当然可以。我假设我们没有
  • 你是对的。但是由于DoStuff&lt;T&gt;() 确实 定义了一个可以解析为TEntity 的泛型参数,因此keyField 有可能指向一个可以解析为TProp 的常量类型的属性。无论如何,很好的答案。
  • @DavidG 好的,我重新打开了follow-up question。 Guid 比较的东西仍然存在问题。
【解决方案2】:

与 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);

【讨论】:

    【解决方案3】:

    我喜欢使用具有类型 T 的 Id 属性的接口 IBaseEntity。这将使您的查询:

    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);
    

    注意:这需要在上下文和实体中进行一些设置。数据库可以有您想要的任何键,您可以将它们单独映射到上下文的 OnModelCreating 方法中的属性 Id。

    【讨论】:

    • 如果您知道该属性的名称总是相同的,那就可以了。但在实践中,这种情况很少见。
    • 正如@DavidG 所说,我的关键字段的名称都不同,即CustomerIdInvoiceId 等。
    • @ShaulBehr - 就实体框架而言,我使用上下文使键相同,但它们映射到数据库中的命名主键。
    • 如果属性类型不一致则无济于事。一个实体可能是 int,但其他地方可能是 Guidstring
    • @DavidG - 我简化了答案,但我们所做的是 IBaseEntity 其中 T 是主键的类型
    猜你喜欢
    • 2020-10-22
    • 2011-05-08
    • 2020-01-26
    • 1970-01-01
    • 1970-01-01
    • 2010-10-16
    • 2020-10-22
    • 1970-01-01
    • 2019-01-13
    相关资源
    最近更新 更多