- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Projection question/problem
Joined: 21-Jan-2009
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.DataProjectorToObjectList
1. SD.LLBLGen.Pro.ORMSupportClasses.IGeneralDataProjector.AddProjectionResultToContainer(List1 valueProjectors, Object[] rawProjectionResult)
at SD.LLBLGen.Pro.ORMSupportClasses.ProjectionUtils.FetchProjectionFromReader(List
1 valueProjectors, IGeneralDataProjector projector, IDataReader datasource, Int32 maxNumberOfItemsToReturn, Int32 pageNumber, Int32 pageSize, Boolean clientSideLimitation, Boolean clientSideDistinctFiltering, Boolean clientSidePaging, UniqueList1 stringCache, Dictionary
2 typeConvertersToRun)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchProjection(List1 valueProjectors, IGeneralDataProjector projector, IRetrievalQuery queryToExecute, Dictionary
2 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](IQueryable
1 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:
Joined: 21-Jan-2009
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; }}
}
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).
Joined: 21-Jan-2009
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.