Правильная организация и трансформация объектов - PullRequest
0 голосов
/ 07 февраля 2020

У меня сложный запрос SQL, который возвращает таблицу, подобную этой:

enter image description here

У меня есть этот объект, описанный - 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; }
}

Особенностью набора этих объектов является то, что они сгруппированы по первым трем полям (если они одинаковые), остальные поля разные.

Моя задача - реализовать иерархическое представление этих данных, которое должно выглядеть примерно так:

enter image description here

Я решил для себя что мой последний объект вида должен иметь следующую структуру - 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 : Могу ли я написать запрос LINQ, который преобразует IEnumerable<Invoice> в Dictionary<InvoiceHeader, List<InvoiceHierarchi>> и как это сделать правильно?

Вопрос № 2 : В общем, я выбрал правильный подход для решения своей задачи?

1 Ответ

4 голосов
/ 07 февраля 2020

Можно ли написать запрос LINQ, который преобразует IEnumerable<Invoice> в Dictionary<InvoiceHeader, List<InvoiceHierarchi>> и как это сделать правильно?

Да, вы можете. Попробуйте сгруппировать счета по 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() и Equals() для этого класса или реализацию IEquatable<T> interface

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() с изменяемыми объектами, так как состояние объекта можно изменить, но ha sh остается прежним

Вы можете переопределить GetHashCode() для неизменяемых ссылочных типов. В целом, для изменяемых ссылочных типов вы должны переопределять GetHashCode() только в том случае, если:

Вы можете вычислить код ha sh из полей, которые не являются изменяемыми;

Вы можете гарантировать, что код ha sh изменяемого объекта не изменится, пока объект содержится в коллекции, которая использует свой код ha sh.

В противном случае вы можете подумать, что изменяемый объект потерян в таблице ha sh.

Итак, лучше сделать любые (или все) свойства InvoiceHeader доступными только для чтения, инициализировать их из конструктора и использовать для получения кода ha sh *

...