0

I hope this is not a duplicate but I wasn't able to find an answer on this. It either seems to be an undesired behavior or missing knowledge on my part.

I have a list of platform and configuration objects. Both contains a member string CodeName in it. The list of CodeNames look like this:

dbContext.Platforms.Select(x => x.CodeName) => {"test", "PC", "Nintendo"}
dbContext.Configurations.Select(x => x.CodeName) => {"debug", "release"}

They are obtained from a MySQL database hence the dbContext object.

Here is a simple code that I was to translate in LINQ because 2 foreach are things of the past:

var choiceList = new List<List<string>>();
foreach (Platform platform in dbContext.Platforms.ToList())
{
    foreach (Configuration configuration in dbContext.Configurations.ToList())
    {
        choiceList.Add(new List<string>() { platform.CodeName, configuration.CodeName });
    }
}

This code gives my exactly what I want, keeping the platform name first which looks like :

var results = new List<List<string>>() {
{"test", "debug"},
{"test", "release"},
{"PC", "debug"}
{"PC", "release"}
{"Nintendo", "debug"}
{"Nintendo", "release"}};

But if I translate that to this, my list contains item in a different order:

var choiceList = dbContext.Platforms.SelectMany(p => dbContext.Configurations.Select(t => new List<string>() { p.CodeName, t.CodeName })).ToList();

I will end up with this, where the platform name isn't always first, which is not what is desired:

var results = new List<List<string>>() {
{"debug", "test"},
{"release", "test"},
{"debug", "PC"}
{"PC", "release"}
{"debug", "Nintendo"}
{"Nintendo", "release"}};

My question is, is it possible to obtain the desired result using LINQ?

Let me know if I'm not clear or my question lacks certain details. Thanks

EDIT: So Ivan found the explanation and I modified my code in consequence. In fact, only the Enumerable in front of the SelectMany needed the .ToList(). I should also have mentioned that I was stuck with the need of a List>.

Thanks everyone for the fast input, this was really appreciated.

christ.s
  • 181
  • 1
  • 10
  • What if you project to a strong type (`new { Platform = ..., Configuration = ...}` instead of a list of strings? And can you post the SQL that's generated? It seems very odd that the order would be inconsistent. – D Stanley Oct 03 '16 at 19:45
  • @DStanley What ordered structure would you expect the code to be generated to in SQL? I'd expect it to create a new bag with those two elements, as that's pretty much the only collection SQL really uses, and it's inherently unordered unless explicitly ordered. – Servy Oct 03 '16 at 19:52
  • 1
    try your 2 foreach loops without the ToList at end, you will see the same behaviour. – sachin Oct 03 '16 at 20:18

3 Answers3

1

Rather than projecting it out to an array, project it out two a new object with two fields (potentially an anonymous object) and then, if you need it, project that into a two element array after you have retrieved the objects from the database, if you really do need these values in an array.

Servy
  • 202,030
  • 26
  • 332
  • 449
1

When you use

var choiceList = dbContext.Platforms.SelectMany(p => dbContext.Configurations.Select(t => new List<string>() { p.CodeName, t.CodeName })).ToList();

it's really translated to some SQL query where the order of the returned records in not defined as soon as you don't use ORDER BY.

To get the same results as your nested loops, execute and materialize both queries, and then do SelectMany in memory:

var platforms = dbContext.Platforms.ToList();
var configurations = dbContext.Configurations.ToList();
var choiceList = platforms.SelectMany(p => configurations,
    (p, c) => new List<string>() { p.CodeName, c.CodeName })
    .ToList();
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • I think the 2 foreach loops that the OP has, seem to be working because of the ToList he has there. – sachin Oct 03 '16 at 20:19
  • @sachin Indeed. – Ivan Stoev Oct 03 '16 at 20:23
  • @IvanStoev Yeah I remember reading something about this elsewhere. I never thought it could apply in my case because of my list of strings. So I just did a couple of tests and you are completely right. In fact, if we want to be precise, only the Enumerable in front of the SelectMany needs the ToList. – christ.s Oct 04 '16 at 12:08
  • 1
    @christ.s Actually the outer query (`platforms`) could be just `AsEnumerable()`, but the inner query (`configurations`) really needs `ToList` and store to a variable in advance, otherwise it will execute the db query for each outer record, which makes no sense (and the performance will suffer). – Ivan Stoev Oct 04 '16 at 13:02
0

Try this-

var platforms= dbContext.Platforms.Select(x=>x.CodeName);
var configurations=dbContext.Configurations.Select(x=>x.CodeName);

var mix=platforms.SelectMany(num => configurations, (n, a) => new { n, a });

If you want to learn more in detail- Difference between Select and SelectMany

Community
  • 1
  • 1
Ashutosh
  • 1,000
  • 15
  • 39
  • Not exactly what I wanted and I have edited my question accordingly. But it explains the anonymous type state in other comments. Could be really interesting in other cases, tho. Thanks. – christ.s Oct 04 '16 at 12:20