2011-09-10 8 views
1

後で別のものが指定されている場合は、二次注文のように動作するIQueryableの注文を提供したいことがよくあります。たとえば、次のよう:これはSQLにLINQを使用して正常に働いていたEntityFramework 4 OrderByが前のOrderBy呼び出しを上書きする

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

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

はに同等である必要があります。しかし、EntityFramework 4に、生成されたSQL内のOrderBy句は次のようになります。

ORDER BY [Project1].[SerialNumber] ASC 

それは完全に実際に安定したソートされて[並べ替えを破る最初のOrderBy文を無視します。順序付けは常に(例えば、声明上記[A]で、OrderBy(o => o.Name)がリポジトリで定義することができ、同じ場所で定義されていないのでThenByは、私のためのオプションではありません。それはdoesnのためIQueryable<TModel>への拡張は、どちらか良い解決策ではありません「がtは異なるリポジトリにソート異なっ許可し、それはその心配はありませんので、消費するコードは、いくつかの.SortDefault()コードを呼び出す必要はありません。

複数のOrderBy文を尊重するエンティティへのLINQを強制的に何か良い方法はありますか?

ありがとう

+0

http://connect.microsoft.com/に投稿し、あなたの質問に新しいバグレポートへのリンクを追加してください。 –

答えて

0

[OK]を、それは最もエレガントな解決策ではないのですが、私はすべてのファンキーな反射が、それは遅すぎることがあり疑われるものの、正常に動作するような方法でこれを克服することができました。 ExpressionVisitorを使用してGetEnumeratorおよびExecute呼び出しでその訪問者に.Visit()を呼び出す独自のカスタムIQueryableクラスと関連クエリプロバイダを作成しました。私のベースリポジトリクラスは新しい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クラスは、[並べ替えや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に由来するクエリ可能でのみ使用します。 「

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

私はそれに続くことに同意しないOrderByThenByに相当するはずです。そうであれば、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

あなたは私の質問を誤解しました。 OrderByは安定した並べ替えであると考えられます。つまり、2つのレコードの順序が同じであれば、元の順序が維持されます。私がOrderBy(A).OrderBy(B)を行うと、これはOrderBy(B).ThenBy(A)と同等でなければなりません。それは間違いないはずです。元の注文の上にプライマリ注文を追加しようとしている間は、セカンダリ注文のみが提供されるため、あなたの回答は機能しません。 –

0

だから、次のことができます。この式の訪問者のためのコードは、(私はまた、定数に使用した場合であっても.ToString()は今ではDbContext.Set<T>().Where(o => o.Name == SomeConstant.ToString())作品、SQLに変換されないという事実の修正に投げました)最初のOrderByがスキップされる可能性があるのでThenByを使用しますか?初めのダミーの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); 

等...

EDIT:ネヴァーマインド。これは機能しません。私が思っていたように、別の行でThenByを単独で使用することはできません。

関連する問題