EntityFramework 4 OrderBy перезаписывает предыдущие вызовы OrderBy - PullRequest
1 голос
/ 10 сентября 2011

Я часто хочу предоставить заказы для IQueryables, которые должны действовать как вторичные заказы, если другой будет указан позже.Например, следующее:

Repository.All.OrderBy(o => o.Name).OrderBy(o => o.SerialNumber)  [A]

Должно быть эквивалентно:

Repository.All.OrderBy(o => o.SerialNumber).ThenBy(o => o.Name)

Это работало правильно с использованием LINQ to SQL.Однако в EntityFramework 4 предложение Order-By в сгенерированном SQL выглядит следующим образом:

ORDER BY [Project1].[SerialNumber] ASC

Он полностью игнорирует первый оператор OrderBy, который фактически нарушает порядок OrderBy как стабильную сортировку.Тогда для меня это не вариант, потому что упорядочения не всегда определяются в одном и том же месте (например, в приведенном выше утверждении [A] можно указать OrderBy(o => o.Name) в репозитории. Расширения до IQueryable<TModel> не годятсярешение также, потому что он не позволяет различным хранилищам сортировать по-разному, и потребляющий код не должен вызывать некоторый код .SortDefault(), поскольку это не его задача.

Есть ли какой-нибудь хороший способ заставить LinqЛица, соблюдающие несколько заявлений OrderBy?

Спасибо

Ответы [ 3 ]

3 голосов
/ 10 сентября 2011

Я не согласен с тем, что последующий OrderBy должен быть эквивалентен ThenBy. Если бы это было так, в ThenBy не было бы необходимости, и вы бы никогда не смогли переопределить существующую сортировку.

Не могу сказать, что мне это нравится, но мне кажется, что это вариант для последующей сортировки:

IQueryable<Item> items = Repository.GetAllWhichMightBeOrderedAlready();
return items is IOrderedEnumerable<Item>
    ? ((IOrderedQueryable<Item>)items).ThenBy(x => x.SomeProperty)
    : items.OrderBy(x => x.SomeProperty);

Заменить IOrderedEnumerable<T> на соответствующий.

0 голосов
/ 01 мая 2013

То есть вы не можете использовать ThenBy, потому что начальный OrderBy может быть пропущен? Как насчет того, чтобы сделать первоначальный фиктивный OrderBy, тогда все остальные будут ThenBy.

// Basically, everything gets the same orderby ranking
// I don't know offhand if you can use a constant here, but if you have an id,
// you should be able to this.
var list = context.MyTable.OrderBy(mt => mt.id - mt.id);

if (order by field1)
    list = list.ThenBy(mt => mt.field1);

if (order by field2)
    list = list.ThenBy(mt => mt.field2);

и т.д ...

РЕДАКТИРОВАТЬ: Неважно. Это не работает Я не могу использовать ThenBy на отдельной линии, как я думал.

0 голосов
/ 14 сентября 2011

Хорошо, это не самое элегантное решение, но я смог преодолеть это таким образом, который, кажется, работает нормально, хотя я подозреваю, что все смешные размышления могут сделать его слишком медленным. Я создал свой собственный класс IQueryable и связанный поставщик запросов, которые принимают ExpressionVisitor и вызывают .Visit () для этого посетителя в вызовах GetEnumerator и Execute. Мой базовый класс репозитория возвращает новый MappedExpressionQuery и передает ему запрос, возвращенный из DbContext.Set (), вместе с ExpressionVisitor, который производит желаемое упорядочение. Пользовательские запрашиваемые классы и классы провайдера:

public class MappedExpressionQuery<T> : IOrderedQueryable<T>
{
  private IQueryable<T> baseQuery;
  private MappedExpressionQueryProvider<T> provider;

  public MappedExpressionQuery(IQueryable<T> query, ExpressionVisitor expressionMap)
  {
    baseQuery = query;
    provider = new MappedExpressionQueryProvider<T>(query.Provider, expressionMap);
  }

  #region IOrderedQueryable<T> Members

  public IEnumerator<T> GetEnumerator()
  {
    return baseQuery.Provider.CreateQuery<T>(provider.ExpressionMap.Visit(baseQuery.Expression)).GetEnumerator();
  }

  IEnumerator IEnumerable.GetEnumerator()
  {
    return baseQuery.Provider.CreateQuery(provider.ExpressionMap.Visit(baseQuery.Expression)).GetEnumerator();
  }

  public Type ElementType
  {
    get { return baseQuery.ElementType; }
  }

  public Expression Expression
  {
    get { return baseQuery.Expression; }
  }

  public IQueryProvider Provider
  {
    get { return provider; }
  }

  #endregion
}

public class MappedExpressionQueryProvider<T> : IQueryProvider
{
  public ExpressionVisitor ExpressionMap { get; private set; }
  private IQueryProvider baseProvider;

  public MappedExpressionQueryProvider(IQueryProvider baseProvider, ExpressionVisitor expressionMap)
  {
    this.ExpressionMap = expressionMap;
    this.baseProvider = baseProvider;
  }

  #region IQueryProvider Members

  public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
  {
    return new MappedExpressionQuery<TElement>(baseProvider.CreateQuery<TElement>(expression), ExpressionMap);
  }

  public IQueryable CreateQuery(Expression expression)
  {
    throw new NotImplementedException();
  }

  public TResult Execute<TResult>(Expression expression)
  {
    return baseProvider.Execute<TResult>(ExpressionMap.Visit(expression));
  }

  public object Execute(Expression expression)
  {
    return baseProvider.Execute(ExpressionMap.Visit(expression));
  }

  #endregion
}

Когда мой пользовательский класс ExpressionVisitor находит оператор OrderBy или ThenBy, он перемещается по дереву выражений, записывая правильные порядки, в которых должен находиться каждый вид сортировки, пока не найдет оператор, который не является оператором Order и не является коммутативным с оператором Order. Затем он строит все утверждения снова в конце выражения. Таким образом, OrderBy(A).ThenBy(B).OrderBy(C).OrderBy(D).ThenBy(E) возвращается со следующими дополнительными выражениями в конце: .OrderBy(D).ThenBy(E).ThenBy(C).ThenBy(A).ThenBy(B). Да, это избыточно, но EntityFramework в любом случае игнорирует выражения дальше по цепочке, и я использую этот QueryProvider только с запросами, полученными из DbContext. Код для этого посетителя выражения (я также добавил исправление для того факта, что .ToString () не переводится в SQL, даже когда используется для констант, поэтому DbContext.Set<T>().Where(o => o.Name == SomeConstant.ToString()) теперь работает):

public abstract class QueryModifier : ExpressionVisitor
{
  private bool OrganizedOrdering { get; set; }

  protected override Expression VisitMethodCall(MethodCallExpression node)
  {
    if (node.Method.Name == "ToString" && node.Method.DeclaringType == typeof(object))
    {
      try
      {
        //If the object calling ToString is parameterless, invoke the method and convert it into a constant.
        return Expression.Constant(Expression.Lambda(node).Compile().DynamicInvoke());
      }
      catch (InvalidOperationException)
      {
        throw new InvalidOperationException("ToString() can only be translated into SQL when used on parameterless expressions.");
      }
    }
    else if (IsOrderStatement(node.Method))
    {
      if (!OrganizedOrdering)
      {
        OrganizedOrdering = true;
        return RearrangeOrderStatements(node);
      }
      else
        return base.VisitMethodCall(node);
    }
    else if (OrganizedOrdering && !IsOrderCommutative(node.Method))
    {
      OrganizedOrdering = false;
      return base.VisitMethodCall(node);
    }
    else
    {
      return base.VisitMethodCall(node);
    }
  }

  private Expression RearrangeOrderStatements(MethodCallExpression node)
  {
    //List to store (OrderBy expression, position) tuples
    List<Tuple<MethodCallExpression, double>> orderByExpressions = new List<Tuple<MethodCallExpression, double>>();
    double low = 0;
    double high = 1;

    MethodCallExpression startNode = node;
    Expression lastNode = node.Arguments[0];

    //Travel down the chain and store all OrderBy and ThenBy statements found with their relative positions
    while (node != null && node.Method.DeclaringType == typeof(System.Linq.Queryable))
    {
      if (node.Arguments.Count == 0)
        break;

      if (node.Method.Name.StartsWith("OrderBy"))
      {
        orderByExpressions.Add(new Tuple<MethodCallExpression, double>(node, low));
        low = low + 1;
        high = low + 1;
      }
      else if (node.Method.Name.StartsWith("ThenBy"))
      {
        double pos = (high - low) * 0.9 + low;
        orderByExpressions.Add(new Tuple<MethodCallExpression, double>(node, pos));
        high = pos;
      }
      else if (!IsOrderCommutative(node.Method))
      {
        break;
      }

      lastNode = node.Arguments[0];
      node = lastNode as MethodCallExpression;
    }

    lastNode = startNode;
    var methods = typeof(Queryable).GetMethods().Where(o => IsOrderStatement(o));

    Type queryType = startNode.Arguments[0].Type.GetGenericArguments()[0];

    bool firstStatement = true;
    foreach (var tuple in orderByExpressions.OrderBy(o => o.Item2))
    {
      string methodName;
      if (firstStatement)
      {
        methodName = "OrderBy";
        firstStatement = false;
      }
      else
        methodName = "ThenBy";
      if (tuple.Item1.Method.Name.EndsWith("Descending"))
        methodName = methodName + "Descending";

      Type orderByTValueType = tuple.Item1.Arguments[1].Type.GetGenericArguments()[0].GetGenericArguments()[1];

      if (tuple.Item1.Arguments.Count == 3)
      {
        var method = methods.Single(o => o.Name == methodName && o.GetParameters().Length == 3)
          .MakeGenericMethod(queryType, orderByTValueType);
        lastNode = Expression.Call(method, lastNode, tuple.Item1.Arguments[1], tuple.Item1.Arguments[2]);
      }
      else
      {
        var method = methods.Single(o => o.Name == methodName && o.GetParameters().Length == 2)
          .MakeGenericMethod(queryType, orderByTValueType);
        lastNode = Expression.Call(method, lastNode, tuple.Item1.Arguments[1]);
      }
    }

    return Visit(lastNode);
  }

  /// <summary>
  /// Returns true if the given method call expression is commutative with OrderBy statements.
  /// </summary>
  /// <param name="expression"></param>
  /// <returns></returns>
  private bool IsOrderCommutative(MethodInfo method)
  {
    return new string[] { "Where", "Distinct", "AsQueryable" }.Contains(method.Name)
      && method.DeclaringType == typeof(System.Linq.Queryable);
  }

  private bool IsOrderStatement(MethodInfo method)
  {
    return (method.Name.StartsWith("OrderBy") || method.Name.StartsWith("ThenBy"))
      && method.DeclaringType == typeof(System.Linq.Queryable);
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...