9

テーブルの ID 列でテーブルの最後の行を取得しようとしています。私が現在使用しているもの:

var x = db.MyTable.OrderByDescending(d => d.ID).FirstOrDefault();

より効率的な速度で同じ結果を得る方法はありますか?

4

1 に答える 1

5

これがテーブル全体を照会していることはわかりません。

ID列にインデックスがありませんか?

クエリを分析した結果を質問に追加できますか。これは本来あるべき方法ではないためです。

分析結果と同様に、SQL が生成されました。select top 1 * from MyTable order by id desc明示的な列名といくつかのエイリアシングのみを使用する以外に、それがどのようになるかわかりません。また、idそのインデックスのスキャン以外の方法に関するインデックスがある場合もありません。

編集:約束された説明。

Linq は一連の共通インターフェイスを提供します。C# と VB.NET の場合は、0 個以上の項目を返すソースに対するさまざまな操作 (メモリ内コレクション、データベース呼び出し、XML ドキュメントの解析など) に対して、いくつかのキーワード サポートを提供します。など)。

これにより、基になるソースに関係なく、同様のタスクを表現できます。たとえば、クエリにはソースが含まれていますが、より一般的な形式で実行できます。

public static YourType FinalItem(IQueryable<YourType> source)
{
  return source.OrderByDesending(d => d.ID).FirstOrDefault();
}

これで、次のことができます。

