C#LINQ Group通过自定义属性的多个字段

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#LINQ Group通过自定义属性的多个字段相关的知识,希望对你有一定的参考价值。

我正在尝试使用Linq聚合多个属性的列表。我的第二个字段是字符串列表+其他字符串列表。

这是我的代码示例:

using System;
using System.Collections.Generic;
using System.Linq;

public class RefValueData
{
    public int ReferenceId { get; set; }
    public int SiteId { get; set; }
    public string SiteName { get; set; }
    public string Code { get; set; }
    public decimal UnitPoints { get; set; }
    public List<TranslationData> Texts { get; set; }
}

public class TranslationData
{
    public string Text { get; set; }
    public List<TranslationValue> Translations { get; set; }
}

public class TranslationValue
{
    public string Culture { get; set; }
    public string TranslationText { get; set; }
}



public class Program
{
    public static void Main()
    {       
        var values = new List<RefValueData>
            {
                new RefValueData(){
                    ReferenceId = 4,
                    Code = "Code",
                    SiteId = 2,
                    SiteName = "Paris",
                    UnitPoints = 50,
                    Texts = new List<TranslationData>
                    {
                        new TranslationData(){
                            Text = "A",
                            Translations = new List<TranslationValue>
                            {
                                new TranslationValue() { Culture = "FR-fr", TranslationText = "Bonjour" },
                                new TranslationValue() { Culture = "ES-es", TranslationText = "Hola" },
                            }
                        }
                    }
                },
                new RefValueData()
                {
                    ReferenceId = 5,
                    Code = "Code",
                    SiteId = 4,
                    SiteName = "Lyon",
                    UnitPoints = 50,
                    Texts = new List<TranslationData>
                    {
                        new TranslationData(){
                            Text = "A",
                            Translations = new List<TranslationValue>
                            {
                                new TranslationValue() { Culture = "FR-fr", TranslationText = "Bonjour" },
                                new TranslationValue() { Culture = "ES-es", TranslationText = "Hola" },
                            }
                        }
                    }
                },
                new RefValueData()
                {
                    ReferenceId = 6,
                    Code = "Code",
                    SiteId = 3,
                    SiteName = "Paris",
                    UnitPoints = 52,
                    Texts = new List<TranslationData>
                    {
                        new TranslationData(){
                            Text = "B",
                            Translations = new List<TranslationValue>
                            {
                                new TranslationValue() { Culture = "FR-fr", TranslationText = "Salut" },
                                new TranslationValue() { Culture = "ES-es", TranslationText = "Ciao" },
                            }
                        }
                    }
                }
            };


        var values2 = values
            .Distinct()
            .GroupBy(x => new
                     {
                         x.UnitPoints,
                         x.Texts
                     })
            .Select(x => new
                    {
                        x.Key.UnitPoints,
                        Texts = x.Key.Texts,
                        Site = x.Select(y=>y.SiteName)
                    })
            .ToList();
        Console.WriteLine(values2.Count);
    }
}

我想在我的values2列表中只有两行,但每次它都返回整个列表。

当我只按Unit Point分组时,它的工作非常棒!

我尝试使用一些自定义Linq查询对列表的前两行进行分组,但它根本不起作用...

任何帮助/建议非常感谢:)!

编辑:我也尝试过像这样的Equals方法的覆盖,但我无法使其工作:

public class TranslationValue
{
    public string Culture { get; set; }
    public string TranslationText { get; set; }

    public override bool Equals(object obj)
    {
        var other = obj as TranslationValue;

        if (other == null)
        {
            return false;
        }

        return Culture == other.Culture && TranslationText == other.TranslationText;
    }

    public override int GetHashCode()
    {
        var hashCode = -2095322044;
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Culture);
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(TranslationText);
        return hashCode;
    }
}

public class TranslationData
{
    public string Text { get; set; }
    public List<TranslationValue> Translations { get; set; }

    public override bool Equals(object obj)
    {
        var other = obj as TranslationData;

        if (other == null)
        {
            return false;
        }
        return Text == other.Text && Translations.SequenceEqual(other.Translations);
    }

    public override int GetHashCode()
    {
        var hashCode = -1551681861;
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Text);
        hashCode = hashCode * -1521134295 + EqualityComparer<List<TranslationValue>>.Default.GetHashCode(Translations);
        return hashCode;
    }

}

EDIT2:这是我的“真实”代码:

