4

I have two lines of code, one is

AllItems().Where(c => c.Id== id)
          .Select(d => new Quality(d.QualityType)).ToList();

and the other one

AllItems().Where(c => c.Id== id).ToList()
          .Select(d => new Quality(d.QualityType)).ToList();

The only difference is on the second statement ToList() is called after the Where statement. The second statment works just fine.

On the first statement, the default parameterless constructor is hit instead of the constructor with the parameter. so the list is created but the objects in the list are initialized with default values rather than with the d.QualityType.

you can see the full source of the file in question at (Method: GetBestQualityInHistory)

https://github.com/kayone/NzbDrone/blob/master/NzbDrone.Core/Providers/HistoryProvider.cs

**Edit: After further investigation, this seems to be a SubSonic bug, if the Last ToList is replaced by an OrderBy subsonic throws an The construtor 'Void .ctor(NzbDrone.Core.Repository.Quality.QualityTypes, Boolean)' is not supported.

kay.one
  • 7,622
  • 6
  • 55
  • 74
  • You're calling `ToList` in *both* cases here... but in the second case you're calling it *twice*. Why? – Jon Skeet Jun 05 '11 at 19:28
  • sorry, that was just wrong copy past – kay.one Jun 05 '11 at 19:29
  • What type is AllItems? Is it just an in memory list or is it something supplied to you by for example a persistence framework? could be relevant as the persistance framework will be handling the call if no ToList is used. As far as I know .net handles it after tolist (since it is a list afterwards) – thekip Jun 05 '11 at 19:31
  • it's an IQueryable, but its proxied by SubSonic Simple Repository using castle. – kay.one Jun 05 '11 at 19:33
  • @Keivan: And what are you doing with the result afterwards? It sounds like this could easily be a SubSonic bug... it's *always* worth making your LINQ provider clear in a question. – Jon Skeet Jun 05 '11 at 19:37
  • @Keivan: I've just had a look at the full code, and noticed you're calling `OrderBy` and ignoring the results... probably unrelated, but definitely a bug, unless SubSonic is doing *really* weird stuff. LINQ query operators aren't meant to change the source you call them on - they're meant to return a new query with the appropriate modifications (e.g. added ordering). – Jon Skeet Jun 05 '11 at 19:39
  • basically a ToList() is called after, I'll update the question. – kay.one Jun 05 '11 at 19:39
  • @jon, yeah, just noticed that, before Sort was called on the list. which modifies the list itself, when I updated the code I missed that now I'm using Linq's order and should just chain it. thanks for the tip. – kay.one Jun 05 '11 at 19:41
  • I've just tested this with a normal list but this works as expected (calling the specified constructor) so to me this is SubSonic related rather than linq. – thekip Jun 05 '11 at 19:47
  • Yes, just played around with it a little bit, and in some cases subsonic throws and NotSupported exception. – kay.one Jun 05 '11 at 19:50

2 Answers2

4

If SubSonic works in the same way as Entity framework you cannot use constructors with parameters - you must use parameterless constructors and initializers. My very high level explanation of this is that the query is not executed as is - it is translated to SQL and because of that you must use property initializers so that expression tree knows which properties in the projected type should be filled by values. When using constructor with parameters, expression tree doesn't know where the passed parameter belongs to (it doesn't check content of the constructor). The real constructor (parameterless) is called once you execute Tolist and result set is materialized into QuantityType instances.

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • the code was using initializers originally, I changed it to constructor to track down the issue. – kay.one Jun 05 '11 at 19:54
  • Holy wrap. I just tested the use of parameterized constructors in EF 3.5 and I expected you to be wrong... However, you are right. I'm dazzled. EF throws an "Only parameterless constructors and initializers are supported in LINQ to Entities." exception. Is this still the case in EF 4.0? I do this all the time with LINQ to SQL. – Steven Jun 05 '11 at 20:08
  • @Steven: Yes it is same in EFv4 an according to this it should be the same in Linq-to-sql as well but I didn't try it myself. – Ladislav Mrnka Jun 05 '11 at 20:28
  • Here is the link: http://msdn.microsoft.com/en-us/library/bb425822.aspx#linqtosql_topic13 for Linq-to-sql. I forgot to place it in former comment. – Ladislav Mrnka Jun 05 '11 at 20:34
  • Calling a parameterized constructor within a select method has worked with LINQ to SQL from day one; I use it all the time. The only catch is, that L2S prevents you from creating L2S entities yourself inside a `Select`. – Steven Jun 06 '11 at 06:40
0

This isn't really an answer, and I was going to make it a comment, but needed more room for code snippet.

Judging by the SubSonic code, I'm pretty sure that in some cases where you get the "constructor not supported" errors, SS is for some reason trying to parse your new ...() statement into SQL. The offending method is part of the SQL Formatter, and looks like it only handles DateTime:

    protected override NewExpression VisitNew(NewExpression nex)
    {
        if (nex.Constructor.DeclaringType == typeof(DateTime))
        {
            // ...omitted for brevity...
        }
        throw new NotSupportedException(string.Format("The construtor '{0}' is not supported", nex.Constructor));
    }

I think that would normally be hit if you did something like:

someData .Where(data => data.CreatedDate <= new DateTime(2011, 12, 01)) .Select(data => data)

Then that newDateTime would be translated to SQL. So however you change the Linq to get that exception, I think that is what is happening. I assume this is because if you add an .OrderBy() after the .Select() then you are no longer calling the OrderBy on the IQueryable of whatever AllItems() returns, but instead trying to order by what is returned by the .Select(), which is the enumerable of new Quality obejcts, so SS probably tries to turn all that into SQL.

I'm wondering if it would work right if you reversed it?

AllItems().Where(c => c.Id== id)
      .OrderBy(d => d.QualityType)
      .Select(d => new Quality(d.QualityType));
CodingWithSpike
  • 42,906
  • 18
  • 101
  • 138