Select Expressions Cache Entities
Description
Environment
Activity
Closing issues resolved in 4.1.0
The issue isn't with the "case"-ing of booleans (which has more to do with the way that NULL comparisons are handled in DBs vs C#).
The issue is that the expression e.Owner == bob
(where bob
is a captured in-memory entity) does different things when in the WHERE clause and the SELECT clause.
In the WHERE clause this is turned into HQL and eventually translated to SQL as _entity.OwnerId =
0
, and the value of p0
is cached as part of the query plan.
In the SELECT clause e.Owner
is nominated as HQL, but bob
isn't because it's a constant expression. Therefore e.Owner == bob
is not nominated either. The SELECT clause ends up containing statements to select the entire joined Owner
object and a projection expression is extracted: input => input[0] == bob
where input[0]
is the Owner
object and bob
is still the original captured constant instance. The projection expression is then cached along with the query plan.
This has 2 issues:
We query a bunch more data than is needed and (in some cases) create a completely unnecessary join
When a query matching this one occurs in a different session, even if the new instance of
bob
is from the new session, the cached projection will be used, which will fail the object comparison because the objects are no longer the same instance.
However, if we are able to nominate the entire e.Owner == bob
expression and generate HQL, then the SELECT clause looks identical to the WHERE clause and only the primary key is cached in the query plan.
Can you please consider my last pull request? It improves the "case"-ing of Booleans a lot
I'm working up a solution. Basically it involves modifying the SelectClauseHqlNominator
to project constants into HQL for equality (==
) expressions.
The hitch is that when I do this, some conditional expressions start getting nominated, and nominating conditional expressions is... tricky.
The HQL tree builder automatically wraps all non-boolean conditionals in a CAST of the conditional type. But only certain conditional types are cast-able. This is determined by HqlIdent
constructor that takes a Type
as the second argument.
The options are to make all cast-able conditional expressions nominated for HQL or to allow no conditionals to be nominated for HQL unless directly nominated (like by the GroupKeyNominator
).
I'd like to nominate all conditionals, but I'll probably nominate none. It should be enough that equality is nominated so that it is checked by the DB, and by identifier for DB entities.
We have a few queries where we are pulling predicate expressions into the select clause so that we can batch a complex set of queries.
Our problem is that when entity comparison is part of the predicate the expression is not sent to the database but rather broken and the comparison is run in memory. The query plan (including the
ProjectionExpression
) is then cached. Subsequent executions try to compare the newly returned entity with the cached entity, which will fail if the entity was from a different session.Trivial, but clear example
The first time the query executes (within an
ISessionFactory
instance) the values ofOwnedByBob
correctly reflect the ownership. Subsequent executions will have allOwnedByBob
values false.