使用LINQ选择不同列的唯一值

时间:2018-05-02 01:38:21

标签: c# linq

我有一个表(ex的订单),它有多列。

 \\ \b   \c

现在我可以编写三个不同的查询来获取不同的值

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();

我的独特字段的ClassType如下所示

products    categories  subcategories
--------------------------------------

prod1       cat1        sub1
prod2       cat2        sub2
prod5       cat3        sub6
prod8       cat7        sub8
                        sub3

不确定如何以有效的方式执行此操作,以便我不必编写三种方法。该表位于数据库中(因此需要优化)

谢谢!

2 个答案:

答案 0 :(得分:5)

使用Linq是绝对不可改变的要求吗?为什么需要在单个查询中返回它?

建议:使用SQL。 可以在一个查询中完成,但您不喜欢该查询。我假设使用SQL Server(对于其他DBMS可以采用不同的方式)。

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
;

See this working at 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)
))

然后在输出部分使用该类的属性。

答案 1 :(得分:0)

据我所知,您将无法在单个查询中执行此操作。在考虑如何使用C#进行思考之前,请考虑如何在SQL中执行此操作;我可能错了,但对我来说,无论如何你都会写3个查询。

如果您发现一些性能问题,这是您的实际代码:

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

您可能希望首先删除.ToList()扩展方法beacuse,将所有记录检索到内存,然后才应用区别。

那是因为您的查询表达式(from p in ...)返回IQueryable并且在其上调用.ToList() 使其成为IEnumerable强制当前形成运行SQL查询并将结果带入内存。

这种情况的区别在于:延期执行

请参阅:https://www.c-sharpcorner.com/UploadFile/rahul4_saxena/ienumerable-vs-iqueryable/