Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Odd behavior in LINQ to SQL with anonymous objects and constant columns

Tags:

My colleague was getting an error with a more complex query using LINQ to SQL in .NET 4.0, but it seems to be easily reproducible in more simpler circumstances. Consider a table named TransferJob with a synthetic id and a bit field.

If we make the following query

using (var ctx = DBDataContext.Create())
{
    var withOutConstant = ctx.TransferJobs.Select(x => new { Id = x.TransferJobID, IsAuto = x.IsFromAutoRebalance });
    var withConstant = ctx.TransferJobs.Select(x => new { Id = x.TransferJobID, IsAuto = true });//note we're putting a constant value in this one

    var typeA = withOutConstant.GetType();
    var typeB = withConstant.GetType();
    bool same = typeA == typeB; //this is true!

    var together = withOutConstant.Concat(withConstant);
    var realized = together.ToList();//invalid cast exception
}

An invalid cast exception is thrown where noted. But strangely, we have type equality when viewing in a debugger.

Simply changing the second to last line to move from IQueryable's to using linq-to-objects

var together = withOutConstant.ToList().Concat(withConstant.ToList());
var realized = together.ToList();//no problem here

then everything work fine as expected.

After some initial digging, I see that it looks like the programmers of LINQ to SQL were considering performance and are not actually having the generated SQL pull the constant value in the case with the explicit setting of true in the withConstant version.

Finally, if I switch order everything seems to work:

var together = withConstant.Concat(withOutConstant); //no problem this way

However, I'd still like to know if better detail what is really going on. I find it rather odd that these would be considered equal types but cause an invalid cast exception. What's actually happening under the covers? How could I go about proving it to myself?

Stack Trace:

at System.Data.SqlClient.SqlBuffer.get_Boolean()
   at Read_<>f__AnonymousType2`2(ObjectMaterializer`1 )
   at System.Data.Linq.SqlClient.ObjectReaderCompiler.ObjectReader`2.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at KBA.GenericTestRunner.Program.Main(String[] args) in c:\Users\nick\Source\Workspaces\KBA\Main\KBA\KBA.GenericTestRunner\Program.cs:line 59
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

Generated SQL is the following:

SELECT [t2].[TransferJobID] AS [Id], [t2].[IsFromAutoRebalance] AS [IsAuto]
FROM (
    SELECT [t0].[TransferJobID], [t0].[IsFromAutoRebalance]
    FROM [dbo].[TransferJob] AS [t0]
    UNION ALL
    SELECT [t1].[TransferJobID], @p0 AS [value]
    FROM [dbo].[TransferJob] AS [t1]
    ) AS [t2]
-- @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [1]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.34209

With the order reversed (which doesn't crash) the SQL is:

SELECT [t2].[TransferJobID] AS [Id], [t2].[value] AS [IsAuto]
FROM (
    SELECT [t0].[TransferJobID], @p0 AS [value]
    FROM [dbo].[TransferJob] AS [t0]
    UNION ALL
    SELECT [t1].[TransferJobID], [t1].[IsFromAutoRebalance]
    FROM [dbo].[TransferJob] AS [t1]
    ) AS [t2]
-- @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [1]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.34209

To my earlier comment, the constant isn't pulled when doing

withConstant.ToList()

SELECT [t0].[TransferJobID] AS [Id]
FROM [dbo].[TransferJob] AS [t0]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.34209