IEnumerable<YourType> l = SomeCallThatGivesUsAList();
var x = FinalItem(db.MyTable);//same as your code.
var y = FinalItem(l);//item in list with highest id.
var z = FinalItem(db.MyTable.Where(d => d.ID % 10 == 0);//item with highest id that ends in zero.

しかし、本当に重要な部分は、実行したい操作の種類を定義する手段を持っている一方で、実際の実装を私たちから隠すことができるということです。

を呼び出すとOrderByDescending、ソースに関する情報を持つオブジェクトと、順序付けに使用するラムダ関数が生成されます。

への呼び出しにFirstOrDefaultは、その情報があり、それを使用して結果を取得します。

リストの場合、実装は同等の にEnumerable基づくコードを生成することです (Queryableそして、Enumerableお互いの public メンバーをミラーリングし、それらが使用するインターフェイスなどをミラーリングしますIOrderedQueryable) IOrderedEnumerable

これは、関心のある順序 (またはその逆の順序) で既にソートされているかわからないリストでは、各要素を調べるよりも高速な方法がないためです。期待できる最善の操作は O(n) 操作であり、O(n log n) 操作を取得する可能性があります - 順序付けの実装が、そこから 1 つの項目のみが取得される可能性に対して最適化されているかどうかによって異なります*。

別の言い方をすれば、列挙型でのみ機能するコードで手作業でコーディングできる最善の方法は、次のものよりもわずかに効率的です。

public static YourType FinalItem(IEnumerable<YourType> source)
{
  YourType highest = default(YourType);
  int highestID = int.MinValue;
  foreach(YourType item in source)
  {
    curID = item.ID;
    if(highest == null || curID > highestID)
    {
      highest = item;
      highestID = curID;
    }
  }
  return highest;
}

列挙子を直接処理するためのいくつかのマイクロオプトを使用すると、わずかに改善できますが、ほんのわずかであり、余分な複雑さにより、サンプルコードがあまり適切ではなくなります。

手動ではそれ以上のことはできず、linq コードはソースについて私たちが知っている以上のことは何も知らないので、それが一致することを期待できる最善の方法です。うまくいかないかもしれませんが (これも、1 つのアイテムだけが必要であるという特殊なケースが考えられたかどうかによって異なります)、それに勝るものはありません。

ただし、linq が採用するアプローチはこれだけではありません。メモリ内の列挙可能なソースと同等のアプローチを取りますが、ソースはそのようなものではありません。

db.MyTableテーブルを表します。それを列挙すると、ほぼ同等の SQL クエリの結果が得られます。

SELECT * FROM MyTable

ただし、それを呼び出してメモリ内で結果を並べ替えるのと同じでdb.MyTable.OrderByDescending(d => d.ID)はありません。クエリは実行時に全体として処理されるため、実際には SQL クエリの結果は多かれ少なかれ次のようになります。

SELECT * FROM MyTable ORDER BY id DESC

最後に、クエリ全体はdb.MyTable.OrderByDescending(d => d.ID).FirstOrDefault()次のようなクエリになります。

SELECT TOP 1 * FROM MyTable ORDER BY id DESC

または

SELECT * FROM MyTable ORDER BY id DESC LIMIT 1

使用しているデータベース サーバーの種類によって異なります。次に、結果は次の ADO.NET ベースのコードと同等のコードに渡されます。

return dataReader.Read() ?
  new MyType{ID = dataReader.GetInt32(0), dataReader.GetInt32(1), dataReader.GetString(2)}//or similar
  : null;

あなたはもっと良くなることはできません。

そして、そのSQLクエリについて。列にインデックスがある場合id(主キーのように見えるため、必ずあるはずです)、そのインデックスを使用して、各行を調べるのではなく、問題の行を非常に迅速に見つけることができます。

全体として、さまざまな linq プロバイダーがさまざまな手段を使用してクエリを実行するため、可能な限り最善の方法で最善を尽くすことができます。もちろん、不完全な世界にいると、あるものは他のものよりも優れていることに間違いなく気付くでしょう。さらに、さまざまな条件に最適なアプローチを選択することもできます。この一例は、データベース関連のプロバイダーが異なる SQL を選択して、異なるバージョンのデータベースの機能を利用できることです。もう 1 つは、Count()メモリ内列挙型で動作する のバージョンの実装が、このように少し動作することです。

public static int Count<T>(this IEnumerable<T> source)
{
  var asCollT = source as ICollection<T>;
  if(asCollT != null)
    return asCollT.Count;
  var asColl = source as ICollection;
  if(asColl != null)
    return asColl.Count;
  int tally = 0;
  foreach(T item in source)
    ++tally;
  return tally;
}

これはより単純なケースの 1 つです (ここでの例では少し単純化されていますが、実際のコードではなくアイデアを示しています)、より効率的なアプローチが利用可能な場合にそれを利用するコードの基本原則を示しています。配列の長さ O(1) とCountコレクションのプロパティは O(1) の場合もあり、それが O(n) の場合に事態を悪化させたわけではありません - そしてそれらが利用できない場合はフォールバックします効率的ではありませんが、機能的なアプローチです。

これらすべての結果として、Linq はパフォーマンスの点で非常に優れた費用対効果をもたらす傾向があります。

現在、まともなコーダーは、ほとんどの場合、そのアプローチに匹敵するか、それを上回ることができるはずです†。Linq が完璧なアプローチを思いついたとしても、Linq 自体にはいくつかのオーバーヘッドがあります。

ただし、プロジェクト全体の範囲で、Linq を使用すると、比較的限られた数の適切に定義されたエンティティ (データベースに関する限り、通常はテーブルごとに 1 つ) に関連する合理的に効率的なコードを簡潔に作成できることを意味します。特に、匿名関数と結合を使用すると、非常に優れたクエリが得られます。検討:

var result = from a in db.Table1
  join b in db.Table2
  on a.relatedBs = b.id
  select new {a.id, b.name};

ここでは、ここでは気にしない列を無視しています。生成された SQL も同じことを行います。手作業でコード化された DAO クラスに関連するaオブジェクトを作成する場合にどうするかを考えてみましょう。b

  1. aこのの ID との名前の組み合わせを表す新しいクラスとb、インスタンスを生成するために必要なクエリを実行するための関連コードを作成します。
  2. aクエリを実行して、それぞれおよび関連するすべての情報を取得bし、廃棄物を受け入れます。
  3. クエリを実行して、関心のあるそれぞれの情報を取得しab他のフィールドのデフォルト値を設定するだけです。

これらのうち、オプション 2 は無駄が多く、おそらく非常に無駄です。オプション 3 は少し無駄が多く、エラーが発生しやすくなります (正しく設定されていないコード内の別の場所で誤ってフィールドを使用しようとした場合はどうなるでしょうか?)。オプション 1 のみが linq アプローチが生成するものよりも効率的ですが、これは 1 つのケースにすぎません。大規模なプロジェクトでは、これは数十、数百、数千のわずかに異なるクラスを生成することを意味する可能性があります (コンパイラとは異なり、それらが実際に同じであるケースを必ずしも見つけることはできません)。したがって、実際には、効率に関して言えば、linq は私たちにいくつかの大きな恩恵をもたらすことができます。

効率的な linq の適切なポリシーは次のとおりです。

  1. できる限り、最初に使用したクエリのタイプにとどまります。ToList()orなどでアイテムをメモリに取得するときはいつでもToArray、本当に必要かどうかを検討してください。そうする必要があるか、そうすることで得られる利点を明確に述べることができない限り、しないでください。
  2. メモリ内の処理に移行する必要がある場合は、他の手段を優先AsEnumerable()してToList()、一度に 1 つだけ取得します。
  3. SQLProfiler などを使用して、実行時間の長いクエリを調べます。ここでのポリシー 1 が間違っていて、メモリに移動するAsEnumerable()方が実際には優れているケースがいくつかあります (ほとんどはGroupBy、グループ化されていないフィールドで集計を使用しないため、実際には単一の SQL クエリを持たないの使用に関連しています)それらは対応します)。
  4. 複雑なクエリが何度もヒットする場合は、CompiledQuery役立つ可能性があります (4.5 では、役立つケースのいくつかをカバーする自動最適化があるため、それほどではありません) が、通常は最初のアプローチからそれを除外して、それのみを使用する方がよいでしょう。効率の問題であるホットスポットで。
  5. EF に任意の SQL を実行させることはできますが、そのようなコードが多すぎると、全体で linq アプローチを使用して一貫した読みやすさが低下するため、大きな利益でない限り避けてください (ただし、Linq2SQL はストアド プロシージャの呼び出しで EF に勝ると思います。 UDF の呼び出しについても同様ですが、それでもこれは適用されます。コードを見ただけでは、物事が互いにどのように関連しているかはあまり明確ではありません)。

*私の知る限り、この特定の最適化は適用されていませんが、現時点では可能な限り最適な実装について話しているので、適用されているか、適用されていないか、一部のバージョンのみに適用されているかは問題ではありません。

†Linq2SQL は、2005 年に導入される前のバージョンの SQLServer でクエリを作成する方法を考えていたので、APPLY を使用するクエリを作成することがよくあることは認めますが、コードにはそのような種類はありません。古い習慣に固執する人間の傾向。APPLY の使い方をかなり教えてくれました。

于 2012-09-02T01:29:23.593 に答える