I have a bit of an odd scenario where I have a table that references a view that contains rows which are user-specific. The view has a unique row ID, but it is never used to reference the item. Instead a unique value is derived from a combination of a client ID, user ID, and an object key.
public BarMap()
{
Table(DatabaseObject.UserBars);
Id(x => x.Id, "Resource_Id");
Map(x => x.ClientId, "Client_Id");
Map(x => x.UserId, "User_Id");
Map(x => x.Key, "Bar_Key")
.Length(10);
Map(x => x.Name, "Bar_Name")
.Length(255);
I have a filter applied to the class that should kick in the WHERE clause based on the current user and client context. Now in practice I see those filters apply when I try to retrieve a Bar directly. However, when I retreive an object named Foo which references a Bar object, the filters are skipped in a secondary SELECT call.
public class FooMap : ClassMap<Foo>
{
public FooMap()
{
Table(DatabaseObject.Foos);
References<Bar>(x => x.Bar, "BarId")
.PropertyRef(x => x.Key)
.Cascade.None()
.Fetch.Join()
.NotFound.Exception();
Based on the above you would expect that Foo would perform a JOIN to hydrate the Bar object, but it appears to skip it and insist on retrieving a Foo object. I suspect the secondary SELECT call is happening because the NH library is trying to retrieve the object based on a unique value of the Key (in the log
Loader.Loader - SELECT this_.Id as Id12_2_, this_.ClientId as ClientId12_2_, this_.BarId as BarId12_2_, Foo1_.Id as Id13_0_, Foo1_.ClientId as ClientId13_0_, Foo1_.Name as Name13_0_, Bar2_.Resource_Id as Resource1_4_1_, Bar2_.Client_Id as Client7_4_1_, Bar2_.User_Id as User8_4_1_, Bar2_.Bar_Key as Bar9_4_1_, Bar2_.Bar_Name as Bar10_4_1_ FROM Foos this_ inner join FooSchedules Foo1_ on this_.ScheduleId=Foo1_.Id inner join User_Bars Bar2_ on this_.BarId=Bar2_.Bar_Key and (Bar2_.Client_Id = :p0 OR Bar2_.Client_Id IS NULL) and Bar2_.User_Id = :p1 WHERE (Foo1_.ClientId = :p2 OR Foo1_.ClientId IS NULL) and (this_.ClientId = :p3 OR this_.ClientId IS NULL) AND Foo1_.Name = :p4 and Bar2_.Bar_Key = :p5 and Bar2_.Client_Id = :p6 and Bar2_.User_Id = :p7
Loader.Loader - processing result set
Loader.Loader - result set row: 0
I can see right here it loaded the Bar, but it didn't hydrate it for some reason??
Loader.Loader - result row: EntityKey[Core.Domain.FooSchedule#2929992], EntityKey[Core.Domain.Bar#470090], EntityKey[Core.Domain.Foo#3211664]
Loader.Loader - Initializing object from DataReader: [Core.Domain.FooSchedule#2929992]
Loader.Loader - Initializing object from DataReader: [Core.Domain.Foo#3211664]
Loader.Loader - done processing result set (1 rows)
Loader.Loader - total objects hydrated: 2
Engine.TwoPhaseLoad - resolving associations for [Core.Domain.FooSchedule#2929992]
Engine.Loading.LoadContexts - creating collection wrapper:[Core.Domain.FooSchedule.Foos#2929992]
Engine.TwoPhaseLoad - done materializing entity [Core.Domain.FooSchedule#2929992]
Engine.TwoPhaseLoad - resolving associations for [Core.Domain.Foo#3211664]
Engine.Loading.LoadContexts - creating collection wrapper:[Core.Domain.Foo.Items#3211664]
Here is where it makes an attempt to re-load the Bar, but doesn't include the filters causing it to return multiple rows / blow up....
Loader.Entity.AbstractEntityLoader - Static select for entity Core.Domain.Bar: SELECT Bar0_.Resource_Id as Resource1_4_0_, Bar0_.Client_Id as Client7_4_0_, Bar0_.User_Id as User8_4_0_, Bar0_.Bar_Key as Bar9_4_0_, Bar0_.Bar_Name as Bar10_4_0_ FROM User_Bars Bar0_ WHERE Bar0_.Bar_Key=?
Loader.Loader - loading entity: [Core.Domain.Bar#ABFDBC01]
Engine.QueryParameters - BindParameters(Named:NHibernate.Type.StringType) ABFDBC01 -> [0]
Loader.Loader - SELECT Bar0_.Resource_Id as Resource1_4_0_, Bar0_.Client_Id as Client7_4_0_, Bar0_.User_Id as User8_4_0_, Bar0_.Bar_Key as Bar9_4_0_, Bar0_.Bar_Name as Bar10_4_0_ FROM User_Bars Bar0_ WHERE Bar0_.Bar_Key=:p0
Loader.Loader - processing result set
I'd love to refactor the Bar view out of existence but the time constraints won't allow for it unless it is absolutely necessary. Any thoughts on how to get filters to apply? Or is there guidance on how to do context-specific object references?
I found some reference on the JBoss side that Hibernate also might have suffered from a potentially related design issue. Looking into the code the NHibernate source the problem seems to center around EntityJoinWalker:
SqlStringBuilder whereCondition = WhereString(Alias, uniqueKey, batchSize)
//include the discriminator and class-level where, but not filters
.Add(persister.FilterFragment(Alias, new CollectionHelper.EmptyMapClass<string, IFilter>()));
If I change it to this everything works fine, but I'm not sure what the impact of the change is and the comment doesn't really indicate why filters should be excluded.
SqlStringBuilder whereCondition = WhereString(Alias, uniqueKey, batchSize)
.Add(persister.FilterFragment(Alias, enabledFilters));
Help?!