var values = referenceValues.Select(value => new
{
    ReferenceId = value.ReferenceId,
    SiteId = value.Reference.SiteId ?? -1,
    SiteName = value.Reference.Site.Name ?? allSitesName,
    Code = value.Code,
    UnitPoints = value.UnitPoints,
    Texts =     // Type: List<TranslationData> , but it will not use the TranslationDataList class that normally work thanks to your help
        value.ReferenceValueTexts.Select(text =>
            new TranslationData
            {
                Text = text.Text, // string
                Translations = text.TranslationDataValues.Select(translation => // List<TranslationValue>
                new TranslationValue {
                    Culture = translation.Language.StrCulture,
                    TranslationText = translation.Value
                }).ToList()
            }).ToList()
}

朱利安。

答案

这是一个解决方案。它适用于您编写的示例代码。但它需要一些工作才能变得健壮:

// and also change the declarations in the main method to: new TranslationDataList 
public class TranslationDataList : List<TranslationData>
{
    public override int GetHashCode()
    {
        int hash = 13;
        // string.GetHashCode() is not reliable. This should be an algorithm that returns the same value for two different lists that contain the same data
        foreach (var data in this)
            hash = (hash * 7) + data.Text.GetHashCode(); 
        return hash;

    }

    public override bool Equals(object obj)
    {
        var other = obj as TranslationDataList;
        if (other == null) return false;
        if (other.Count != Count) return false;
        // write the equality logic here. I don't know if it's ok!
        for (int i = 0; i < other.Count; i++)
            if (other[i].Text != this[i].Text)
                return false;
        return true;
    }
}
另一答案

首先,您应该向TranslationDataList添加构造函数:

public class TranslationDataList : List<TranslationData>
{
    public TranslationDataList(IEnumerable<TranslationData> translationData)
        : base(translationData)
    { }
    // other members ...
}

现在,您可以在查询中使用TranslationDataList:

var values = referenceValues.Select(value => new
{
    ReferenceId = value.ReferenceId,
    SiteId = value.Reference.SiteId ?? -1,
    SiteName = value.Reference.Site.Name ?? allSitesName,
    Code = value.Code,
    UnitPoints = value.UnitPoints,
    Texts = new TranslationDataList( value.ReferenceValueTexts.Select(text =>
        new TranslationData
        {
            Text = text.Text, // string
            Translations = text.TranslationDataValues.Select(translation => // List<TranslationValue>
            new TranslationValue {
                Culture = translation.Language.StrCulture,
                TranslationText = translation.Value
            }).ToList()
        })); // don't ToList() here anymore
}
另一答案

这是另一种解决方案:GroupBy方法采用IEqualityComparer,它负责比较分组的项目。但问题是您在分组“GroupBy(x => new {x.UnitPoints,x.Texts})”中使用了匿名类型的键。首先,我们必须创建一个类来发挥关键作用:

public class Key
{
    public Key(decimal unitPoints, List<TranslationData> texts)
    {
        UnitPoints = unitPoints;
        Texts = texts;
    }
    public decimal UnitPoints { get; set; }
    public List<TranslationData> Texts { get; set; }
}

然后我们可以实现比较器:

public class Comparer : IEqualityComparer<Key>
{
    public bool Equals(Key x, Key y)
    {
        if (x.UnitPoints != y.UnitPoints) return false;
        if (!ListsAreEqual(x.Texts, y.Texts)) return false;
        return true;
    }

    private bool ListsAreEqual(List<TranslationData> x, List<TranslationData> y)
    {
        if (x.Count != y.Count) return false;
        for (int i = 0; i < x.Count; i++)
            if (x[i].Text != y[i].Text)
                return false;
        return true;
    }

    public int GetHashCode(Key key)
    {
        int hash = 13;
        hash = (hash * 7) + key.UnitPoints.GetHashCode();
        foreach (var data in key.Texts)
            hash = (hash * 7) + data.Text.GetHashCode();
        return hash;
    }
}

最后这是你的查询的样子:

var values2 = values
    .Distinct()
    .GroupBy(x => new Key(x.UnitPoints, x.Texts), new Comparer())
    .Select(x => new
    {
        x.Key.UnitPoints,
        Texts = x.Key.Texts,
        Site = x.Select(y => y.SiteName)
    }).ToList();

我认为第一个解决方案(创建自定义列表类)更好,因为您还可以重构代码并为其提取一些逻辑。

以上是关于C#LINQ Group通过自定义属性的多个字段的主要内容,如果未能解决你的问题,请参考以下文章

C# linq:在同一属性字段的列表中检查多个条件

LINQ Group使用Lambda通过两个对象属性和SUM,AVG或忽略其他对象属性

C# Linq to sql 实现 group by 统计多字段 返回多字段

Linq中的group by多表多字段,Sum求和

带有表连接、case 语句、计数、group by 子句的 Linq 查询

Azure Insights:通过函数执行链跟踪自定义属性