Projection question/problem

Posts   
 
    
kb
User
Posts: 3
Joined: 21-Jan-2009
# Posted on: 21-Jan-2009 22:41:38   

Hi guys, I did some simple tests with LLBL-linq and ran into something that I don't understand. I have the latest version of LLBL and using the Northwind db

Here is my code:


// Works ok
MyCustomer q1 = MapToMyCustomer(md.Customers.First(c => c.CustomerID == customerId));
Console.WriteLine("q1: " + q1.MyCustomerName);

// Works ok
MyCustomer q2 = md.Customers.Where(c => c.CustomerID == customerId).Select(c => new MyCustomer() { MyCustomerName = c.CompanyName }).First();
Console.WriteLine("q2: " + q1.MyCustomerName);

// throws exception
MyCustomer q3 = md.Customers.Where(c => c.CustomerID == customerId).Select(c => MapToMyCustomer(c)).First();
Console.WriteLine("q3: " + q1.MyCustomerName);


static MyCustomer MapToMyCustomer(CustomersEntity c)
{ return new MyCustomer() { MyCustomerName = c.CompanyName };}


Maybe some clever brain out there can tell me what's going on. Probably me doing something wrong.

The ex:

System.InvalidCastException was unhandled Message="Unable to cast object of type 'System.String' to type 'Demo.Db.EntityClasses.CustomersEntity'." Source="Anonymously Hosted DynamicMethods Assembly" StackTrace: at lambda_method(ExecutionScope , Object[] , Int32[] ) at SD.LLBLGen.Pro.LinqSupportClasses.DataProjectorToObjectList1.AddRowToResults(IList projectors, Object[] rawProjectionResult) at SD.LLBLGen.Pro.LinqSupportClasses.DataProjectorToObjectList1. SD.LLBLGen.Pro.ORMSupportClasses.IGeneralDataProjector.AddProjectionResultToContainer(List1 valueProjectors, Object[] rawProjectionResult) at SD.LLBLGen.Pro.ORMSupportClasses.ProjectionUtils.FetchProjectionFromReader(List1 valueProjectors, IGeneralDataProjector projector, IDataReader datasource, Int32 maxNumberOfItemsToReturn, Int32 pageNumber, Int32 pageSize, Boolean clientSideLimitation, Boolean clientSideDistinctFiltering, Boolean clientSidePaging, UniqueList1 stringCache, Dictionary2 typeConvertersToRun) at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchProjection(List1 valueProjectors, IGeneralDataProjector projector, IRetrievalQuery queryToExecute, Dictionary2 typeConvertersToRun) at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchProjection(List1 valueProjectors, IGeneralDataProjector projector, IEntityFields2 fields, IRelationPredicateBucket filter, Int32 maxNumberOfItemsToReturn, ISortExpression sortClauses, IGroupByCollection groupByClause, Boolean allowDuplicates, Int32 pageNumber, Int32 pageSize) at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProvider2.ExecuteValueListProjection(QueryExpression toExecute) at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.ExecuteExpression(Expression handledExpression) at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.Execute(Expression expression) at SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.System.Linq.IQueryProvider.Execute[TResult](Expression expression) at System.Linq.Queryable.First[TSource](IQueryable1 source) at Demo.Qrylab.Program.GetCustomer(String customerId) in E:\k_documents\Projects\Sbs\Demo\Trunk\Demo.Qrylab\Program.cs:line 28 at Demo.Qrylab.Program.Main(String[] args) in E:\k_documents\Projects\Sbs\Demo\Trunk\Demo.Qrylab\Program.cs:line 15 at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException:

Walaa avatar
Walaa
Support Team
Posts: 14951
Joined: 21-Aug-2005
# Posted on: 22-Jan-2009 11:04:43   

Out of my head.

// throws exception MyCustomer q3 = md.Customers.Where(c => c.CustomerID == customerId).Select(c => MapToMyCustomer(c)).First();

I think c doesn't qualify to a a CustomerEntntity.

kb
User
Posts: 3
Joined: 21-Jan-2009
# Posted on: 22-Jan-2009 16:39:43   

thanks

...qualify to a a CustomerEntntity...

but when I do the "same thing" with LinqToSql all three statements runs with no exception


    class Program
    {
        static void Main(string[] args)
        {
            String customerId = "ALFKI";
            using (NorthwindDataContext db = new NorthwindDataContext())
            {
                // ok
                MyCustomer q1 = MapToMyCustomer(db.Customers.First(c => c.CustomerID == customerId));
                Console.WriteLine("q1: " + q1.MyCustomerName);
                // ok 
                MyCustomer q2 = db.Customers.Where(c => c.CustomerID == customerId).Select(c => new MyCustomer() { MyCustomerName = c.CompanyName }).First();
                Console.WriteLine("q2: " + q2.MyCustomerName);
                // Ok in LinqToSql!! throws exception with LLBL 
                MyCustomer q3 = db.Customers.Where(c => c.CustomerID == customerId).Select(c => MapToMyCustomer(c)).First();
                Console.WriteLine("q3: " + q3.MyCustomerName);
            }
            Console.ReadLine();
        }
        static MyCustomer MapToMyCustomer(Customer c)
        { return new MyCustomer() { MyCustomerName = c.CompanyName }; }

        class MyCustomer
        {public String MyCustomerName { get; set; }}
    }



Otis avatar
Otis
LLBLGen Pro Team
Posts: 39619
Joined: 17-Aug-2003
# Posted on: 23-Jan-2009 09:32:42   

The reason is that linq to sql has expression tree to SQL translation and 1 fetch pipeline. LLBLGen Pro has two fetch pipelines (one for projections and one for entity fetches, more on that below) and two stages: expression tree to query elements and query elements to sql.

The specific entity fetch pipeline is required for inheritance over multiple tables, something which isn't supported by linq to sql: this requires a different pipeline as additional information has to be joined to the elements resulting from the query to be able to fetch all types (e.g. subtypes which aren't specified in the query). The reason the projection pipeline isn't used is that it otherwise would be tricky to recognize if the projection specified is indeed for an entity type or not.

Now, your code assumes 'c' is materialized when it is passed to the method at runtime. But that's not the case: it's the raw data from the db which gets passed into the method, as the projection pipeline is used: the method call is compiled into an in-memory delegate which gets the data received from the db when it's called.

The reason you run into this is because you mix linq query elements for a DB with linq elements/client side code for in-memory processing. This is a feature of Linq which can be very handy but at the same time can blur the line where what is executed: is the method you wrote also executed in the db or not? This might sound obvious but that's not always the case.

So rule of thumb: you can't pass variables which are of an entity type to in-memory methods inside a linq query which runs on the db (like your method).

Frans Bouma | Lead developer LLBLGen Pro
kb
User
Posts: 3
Joined: 21-Jan-2009
# Posted on: 23-Jan-2009 10:36:56   

Thanks, for a the explanation!

This is a feature of Linq which can be very handy but at the same time can blur the line where what is executed:

Agree, it can indeed be handy

is the method you wrote also executed in the db or not?

Yes it is, but as you indicate, using linq it's sometimes hard to tell when/where (or even if) the code is exec. in the db.