C # Entity Framework: фильтр Linq для GrandChildren и выбор родительского элемента

Наша компания в настоящее время использует Entity Framework Net Core 2.2 с Sql Server.

Попытка найти всех отличных клиентов, которые приобрели определенный входной параметр продукта. При попытке сделать окончательный выбор он показывает лямбда b как Продукт. Нам нужно, чтобы особые клиенты появлялись последними.

Как написать запрос EF Linq, чтобы получить его для разных клиентов?

var taxAgencyDistinctList = db.Customer
        .SelectMany(b => b.Transactions)
        .SelectMany(b => b.Purchases)
        .Select(b => b.Product)
        .Where(b => b.BKProduct == ProductInput) 
        .Select(b => b.).Distinct();

Эквивалентный SQL прост:

select distinct c.customerName
from dbo.customer customer
inner join dbo.Transactions transaction
    on transaction.customerid = customer.customerid
inner join dbo.Purchases purchases
    on purchases.PurchaseId = transaction.PurchaseId
inner join dbo.Product product 
    on transaction.ProductId = product.ProductId
where tra.BKProduct = @ProductInput

Компания предпочитает метод, при котором мы не используем Linq для Sql, если это возможно.

Ресурсы:

Фильтрация при включении в EF Core

Фильтрация по ThenInclude три вложенных уровня вниз

Net Core: Entity Framework ThenInclude с выбором проекции


person Community    schedule 20.06.2020    source источник
comment
Вы хотите, чтобы гидратировались только объекты Заказчика? Или вы хотите, чтобы клиенты, транзакции и покупки гидратировались? У SQL есть только Заказчик.   -  person granadaCoder    schedule 20.06.2020
comment
привет @granadaCoder мне нужен только клиент   -  person    schedule 20.06.2020
comment
Итак, вам действительно нужен этот SQL. Это достигается с помощью синтаксиса .Any EF. Я пытаюсь найти пример EF. выберите c.customerId из dbo.customer customer WHERE EXISTS (ВЫБЕРИТЕ 1 ИЗ dbo.Transactions внутреннее соединение транзакции dbo.Purchases Purchases на покупки.PurchaseId = transaction.PurchaseId внутреннее соединение dbo.Product product on transaction.ProductId = product.ProductId где tra. BKProduct = @ProductInput И / * отношение к внешнему запросу * / transaction.customerid = customer.customerid) PS все / большинство ответов, получаемых UR, переборщены   -  person granadaCoder    schedule 20.06.2020


Ответы (4)


Хотя вы можете получить нужные данные с некоторыми другими ответами, вы, вероятно, чрезмерно увлажняете (что означает, что вы слишком сильно попадаете в БД) для того, что вам нужно.

.Any - это способ написания WHERE EXISTS предложений в EF.

Вот попытка запроса EF:

