【问题标题】:how do I write LINQ query to retrieve distinct records based on only specific properties?如何编写 LINQ 查询以仅基于特定属性检索不同的记录?
【发布时间】:2016-12-23 07:58:47
【问题描述】:

我有一个对象数组DataTestplans,我尝试使用所示的 LINQ 查询检索特定DataIDProductID 的记录,我当前的查询有Distinct(),它区分所有5 个提到的属性,如何根据属性DataID,TestPlanName,TCIndexListProductID 检索不同的记录?

数据测试计划:-

[
    {
    "DataTestPlanID": 0,
    "DataID": 19148,
    "TestPlanName": "string",
    "TCIndexList": "string",
    "ProductID": 2033915
  },
    {
    "DataTestPlanID": 0,
    "DataID": 19148,
    "TestPlanName": "string",
    "TCIndexList": "string",
    "ProductID": 2033915
  },
      {
    "DataTestPlanID": 0,
    "DataID": 19149,
    "TestPlanName": "string",
    "TCIndexList": "string",
    "ProductID": -2642
  }

]

LINQ

            DataTestPlans_DataID_ProductID = DataTestPlans.Where(c => c.DataID == DataID_ProductID_Record.DataID && c.ProductID == DataID_ProductID_Record.ProductID).Distinct();

【问题讨论】:

标签: c# linq


【解决方案1】:

你可以这样做..

DataTestPlans.Where(c => c.DataID == YourInput && c.ProductID == YourInput)
             .GroupBy(x => new {x.DataID,x.TestPlanName,x.TCIndexList,x.ProductID})
             .Select(x => x.First());

【讨论】:

  • 是的,基本上就是这样。请注意,在此示例中,未分组的字段的值基本上是随机的。如果您关心该值,则必须找到某种从组中聚合它的方法。此外,FirstOrDefault() 不是必需的,因为每个组都至少有一个成员,否则就不是一个组。 (所以它可能只是First()。)
  • 我喜欢您的解决方案,但会考虑将 .Select(x => x.FirstOrDefault()); 替换为 .SelectMany(x => x.Take(1))。如果您在 GroupBy 之后进行过滤,这是一种更清晰的方法,并且可以防止虚假的默认值。
  • @ChrisBerger - 在.GroupBy(...) 之后使用.First() 会出现问题,如果你重构并在它们之间放置.Select(x => x.Where(...)),因为这会产生错误的值。
  • 你能解释一下FirstOrDefault()First() 之间的区别吗?还有SelectMany(x => x.Take(1)).Select(x => x.First()); 用外行的话... on 比另一个有什么优势
  • @user3508811 :如果序列为空,FirstOrDefault() 返回上下文中数据类型的默认值。例如,FirstOrDefault() 在 int 的空列表上返回 0。如果序列为空,First() 将失败并出现异常。
【解决方案2】:

有两种方法可以做,都在this question中突出显示,不需要IComparer。这是一个您可以使用的快速示例(我没有使用您的实际对象,因为这样更容易解释):

class Program
{
    static void Main(string[] args)
    {
        var persons = Setup();

        //option 1, can stream, option suggested by Jon Skeet
        //https://stackoverflow.com/a/1300116/897326
        var result1 = persons.
            DistinctBy(m => new {m.FirstName, m.LastName});

        //option 2, cannot stream, but does reference to DistinctBy
        //https://stackoverflow.com/a/4158364/897326
        var result2 = persons.
            GroupBy(m => new { m.FirstName, m.LastName }).
            Select(group => group.First());
    }

    class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public string Address { get; set; }
    }

    private static List<Person> Setup()
    {
        var p1 = new Person
        {
            FirstName = "John",
            LastName = "Doe",
            Address = "USA"
        };

        var p2 = new Person
        {
            FirstName = "John",
            LastName = "Doe",
            Address = "Canada"
        };

        var p3 = new Person
        {
            FirstName = "Jane",
            LastName = "Doe",
            Address = "Australia"
        };

        var persons = new List<Person>();
        persons.Add(p1);
        persons.Add(p2);
        persons.Add(p3);

        return persons;
    }
}

public static class LinqExtensions
{
    public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        HashSet<TKey> knownKeys = new HashSet<TKey>();
        foreach (TSource element in source)
        {
            if (knownKeys.Add(keySelector(element)))
            {
                yield return element;
            }
        }
    }
}

【讨论】:

  • Darkknight 的答案很有效,而且更简单,这正是我想要的
  • @user3508811:Darkknight 提供了选项 #2。它不需要任何外部依赖,但有点神秘。对于一个不了解 LINQ 的普通程序员来说,这并不明显。 Jon Skeet 还建议了选项#1,它支持流式传输。根据您的数据有多大,这可能很有用。语法也更简洁。
【解决方案3】:

您必须使用GroupBy(),这将创建一个您可以迭代的IEnumerable&lt;IGrouping&lt;TKey, TElement&gt;&gt;。您可以使用 group.First() 或通过组上的某种聚合函数访问记录。

【讨论】:

  • 你能提供一个样本来说明你的意思吗,那真的很有帮助
  • 查看 DarkKnight 的回答。
【解决方案4】:

你可以使用多个链式 wheres 。如果您正在进行 db 调用,请确保它是 IQueryable。下面是一个例子

List<SomeClass> c = new List<SomeClass>();
 var result = c.Where(x => x.ID == 4).Distinct().Where(y => y.Name == "foo").Distinct();

【讨论】:

    【解决方案5】:

    您可以实现IEqualityComparer 并在Distinct() 方法中使用默认值。如果您示例实现DataTestPlansComparer,您可以使用如下示例:

    DataTestPlans_DataID_ProductID = DataTestPlans.Where(c => c.DataID == DataID_ProductID_Record.DataID && c.ProductID == DataID_ProductID_Record.ProductID).Distinct(new DataTestPlansComparer());
    

    注意,您的自定义比较器应作为参数传递给Distinct() 方法。

    你的情况可以是:

            public class DataTestPlanComparer : IEqualityComparer<DataTestPlan>
            {
                public bool Equals(DataTestPlan x, DataTestPlan y)
                {
    
                    if (Object.ReferenceEquals(x, y)) return true;
    
                    if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                        return false;
    
                    return x.DataID == y.DataID && x.ProductID == y.ProductID;
                }
    
    
                public int GetHashCode(DataTestPlan dataTestPlan)
                {
                    if (Object.ReferenceEquals(dataTestPlan, null)) return 0;
    
                    int hashDataTestPlanDataID = dataTestPlan.DataID == null ? 0 : dataTestPlan.DataID.GetHashCode();
    
                    int hashDataTestPlanProductID = dataTestPlan.ProductID.GetHashCode();
    
                    return hashDataTestPlanDataID ^ hashDataTestPlanProductID;
                }
            }
    

    请按照MSDN 指南实施IEqualityComparer。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-26
      • 1970-01-01
      相关资源
      最近更新 更多