如何通过 Id 和列表 属性.GroupBy()?

How to .GroupBy() by Id and by list property?

我有这些 类:

public class AlertEvaluation
{
    public string AlertId { get; set; }
    public ICollection<EvaluatedTag> EvaluatedTags { get; set; }
    public string TransactionId { get; set; }
    public EvaluationStatus EvaluationStatus { get; set; }
    public DateTime EvaluationDate { get; set; }
}

public class EvaluatedTag
{
    public string Id { get; set; }
    public string Name { get; set; }
}

我想获得按 AlertIdEvaluatedTags 分组的警报评估列表,这意味着我想比较和分组不仅具有相同 AlertId,但也有相同的列表 EvaluatedTags。 (而且还能及时得到最后的评价)

我试过这个:

var evaluationsGroupedAndOrdered = evaluations.GroupBy(x => new { x.AlertSettingId, x.EvaluatedLabels })
.Select(x => x.OrderByDescending(z => z.EvaluationDate ).FirstOrDefault()).ToList();

当然,像这样比较列表属性是行不通的。

我在 GroupBy 中读到一些关于添加相等比较器的内容,这意味着比较对象内部的列表对吧?但是我不确定如何以正确的方式实现它。

我试过了(基于):

        public class AlertEvaluationComparer : IEqualityComparer<AlertEvaluation>
    {
        public bool Equals(AlertEvaluation x, AlertEvaluation y)
        {
            return x.AlertId == y.AlertId && x.EvaluatedTags.OrderBy(val => val.Name).SequenceEqual(y.EvaluatedTags.OrderBy(val => val.Name));
        }

        public int GetHashCode(AlertSettingEvaluation x)
        {
            return x.AlertId.GetHashCode() ^ x.EvaluatedTags.Aggregate(0, (a, y) => a ^ y.GetHashCode());
        }
    }

但也没有用。可能是因为我的 EvaluatedTags 列表不是字符串列表,而是单个对象的列表。

有人对此有好的解决方案吗?

尝试一下,稍微远离 Linq,以便在利用排序的同时更轻松地一次构建一个组:

// Build groups by using a combination of AlertId and EvaluatedTags hashcode as group key
var groupMap = new Dictionary<string, SortedSet<AlertEvaluation>>();
foreach (var item in evals)
{
    var combinedKey = item.AlertId + EvaluatedTag.GetCollectionHashCode(item.EvaluatedTags);
    if (groupMap.TryGetValue(combinedKey, out SortedSet<AlertEvaluation>? groupItems))
    {
        // Add to existing group
        groupItems.Add(item);
    }
    else
    {
        // Create new group
        groupMap.Add(combinedKey, new SortedSet<AlertEvaluation> { item });
    }
}

// Get a list of groupings already sorted ascending by EvaluationDate
List<SortedSet<AlertEvaluation>>? groups = groupMap.Values.ToList();

这假定 classes 实现 IComparable 和 Equals/GetHashCode 以促进排序:

public class AlertEvaluation : IComparable<AlertEvaluation>
{
    public string AlertId { get; set; }
    public ICollection<EvaluatedTag> EvaluatedTags { get; set; }
    public string TransactionId { get; set; }
    public EvaluationStatus EvaluationStatus { get; set; }
    public DateTime EvaluationDate { get; set; }

    // Used by SortedSet
    public int CompareTo(AlertEvaluation? other)
    {
        if (other is null)
        {
            return 1;
        }

        return EvaluationDate.CompareTo(other.EvaluationDate);
    }
}

public class EvaluatedTag : IEquatable<EvaluatedTag?>
{
    public string Id { get; set; }
    public string Name { get; set; }

    public bool Equals(EvaluatedTag? other) => other != null && Id == other.Id && Name == other.Name;

    public override int GetHashCode() => HashCode.Combine(Id, Name);

    // Helper to get a hash of item collection
    public static int GetCollectionHashCode(ICollection<EvaluatedTag> items)
    {
        var code = new HashCode();
        foreach (var item in items.OrderBy(i => i.Id))
        {
            code.Add(item);
        }
        return code.ToHashCode();
    }
}

顺便说一下,我在 .NET Core 中使用了新奇的 HashCode class 来覆盖哈希码。

比较两个列表的典型方法是使用 System.Linq 扩展方法 SequenceEquals。此方法 returns 如果两个列表以相同的顺序包含相同的项目则为真。

为了使这个与 IEnumerable<EvaluatedTag> 一起工作,我们需要有一种方法来比较 EvaluatedTag class 的实例是否相等(确定两个项目是否相同) 和排序(因为列表需要以相同的顺序排列它们的项目)。

要做到这一点,我们可以覆盖 EqualsGetHashCode 并实现 IComparable<EvaluatedTag>(为了完整性也可以做 IEquatable<EvaluatedTag>):

public class EvaluatedTag : IEquatable<EvaluatedTag>, IComparable<EvaluatedTag>
{
    public string Id { get; set; }
    public string Name { get; set; }

    public int CompareTo(EvaluatedTag other)
    {
        if (other == null) return -1;
        var result = string.CompareOrdinal(Id, other.Id);
        return result == 0 ? string.CompareOrdinal(Name, other.Name) : result;
    }

    public bool Equals(EvaluatedTag other)
    {
        return other != null &&
               string.Equals(other.Id, Id) &&
               string.Equals(other.Name, Name);
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as EvaluatedTag);
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode() * 17 +
               Name.GetHashCode() * 17;
    }
}

现在我们可以在您问题中的自定义比较器中使用它来排序和比较 EvaluatedTags:

public class AlertEvaluationComparer : IEqualityComparer<AlertEvaluation>
{
    // Return true if the AlertIds are equal, and the EvaluatedTags 
    // contain the same items (call OrderBy to ensure they're in 
    // the same order before calling SequenceEqual).
    public bool Equals(AlertEvaluation x, AlertEvaluation y)
    {
        if (x == null) return y == null;
        if (y == null) return false;
        if (!string.Equals(x.AlertId, y.AlertId)) return false;
        if (x.EvaluatedTags == null) return y.EvaluatedTags == null;
        if (y.EvaluatedTags == null) return false;
        return x.EvaluatedTags.OrderBy(et => et)
            .SequenceEqual(y.EvaluatedTags.OrderBy(et => et));
    }

    // Use the same properties in GetHashCode that were used in Equals
    public int GetHashCode(AlertEvaluation obj)
    {
        return obj.AlertId?.GetHashCode() ?? 0 * 17 +
               obj.EvaluatedTags?.Sum(et => et.GetHashCode() * 17) ?? 0;
    }
}

最后我们可以将您的 AlertEvaluationComparer 传递给 GroupBy 方法来对我们的项目进行分组:

var evaluationsGroupedAndOrdered = evaluations
    .GroupBy(ae => ae, new AlertEvaluationComparer())
    .OrderBy(group => group.Key.EvaluationDate)
    .ToList();