正确组织和转换对象

Proper organization and transformation of objects

我有一个复杂的 SQL 查询 returns 一个 table 像这样:

我用这个对象描述了它 - IEnumerable<Invoice> 其中:

public partial class Invoice
{
    public string DocumentNumber { get; set; }
    public DateTime? DocumentDate { get; set; }
    public string DocumentReference { get; set; }
    public string SerialNumber { get; set; }
    public string ProductCode { get; set; }
    public string Description { get; set; }
    public string Certificate { get; set; }
}

这些对象的集合的特殊之处在于它们按前三个字段分组(如果它们相同),其余字段不同。

我的任务是实现此数据的分层表示,它应该如下所示:

我自己决定我的最终视图对象应该具有以下结构 - Dictionary<InvoiceHeader, List<InvoiceHierarchi>> 其中:

public class InvoiceHeader
{
   public string DocumentNumber { get; set; }
   public DateTime? DocumentDate { get; set; }
   public string DocumentReference { get; set; }
}

public class InvoiceHierarchi
{
   public string SerialNumber { get; set; }
   public string ProductCode { get; set; }
   public string Description { get; set; }
   public string Certificate { get; set; }
}

问题 #1:我可以编写将 IEnumerable<Invoice> 转换为 Dictionary<InvoiceHeader, List<InvoiceHierarchi>> 的 LINQ 查询吗?如何正确执行?

问题 #2:总的来说,我是否选择了正确的方法来解决我的任务?

Can I write LINQ query that converts IEnumerable<Invoice> to Dictionary<InvoiceHeader, List<InvoiceHierarchi>> and how to do it right?

是的,你可以。尝试按 InvoiceHeader 分组发票并使用 ToDictionary 方法

转换结果
var invoices = new List<Invoice>();
var result = invoices.GroupBy(i => new InvoiceHeader
{
    DocumentDate = i.DocumentDate,
    DocumentNumber = i.DocumentNumber,
    DocumentReference = i.DocumentReference
}).ToDictionary(g => g.Key, g => g.Select(i => new InvoiceHierarchi
{
    Certificate = i.Certificate,
    SerialNumber = i.SerialNumber,
    ProductCode = i.ProductCode,
    Description = i.Description
}).ToList());

另一种选择是使用ToLookup方法

var anotherResult = invoices.ToLookup(i => new InvoiceHeader
    {
        DocumentDate = i.DocumentDate,
        DocumentNumber = i.DocumentNumber,
        DocumentReference = i.DocumentReference
    },
    i => new InvoiceHierarchi
    {
        Certificate = i.Certificate,
        SerialNumber = i.SerialNumber,
        ProductCode = i.ProductCode,
        Description = i.Description
    });

要正确使用 InvoiceHeader 作为 Dictionary 键,您应该覆盖 GetHashCode() and Equals() methods for this class, or implelement IEquatable<T> 接口

public class InvoiceHeader : IEquatable<InvoiceHeader>
{
    public string DocumentNumber { get; set; }
    public DateTime? DocumentDate { get; set; }
    public string DocumentReference { get; set; }

    public override int GetHashCode()
    {
        return DocumentNumber.GetHashCode() ^ DocumentDate.GetHashCode() ^ DocumentReference.GetHashCode();
    }

    public bool Equals(InvoiceHeader other)
    {
        if (ReferenceEquals(null, other))
            return false;
        if (ReferenceEquals(this, other))
            return true;

        return DocumentNumber == other.DocumentNumber && 
               Nullable.Equals(DocumentDate, other.DocumentDate) &&
               DocumentReference == other.DocumentReference;
    }

    public override bool Equals(object? obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((InvoiceHeader)obj);
    }
}

你会得到一个警告,在 GetHashCode() 中使用了非只读 属性,因为不建议对可变对象使用 GetHashCode(),因为对象的状态可以被更改,但哈希保持不变

You can override GetHashCode() for immutable reference types. In general, for mutable reference types, you should override GetHashCode() only if:

You can compute the hash code from fields that are not mutable;

You can ensure that the hash code of a mutable object does not change while the object is contained in a collection that relies on its hash code.

Otherwise, you might think that the mutable object is lost in the hash table.

因此,最好将 InvoiceHeader 的任何(或所有)属性设为只读,从构造函数中初始化它们并用于获取哈希码