- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Using an Entity directly versus a clone thereof in projection: entity fails, clone succeeds.
Joined: 20-Nov-2008
Experimenting, I found a workaround but wondering if we should pursue further as potential bug.
The more direct, non-clone approach ( commented out in the code ) yields invalid cast exception with following stack trace:
" 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)
....
(try to format stacktraces next time please -- Otis )
Note: the method CountAllTypesOfBuyersForLO(..), used in the final projection, contains a subquery and returns an int[3].
/// <summary>
/// Example client call of GetLOAggregate_1() the which fails with InvalidCastException:
/// List<LOAggregate_1> loAggregates = (LoData.GetLOAggregate_1())
/// .TakePage(page, pageSize)
/// .ToList();
/// </summary>
/// <returns>IQueryable producing a collection of objects of type LOAggregate_1.</returns>
public IQueryable<LOAggregate_1> GetLOAggregate_1()
{
BO_Buyers buyers = new BO_Buyers();
IQueryable< LOAggregate_1> loRowCollection =
from c in md.Contact.AsQueryable()
// Left outer join.
join cd in md.ContactDetail
on c.ContactId equals cd.ContactId
into localContactDetails
//from lcd in localContactDetails.DefaultIfEmpty()
// Left outer join
join us in md.User
on c.ContactUi equals us.ContactUi
into localUsers
from u in localUsers.DefaultIfEmpty()
orderby c.LastName
//thenby c.FirstName
//thenby lcd.MiddleName
where c.ContactStatus.StatusId == (byte)ContactStatusId.Active
&&
( // subquery
(from cr in md.ContactRelationship
where cr.ContactRelationshipTypeId == (long)ContactRelationshipTypeId.BuyerLO
select cr.RightContactId
).Contains(c.ContactId))
select new LOAggregate_1(
// SUCCEEDS: create new ContactEnity, filling only to-be-used fields
new ContactEntity(){ ContactId = c.ContactId, PrimaryEmail = c.PrimaryEmail, StatusId = c.StatusId},
// whereas taking the existing contact, c below, FAILS with exception
// Unable to cast object of type 'System.Int64' to type 'HBM2.ORM.CERM.EntityClasses.ContactEntity'.
// c,
u.UserName, u.Password, buyers.CountAllTypesOfBuyersForLO(c.ContactId));
return loRowCollection;
}
// Constuctor for "data package class" LOAggregate_1 that was used in final projection containing the offending "cast":
public LOAggregate_1(ContactEntity contact,
String userName,
String password,
int[] buyerCounts )
{
Contact = contact;
UserName = userName;
Password = password;
BuyerCount = buyerCounts[0].ToString();
ActiveBuyerCount = buyerCounts[1].ToString();
InactiveBuyerCount = buyerCounts[2].ToString();
}
}
Please provide more info, with these kind of queries we can't start reproducing it. Furthermore, you don't give info about the runtime lib build you're using. See: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=12769
Joined: 20-Nov-2008
Otis wrote:
Please provide more info, with these kind of queries we can't start reproducing it. Furthermore, you don't give info about the runtime lib build you're using. See: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=12769
OK, first 'more info'
SD.LLBLGen.Pro.LinqSupportClasses.NET35 ( version 2.6.0.0 )
SD.LLBLGen.Pro.ORMSupportClasses.NET20 ( version 2.6.0.0 )
Perhaps, related to the invalid cast ( Int64), the primary key of ContactEntity is long!
I'm convinced this is a bug or, if not, a feature request for support of "more natural syntax which I explain forthwith. An entity, ( here, ContactEntity c ) should be usable directly within a constructor inside the projection. In this case, the constructed type, LOAggregate_1, is essentially a simple "property class" -- so no big challenge. To circumvent the apparent bug ( as indicated in the SUCCEEDS comment ), I have to first EXPLICITLY instantiate a clone using the individual properties of the original contact, c. In my view, the runtime ( or similar ) should implicitly make a clone to implement a pass by value ( of c ) to the LOAggregate_1 constructor. Instead, I'm having to embed an explicit constructor call within the LOAggregate_1 constructor call ( unnatural ).
Should be easy for you to duplicate as demonstrated by this cut-down version:
public IQueryable<LOAggregate_1> GetLOAggregate_1()
{
BO_Buyers buyers = new BO_Buyers(); // provides adapter
IQueryable<LOAggregate_1> loRowCollection =
from c in md.Contact.AsQueryable()
select new LOAggregate_1(
// SUCCEEDS: create new ContactEnity, filling only the required fields
// new ContactEntity() { ContactId = c.ContactId, PrimaryEmail = c.PrimaryEmail, StatusId = c.StatusId },
// whereas taking the existing c FAILS with exception
// Unable to cast object of type 'System.Int64' to type 'HBM2.ORM.CERM.EntityClasses.ContactEntity'.
c, // the only non-primitive in constructor call.
"userName", "Password", "1", "2", "3");
return loRowCollection;
}
Now, part of the simplification was to use a simpler variant of the constructor LOAggregate_1 ( avoiding the constructor parameter as embedded query, of yesterday ) which is as follows:
public LOAggregate_1(ContactEntity contact,
String userName,
String password,
String buyerCount,
String activeBuyerCount,
String inactiveBuyerCount)
{
Contact = contact;
UserName = userName;
Password = password;
BuyerCount = buyerCount;
ActiveBuyerCount = activeBuyerCount;
InactiveBuyerCount = inactiveBuyerCount;
}
While experimenting, I saw signs that this bug could be "multilevel". So, if we solve the first level, reversion to the prior LOAggregate_1 constructor should serve as excellent QA test.
A wild guess: the ContacEntity constructor using primary key is being called instead of ( a la C++ ) an LLBLGen-generated copy constructor ( i.e. clone factory ). I would expect that such copy constructors are available ( but maybe hidden ) within the LLBLGen generated code -- for each DB entity type.
Joined: 20-Nov-2008
BTW: As exhibitted within comment lines of initial message, we have need Linq provider support for ThenBy so that we can sort on multiple criteria. The example there was LastName, FirstName, MiddleName/Initial. If I'm not mistaken, currently LLBLGenPro only supports a single sort criteria.
MichaelJLiddell wrote:
SD.LLBLGen.Pro.LinqSupportClasses.NET35 ( version 2.6.0.0 ) SD.LLBLGen.Pro.ORMSupportClasses.NET20 ( version 2.6.0.0 )
Those aren't runtime library version. To see the runtime library version please read this:
The runtime library version is obtainable by rightclicking the SD.LLBLGen.Pro.ORMSupportClasses.NETxy.dll in windows explorer and then by selecting properties and the version tab. The version is then enlisted at the top as the fileversion. It has the typical format as 2.0.0.YYMMDD, or starting in 2007, the format 2.0.YY.MMDD
MichaelJLiddell wrote:
The example there was LastName, FirstName, MiddleName/Initial. If I'm not mistaken, currently LLBLGenPro only supports a single sort criteria.
LLBLGenPro API does support multiple sort criteria (ref)
As for the scenario, I will try to reproduce it with the Northwind schema. I you can provide a repro solution in northwind, that would be helpful.
Joined: 20-Nov-2008
daelmo wrote:
MichaelJLiddell wrote:
SD.LLBLGen.Pro.LinqSupportClasses.NET35 ( version 2.6.0.0 ) SD.LLBLGen.Pro.ORMSupportClasses.NET20 ( version 2.6.0.0 )
Those aren't runtime library version. To see the runtime library version please read this:
The runtime library version is obtainable by rightclicking the SD.LLBLGen.Pro.ORMSupportClasses.NETxy.dll in windows explorer and then by selecting properties and the version tab. The version is then enlisted at the top as the fileversion. It has the typical format as 2.0.0.YYMMDD, or starting in 2007, the format 2.0.YY.MMDD
MichaelJLiddell wrote:
The example there was LastName, FirstName, MiddleName/Initial. If I'm not mistaken, currently LLBLGenPro only supports a single sort criteria.
LLBLGenPro API does support multiple sort criteria (ref)
As for the scenario, I will try to reproduce it with the Northwind schema. I you can provide a repro solution in northwind, that would be helpful.
============== New ======== Runtime Version = v2.0.50727
I was aware LLBLGenPro supports multiple "sort keys". However, these are ( apparently ) introduced only through the adapter as documented in the reference you directed me to. How does this work in conjunction with OrderBy specified via the linq syntax? My perhaps naive request was to support multiple sort keys directly via Linq provider rather than combination of linq for the intial sort and adapter for subsequent. I think the Microsoft's linq syntax for secondary sorts is ThenBy. I was under the impression that ThenBy not supported by LLBLGen's linq provider. And as I said above, I'm not sure how the adapter would intervene, especially in view of TakePage, etc.
However, at the moment, I'm most bothered by the original problem which seems to indicate than an explicit clone is required.
MichaelJLiddell wrote:
daelmo wrote:
MichaelJLiddell wrote:
SD.LLBLGen.Pro.LinqSupportClasses.NET35 ( version 2.6.0.0 ) SD.LLBLGen.Pro.ORMSupportClasses.NET20 ( version 2.6.0.0 )
Those aren't runtime library version. To see the runtime library version please read this:
The runtime library version is obtainable by rightclicking the SD.LLBLGen.Pro.ORMSupportClasses.NETxy.dll in windows explorer and then by selecting properties and the version tab. The version is then enlisted at the top as the fileversion. It has the typical format as 2.0.0.YYMMDD, or starting in 2007, the format 2.0.YY.MMDD
MichaelJLiddell wrote:
The example there was LastName, FirstName, MiddleName/Initial. If I'm not mistaken, currently LLBLGenPro only supports a single sort criteria.
LLBLGenPro API does support multiple sort criteria (ref)
As for the scenario, I will try to reproduce it with the Northwind schema. I you can provide a repro solution in northwind, that would be helpful.
============== New ======== Runtime Version = v2.0.50727
That's the .net version. PLease read the link. Rightclick the dll in windows explorer -> properties -> version tab. ANyway, please download the latest build and retry. We release new builds regularly.
I was aware LLBLGenPro supports multiple "sort keys". However, these are ( apparently ) introduced only through the adapter as documented in the reference you directed me to.
What are sort keys? You can sort on multiple fields, simply add more orderby statements.
How does this work in conjunction with OrderBy specified via the linq syntax? My perhaps naive request was to support multiple sort keys directly via Linq provider rather than combination of linq for the intial sort and adapter for subsequent. I think the Microsoft's linq syntax for secondary sorts is ThenBy. I was under the impression that ThenBy not supported by LLBLGen's linq provider. And as I said above, I'm not sure how the adapter would intervene, especially in view of TakePage, etc.
Of course ThenBy is supported.
However, at the moment, I'm most bothered by the original problem which seems to indicate than an explicit clone is required.
I'll see if I understand your problem, but for the moment I have a hard time understanding what the problem is actually..
(edit) you IMHO described the problem in a complex way with lots of commented out code etc. Please understand that we can't proceed with cryptic descriptions. Just post a query which fails and query which succeeds. Trim it down to the most smallest queries possible and explain the entities in question, we then can see what's the problem. I hope I understood your problem so here it goes:
THe entity fails, because an entity fetch uses a different pipeline than a normal class fetch. The reason is inheritance. So to materialize an entity the entity fetch code has to be called and that uses a different projection type internally (inside the expression tree). To avoid re-creating the same code again we don't support in-query entity materialization if the entity is used inside the query and not returned as a projection result. In your query, it's not necessary to materialize to an entity instance so use an anonymous type.
Your edge case passes the entity to a method which is the real problem: the method is called in the projection, and that is the projection. To support this, the entity first has to be materialized using a projection, THEN it should be passed to the method in the actual projection. THis isn't supported. The workaround you found is the reason why we didn't support it: if it's possible to support it (it's expected to be alot of complex code), it's not that necessary, as a normal workaround exists, which will always be more efficient as well.
The sorting over multiple fields is supported btw.
Some tips:
- don't call AsQueryable on a property unless you have to. md is metaData I pressume? then Contact implements IQueryable, so you don't have to do that. These calls are added to the expression tree and could give unexpected results.
- In general avoid methods which accept data from elements in the query in the projection, unless you have to, of course. The reason is that if you append something to that query, it might be that the projection is folded into a subquery and the query will fail because hte method is then suppose to be executed in the db (as it's no longer the outer projection). This is due to linq's design and it could be you run into this unexpectedly.
- you don't need to materialize entities inside queries to obtain the data of the fields of an entity, just refer to the fields
Joined: 20-Nov-2008
Thanks without reservation for the tips on "AsQueryable() on a property" and "ThenBy". WRT the latter, it was from my original encounter with 'Beta Linq feature bulletin' that I developed the perhaps mistaken impression that only OrderBy was Linq-provider supported. As a harbinger of the impact of your other recommendations, note how my fear of non-support for "ThenBy" prevented me from using it until told otherwise! Fear persists!
To paraphrase the other"guidelines" or suggestions: 1. project to the fields of entities rather than entities themselves; 2. projection to anonymous types is preferred; 3. a nested subquery, direct from elements of the enclosing projection or indirectly via function applied to same and which itself contains a query IS RISKY AT BEST.
I can live with them ( more or less, I equate them to clarification of a long-standing recommendation to avoid ChangeType() in a projection! )
but taken as a whole, th,ey will cause headaches if not migraines! And, perhaps worse,
they provide a great excuse for the timid to shy away from Linq.
Anonymous classes recommended by guideline 2,
are especially attractive when one is permitted to
call a Linq provider from the presentation layer
-- you don't have to declare "transport/transfer" classes visible to and carrying data between 3 tiers ( Presentation, Business & Persistence )
of our 4 tier system which also includes a data/ORM layer.
But, I'm bucking the tide here by using Linq and have to not only define
those classes but also include in them a MIXTURE of entities and entity-sourced properties.
Now, guideline 3 is hard to take for reasons of inefficiency: coding and runtime. In the original sample of this thread, there are 3 "derived fields" AND ( a month ago ) independent ( by function call ) subqueries were used to fill each. Today, a single query produces an an array holding all three derived COUNTs and these are broken down into separate fields by the constructor of the transport class. It's a big win because a single efficient query is now used to product 3 results, so, in this case, the risk of 3 is worth it. It succeeds perhaps due to the provider's special attention to TakePage(..). Nevertheless, you've scared me!
Of course, I could ramble on and on about execution semantics or natural interpretation of syntax. Suffice to say: I implore you will up the priority of implicit cloning or copy constructor within ( outer ) projections.
OR as a 'crutch' alternative: add a "cast-like" cloning keyword to force the entity clone/copy construction in-situ with respect to expression tree or projection. A cloning keyword explicitly applied as method to an entity instance would guide the provider's execution and would be more concise than explicit construction via a list of object initializers. And, note, the copy constructor is NOT an ORM/LLBLGenPro constructor, just a complete property copy to a new object of same type. Clearly, the object to be cloned can be "materialized" based on all that precede's the projection's Select. Perhaps, an intermediate and hidden projection object should be created to hold it ( and other's ) prior to the REAL FINAL projection. However, that and the hidden object's ownership of paging/sorting operators are implementer's decision.
MichaelJLiddell wrote:
Thanks without reservation for the tips on "AsQueryable() on a property" and "ThenBy". WRT the latter, it was from my original encounter with 'Beta Linq feature bulletin' that I developed the perhaps mistaken impression that only OrderBy was Linq-provider supported. As a harbinger of the impact of your other recommendations, note how my fear of non-support for "ThenBy" prevented me from using it until told otherwise! Fear persists!
We wrote a very large user manual as well, you know With a linq section with all the fine details about what's supported and what not.
To paraphrase the other"guidelines" or suggestions: 1. project to the fields of entities rather than entities themselves; 2. projection to anonymous types is preferred; 3. a nested subquery, direct from elements of the enclosing projection or indirectly via function applied to same and which itself contains a query IS RISKY AT BEST.
'risky' as in: will it work at runtime... yes. This isn't our fault, linq's design makes it impossible to see directly where what part of the query is executed. This is something you should keep in mind, most of the time however you won't run into this.
I can live with them ( more or less, I equate them to clarification of a long-standing recommendation to avoid ChangeType() in a projection! ) but taken as a whole, th,ey will cause headaches if not migraines! And, perhaps worse, they provide a great excuse for the timid to shy away from Linq.
I wouldn't go that far. In fact, it can be a great help, because you can also use this to your advantage. Rule of thumb: if you use a function in the last projection, you're safe. If you append stuff to that query later on, you're risking a runtime error. So if you stay away from appending stuff to a linq query, you're fine if you keep the function usage to the outer projection.
Anonymous classes recommended by guideline 2, are especially attractive when one is permitted to call a Linq provider from the presentation layer -- you don't have to declare "transport/transfer" classes visible to and carrying data between 3 tiers ( Presentation, Business & Persistence )
of our 4 tier system which also includes a data/ORM layer. But, I'm bucking the tide here by using Linq and have to not only define those classes but also include in them a MIXTURE of entities and entity-sourced properties.
I'm not sure if I understand you here correctly. What you should do is either: - use entity fetches, with prefetch paths and the like OR - use custom projections to custom classes / anonymous types.
You can mix entity fetches inside custom projections using nested queries. See the linq section in the manual to get more details about nested queries in the projection.
Now, guideline 3 is hard to take for reasons of inefficiency: coding and runtime. In the original sample of this thread, there are 3 "derived fields" AND ( a month ago ) independent ( by function call ) subqueries were used to fill each. Today, a single query produces an an array holding all three derived COUNTs and these are broken down into separate fields by the constructor of the transport class. It's a big win because a single efficient query is now used to product 3 results, so, in this case, the risk of 3 is worth it. It succeeds perhaps due to the provider's special attention to TakePage(..). Nevertheless, you've scared me!
Nothing to be scared about. The thing you should keep in mind is that if you refer to an entity field inside a query, it's not a property on an entity object you're referring to, but the data of a field of an entity in the database. So if I do:
var q = from c in metaData.Customer select new { c.CompanyName};
Here I'm not materializing customer entities of which I'm grabbing the companyname property. Instead this query is simply fetching a set of companynames and per row an instance of an anonymous type is created which will contain the values of the row.
This works the same in your query: you materialize the custom class by first trying to create an entity instance, then grab the properties of the entity object and store it inside the custom class instance. Instead, you can also pass the entity fields directly, like so: var q = from c in metaData.Customer select new MyCustomClass(c.CustomerId, c.CompanyName);
here, I create a new MyCustomClass instance per customer, and fill it with 2 fields of each customer entity in the db. I don't materialize the entity class.
Of course, I could ramble on and on about execution semantics or natural interpretation of syntax. Suffice to say: I implore you will up the priority of implicit cloning or copy constructor within ( outer ) projections.
Sorry but that's not going to happen. I've explained why in my previous post, and I've explained above why it's not necessary as well.
Joined: 20-Nov-2008
Thanks for your help.
Paraphrasing your ENTITY MATERIALIZATION RULE with a positive twist: In a Linq query's projection, any reference to an entity that is materializable due to prior code within the query must be a field/field reference. By including such references in constructor or function call, one can effectively utilize any/all public properties of the in-scope entity.
In the long run ( eventually ), it would be nice to have in the user's manual a list of Extension which are NOT runtime risky WRT Otis' RULE OF THUMB: "If you use a function in the last projection, you're safe. If you append stuff to that query later on, you're risking a runtime error."
Of course, I realize that in the short run the order/priority of processing of extensions within the provider may shift from time-to-time. However, I do get the impression that even now, some extensions are safe or nearly so in that they are implemented with the sticking power of their relative priority as post process.
Thanks, again.