0

I am having troubles with IEnumerable. _entities is IQueryable from EF Core DbContext. _result is IEnumerable.

This code does not work; _result remains unsorted.

var _result = _entities.Select(s => this.IMapper.Map<Models.role, DTO.role>(s));

foreach (var _role in _result)
{
    _role.permissions = _role.permissions.OrderBy(o => o.module).ThenBy(o => o.feature).ToArray();
}

return _result;

This code does work; _result is sorted.

var _result = _entities.Select(s => this.IMapper.Map<Models.role, DTO.role>(s)).ToArray();

foreach (var _role in _result)
{
    _role.permissions = _role.permissions.OrderBy(o => o.module).ThenBy(o => o.feature).ToArray();
}

return _result;

Can anyone explain?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Luke1988
  • 1,850
  • 2
  • 24
  • 42
  • You say that _result remains unsorted, yet you don't have any code that sorts _result. You are sorting another list inside the _result list, is this what you mean? – Justin Harvey Feb 26 '18 at 15:24
  • @Justin: Indeed. My bad, unclear post. _result's property `permission` remains unsorted. – Luke1988 Feb 26 '18 at 15:26
  • `IEnumerable` is a cornerstone interface, but it's best to think of `IEnumerable`s as *projections* of a collection, rather than collections themselves. If you want to somehow mutate the collection or its items, consider first transforming the projection to a collection type (list, etc.) – Kevin Fichter Feb 26 '18 at 15:30
  • Aside: You might be better off putting this code into the Mapper itself so that every mapping to a DTO.role comes with sorted permissions. That would also solve your issue of the Enumerable being evaluated twice (or more). – Ian Mercer Feb 26 '18 at 15:31

1 Answers1

0

The reason is that LINQ evaluates the Select on the iteration of the IEnumerable and on EACH iteration of the IEnumerable.

This means that if you perform changes on the items returned from the Select those object will be thrown away, and on the next iteration, you will bet a new object with the original data.

Putting ToArray will cause the result of Select to be stored in a collection, so changes you perform on the objects will work as expected.

To demonstrate this behavior you can see the output if you add some Console.WriteLine:

class Item
{
    public int Value { get; set; }
    static int id = 0;
    public int Id { get; } = id++; // generate next id every time
}
static void Main(string[] args)
{
    var range = new int[] { 1, 2, 3 }
        .Select(i => new Item { Value = i });

    foreach (var item in range)
    {
        Console.WriteLine(item.Value + "- id:" + item.Id);
        // Will output 
        //1- id:0
        //2- id:1
        //3- id:2
    }
    foreach (var item in range)
    {
        Console.WriteLine(item.Value + "- id:" + item.Id);
        // Will output 
        //1- id:3
        //2- id:4
        //3- id:5
    }
}
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • So it is a combination of IEnumerable AND Select or default behaviour of IEnumerable? – Luke1988 Feb 26 '18 at 15:23
  • @Luke1988 IEnumerable just describes how you can iterate, how the items in the iteration are built is up to the implementation of IEnumerable. `Select` is lazy and creates objects when needed and does not keep any objects in memory. If you iterate over a list the behavior is different since that iteration returns the same objects every time – Titian Cernicova-Dragomir Feb 26 '18 at 15:25