Как считать связанные объекты, не выбирая их в Entity Framework - PullRequest
42 голосов
/ 06 января 2010

Я некоторое время размышлял об этом, поэтому подумал, что стоило бы использовать мой первый пост с переполнением стека, чтобы спросить об этом.

Представьте, что у меня есть обсуждение со связанным списком сообщений:

DiscussionCategory discussionCategory = _repository.GetDiscussionCategory(id);

DiscussionCategory.Discussions - список сущностей Дискуссии, который в данный момент не загружен.

То, что я хочу, - это иметь возможность перебирать обсуждения в категории обсуждения и указывать, сколько сообщений содержится в каждом обсуждении без извлечения данных сообщения.

Когда я попробовал это раньше, мне пришлось загрузить обсуждения и сообщения, чтобы я мог сделать что-то вроде этого:

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

Надеюсь, это прояснит мою проблему. Дайте мне знать, если вам нужно больше деталей кода.

Ответы [ 6 ]

38 голосов
/ 06 января 2010

Легкий; просто спроецируйте на POCO (или анонимный) тип:

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

Когда вы посмотрите на сгенерированный SQL, вы увидите, что Count() выполняется сервером БД.

Обратите внимание, что это работает как в EF 1, так и в EF 4.

10 голосов
/ 27 сентября 2012

Если вы используете 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

5 голосов
/ 14 мая 2015

Я знаю, что это старый вопрос, но кажется, что это постоянная проблема, и ни один из ответов, приведенных выше, не обеспечивает хорошего способа работы с агрегатами SQL в представлениях списков.

Я предполагаю прямые модели POCO и Code First, как в шаблонах и примерах. Несмотря на то, что решение SQL View является хорошим с точки зрения администратора баз данных, оно вновь вводит проблему поддержания параллельной работы как кода, так и структур базы данных. Для простых агрегатных запросов SQL вы не увидите большого прироста скорости в представлении. Чего вам действительно нужно избегать, так это нескольких (n + 1) запросов к базе данных, как в приведенных выше примерах. Если у вас 5000 родительских сущностей и вы подсчитываете дочерние сущности (например, сообщения на обсуждение), это 5001 SQL-запрос.

Вы можете вернуть все эти значения в одном запросе 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. Захватить значения в словаре. Это создает один запрос SQL GROUP BY со всеми родительскими идентификаторами и количеством дочерних объектов. (Предполагается, что DiscussionID - это FK в Messages.)

    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 и выполнить поиск непосредственно в View, но это запутывает ваш взгляд кодом, который там не требуется.

В конце концов, я бы хотел, чтобы у EF был способ "ленивого счета". Проблема с отложенной и явной загрузкой заключается в том, что вы загружаете объекты. И если вам нужно загрузить для подсчета, это потенциальная проблема производительности. Ленивый подсчет не решит проблему n + 1 в представлениях списков, но было бы неплохо иметь возможность просто вызывать @item.Messages.Count из представления, не беспокоясь о возможной загрузке тонн нежелательных данных объекта.

Надеюсь, это поможет.

0 голосов
/ 14 января 2015

Если это не единичный случай, и вам нужно подсчитать количество различных связанных сущностей, представление базы данных может быть более простым (и потенциально более подходящим) выбором:

  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, см. этот SO-ответ о том, как создать представление.)

  2. В EF просто используйте вид вместо исходного объекта:

    // You'll need to implement this!
    DiscussionCategoryWithStats dcs = _repository.GetDiscussionCategoryWithStats(id);
    
    int i = dcs.MessageCount;
    ...
    
0 голосов
/ 18 сентября 2013

Я сталкивался с одной и той же проблемой при работе с несколькими сопоставителями, включая EF и DevExpress XPO (которые даже не позволяют одному объекту сопоставляться с несколькими таблицами).То, что я нашел лучшим решением, - это в основном использовать шаблоны EDMX и T4 для генерации обновляемых представлений в SQL Server (с использованием вместо триггеров), и таким образом вы имеете низкоуровневый контроль над SQL, так что вы можете выполнять подзапросы в selectпредложение, использовать все виды сложных объединений для ввода данных и т. д.

0 голосов
/ 06 января 2010

У меня нет прямого ответа, но я могу лишь указать вам на следующее сравнение между NHibernate и EF 4.0, которое, по-видимому, предполагает, что даже в EF 4.0 нет встроенной поддержки для получения количества связанных коллекций объектов без извлечения коллекции.

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

Я проголосовал и снял ваш вопрос. Надеемся, что кто-то предложит подходящее решение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...