GroupJoin: сгенерировано исключение: System.InvalidOperationException - PullRequest
1 голос
/ 10 июля 2019

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

   db.SALETABLES
  .GroupJoin(
        db.SALES.Where(c => c.CLOSEDTIME == null),
        t => t.ID,
        sa => sa.ID_TABLE,
     (ta, s) => new
            {
               ta.ID,
               ta.DESCRIPTION,
                NR_SALE = s.Any() ? s.First().NR_SALE : 0,
                IDSALE = s.Any() ? s.First().ID : 0,
                IDUSER = s.Any() ? s.First().IDUSER : 0,
                USERNAME = s.Any() ? s.First().USERS.USERNAME :"" ,
                SALESUM = s.Any() ? s.First().SALES_DETAIL.Sum(p => p.PRICE * p.CANT) : 0
                         }

но получил эту ошибку:

Исключение: «System.InvalidOperationException» в System.Private.CoreLib.dll

спасибо за любую помощь

Ответы [ 2 ]

3 голосов
/ 10 июля 2019

Так как исключение составляет инфраструктура EF Core, очевидно, вы сталкиваетесь с текущей ошибкой реализации EF Core.

Но вы можете помочь транслятору запросов EF Core (таким образом избежать ошибок, вызванных отсутствующими сценариями использования), выполнив следующие действия:некоторые правила при написании запросов LINQ to Entities.Эти правила также устранят в большинстве случаев оценку запроса клиентом (или исключение в EF Core 3.0 +).

Одним из правил, являющихся источником проблем с этим конкретным запросом, является - никогда не используйте First.Поведение LINQ to Objects First - генерировать исключение, если набор пуст.Это не естественно для SQL, который естественным образом поддерживает и возвращает NULL даже для значений, которые обычно не допускают NULL.Чтобы эмулировать поведение LINQ to Objects, EF Core должен оценить First() сторону клиента, что не очень хорошо, даже если он работает.Вместо этого используйте FirstOrDefault(), который имеет ту же семантику, что и SQL, и, следовательно, переводится.

Чтобы подвести итог, используйте FirstOrDefault(), когда вам нужно, чтобы результат был одним "объектом" или null, или Take(1) когда вы хотите, чтобы результатом был набор с 0 или одним элементом.

В этом конкретном случае лучше включить правило SALE, связанное с 0 или 1, непосредственно в подзапрос соединения, удаливGroupJoin и заменить его на SelectMany на коррелированный Where.И проверки Any() заменяются проверками != null.

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

var query = db.SALETABLES
    .SelectMany(ta => db.SALES
        .Where(s => ta.ID == s.ID_TABLE && s.CLOSEDTIME == null).Take(1), // <--
    (ta, s) => new
    {
        ta.ID,
        ta.DESCRIPTION,
        NR_SALE = s != null ? s.NR_SALE : 0,
        IDSALE = s != null ? s.ID : 0,
        IDUSER = s != null  ? s.IDUSER : 0,
        USERNAME = s != null ? s.USERS.USERNAME : "",
        SALESUM = s != null ? s.SALES_DETAIL.Sum(p => p.PRICE * p.CANT) : 0
    });
2 голосов
/ 10 июля 2019

Вы не указываете исключение, но я предполагаю, что речь идет об оценке на стороне клиента (CSE), и вы настроили EF на выдачу исключения при его возникновении.

Это может быть First(), который вызывает CSE, или GroupJoin. Первый можно легко исправить с помощью FirstOrDefault(). GroupJoin имеет больше к этому.

Во многих случаях совсем не обязательно использовать GroupJoin, Join. Обычно, вручную закодированные объединения могут и должны быть заменены свойствами навигации. Это не только делает код более читабельным, но и позволяет избежать нескольких проблем, которые EF 2.x имеет с GroupJoin.

Ваш класс SaleTable (я не собираюсь следовать вашим именам на основе базы данных) должен иметь свойство Sales:

public ICollection<Sale> Sales { get; set; }

А если хотите, Sale может иметь свойство обратной навигации:

public SaleTable SaleTable { get; set; }

Настроен как

modelBuilder.Entity<SaleTable>()
    .HasMany(e => e.Sales)
    .WithOne(e => e.SaleTable)
    .HasForeignKey(e => e.SaleTableId) // map this to ID_TABLE
    .IsRequired();

Теперь использование свойства таблицы Sales будет иметь тот же эффект, что и GroupJoin - уникальный ключ, здесь SaleTable, с собственной коллекцией - но без проблем.

Следующим улучшением является упрощение запроса. Двумя способами. 1. Вы неоднократно обращаетесь к первому Sale, поэтому используйте оператор let. 2. Запрос переведен на SQL, поэтому не беспокойтесь о пустых ссылках , но подготовьтесь к нулевым значениям . Улучшенный запрос прояснит, что я имею в виду.

var query = from st in db.SaleTables
            let firstSale = st.Sales.FirstOrDefault()
            select new
            {
                st.ID,
                NrSale = (int?)firstSale.NrSale ?? 0,
                IdSale = (int?)firstSale.ID ?? 0,
                ...
                SalesSum = (int?)firstSale.SalesDetails.Sum(p => p.Price * p.Cant) ?? 0
            }

Использование NrSale = firstSale.NrSale, вызовет исключение для SaleTable с без Sales (объект Nullable должен иметь значение).

...