-1

I have a LINQ select query which fetches a list of books:

var books = from b in _context.Books
            where b.Available && b.BookType == bookType
            orderby b.Title
            select new BookViewModel
            {
                Title = b.Title,
                Description = b.Description,
                Id = b.Id,
                SortOrder = ?
            };

BookViewModel has a SortOrder property which I want to set in the query. The first book in the list would have SortOrder = 1, the second book would have 2, etc. It's essentially just the index of the book in the returned list.

Is there a way to set this within the query? I understand I could do another linq query once I have the list, but this seems inefficient. I don't properly understand the innards of a linq select, but given it's sorted by the orderby I don't see why there wouldn't exist some way to set the index of the element.

Drew
  • 1,076
  • 1
  • 18
  • 34
  • 1
    @Stackberg `SortOrder` is not part of the Book entity it's a value the OP wants to calculate based on the results of the query. – juharr Sep 24 '19 at 18:47
  • 1
    You just need to use the LINQ method syntax instead of query syntax. With that, you can use the `Select()` overload that gives you the index of the enumerated value. See marked duplicate. – Peter Duniho Sep 24 '19 at 18:51
  • Thanks @PeterDuniho, though I can't seem to find anything in the linked duplicate that talks specifically about the `.Select((x, index) =>` syntax that Rufus' answer explains. – Drew Sep 24 '19 at 18:57
  • 1
    Look at the highest-voted answer: https://stackoverflow.com/a/11437562 (though, you could use the accepted answer too, just follow its advice and use your own indexer when enumerating the orderby results, to create the final select for `BookViewModel`) – Peter Duniho Sep 24 '19 at 18:58
  • Ah, it was linked to [this post](https://stackoverflow.com/questions/2908029/linq-to-sql-orderby-thenby) when I saw it marked as duplicate. Thanks – Drew Sep 24 '19 at 18:59

1 Answers1

2

With a Select statement, you can select both the index and the item, so you should be able to do something like this:

var books = _context.Books
    .Where(book => book.Available && book.BookType == bookType)
    .OrderBy(book => book.Title)
    .ToList()
    .Select((book, index) => new BookViewModel
    {
        Title = book.Title,
        Description = book.Description,
        Id = book.Id,
        SortOrder = index + 1
    });
Rufus L
  • 32,853
  • 5
  • 25
  • 38
  • I think the OP actually would want `SortOrder = index + 1` to get the first book to have a `SortOrder` of 1 if the magnitude actually matters. – juharr Sep 24 '19 at 18:51
  • @juharr Thanks! Updated sample. – Rufus L Sep 24 '19 at 18:52
  • Sweet, this is just what I was looking for. Thanks! – Drew Sep 24 '19 at 18:52
  • It turns out including index in the select called on `IOrderedQueryable` is not supported: `This overload of the method 'System.Linq.Queryable.Select' is currently not supported.` I'll have to find an alternative solution. Unfortunately there are no answers for [this post](https://stackoverflow.com/questions/20427440/how-can-i-get-the-index-of-an-item-in-an-iorderedqueryable) . – Drew Sep 24 '19 at 20:18
  • 1
    I'm not at my computer, but does calling `ToList()` resolve the issue? It should force the database query to execute and return the items, which can then be accessed by index. See edited sample above. – Rufus L Sep 24 '19 at 20:22
  • Nice, that works, thanks. It's annoying that there has to be an extra performance hit by calling `ToList()`, but I guess if accessing the index on an `IQueryable` were possible it would be doing something like that under the hood anyway. – Drew Sep 24 '19 at 20:31
  • It turns out that calling `.ToList()` in the middle of the query like that results in a massive performance hit. I've decided to just call `ToList()` after the select and then loop through and set `SortOrder` for each element. – Drew Sep 25 '19 at 19:59
  • Ok, that makes sense. – Rufus L Sep 25 '19 at 20:02