web-dev-qa-db-ja.com

エンティティフレームワークでそれらをフェッチせずに関連エンティティをカウントする方法

私はこれについてしばらく考えていたので、最初のStack Overflow投稿を使用して質問する価値があると思いました。

メッセージの関連リストとのディスカッションがあるとします。

DiscussionCategory discussionCategory = _repository.GetDiscussionCategory(id);

discussionCategory.Discussionsは、現在読み込まれていないディスカッションエンティティのリストです。

私が欲しいのは、discussionCategoryのディスカッションを繰り返し、メッセージデータをフェッチせずに各ディスカッションにいくつのメッセージがあるかを言うことができることです。

これを試す前に、ディスカッションとメッセージをロードして、次のようなことができるようにする必要がありました。

discussionCategory.Discussions.Attach(Model.Discussions.CreateSourceQuery().Include("Messages").AsEnumerable());

foreach(Discussion discussion in discussionCategory.Discussions)
{

int messageCount = discussion.Messages.Count;

Console.WriteLine(messageCount);

}

データベースから数百のメッセージ本文を取得する可能性があるため、これは私にとってはかなり非効率的です。表示する目的で数を数えるだけの場合は、メッセージ本文をメモリに保持しています。

私はこの問題に関連するいくつかの質問を見ましたが、彼らはそれを直接取り上げていないようです。

この件に関してご意見をお寄せいただき、ありがとうございます。

更新-要求に応じてさらにいくつかのコード:

public ActionResult Details(int id)
    {  
        Project project = _repository.GetProject(id);
        return View(project);
    }

次に、ビューで(テストするだけです):

Model.Discussions.Load();
var items = from d in Model.Discussions select new { Id = d.Id, Name = d.Name, MessageCount = d.Messages.Count() };

