Подтвердить что ты не робот

Как подсчитать ассоциированные объекты без их извлечения в инфраструктуре Entity

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

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

DiscussionCategory discussionCategory = _repository.GetDiscussionCategory(id);

discussionCategoryDiscussions - это список объектов обсуждения, которые в настоящее время не загружены.

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

Ответ 3

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

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

Вы можете вернуть все эти подсчеты в одном SQL-запросе. Вот как.

  • Добавьте свойство placeholder в вашу модель класса, используя аннотацию данных [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; }
        }
    }
    
  • В вашем контроллере получите список родительских объектов.

    var discussions = db.Discussions.ToList();
    
  • Захват счетчиков в словаре. Это генерирует один запрос SQL GROUP BY со всеми родительскими идентификаторами и подсчетами дочерних объектов. (Предполагая DiscussionID является FK в Messages.)

    var _counts = db.Messages.GroupBy(m => m.DiscussionID).ToDictionary(d => d.Key, d => d.Count());
    
  • Прокрутите родительские объекты, найдите счетчик из словаря и сохраните его в свойстве placeholder.

    foreach (var d in discussions)
        {
            d.MessageCount = (_counts.ContainsKey(d.ID)) ? _counts[d.ID] : 0;
        }
    
  • Верните список обсуждений.

    return View(discussions);
    
  • Ссылка на свойство MessageCount в представлении.

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

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

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

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

Ответ 4

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

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

Я поддержал и снял свой вопрос. Надеюсь, кто-то заиграет с работоспособным решением.

Ответ 5

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

Ответ 6

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

  • Создайте представление базы данных.

    Предполагая, что вы хотите, чтобы все исходные свойства объекта плюс связанное количество сообщений:

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

    (Если вы используете First Migrations Entity Framework Code First Migrations, см. этот SO ответ о том, как создать представление.)

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

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