2016-03-04 5 views
2

Все,EF Linq group by ICollection of objects

У меня есть запрос Linq, который извлекает список событий, который отлично работает. Проблема, с которой я сталкиваюсь, заключается в том, что События содержит ICollection Артисты под названием headliners и в списке Мне нужно всего 1 событие на, набор, Исполнитель (ы).

Запрос под отлично работает, но: я требую топ 10 из События но только один событие на, набор, художника (ов) для сортировки популярности художника с самой высокой популярности можно использовать - Не то, что я хочу.

Context.Events 
     .Where(x => x.Stage.Venue.AreaId == 1 && x.StartDateTimeUtc > DateTime.UtcNow && x.IsVerified) 
     .OrderByDescending(x => x.Headliners.Max(y => y.Popularity)) 
     .Take(10) 
     .ToList(); 

Как я могу настроить запрос выше, что я только получить один событие за Исполнитель. Мне нужно будет сделать какую-то группировку, чтобы увидеть, выполняется ли событие одним и тем же (набором) Artist (s).

Я ищу использование первичного ключа исполнителя, но поскольку это коллекция, я не могу заставить ее работать. Я уже попробовал String.Join получить единственный уникальный ключ для хедлайнеров. Однако это не поддержка в инфраструктуре сущности.

Это что-то, что может (изящно) поддерживать Linq в EF?

Следующий запрос SQL делает почти то, что я хочу ожидать, что он не будет работать с несколькими художником для того же события

SELECT MAX(E.EventId), MAX(E.Name) 
FROM [dbo].[Events] E 
INNER JOIN [dbo].[Stages] S ON E.StageId = S.StageId 
INNER JOIN [dbo].[Venues] V ON S.VenueId = V.VenueId 
INNER JOIN [dbo].[Areas] A ON V.AreaId = A.AreaId 
INNER JOIN [dbo].[Headliners] H ON E.EventId = H.EventId 
INNER JOIN [dbo].[Artists] A2 ON A2.ArtistId = H.ArtistId 
WHERE E.IsVerified = 1 AND E.StartDateTimeUtc>GETDATE() AND A.AreaId = 1 
GROUP BY A2.ArtistId, A2.Name, A2.EchoNestHotttnesss 
ORDER BY A2.EchoNestHotttnesss desc 
+1

Можете ли вы предоставить свою модель и что именно вы просите. Будет здорово, если вы предоставите ожидаемый результат – Eldho

ответ

2

сложная задача, но здесь это:

var availableEvents = db.MusicEvents.Where(e => 
    e.Stage.Venue.AreaId == 1 && e.StartDateTimeUtc > DateTime.UtcNow && e.IsVerified); 

var topEvents = 
    (from e1 in availableEvents 
    where e1.Headliners.Any() && 
     !availableEvents.Any(e2 => e2.StartDateTimeUtc < e1.StartDateTimeUtc && 
      !e2.Headliners.Any(a2 => !e1.Headliners.Any(a1 => a1.Id == a2.Id)) && 
      !e1.Headliners.Any(a1 => !e2.Headliners.Any(a2 => a2.Id == a1.Id))) 
    orderby e1.Headliners.Max(a => a.Popularity) descending 
    select e1) 
    .Take(10) 
    .ToList(); 

Первый подзапрос (availableEvents) только для повторного использования «доступность» фильтр внутри основного запроса. Он не выполняется отдельно.

Важной частью является условием

!availableEvents.Any(e2 => e2.StartDateTimeUtc < e1.StartDateTimeUtc && 
    !e2.Headliners.Any(a2 => !e1.Headliners.Any(a1 => a1.Id == a2.Id)) && 
    !e1.Headliners.Any(a1 => !e2.Headliners.Any(a2 => a2.Id == a1.Id))) 

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

Исключить событие, если есть еще одно доступное событие, начинающееся ранее, и нет хотя бы одного исполнителя из любого события, которое не является хедлайнером другого события (то есть у них одинаковый набор хедлайнеров).

+0

