У меня есть таблица (заказы, например), в которой есть несколько столбцов.
products categories subcategories
--------------------------------------
prod1 cat1 sub1
prod1 cat2 sub2
prod2 cat3 sub6
prod1 cat1 sub1
prod5 cat2 sub8
prod2 cat1 sub1
prod1 cat7 sub3
prod8 cat2 sub2
prod2 cat3 sub1
Теперь я могу написать три разных запроса, чтобы получить разные значения.
var prod = (from p in _context.orders select p.products).ToList().Distinct();
аналогично я могу написать это для других.
Теперь мне нужно получить отдельные значения каждого столбца в одном запросе, для которого результат должен выглядеть как
products categories subcategories
--------------------------------------
prod1 cat1 sub1
prod2 cat2 sub2
prod5 cat3 sub6
prod8 cat7 sub8
sub3
Мой ClassType для уникальных полей выглядит так
public class UniqueProductFields
{
public IEnumerable<string> Products { get; set; }
public IEnumerable<string> Categories { get; set; }
public IEnumerable<string> Subcategories { get; set; }
}
Не знаю, как это сделать эффективно, чтобы не писать три метода. Таблица находится в базе данных (отсюда и необходимость оптимизации)
Спасибо!
Насколько я знаю, вы не сможете сделать это в одном запросе. Прежде чем думать, как бы вы это сделали с помощью C#, подумайте, как бы вы это сделали в SQL; Возможно, я ошибаюсь, но для меня вы все равно напишете 3 запроса.
Если вы заметили некоторые проблемы с производительностью, и это ваш фактический код:
var prod = (from p in _context.orders select p.products).ToList().Distinct();
Вы можете начать с удаления метода расширения .ToList()
, поскольку он извлекает все записи в память и только после этого применяется различие.
Это потому, что ваше выражение запроса (from p in ...
) возвращает IQueryable
и вызывает .ToList()
на нем делает его заставляет текущий сформированный запрос SQL запускаться и переносить результаты в память.IEnumerable
.
Разница в этом случае: Отсроченное исполнение
См .: https://www.c-sharpcorner.com/UploadFile/rahul4_saxena/ienumerable-vs-iqueryable/
Что ж, если вы найдете способ сделать именно это, разместите его здесь =). Если вы думаете о SQL, один запрос возвращает строки, а не отдельные коллекции данных. Одно правило различения может подходить для одного столбца, но конфликтовать с другим.
Немного ошибочно будет сказать, что при вызове ToList()
возвращается тип IEnumerable
, а также что основное отличие заключается в отложенном выполнении, потому что это почти подразумевает, что IEnumerable
является нетерпеливым исполнением, а это не так. ToList()
технически делает возвращаемый тип List<T>
, который реализует IEnumerable
, но то же самое делает IQueryable
, поэтому они обаIEnumerable
, и все IEnumerables
технически являются отложенным исполнением, хотя класс реализации может выполнить большую часть своей работы заранее.
Хитрость в том, что некоторые Linq методы вызывают немедленное выполнение, а другие нет. Select
и Where
- это отложенное исполнение, но GroupBy
и Order
- нетерпеливое исполнение. Это имеет смысл, если подумать, потому что последние два не могут выдать ни одного значения, пока не будет перечислен весь набор - любое значение, которое оно выдало раньше, может оказаться неполным (в GroupBy отсутствует элемент из группы) или может быть неправильным первым элементом (OrderBy вышел из строя).
Эрик, ты прав насчет моего неправильного толкования интерфейса IEnumerable, я исправлю это. Что касается трюка, это не совсем в методах Linq, это зависит от поставщика и от того, как была сделана реализация. Возьмем, например, LinqToSql, если вы используете OrderBy в IQueryable, возвращаемый тип - это IOrderedQueryable, который не выполняется немедленно.
Лямбда-код, который мы используем в фильтрах Where, in OrderBy и других, используется для формирования SQL-запроса (речь идет о LinqToSql). Подсказка, которую мы могли бы использовать, - это параметры метода, если он запрашивает выражение, он может использовать отложенное выполнение, но оно не предоставляется, опять же, это зависит от реализации поставщика.
Является ли использование Linq абсолютно неизменным требованием? Зачем нужно возвращать его одним запросом?
Предложение: используйте SQL. может можно выполнить в одном запросе, но он вам не понравится. Я предполагаю SQL Server (для других СУБД можно сделать иначе).
WITH V AS (
SELECT DISTINCT
V.*
FROM
Orders O
CROSS APPLY (
VALUES (1, O.Products), (2, O.Categories), (3, O.Subcategories)
) V (Which, Value)
),
Nums AS (
SELECT
Num = Row_Number() OVER (PARTITION BY V.Which ORDER BY V.Value),
V.Which,
V.Value
FROM
V
)
SELECT
Products = P.[1],
Categories = P.[2],
Subcategories = P.[3]
FROM
Nums N
PIVOT (Max(N.Value) FOR N.Which IN ([1], [2], [3])) P
;
Выход:
Products Categories Subcategories
-------- ---------- -------------
prod1 cat1 sub1
prod2 cat2 sub2
prod5 cat3 sub3
prod8 cat7 sub6
null null sub8
Если вы связаны и полны решимости использовать Linq, что ж, я не могу вам помочь с синтаксисом в стиле запроса. Я знаю только синтаксис стиля кода C#, но вот здесь укол. К сожалению, я не думаю, что это принесет вам пользу, потому что мне пришлось использовать несколько довольно забавных вещей, чтобы заставить его работать. Он использует по существу тот же метод, что и приведенный выше SQL-запрос, только в Linq нет эквивалента PIVOT
и нет реального естественного объекта строки, кроме настраиваемого класса.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program {
public static void Main() {
var data = new List<Order> {
new Order("prod1", "cat1", "sub1"),
new Order("prod1", "cat2", "sub2"),
new Order("prod2", "cat3", "sub6"),
new Order("prod1", "cat1", "sub1"),
new Order("prod5", "cat2", "sub8"),
new Order("prod2", "cat1", "sub1"),
new Order("prod1", "cat7", "sub3"),
new Order("prod8", "cat2", "sub2"),
new Order("prod2", "cat3", "sub1")
};
int max = 0;
var items = data
.SelectMany(o => new List<KeyValuePair<int, string>> {
new KeyValuePair<int, string>(1, o.Products),
new KeyValuePair<int, string>(2, o.Categories),
new KeyValuePair<int, string>(3, o.Subcategories)
})
.Distinct()
.GroupBy(d => d.Key)
.Select(g => {
var l = g.Select(d => d.Value).ToList();
max = Math.Max(max, l.Count);
return l;
})
.ToList();
Enumerable
.Range(0, max)
.Select(i => new {
p = items[0].ItemAtOrDefault(i, null),
c = items[1].ItemAtOrDefault(i, null),
s = items[2].ItemAtOrDefault(i, null)
})
.ToList()
.ForEach(row => Console.WriteLine($"p: {row.p}, c: {row.c}, s: {row.s}"));
}
}
public static class ListExtensions {
public static T ItemAtOrDefault<T>(this List<T> list, int index, T defaultValue)
=> index >= list.Count ? defaultValue : list[index];
}
public class Order {
public Order(string products, string categories, string subcategories) {
Products = products;
Categories = categories;
Subcategories = subcategories;
}
public string Products { get; set; }
public string Categories { get; set; }
public string Subcategories { get; set; }
}
Я полагаю, мы могли бы поменять это местами
.Select(i => new {
p = items[0].ItemAtOrDefault(i, null),
c = items[1].ItemAtOrDefault(i, null),
s = items[2].ItemAtOrDefault(i, null)
})
за это:
.Select(i => new Order(
items[0].ItemAtOrDefault(i, null),
items[1].ItemAtOrDefault(i, null),
items[2].ItemAtOrDefault(i, null)
))
Затем используйте свойства этого класса в разделе вывода.
Отличные навыки, Эрик !, меня так заинтересовало, что пришлось попробовать! Кстати, есть небольшая ошибка с псевдонимом в операторе PIVOT, вы используете V.Value вместо N.Value :) Не могу редактировать, так как это только один символ: P
@ AarónBC. Извините за две опечатки. Написал полностью по памяти и без проверки синтаксиса. С тех пор, как я последний раз касался SQL Server, прошел год. Я добавил скрипт db <>, где вы можете попробовать.
Вы также можете использовать только один CTE. Однако не уверен, что это значительно улучшит производительность.
Ради интереса, есть ли у List<KeyValuePair<int, string>>
какое-то преимущество перед просто IEnumerable<int, string>
анонимного типа?
@onedaywhen IEnumerable<int, string>
не существует. Теоретически вы можете использовать IEnumerable<dynamic>
с анонимным типом, и я пробовал это сначала, но это взорвалось, и я не использовал Visual Studio, что упростило бы диагностику, поэтому я просто переключился на KVP. Этого вполне достаточно.
Да, я понимаю IEnumerable Vs IQueryable. Я только хочу уменьшить количество методов, которые мне нужно написать. Я пробовал использовать SQL, но не смог продвинуться дальше, поскольку в примерах иногда использовались CTE.