6


6

ObjectQuery <T>式のツリーにOrderByがあるかどうかを確認する方法

LINQ to Entitiesエンティティ用のリポジトリを生成するためにT4を使用しています。

リポジトリには、ページングに適したListメソッドが含まれています。 http://msdn.microsoft.com/ja-jp/library/bb738474.aspx [Supported and Unsupported Methods]のドキュメントでは言及されていませんが、順番のない IQueryable`に対して `Skip`を呼び出すことはできません。 次のような例外が発生します。

_ System.NotSupportedException:メソッド 'Skip’は、LINQ to Entitiesのソートされた入力でのみサポートされています。 メソッド 'OrderBy’は、メソッド 'Skip’の前に呼び出す必要があります。 _

私は部分的な方法でデフォルトのソートを定義できるようにすることでそれを解決しました。 しかし、私は式ツリーが確かに `OrderBy`を含んでいるかどうかをチェックするのに問題があります。

できるだけ問題のないコードに問題を減らしました。

public partial class Repository
{
    partial void ProvideDefaultSorting(ref IQueryable currentQuery);

    public IQueryable List(int startIndex, int count)
    {
        IQueryable query = List();
        ProvideDefaultSorting(ref query);
        if (!IsSorted(query))
        {
            query = query.OrderBy(c => c.CategoryID);
        }
        return query.Skip(startIndex).Take(count);
    }
    public IQueryable List(string sortExpression, int startIndex, int count)
    {
           return List(sortExpression).Skip(startIndex).Take(count);
    }
    public IQueryable List(string sortExpression)
    {
        return AddSortingToTheExpressionTree(List(), sortExpression);
    }
    public IQueryable List()
    {
           NorthwindEntities ent = new NorthwindEntities();
           return ent.Categories;
    }

    private Boolean IsSorted(IQueryable query)
    {
        return query is IOrderedQueryable;
    }
}

public partial class Repository
{
    partial void ProvideDefaultSorting(ref IQueryable currentQuery)
    {
        currentQuery = currentQuery.Where(c => c.CategoryName.Contains(" ")); // no sorting..
    }
}

これは私の本当の実装ではありません!

しかし私の*質問*は、 IsSorted`メソッドをどのように実装できるのでしょうか? 問題は、LINQ to Entitiesクエリは常に `ObjectQuery`型で、これは IOrderedQueryable`を実装しているということです。

それでは、どのようにして式ツリーに `OrderBy`メソッドが存在することを確認する必要がありますか? ツリーを解析する唯一の方法はありますか?

*更新*リポジトリにソートサポートを追加する方法ではなく、 ProvideDefaultSorting`部分メソッドが式ツリーに OrderBy`を追加したかどうかを確認する方法を明確にするために、他に2つのオーバーロードを追加しました。

問題は、最初の部分クラスがテンプレートによって生成され、部分クラスの2番目の部分の実装が別の時点でチームメンバーによって行われることです。 あなたはそれを.NET Entity FrameworkがEntityContextを生成する方法と比較することができます、それは他の開発者のための拡張ポイントを可能にします。 ですので `ProvideDefaultSorting`が正しく実装されていなくても頑強にしてクラッシュしないようにしたいと思います。

それで、多分問題はもっと、私がどうやって `ProvideDefaultSorting`が式ツリーにソートを追加したことをどうやって確認することができるかです。

*更新2 *新しい質問に回答し、承認しました。質問にさらに一致するようにタイトルを変更する必要があると思います。 それとも同じ問題を抱えている人々をこの解決策に導いてくれるので、私は現在のタイトルを残すべきですか?

6 Answer


2


ページングは​​、強い順序で注文に依存します。 操作を密接に結び付けないのはなぜですか。 これを行う1つの方法は次のとおりです。

サポート対象

パブリックインターフェイスIOrderByExpression {ApplyOrdering(ref IQueryable query); }

