deCoder
deCoder

Reputation: 3

OrderBy on nested collections

I'm trying to sort this complex object:

Order _sut = new Order
{
    OrderDataArray = new[]
    {
        new OrderData
        {
            OrderHeaderArray = new[]
            {
                new OrderHeader
                {
                    SequenceNumber = 1,
                    OrderPositionArray = new[]
                    {
                        new OrderPositions
                        {
                            LineNumber = 3
                        },
                        new OrderPositions
                        {
                            LineNumber = 2
                        },
                        new OrderPositions
                        {
                            LineNumber = 1
                        }
                    }
                }
            }
        }
    }
};

Using the code:

[Fact]
public void Sorts_By_Sequence_Number()
{
    var ordered = _sut.OrderDataArray
        .OrderBy(o => o.OrderHeaderArray
            .OrderBy(a => a.OrderPositionArray
                .OrderBy(p => p.LineNumber)))
        .ToArray();

    _sut.OrderDataArray = ordered;
    OutputHelper(_sut);
}

I don't understand why this doesn't work, meaning the sorting routine simply keeps initial order of LineNumber object. I've tried various things with OrderBy, but looks like it doesn't sort.

EDIT

Thank you for responses, both are correct. I have accepted poke's response as it provides a bit more detailed information on the inner workings of the OrderBy method. Basically I was missing the assignment within the loop, I was trying to sort all objects at once.

Upvotes: 0

Views: 5321

Answers (2)

poke
poke

Reputation: 388463

You should consider what OrderBy does. It orders a collection by the value you determine in the lambda expression and then returns an enumerable.

Your outer call is good for that:

_sut.OrderDataArray.OrderBy(o => something).ToArray();

You sort by something, and then convert the result into a (then sorted) array. There are two things that matter here: First of all, at least in your example, there is only one object in OrderDataArray, so there is no sort happening. Second, it depends on the return value of something how those objects are sorted.

So in that case, what is something? It’s the following:

o.OrderHeaderArray.OrderBy(a => somethingElse)

So regardless of somethingElse, what does this return? An IEnumerable<OrderHeader>. How do multiple enumerables compare to each other? They are not really comparable; and they especially don’t tell you anything about the order based on their content (you’d have to enumerate it first). So essentially, you order that OrderHeaderArray by “something else”, use the result which does not tell you anything about an order as the key to order the OrderDataArray. Then, you throw the sorted OrderHeaderArray away.

You do the same exactly one level deeper with the OrderPositionArray, which again will not do anything useful. The only actual useful ordering happens to the OrderPositionArray itself but that result is again thrown away.


Now, if you want to order your structure, you should do so properly, by reassinging the sorted structure to the array. So at some point, you would have to do the following:

a.OrderPositionArray = a.OrderPositionArray.OrderBy(p => p.LineNumber).ToArray();

But apart from the OrderPositionArray itself and the OrderHeader, you don’t really have anything that can be sorted (because you can’t really sort a collection by the order of a subcollection). So you could could solve it like this:

foreach (OrderData data in _sut.OrderDataArray)
{
    foreach (OrderHeader header in data.OrderHeaderArray)
    {
        header.OrderPositionArray = header.OrderPositionArray.OrderBy(p => p.LineNumber).ToArray();
    }

    data.OrderHeaderArray = data.OrderHeaderArray.OrderBy(h => h.SequenceNumber).ToArray();
}

Instead of Linq, you can also sort the arrays in-place, which maybe makes it a bit nicer since you are not creating new inner arrays:

var c = Comparer<int>.Default;
foreach (OrderData data in _sut.OrderDataArray)
{
    foreach (OrderHeader header in data.OrderHeaderArray)
    {
        Array.Sort(header.OrderPositionArray, new Comparison<OrderPositions>((x, y) => c.Compare(x.LineNumber, y.LineNumber)));
    }

    Array.Sort(data.OrderHeaderArray, new Comparison<OrderHeader>((x, y) => c.Compare(x.SequenceNumber, y.SequenceNumber)));
}

Upvotes: 2

Yeldar Kurmangaliyev
Yeldar Kurmangaliyev

Reputation: 34244

Here,

var ordered = _sut.OrderDataArray.OrderBy(o => ...

expects the Func<OrderData, TKey>, and the values will be sorted by comparing the result of this function execution.

At the same time, you pass the result of another OrderBy, which is IOrderedEnumerable. It simply doesn't make much sense.

In order to sort all the nested collections, you can do the following:

foreach (var orderData in _sut.OrderDataArray)
{
  foreach (var orderHeader in orderData.OrderHeaderArray)
  {
    orderHeader.OrderPositionArray = orderHeader.OrderPositionArray
        .OrderBy(x => x.LineNumber).ToArray();
  }

  orderData.OrderHeaderArray = orderData.OrderHeaderArray
      .OrderBy(x => x.SequenceNumber).ToArray();
}

_sut.OrderDataArray = _sut.OrderDataArray
    .OrderBy(x => ...).ToArray();

It sorts every OrderPositionArray item by its items' LineNumber.
It sorts every OrderHeaderArray by headers' SequenceNumber.

However, it is pretty unclear how you want to sort _sut.OrderDataArray - it is marked as x => ... in the example.
It has no comparable properties which can be used for sorting.

Upvotes: 0

Related Questions