C # LINQ Group по нескольким полям с пользовательскими свойствами - PullRequest
0 голосов
/ 15 сентября 2018

Я пытаюсь составить список нескольких свойств с помощью 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 было только две строки, но каждый раз он возвращает мне весь список.

Когда я группирую только по единице, это прекрасно работает!

Я попытался сгруппировать первые две строки моего списка с помощью какого-то специального запроса 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;
    }

}

РЕДАКТИРОВАТЬ2: Вот мой «реальный» код:

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

Julien.

Ответы [ 3 ]

0 голосов
/ 16 сентября 2018

Прежде всего вы должны добавить конструктор в 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
}
0 голосов
/ 16 сентября 2018

А вот еще одно решение: Метод 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();

Я думаю, что первое решение (создание настраиваемого класса списка) лучше, потому что вы также можете реорганизовать свой код и извлечь из него некоторую логику.

0 голосов
/ 15 сентября 2018

Вот одно из решений. Это работает для примера кода, который вы написали. Но для этого нужно немного поработать:

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