Хорошее решение, но я думаю, что он действительно должен подумать о создании ссылки с 'Artist' на' Event' тоже в своей модели. Все эти «Любые», вероятно, будут плохими для производительности (что может быть или не быть проблемой, конечно, в зависимости от его требований). –

+0

@AlexanderDerck На самом деле они не так уж плохи - все они приводят к «ссылке» таблицы. Индекс PK ищет. –

1

Edit:

довольно приличный частичной LINQ лениво выполненного решение может быть выполнено следующим образом:

Прежде всего, доведите запрос до заказанных событий по популярности:

var evArtists = Context.Events 
    .Where(x => x.Stage.Venue.AreaId == 1 && x.StartDateTimeUtc > DateTime.UtcNow && x.IsVerified) 
    .OrderByDescending(x => x.Headliners.Max(y => y.Popularity)); 

Во-вторых, так как ICollection<Artist> может быть неупорядоченным еще образуя равный набор, создает промежуточную функцию, чтобы проверить, если два ICollection<Artist> имеют одинаковые члены:

private bool areArtistsEqual(ICollection<Artist> arts1, ICollection<Artist> arts2) { 
    return arts1.Count == arts2.Count && //have the same amount of artists 
     arts1.Select(x => x.ArtistId) 
     .Except(arts2.Select(y => y.ArtistId)) 
     .ToList().Count == 0; //when excepted, returns 0 
} 

В-третьих, использовать вышеупомянутый метод, чтобы получить уникальные художники, установленные в результатах запроса, поместить результаты в List, и заполнить List с числом элементов, которые необходимо (например, 10 элементов):

List<Events> topEvList = new List<Events>(); 
foreach (var ev in evArtists) { 
    if (topEvList.Count == 0 || !topEvList.Any(te => areArtistsEqual(te.Headliners, ev.Headliners))) 
     topEvList.Add(ev); 
    if (topEvList.Count >= 10) //you have had enough events 
     break; 
} 

Ваш результат в topEvList.

Преимущества:

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

Обратите внимание, что с использованием вышеописанного метода вам не нужно ссылаться на evArtists (который является вашим большим запросом), за исключением его отдельного элемента ev. С помощью решения full-LINQ можно указать , но вам может потребоваться обратиться к evArtists.Any, чтобы найти набор исполнителей дубликатов (так как у вас есть память о том, какие наборы были выбраны ранее) из самого исходного упорядоченного запроса (а не просто используя свой элемент (ev) один за другим).

Это возможно, потому что вы создаете временную память topEvList, которая записывает, какие наборы были выбраны ранее, и только нужно проверить, не является ли следующий элемент (ev) не среди уже выбранного набора исполнителей. Таким образом, вы делаете не, нарушая вашу производительность, проверяя каждый раз, когда вы устанавливаете художников по всему упорядоченному запросу.


Оригинал:

Вы почти там на самом деле. То, что вы в дальнейшей потребности в LINQGroupBy и First, и положить ваши Take(10) последний:

var query = Context.Events 
    .Where(x => x.Stage.Venue.AreaId == 1 && x.StartDateTimeUtc > DateTime.UtcNow && x.IsVerified) 
    .OrderByDescending(x => x.Headliners.Max(y => y.Popularity)) 
    .GroupBy(a => a.ArtistId) 
    .Select(e => e.First()) 
    .Take(10); 

Поскольку в этом запросе вы перебрали свой потолочный исполнитель:

.OrderByDescending(x => x.Headliners.Max(y => y.Popularity)) 

Тогда вам нужно только сгруппировать хедлайнерами по ArtistId:

.GroupBy(a => a.ArtistId) 

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

.Select(e => e.First()) 

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

.Take(10); 

И вы сделали!


+0

Это не сработает, потому что группировка: .GroupBy (a => a.ArtistId) ', похоже, не разрешена, поскольку Headliners - это объект ICollection of Artist, a '- объект Event. – Frank

+0

@Frank в этом случае, какие поля у вас есть в ваших мероприятиях? – Ian

+0

В событии отсутствует ArtistId, он содержит отношение много ко многим (авто EF), называемое Headliners, которое содержит объекты Artist. Проблема связана с этим отношением. – Frank