У меня есть запрос, в котором мне нужно выбрать отдельные номера заказов из таблицы заказов. Номер заказа может соответствовать нескольким записям в таблице заказов, но мне нужны только отдельные заказы. И.Е. Я хочу получить только одну запись для данного номера заказа. Затем я хочу объединить это с таблицей клиентов на o.CustomerKey, равной c.CustomerKey. Я пробовал сделать это несколькими разными способами и не нашел быстрого и краткого способа сделать это. Моя мысль состоит в том, чтобы выбрать отдельные заказы по номеру заказа, затем присоединиться к таблице «Клиенты», где соблюдаются условные критерии, а затем выбрать данные, которые мне нужно вернуть. Это с Entity Framework Core 8.0.6.
CustomerOrder
— это DTO с полями, которые можно увидеть в запросе.
Это соответствующие столбцы таблицы «Клиент»: [Ключ_Клиента],[Номер_Клиента],[Фамилия_Имя],[Имя_Имя],[Адрес],[Город],[Штат],[Почтовый_код],[электронная почта],[Телефон].
Это соответствующие столбцы заказа: [Номер_заказа],[Ключ_клиента]
Эти таблицы были созданы для создания сущностей в моем коде.
Это один запрос, который я пробовал:
public async Task<List<CustomerOrder>> GetOrdersMatchingAsync(string lastName, string address, string city, string state, string zipCode)
{
var query =
from c in DimensionCustomers
join o in (
from o in FactDirectOrders
select new { o.OrderNumber, o.CustomerKey }
).Distinct() on c.CustomerKey equals o.CustomerKey
where
(string.IsNullOrEmpty(lastName) || c.LastName ==
lastName) &&
(string.IsNullOrEmpty(address) || c.Address == address)
&&
(string.IsNullOrEmpty(zipCode) || c.ZipCode == zipCode)
&&
(string.IsNullOrEmpty(city) || c.City == city) &&
(string.IsNullOrEmpty(state) || c.State == state)
select new CustomerOrder
{
OrderNumber = o.OrderNumber,
CustomerNo = c.CustomerNo,
CustomerKey = c.CustomerKey,
FirstName = c.FirstName,
LastName = c.LastName,
Address = c.Address,
City = c.City,
State = c.State,
ZipCode = c.ZipCode,
};
return await query.AsNoTracking().ToListAsync();
}
В настоящее время это работает, но я не уверен, что это оптимально. У меня есть пара подобных запросов, в которых я хочу выбрать отдельные значения перед объединением, но, возможно, это не лучшая практика? Любые рекомендации или советы будут оценены по достоинству.
@GertArnold отредактировал дополнительную информацию, дайте мне знать, если есть что-нибудь еще, что можно было бы добавить.
Я вижу несколько проблем с вашим запросом. Сначала очевидные вещи:
Это можно значительно упростить, используя свойства навигации для связей между сущностями. EF — это не просто замена ADO с поддержкой Linq и эффективное создание операторов SQL.
При проектировании (с использованием Select
) использования DTO, ViewModels или заполнения «новых» экземпляров сущностей вам не нужен AsNoTracking()
, DTO CustomerOrder — это не что-то, что будет отслеживать DbContext, а также сущность/таблица, на которую ссылается запрос.
При работе с критериями условного поиска лучше извлечь их из выражения Linq и применять в Linq только в том случае, если они необходимы, а не встраивать в Linq такие вещи, как проверки #null. То, как они у вас есть, приведет к множеству сравнений #null в SQL.
Используя свойства навигации, я ожидаю, что сущность «Клиент» должна иметь коллекцию заказов или, по крайней мере, «Заказ» имеет свойство навигации обратно к своему «Клиенту». Чтобы получить запрос с использованием свойств навигации и извлечения условных проверок #null, ваш запрос можно упростить до чего-то вроде:
var query = _context.DirectOrders.AsQueryable();
// Each filter will only be appended (ANDed together) if the value is present.
if (!string.IsNullOrEmpty(lastName))
query = query.Where(o => o.Customer.LastName == lastName);
if (!string.IsNullOrEmpty(address))
query = query.Where(o => o.Customer.Address == address);
if (!string.IsNullOrEmpty(zipCode))
query= query.Where( o => o.Customer.ZipCode == zipCode);
if (!string.IsNullOrEmpty(city)
query = query.Where*o => o.Customer.City == city));
if (!string.IsNullOrEmpty(state))
query = query.Where(o => o.Customer.State == state)
var results = await query.Select(o => new CustomerOrder
{
OrderNumber = o.OrderNumber,
CustomerNo = o.Customer.CustomerNo,
CustomerKey = o.Customer.CustomerKey,
FirstName = o.Customer.FirstName,
LastName = o.Customer.LastName,
Address = o.Customer.Address,
City = o.Customer.City,
State = o.Customer.State,
ZipCode = o.Customer.ZipCode,
}).Distinct().ToListAsync();
return results;
Использование Distinct()
предполагает, что ваши DirectOrders могут иметь несколько строк для одного и того же OrderNumber. Свойства навигации между сущностями могут быть двунаправленными (Заказ имеет ссылку на Клиента, И Клиент имеет коллекцию Заказов) или однонаправленными. (Заказ имеет ссылку на клиента ИЛИ у клиента есть коллекция заказов). Обычно я рекомендую использовать свойства однонаправленной навигации, где одна сущность может служить совокупным корнем для другой. Однако в некоторых случаях, когда две сущности обычно работают как сущности верхнего уровня (например, «Клиент» и «Заказ»), имеет смысл использовать двунаправленные ссылки. Например, если у вас есть только Customer.Orders и нет Order.Customers, запрос немного изменится:
var query = _context.Customers.AsQueryable();
// Each filter will only be appended (ANDed together) if the value is present.
if (!string.IsNullOrEmpty(lastName))
query = query.Where(c => c.LastName == lastName);
if (!string.IsNullOrEmpty(address))
query = query.Where(c => c.Address == address);
if (!string.IsNullOrEmpty(zipCode))
query= query.Where( c => c.ZipCode == zipCode);
if (!string.IsNullOrEmpty(city)
query = query.Where*c => c.City == city));
if (!string.IsNullOrEmpty(state))
query = query.Where(c => c.State == state)
var results = await query.SelectMany(c => c.Orders.Select( o => new CustomerOrder
{
OrderNumber = o.OrderNumber,
CustomerNo = c.CustomerNo,
CustomerKey = c.CustomerKey,
FirstName = c.FirstName,
LastName = c.LastName,
Address = c.Address,
City = c.City,
State = c.State,
ZipCode = c.ZipCode,
})).Distinct().ToListAsync();
return results;
При этом выполняется запрос с уровня клиента, а затем SelectMany()
, чтобы перейти к заказам, создавая DTO из объединенного клиента (c=>) и заказа (o=>).
Я ценю ответ, я думаю, что здесь у меня есть хорошие возможности для обучения. В дальнейшем, помогут ли эти запросы решить мою проблему? И.Е. использование Distinct()
будет ли это также отличать строки по CustomerNo
? Мы могли бы упростить пример, чтобы он относился не только к моему коду. По сути, я хочу выбрать первый экземпляр каждого номера заказа, в котором совпадают данные Клиента. Это означает, что результат может содержать несколько одинаковых клиентов для разных заказов. У заказов нет Customer
, только CustomerKey и CustomerNo в заказе.
Distinct
почти наверняка не требуется, если настроены свойства навигации. Если вам нужен список клиентов, соответствующих предоставленным критериям, с каждым из соответствующих номеров заказов, а ваша сущность «Клиент» настроена с коллекцией «Заказы», тогда вам просто нужно использовать второй пример выше и удалить Distinct()
, как это красиво. многое в этом случае ничего не даст.
Ах, я понимаю, SelectMany() в сочетании с Distinct() выбирает все отдельные заказы для данного клиента. Спасибо!
Что мы здесь видим? Что такое
Orders
, что такоеCustomers
? Вроде бы это EF, но какая версия? И как выглядят занятия, особенно? их навигационные свойства? Пожалуйста, отредактируйте вопрос и теги, чтобы добавить дополнительную информацию.