foreach (var item in items) {
//etc

私の問題が少し明確になることを願っています。コードの詳細がさらに必要な場合はお知らせください。

43
Oligarchia

簡単です。 POCO(または匿名)タイプに投影するだけです。

_var q = from d in Model.Discussions
        select new DiscussionPresentation
        {
            Subject = d.Subject,
            MessageCount = d.Messages.Count(),
        };
_

生成されたSQLを見ると、Count()がDBサーバーによって実行されていることがわかります。

これはEF 1とEF 4の両方で機能することに注意してください。

39
Craig Stuntz

Entity Framework 4.1以降を使用している場合は、以下を使用できます。

var discussion = _repository.GetDiscussionCategory(id);

// Count how many messages the discussion has 
var messageCount = context.Entry(discussion)
                      .Collection(d => d.Messages)
                      .Query()
                      .Count();

ソース: http://msdn.Microsoft.com/en-US/data/jj574232

10
Ricardo

私はこれが古い質問であることを知っていますが、それは継続的な問題のようであり、上記のどの回答もリストビューでSQL集計を処理するための良い方法を提供しません。

テンプレートと例のように、POCOモデルとコードファーストを想定しています。 SQL ViewソリューションはDBAの観点から見ればニースですが、コードとデータベース構造の両方を並行して維持するという課題が再導入されています。単純なSQL集計クエリの場合、ビューからの速度向上はそれほどありません。上記の例のように、実際に避ける必要があるのは、複数の(n + 1)データベースクエリです。 5000の親エンティティがあり、子エンティティ(ディスカッションごとのメッセージなど)をカウントしている場合、それは5001 SQLクエリです。

これらすべてのカウントを1つのSQLクエリで返すことができます。方法は次のとおりです。

  1. [NotMapped]名前空間のSystem.ComponentModel.DataAnnotations.Schemaデータアノテーションを使用して、プレースホルダープロパティをクラスモデルに追加します。これにより、実際にデータベースに列を追加したり、不要なビューモデルに投影したりせずに、計算されたデータを保存する場所が提供されます。

    ...
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace MyProject.Models
    {
        public class Discussion
        {
            [Key]
            public int ID { get; set; }
    
            ...
    
            [NotMapped]
            public int MessageCount { get; set; }
    
            public virtual ICollection<Message> Messages { get; set; }
        }
    }
    
  2. コントローラーで、親オブジェクトのリストを取得します。

    var discussions = db.Discussions.ToList();
    
  3. 辞書でカウントをキャプチャします。これにより、すべての親IDと子オブジェクトの数を含む単一のSQL GROUP BYクエリが生成されます。 (DiscussionIDMessagesのFKであると想定しています。)

    var _counts = db.Messages.GroupBy(m => m.DiscussionID).ToDictionary(d => d.Key, d => d.Count());
    
  4. 親オブジェクトをループして、ディクショナリからカウントを調べ、プレースホルダープロパティに格納します。

    foreach (var d in discussions)
        {
            d.MessageCount = (_counts.ContainsKey(d.ID)) ? _counts[d.ID] : 0;
        }
    
  5. ディスカッションリストを返します。

    return View(discussions);
    
  6. ビューでMessageCountプロパティを参照します。

    @foreach (var item in Model) {
        ...
        @item.MessageCount
        ...
    }
    

はい、そのディクショナリをViewBagに詰め込んで、ビューで直接ルックアップを行うことができますが、そこにある必要のないコードでビューを混乱させます。

結局、EFに「遅延カウント」を実行する方法があったらいいのにと思います。遅延読み込みと明示読み込みの両方の問題は、オブジェクトがloadingであることです。カウントするためにロードする必要がある場合、それは潜在的なパフォーマンスの問題です。レイジーカウントではリストビューのn + 1の問題は解決されませんが、不要なオブジェクトデータを大量にロードする可能性を心配する必要なく、ビューから@item.Messages.Countを呼び出すことができるのは素晴らしいことです。

お役に立てれば。

8
Neil Laslett

EFやDevExpress XPOを含む複数のマッパーを処理するときに、同じ問題に遭遇しました(単一のエンティティが複数のテーブルにマップすることさえできません)。私が最善の解決策であるとわかったのは、基本的にEDMXおよびT4テンプレートを使用してSQL Serverで(トリガーの代わりに)更新可能なビューを生成し、SQLを低レベルで制御して、選択でサブクエリを実行できるようにすることです。句では、あらゆる種類の複雑な結合を使用してデータなどを取り込みます。

0
Sheldmandu

私は直接的な答えはありませんが、NHibernateとEF 4.0の間の以下の比較を指摘することしかできません。これは、EF 4.0でも、取得せずに関連するエンティティコレクションのカウントを取得するためのサポートはないことを示唆しているようですコレクション。

http://ayende.com/Blog/archive/2010/01/05/nhibernate-vs.-entity-framework-4.0.aspx

私はあなたの質問に賛成してスターを付けました。うまくいけば、誰かが実行可能な解決策を講じてくれるでしょう。

0
Brian Hasden

これが1つではなく、関連付けられているさまざまなエンティティを数える必要がある場合は、データベースビューの方が簡単な(そして適切な可能性がある)可能性があります。

  1. データベースビューを作成します。

    すべての元のエンティティプロパティと関連するメッセージ数が必要であると仮定します。

    CREATE VIEW DiscussionCategoryWithStats AS
    SELECT dc.*,
          (SELECT count(1) FROM Messages m WHERE m.DiscussionCategoryId = dc.Id)
              AS MessageCount
    FROM DiscussionCategory dc
    

    (Entity Framework Code First Migrationsを使用している場合は、ビューの作成方法について this SO answer を参照してください。)

  2. EFでは、元のエンティティの代わりにビューを使用するだけです。

    // You'll need to implement this!
    DiscussionCategoryWithStats dcs = _repository.GetDiscussionCategoryWithStats(id);
    
    int i = dcs.MessageCount;
    ...
    
0
Dunc