views:

152

answers:

2

These two statements look the same logically to me, but they're resulting in different SQL being generated:

#1 
var people = _DB.People.Where(p => p.Status == MyPersonEnum.STUDENT.ToString());
var ids = people.Select(p => p.Id);
var cars = _DB.Cars.Where(c => ids.Contains(c.PersonId));

#2 
string s = MyPersonEnum.STUDENT.ToString();
var people = _DB.People.Where(p => p.Status == s);
var ids = people.Select(p => p.Id);
var cars = _DB.Cars.Where(c => ids.Contains(c.PersonId));

Example #1 doesn't work, but example #2 does.

The generated SQL for the var people query is identical for both, but the SQL in the final query differs like this:

#1
SELECT [t0].[PersonId], [t0].[etc].....
FROM [Cars] AS [t0]
WHERE EXISTS(
    SELECT NULL AS [EMPTY]
    FROM [People] AS [t1]
    WHERE ([t1].[Id] = [t0].[PersonId]) AND ([t1].[Status] = (CONVERT(NVarChar,@p0)))
    )

#2
SELECT [t0].[PersonId], [t0].[etc].....
FROM [Cars] AS [t0]
WHERE EXISTS(
    SELECT NULL AS [EMPTY]
    FROM [People] AS [t1]
    WHERE ([t1].[Id] = [t0].[PersonId]) AND ([t1].[Status] = @p0)
    )

Why is there this difference?

Edit:

Up until now all I've done to get the SQL generated is to inspect the queryable in the debugger. However, after setting up a logger as Jon suggested, it seems that the real sql executed is different.

#1 
SELECT [t1].[Id], [t1].etc ... [t0].Id, [t1].etc ...
FROM [Cars] AS [t0], [People] AS [t1]
WHERE ([t1].[Id] = [t0].[PersonId]) AND (EXISTS(
    SELECT NULL AS [EMPTY]
    FROM [People] AS [t2]
    WHERE ([t2].[Id] = [t0].[PersonId]) AND ([t2].[Status] = (CONVERT(NVarChar,@p0)))
    )) AND ([t1].[Status] = @p1)
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [2]
-- @p1: Input NVarChar (Size = 7; Prec = 0; Scale = 0) [STUDENT]

#2
SELECT [t1].[Id], [t1].etc ... [t0].Id, [t1].etc ...
FROM [Cars] AS [t0], [People] AS [t1]
WHERE ([t1].[Id] = [t0].[PersonId]) AND (EXISTS(
    SELECT NULL AS [EMPTY]
    FROM [People] AS [t2]
    WHERE ([t2].[Id] = [t0].[PersonId]) AND ([t2].[Status] = @p0)
    )) AND ([t1].[Status] = @p1)
-- @p0: Input NVarChar (Size = 7; Prec = 0; Scale = 0) [STUDENT]
-- @p1: Input NVarChar (Size = 7; Prec = 0; Scale = 0) [STUDENT]
+1  A: 

No, they're different. In the first version, the expression MyPersonEnum.STUDENT.ToString() is within the expression tree - it's part of what LINQ to SQL has to convert into SQL. I'd be interested to see what @p0 is when the query is executed...

In the second version, you've already evaluated the expression, so LINQ to SQL just sees a reference to a variable which is already a string.

We know that they mean the same thing, but presumably LINQ to SQL doesn't have quite enough knowledge to understand that.

Out of interest, do both of them work?

EDIT: Okay, so the second version works. I suggest you use that form then :) In an ideal world, both would work - but in this case it seems you need to help LINQ to SQL a bit.

Jon Skeet
Do you mean that in #1 MyPersonEnum.STUDENT.ToString() is not evaluated at all at compile time?
Dmitry Tashkinov
Editted to give more info, if you step through in the debugger and "Enumerate the IEnumerable" during the first step( i.e. the people query) it grabs all the results you'd expect. It falls down when trying to get the cars
Kirschstein
@DTashkinov: In the first version it builds an expression tree instead, yes.
Jon Skeet
I'm fine with using the version that works, it's more curiosity of finding out what's going wrong that's driving me ;) How can I check to see what value `@p0` is when the query is executed?
Kirschstein
Then maybe the thing is that in the first virsion compiler can determine the length or something of the string to ensure it can be passed to SQL as is, while in the second it cannot determine it and so just in case ads a convert function.
Dmitry Tashkinov
@Kirschstein: Set the Log property of the data context and it will show both the query *and* the parameter values.
Jon Skeet
@Jon: I dont think it will ever get there :(
leppie
@leppie: You don't think what will ever get there?
Jon Skeet
@Jon Marked George's answers as correct, seems to be on the right track I think. Does it seem like the correct reason to you?
Kirschstein
@Kirschstein: It's largely right, yes. Basically LINQ to SQL is interpreting MyPersonEnum.STUDENT.ToString() as an int.ToString() conversion - which is a bit broken, frankly.
Jon Skeet
+1  A: 

First, think of dual nature of e Enum:

enum MyPersonEnum
{
  STUDENT, // implicit 1
  TEACHER, // implicit 2
  DIRECTOR = 10 // explicit 10
}

...

Assert.AreEqual(1, (int)MyPersonEnum.STUDENT);
Assert.AreEqual("STUDENT", MyPersonEnum.STUDENT.ToString());

In the second example, C# have converted Enum to string, so no conversion needed, and it's assumed that your database People.Status column accepts "STUDENT", "TEACHER", "DIRECTOR" strings as valid values in the logic.

The difference is, enum internal representation in CLR is integer, and the first example, @p parameter is passed as an integer, it's an L2S query builder behaviour, that's why the conversion.

The first one would work, if your database column was an int that takes values assigned to the Enum members {1,2,10} in my example.

George Polevoy