Linq Group По нескольким столбцам с проекцией на новый объект - PullRequest
0 голосов
/ 14 июля 2020

Я использую Microsoft.EntityFrameworkCore версии 3.1.3:

У меня есть следующий объект:

public class OrganisationMention
{
    public int Id { get; set; }
    public int OrganisationId { get; set; }
    public Organisation Organisation { get; set; }
    public int PublicationId { get; set; }
    public Publication Publication { get; set; }
    public int LocationId { get; set; }
    public Location Location { get; set; }
    public int PracticeAreaId { get; set; }
    public PracticeArea PracticeArea { get; set; }
    public int Count { get; set; }
    public int MarketAverage { get; set; }
}

Я хочу объединить список OrganisationMentions по Publication \ Location \ PracticeArea в получить список объектов RelatedOrganisationMentions с суммой подсчета, например:

public class RelatedOrganisationMentions
{
    public int PublicationId { get; set; }
    public Publication Publication { get; set; }
    public int LocationId { get; set; }
    public Location Location { get; set; }
    public int PracticeAreaId { get; set; }
    public PracticeArea PracticeArea { get; set; }
    public int Count { get; set; }
    public int MarketAverage { get; set; }
}

Если я использую следующий запрос LINQ, я получаю ужасную ошибку, связанную с проекцией объектов Publication \ Location \ PracticeArea. Если я их опущу, запрос вернется нормально. Однако я хочу, чтобы они вернулись в агрегированный результат, и могу гарантировать, что каждый объект Publication будет одним и тем же, если PublicationId одинаков:

        IQueryable<RelatedOrganisationMentions> relatedOrganisationMentions = collection
            .GroupBy(m => new { m.LocationId, m.PublicationId, m.PracticeAreaId })
            .Select(am => new RelatedOrganisationMentions
            {
                PracticeArea = am.First().PracticeArea, //Causes long and horrid error
                Location = am.First().Location,         //Causes long and horrid error
                Publication = am.First().Publication,   //Causes long and horrid error
                PracticeAreaId = am.Key.PracticeAreaId,
                PublicationId = am.Key.PublicationId,
                LocationId = am.Key.LocationId,
                Count = am.Sum(x => x.Count)
            })
        .OrderBy(r => r.Publication.Description)
        .ThenBy(r => r.PracticeArea.Description);

    return await relatedOrganisationMentions.ToListAsync(token).ConfigureAwait(false);

Как группировать по нескольким столбцам и сохранять объекты в агрегированный прогноз, пожалуйста?

ОШИБКА

System.InvalidOperationException: выражение LINQ '(GroupByShaperExpression: KeySelector: new {LocationId = EntityMaterializerSource.TryReadValue (grouping.Key, 0, Property : OrganisationMention.LocationId (int) Требуемый индекс FK), PublicationId = EntityMaterializerSource.TryReadValue (grouping.Key, 1, Свойство: OrganisationMention.PublicationId (int) Требуемый индекс FK), PracticeAreaId = EntityMaterializerSource.TryReadValue (grouping. : OrganisationMention.PracticeAreaId (int) Требуемый индекс FK)}, ElementSelector: (EntityShaperExpression: EntityType: OrganisationMention ValueBufferExpression: (ProjectionBindingExpression: EmptyProjectionMember) IsNullable: False )) .First () 'не может быть переведено. Либо перепишите запрос в форме, которая может быть переведена, либо явно переключитесь на оценку клиента, вставив вызов либо AsEnumerable (), AsAsyncEnumerable (), ToList (), либо ToListAsyn c (). См. https://go.microsoft.com/fwlink/?linkid=2101038 для получения дополнительной информации. at

ОБНОВЛЕНИЕ 1: Похоже, это известная проблема с efcore 3.1 Сейчас ищем возможные обходные пути.

https://github.com/dotnet/efcore/issues/12088

Ответы [ 2 ]

1 голос
/ 15 июля 2020

Краткий ответ

Обычно достаточно сначала Select нужного свойства, а затем FirstOrDefault:

PracticeArea = am.Select(amItem => amItem.PracticeArea)
    .FirstOrDefault(),
Location = am.Select(amItem => amItem.Location)
    .FirstOrDefault(),
Publication = am.Select(amItem => amItem.Publication)
    .FirstOrDefault(),

Объяснение ошибки

Вы должны знать разницу между IEnumerable<...> и 'IQueryable <...> `.

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

На самом низком уровне это выполняется с помощью GetEnumerator(). После того как у вас есть перечислитель, вы можете обращаться к элементам последовательности один за другим, многократно используя MoveNext() и Current.

На более высоком уровне это делается с помощью foreach. Это также делается в каждом методе LINQ, который не возвращает IEnumerable<...>

. С другой стороны, IQueryable<...> предназначен для обработки другим процессом, обычно системой управления базами данных. Он содержит Expression и Provider. Выражение представляет запрос в некотором общем формате c, провайдер знает, кто должен выполнить запрос и какой язык используется для связи с базой данных (обычно SQL).

Если вы внимательно посмотрите на LINQ, вы увидите, что есть две группы: те, которые возвращают IQueryable<...> (или IEnumerable<...>), и другие. Методы первой группы используют отложенное выполнение (иногда называемое ленивым исполнением). В каждом описании этого метода LINQ вы найдете этот термин.

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

Только когда вы начинаете перечисление, на низком уровне, используя GetEnumerator(), или используя foreach, или любой из методов LINQ второй группы, например ToList(), `` Count () , Any () `, et c. Выражение отправляется Провайдеру, который попытается перевести его в SQL и выполнить запрос в базе данных.

Проблема в том, что Провайдер не знает, как перевести все методы в SQL. Например, он не знает ваших собственных методов. Кроме того, есть несколько методов LINQ, которые не поддерживаются. См. Список поддерживаемых и неподдерживаемых методов LINQ (LINQ to entity) .

Ваш провайдер не может перевести ваше использование First():

...First()' could not be translated.
1 голос
/ 14 июля 2020

First() не может быть переведено Linq на SQL. Вам необходимо использовать FirstOrDefault().

...
PracticeArea = am.FirstOrDefault().PracticeArea,
Location = am.FirstOrDefault().Location,
Publication = am.FirstOrDefault().Publication,
...

Изменить Предлагаемое решение, чтобы избежать ошибок:

Требуется доступ к: context.PracticeAreas, context.Locations и context.Publications

IQueryable<RelatedOrganisationMentions> relatedOrganisationMentions = collection
    .GroupBy(
        m => new { m.LocationId, m.PublicationId, m.PracticeAreaId },
        (k, g) => new{ k.LocationId, k.PublicationId, k.PracticeAreaId, Count = g.Sum(x => x.Count) }
    )
    .Select(am => new RelatedOrganisationMentions
    {
        PracticeArea = context.PracticeAreas.FirstOrDefault(x => x.Id == am.PracticeAreaId),
        Location = context.Locations.FirstOrDefault(x => x.Id == am.LocationId),
        Publication = context.Publications.FirstOrDefault(x => x.Id == am.PublicationId),
        PracticeAreaId = am.PracticeAreaId,
        PublicationId = am.PublicationId,
        LocationId = am.LocationId,
        Count = x.Count
    })
    .OrderBy(r => r.Publication.Description)
    .ThenBy(r => r.PracticeArea.Description);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...