Выбор уникальных значений разных столбцов с помощью LINQ

У меня есть таблица (заказы, например), в которой есть несколько столбцов.

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; }
}   

Не знаю, как это сделать эффективно, чтобы не писать три метода. Таблица находится в базе данных (отсюда и необходимость оптимизации)

Спасибо!

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
783
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Насколько я знаю, вы не сможете сделать это в одном запросе. Прежде чем думать, как бы вы это сделали с помощью C#, подумайте, как бы вы это сделали в SQL; Возможно, я ошибаюсь, но для меня вы все равно напишете 3 запроса.

Если вы заметили некоторые проблемы с производительностью, и это ваш фактический код:

var prod = (from p in _context.orders select p.products).ToList().Distinct();

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

Это потому, что ваше выражение запроса (from p in ...) возвращает IQueryable и вызывает .ToList() на нем делает его IEnumerable. заставляет текущий сформированный запрос SQL запускаться и переносить результаты в память.

Разница в этом случае: Отсроченное исполнение

См .: https://www.c-sharpcorner.com/UploadFile/rahul4_saxena/ienumerable-vs-iqueryable/

Да, я понимаю IEnumerable Vs IQueryable. Я только хочу уменьшить количество методов, которые мне нужно написать. Я пробовал использовать SQL, но не смог продвинуться дальше, поскольку в примерах иногда использовались CTE.

Navyseal 02.05.2018 04:18

Что ж, если вы найдете способ сделать именно это, разместите его здесь =). Если вы думаете о SQL, один запрос возвращает строки, а не отдельные коллекции данных. Одно правило различения может подходить для одного столбца, но конфликтовать с другим.

AarónBC. 02.05.2018 04:31

Немного ошибочно будет сказать, что при вызове ToList() возвращается тип IEnumerable, а также что основное отличие заключается в отложенном выполнении, потому что это почти подразумевает, что IEnumerable является нетерпеливым исполнением, а это не так. ToList() технически делает возвращаемый тип List<T>, который реализует IEnumerable, но то же самое делает IQueryable, поэтому они обаIEnumerable, и все IEnumerables технически являются отложенным исполнением, хотя класс реализации может выполнить большую часть своей работы заранее.

ErikE 02.05.2018 07:42

Хитрость в том, что некоторые Linq методы вызывают немедленное выполнение, а другие нет. Select и Where - это отложенное исполнение, но GroupBy и Order - нетерпеливое исполнение. Это имеет смысл, если подумать, потому что последние два не могут выдать ни одного значения, пока не будет перечислен весь набор - любое значение, которое оно выдало раньше, может оказаться неполным (в GroupBy отсутствует элемент из группы) или может быть неправильным первым элементом (OrderBy вышел из строя).

ErikE 02.05.2018 07:44

Эрик, ты прав насчет моего неправильного толкования интерфейса IEnumerable, я исправлю это. Что касается трюка, это не совсем в методах Linq, это зависит от поставщика и от того, как была сделана реализация. Возьмем, например, LinqToSql, если вы используете OrderBy в IQueryable, возвращаемый тип - это IOrderedQueryable, который не выполняется немедленно.

AarónBC. 02.05.2018 08:21

Лямбда-код, который мы используем в фильтрах Where, in OrderBy и других, используется для формирования SQL-запроса (речь идет о LinqToSql). Подсказка, которую мы могли бы использовать, - это параметры метода, если он запрашивает выражение, он может использовать отложенное выполнение, но оно не предоставляется, опять же, это зависит от реализации поставщика.

AarónBC. 02.05.2018 08:26
Ответ принят как подходящий

Является ли использование 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
;

Смотрите, как это работает на db <> fiddle

Выход:

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. 02.05.2018 06:26

@ AarónBC. Извините за две опечатки. Написал полностью по памяти и без проверки синтаксиса. С тех пор, как я последний раз касался SQL Server, прошел год. Я добавил скрипт db <>, где вы можете попробовать.

ErikE 02.05.2018 07:02

Вы также можете использовать только один CTE. Однако не уверен, что это значительно улучшит производительность.

Andriy M 02.05.2018 08:20

Ради интереса, есть ли у List<KeyValuePair<int, string>> какое-то преимущество перед просто IEnumerable<int, string> анонимного типа?

onedaywhen 02.05.2018 12:30

@onedaywhen IEnumerable<int, string> не существует. Теоретически вы можете использовать IEnumerable<dynamic> с анонимным типом, и я пробовал это сначала, но это взорвалось, и я не использовал Visual Studio, что упростило бы диагностику, поэтому я просто переключился на KVP. Этого вполне достаточно.

ErikE 02.05.2018 17:09

Другие вопросы по теме