パブリッククラスOrderByExpression:IOrderByExpression {パブリックIQueryable ApplyOrderBy(ref IQueryableクエリ){query = query.OrderBy(exp);} // TODO OrderByDescending、ThenBy、ThenByDescendingの各メソッド。

private Expression> exp = null;

// TODOブールが降順? public OrderByExpression(Expression> myExpression){exp = myExpression; }}

検討中の方法:

パブリックIQueryableリスト(int startIndex、intカウント、IOrderByExpressionの順序){NorthwindEntities ent = new NorthwindEntities(); IQueryable query = ent.Categories; if(ordered == null){ordered = new OrderByExpression(c => c.CategoryID)} orders.ApplyOrdering(ref query);

query.Skip(startIndex).Take(count);を返します。 }

しばらくして、メソッドを呼び出します。

var query = List(20、20、new OrderByExpression(c => c.CategoryName));


1


それよりも少し難しいです。 Entity Frameworkは、特定の状況では 静かにOrderByを無視します。したがって、検索するだけでは不十分です。式ツリーのOrderBy OrderByは「正しい」場所になければならず、「正しい」場所の定義はEntity Frameworkの実装の詳細です。

あなたが今までに推測したように、私はあなたと同じ場所にいます。私はエンティティリポジトリパターンを使い、プレゼンテーション層でTake / Skipをしています。 私が使用した解決策は、おそらく理想的ではないが、私がしていることには十分なので、OrderByが常に式ツリーの最後のものになるように、可能な限り最後まで順序付けを行わないことです。 そのため、(直接または間接的に)Take / Skipを実行しようとしているアクションは、最初にOrderByを挿入します。 コードは、これが一度だけ発生するように構成されています。


1


これをProvideDefaultSortingの戻り型で解決できます。 このコードはビルドされません:

public IOrderedQueryable GetOrderedQueryable(){IQueryable myInts = new List(){3、4、1、2} .AsQueryable(); myIntsを返します。Where(i => i == 2); }

このコードはビルドされますが、気を付けているのでコーダーは彼らが値するものを手に入れます。

public IOrderedQueryable GetOrderedQueryable(){IQueryable myInts = new List(){3、4、1、2} .AsQueryable(); myrntsQueryableとしてmyInts.Where(i => i == 2)を返します。 }

'' '' '

refと同じ物語(これは構築されません):

public void GetOrderedQueryable(参照IOrderedQueryableクエリ){query = query.Where(i => i == 2); }


1


David Bのおかげで私は以下の解決策を得ました。 (私は部分的なメソッドが実行されなかったか、単にそのパラメータを返したという状況の検出を追加しなければなりませんでした)。

public部分クラスリポジトリ{partial void ProvideDefaultSorting(参照IOrderedQueryable currentQuery);

パブリックIQueryable List(int startIndex、int count){NorthwindEntities ent = new NorthwindEntities(); IOrderedQueryable query = ent.CategorySet; var oldQuery = query; ProvideDefaultSorting(ref query); if(oldQuery.Equals(query))//部分メソッドがクエリに対して何もしなかったか、または存在しなかっただけです{query = query.OrderBy(c => c.CategoryID); query.Skip(startIndex).Take(count);を返します。 }     // 残り.. }

パブリック部分クラスリポジトリ{部分無効ProvideDefaultSorting(ref IOrderedQueryable currentQuery){currentQuery = currentQuery.Where(c => c.CategoryName.Contains( ""))。OrderBy(c => c.CategoryName); //コンパイル時の強制集計}}

パーシャルメソッドが実装されている場合は、コンパイル時に少なくともIOrderdQueryableを保持する必要があります。

そして部分的なメソッドが実装されていないか単にそのパラメータを返すとき、クエリは変更されず、それはフォールバックソートを使います。


0


ProvideDefaultSorting(ref query); if(!IsSorted(query)){query = query.OrderBy(c => c.CategoryID); }

への変更:

//デフォルトの順序付けを適用するquery = query.OrderBy(c => c.CategoryID); // ProvideDefaultSorting(ref query);を順序に追加します。

それは完璧な解決策ではありません。

それはあなたが述べた「順序付け関数のフィルタ」問題を解決しません。 「注文を実行するのを忘れた」または「注文しないことを選択した」という問題が解決されます。

私はLinqToSqlでこのソリューションをテストしました:

public void OrderManyTimes(){DataClasses1DataContext myDC = new DataClasses1DataContext(); var query = myDC.Customers.OrderBy(c => c.Field3); query = query.OrderBy(c => c.Field2); query = query.OrderBy(c => c.Field1);

Console.WriteLine(myDC.GetCommand(query).CommandText);

}

生成します(逆の順序で並べ替えます)。

SELECTフィールド1、フィールド2、フィールド3 [dbo]からの[顧客] AS [t0]順序[t0]。[フィールド1]、[t0]。[フィールド2]、[フィールド3]。[フィールド3]


0


デフォルトのソート順が指定されていないため、コレクションを主キーでソートするソリューションを実装しました。 おそらくそれはあなたのために働くでしょう。

http://johnkaster.wordpress.com/2011/05/19/a-bug-fix-for-system-linq-dynamic-and-a-solution-for-the-entity-framework-4-skip-problemを参照してください。 /議論と汎用コードのために。 (そして、動的LINQに対する付随的なバグ修正。)