IEnumerable<Customer> justCustomersHydrated = db.Customer
                      .Where(p => p.Transactions.SelectMany(c => c.Purchases).Select(gc => gc.Product.Where(gc => gc.BKProduct == ProductInput).Any());

Я использую p как Parent, c как Child и gc как GrandChild. Вы, конечно, можете заменить их, но я пытаюсь показать намерение в коде.

Вы пытаетесь получить (сгенерированный) SQL, который больше похож на это.

select c.customerId /* and c.AllOtherColumns */
from dbo.customer customer
WHERE EXISTS
(
    SELECT 1 FROM dbo.Transactions transaction
inner join dbo.Purchases purchases
    on purchases.PurchaseId = transaction.PurchaseId
inner join dbo.Product product 
    on transaction.ProductId = product.ProductId
where tra.BKProduct = @ProductInput

AND /* relationship to outer query */
 transaction.customerid = customer.customerid
 )

Это приведет к гидратации объекта Customer (все скаляры и отсутствие свойств навигации объекта Customer).

При желании вы можете выбрать (меньшее количество скалярных свойств клиента) .... Вы также можете просто выбрать пользовательский идентификатор (хотя обычно выбор всех столбцов из родительской таблицы не слишком ужасен, если эта таблица не имеет много / много столбцов или большой столбец data (image / varbinary (max)) где-то там.

Смотрите этот ответ:

Entity Framework - проверьте, есть ли записи о внуках

Если в этом ответе есть новое {для менее агрессивного SELECT.

person granadaCoder    schedule 20.06.2020
comment
Я лично сделал .Any (WHERE EXISTS) с 2.1. Кстати, не по теме, что 2.1 - это LTS (долгосрочная поддержка), а 3.1 - LTS, но 2.2 НЕ LTS. и 2.2 LTS закончился. Вам следует перейти на 3.1, но если вы не можете, вам следует вернуться к 2.1 (как бы интуитивно это ни звучало) dotnet.microsoft.com/platform/support/policy/dotnet-core - person granadaCoder; 20.06.2020
comment
Но я обещаю, что вы должны продолжать это (выяснять синтаксис) ... с ГДЕ СУЩЕСТВУЕТ / .Любой точки зрения. Если вы научитесь делать это сейчас, это окупится в будущем. Избыточная гидратация - распространенная проблема с большим количеством кода, который я видел. Выполнение Select customer. *, Purchases. *, Transaction. *, Product. * FROM (JOINS on all 4 table) - это лишний способ составить список клиентов. - person granadaCoder; 20.06.2020
comment
Мой собственный пример 2.1. Любой код: github.com/granadacoder/oracle-ef-issues-demo/blob/master/src/ - person granadaCoder; 20.06.2020
comment
И прочее не по теме, но по теме. Вы должны узнать, как подключить регистратор к контексту базы данных, и таким образом вы сможете увидеть генерируемый SQL. См. Волшебную строку здесь: github.com/granadacoder/oracle-ef-issues-demo/blob/master/src/ - person granadaCoder; 20.06.2020
comment
Это другой вопрос. Но попробуйте разместить .Include после Customer и до. Где - person granadaCoder; 20.06.2020
comment
В таком случае этикет будет отмечать это как ответ. И, возможно, удалите ненужные устаревшие комментарии, чтобы все было в порядке для будущих читателей. Я бы также поместил ваш окончательный код в конец вашего исходного вопроса. Вы (мы) пытаемся помочь и будущим читателям. - person granadaCoder; 20.06.2020
comment
также оставил здесь еще один быстрый вопрос, спасибо, stackoverflow.com/questions/62505149/ - person ; 22.06.2020

Если вы используете внутренние соединения, это должно работать нормально.


    var taxAgencyDistinctList = db.Customer
        .Join(db.Transactions, customer => customer.customerId, transaction => transaction.customerid, (customer, transaction) => new 
        {
        Customer = customer,
        Transaction = transaction
        })
        .Join(db.Purchases, comb => comb.Transaction.PurchaseId, purchase => purchase.PurchaseId, (comb, purchase) => new
        {
        OldCombinedObject = comb,
        Purchase = purchase
        })
        .Join(db.Product, comb => comb.OldCombinedObject.Transaction.ProductId, product => product.ProductId, (comb, product) => new
        {
        LastCombinedObject = comb,
        Product = product
        })
        .Where(comb => comb.LastCombinedObject.OldCombinedObject.Transaction.BKProduct == ProductInput) 
        .Select(comb => comb.LastCombinedObject.OldCombinedObject.Customer).Distinct();

person Giorgi Anakidze    schedule 20.06.2020

Вам действительно нужно присоединиться к Покупкам. IE, есть ли какие-то транзакции без покупок, и вы хотите исключить те, которые имеют внутреннее соединение? Если нет, то

select distinct c.customerId 
from dbo.customer customer
inner join dbo.Transactions transaction
    on transaction.customerid = customer.customerid
inner join dbo.Purchases purchases
    on purchases.PurchaseId = transaction.PurchaseId
inner join dbo.Product product 
    on transaction.ProductId = product.ProductId
where tra.BKProduct = @ProductInput

просто

 var cids = db.Transactions
              .Where( t => t.Purchase.BKProduct = productImput )
              .Select(t => new
                     {
                       t.Purchase.CustomerId,
                       t.Purchase.Customer.CustomerName
                     })
              .Distinct();
person David Browne - Microsoft    schedule 20.06.2020
comment
привет Дэвид, извиняюсь, мне действительно нужен CustomerName, атрибут CustomerTable, также Транзакции не содержат данных о продукте, они должны пройти через Purcahses - person ; 20.06.2020
comment
хорошо транзакции присоединяются через покупки в нашей базе данных еще - person ; 20.06.2020
comment
Не в том, что вы разместили, а в чем угодно. См. Дополнительное обновление. В любом случае шаблон должен начинаться снизу или рядом с ним и при необходимости перемещаться по свойствам навигации. - person David Browne - Microsoft; 20.06.2020

var taxAgencyDistinctList = db.Purchases
        .Include(p => p.Transaction).ThenInclude(t => t.Customer)
        .Where(p => p.ProductId == ProductInput.ProductId)
        .Select(b => b.Transaction.Customer).Distinct();

Можно пойти с другой стороны. Когда вы делаете выбор, linq продолжает работу с этого выбранного типа. В вашем случае это продукт.

Второй подход - начать с клиента и идти в том числе. Затем, где внимательно проверьте клиентов purcheses.any (m => m.ProductId == input.ProductId) или что-то в этом роде.

person Bahtiyar Özdere    schedule 20.06.2020
comment
Мне нужен фильтр в столбце атрибутов таблицы товаров, поэтому это не сработает. - person ; 20.06.2020