When does NHibernate JoinQueryOver load full collection? - nhibernate

I've got a model where a Person has a lazy-loaded collection of Registrations. My NHibernate 3.3.3 query uses JoinQueryOver across that relation and some more:
var reg = Session.QueryOver<Person>()
.Where(p => p.ID == pid)
.JoinQueryOver<Registration>(p => p.Registrations)
.Where(r => r.IsDropped == false)
.JoinQueryOver<Slot>(r => r.Slot)
.JoinQueryOver<Position>(s => s.Position)
.JoinQueryOver<AcademicTerm>(p => p.Term)
.Where(t => t.ID == currentTerm.ID)
.SingleOrDefault();
In the log, I see the expected SQL for the query, and I get the expected Person instance, but when I access the Registrations collection, the full collection of registrations gets (lazy) loaded. I was expecting it would just be a partial collection of the registrations that meet the criteria.
In a previous question, I had the opposite problem, and fixed it by adding a Fetch() clause to the query.
When does a JoinQueryOver criterion cause the root entity to have a partial collection, and when does it have an unloaded collection that will get lazy loaded? I feel like I should understand this.
My fluent mapping for Person:
public class PersonMapping : ClassMap<Person>
{
public PersonMapping()
{
Id(x => x.ID).GeneratedBy.Guid();
Map(x => x.Username).Length(20).Unique();
Map(x => x.HashedPassword).Length(Security.HashedPasswordEncodedLength);
Map(x => x.PasswordSalt).Length(Security.PasswordSaltEncodedLength);
Map(x => x.LastName).Length(25).Index("FullName");
Map(x => x.FirstName).Length(20).Index("FullName");
Map(x => x.StudentID).Length(12).Nullable();
Map(x => x.EmployeeID).Length(12).Nullable();
Map(x => x.EmailAddress).Length(72);
Map(x => x.IsAdmin).Nullable();
Map(x => x.IsAgencyStaff).Nullable();
Map(x => x.IsCenterStaff).Nullable();
Map(x => x.IsFaculty).Nullable();
Map(x => x.IsRegistrant).Nullable();
Map(x => x.IsStudent).Nullable();
Map(x => x.Type);
Map(x => x.AuthenticationMode);
HasMany<Registration>(x => x.Registrations).KeyColumn("Registrant_id")
.LazyLoad()
.AsBag()
.Inverse()
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate();
}
}
Update
Comparing this with my other question (linked above), where I had the opposite problem, that one uses a Left.JoinQueryOver to get from the base entity to the collection. I tried that here, and then got the expected partial collection. (I don't actually need the left join in the other case; I don't remember why it's there.)
The records fetched are the same with or without the left join, but NHibernate populates the collection in one case, and not in the other. I doubt that's behavior I can depend on, so I've changed my strategy to query for the registrations separately.

This condition in the QueryOver:
...
.JoinQueryOver<Registration>(p => p.Registrations)
.Where(r => r.IsDropped == false)
...
Is in fact a WHERE clause for a Person. I mean, this condition is deciding if there is such a Person, having at least one Registration which is NOT dropped. It is not a way, how to filter the Registration colleciton. BUT:
NHibernate has two nice ways how to filter collections themselves. These are where clause and filter.
1. where clause
Easily said: we can extend the collection mapping, whit a SQL Statement, which will be always added as a where condition. See where 6.2. Mapping a Collection, extract:
where="arbitrary sql where condition"
== (optional) specify an arbitrary SQL WHERE condition to be used when retrieving or removing the collection (useful if the collection should
contain only a subset of the available data)
so in our case the mapping:
HasMany<Registration>(
...
.Where("IsDropped = 0"); // SQL Statement
2. filter setting
While where clause is static, baked into the assembly/mapping... filter is a way how to do that dynamically. So just imagine, that we would sometimes like to have a set of Registrations, which are dropped, next time, which are not dropped.
We would like to be able to change "IsDropped = 0" to "IsDropped = 1" (better scenario could be switch by culture or lang). And that's where the filter could be applied, see 18.1. NHibernate filters
There is a small draft of the filter stuff with fluent, see the links below to get a complete picture.
HasMany
...
.ApplyFilter<MyFilter>()
And filter could be like:
public class MyFilter: FilterDefinition
{
public MyFilter()
{
WithName("dropped")
.WithCondition("IsDropped == :isDropped")
.AddParameter("isDropped", NHibernate.NHibernateUtil.Boolean);
}
}
And finally, we can enable the Filter for a whole session (coool, all collections will be filtered the same way in that transaction/session, all collection mapped with MyFilter)
session.EnableFilter("dropped").SetParameter("isDropped", false);
See more here:
Syntax to define a NHibernate Filter with Fluent Nhibernate (NOT Accepted answer)
How do you do additional filtering on a HasMany mapping in Fluent nHibernate?

Related

Building Fluent NHibernate Maps at runtime

I am working on a project that looks at the database created by Mirth Connect v3.
In this version, tables are created at run time based on channels created.
For example, if I create a new channel, it creates an entry in a table that has a unique id. Then, using that id, a set of tables is created with that id added onto the end of the name. ie for a channel with id 8, a table called d_ms8 (among others) is created.
All of these tables (d_ms*) have the same structure.
What I am wondering is whether or not there is a way to map this sort of thing in Fluent NHibernate.
public class MapChannelStats : ClassMap<ChannelStatsObj>
{
public const String TableName = "d_ms8";
public MapChannelStats()
{
Table(TableName);
Id(x => x.MetadataID).Column("metadata_id");
Map(x => x.ServerID).Column("server_id");
Map(x => x.Errored).Column("error");
Map(x => x.ErroredLifetime).Column("error_lifetime");
Map(x => x.Filtered).Column("filtered");
Map(x => x.FilteredLifetime).Column("filtered_lifetime");
Map(x => x.Received).Column("received");
Map(x => x.ReceivedLifetime).Column("received_lifetime");
Map(x => x.Sent).Column("sent");
Map(x => x.SentLifetime).Column("sent_lifetime");
}
}
Thanks,
Bruce.
It is a difficult thing what you are asking, because I think you are not able to change the table's name at runtime. However, if you change your approach, there should be some possibilities, like:
You could create a column to specify the tenant ID, instead of creating a whole table for it. Then, you can set a filter specifically for this column (reference), which you can change like this:
Session.EnableFilter("tenant-filter").SetParameter("TenantId", "2");
Or you could create a database for which tenant and use a component like Boot.Multitenancy.

How do I reference a row using multiple properties in the current entity?

I need to reference a row in a table not by its primary key but by two other columns. How do I configure that in fluent-nhibernate?
I am using FluentNHibernate 1.3
Example: If the other table YY has columns foo and bar (assume datatype is integer if that helps) which are not primary keys. My current class XX has those properties and I want to reference a YY object from XX. Should I put the following in the ClassMap<XX> class if the local member is YYObject and the fields to reference it are XX.foo and XX.bar ?
CompositeId(x => x.YYObject).KeyProperty(x => x.foo).KeyProperty(x => x.bar);
I am also not quite sure how the ClassMap<YY> class should be adjusted accordingly. If you have done such things we could use some guidance.
We are dealing with a legacy situation so I cannot wave my hands and make it all go away.
if possible define a Component around the two properties
Component(x => x.Comp, c =>
{
c.Map(x => x.Foo);
c.Map(x => x.Bar);
});
and reference it
References(x => x.ParentObject)
.KeyColumns.Add("parent_foo", "parent_bar");
.PropertyRef(p => p.Comp);

property filter with fluent nHibernate automapping

i'm trying to create a filter, using fluent nH (1.2) automapping with nH 2.1.2.
I've followed the example here, but I keep getting the exception:
filter-def for filter named 'DateFilter' was never used to filter classes nor collections..
the filter class:
public class DateFilter : FilterDefinition
{
public DateFilter()
{
WithName(Consts.FilterConsts.DATE_FILTER)
.AddParameter("date", NHibernate.NHibernateUtil.DateTime)
.WithCondition("DATEPART(dayofyear,EntityTime) = DATEPART(dayofyear, :date)")
;
}
}
and in the mapping override:
mapping.HasMany(x => x.Stuff)
.LazyLoad()
.ReadOnly()
.ApplyFilter<DateFilter>();
here's my configuration code.
Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.DefaultSchema("dbo") //set default schema to enable full-qualified queries
.AdoNetBatchSize(batchSize > 0 ? batchSize : 1)
.UseReflectionOptimizer()
.ConnectionString(c => c.FromConnectionStringWithKey(connectionStringKey))
.Cache(c => c.UseQueryCache()
.ProviderClass(
isWeb ? typeof(NHibernate.Caches.SysCache2.SysCacheProvider).AssemblyQualifiedName //in web environment- use sysCache2
: typeof(NHibernate.Cache.HashtableCacheProvider).AssemblyQualifiedName //in dev environmet- use stupid cache
))
)
.Mappings(m => m.AutoMappings.Add(
AutoMap.AssemblyOf<Domain.Entity>(cfg) //automapping the domain entities
.IncludeBase<Domain.SomethingBase>() //ensure that although SomethingBase is a base class, map it as well. this enables us to store all Something sub-classes in the same table
.IncludeBase<Domain.OrOtherBase>() //create a table for the abstract 'OrOtherBase' class
.UseOverridesFromAssemblyOf<MappingOverrides.MappingOverride>()
.Conventions.Add(DefaultCascade.All()) //make sure that all saves are cascaded (i.e when we save a zone, its queues are saved as well)
.Conventions.AddFromAssemblyOf<IdGenerationWithHiLoConvention>()
))
.Mappings(m => m.FluentMappings.Add(typeof(DateFilter)));
if I move the line before the automapping part, I get the exception:
NHibernate.MappingException: filter-def for filter named 'DateFilter' was not found.
can anybody tell me what I'm doing wrong?
OK, so I figured this out. When you add the mappings separately like that, they end up in different mappings and it will either complain that you never use the filter or complain that it can't find the filter, because it's not looking both places. The solution is to add it directly to the automap, so in your case like:
//other stuff up here
.Mappings(m => m.AutoMappings.Add(() => {
var a = AutoMap.AssemblyOf<Domain.Entity>(cfg)
.IncludeBase<Domain.SomethingBase>() //and also cascades and conventions and stuff
a.Add(typeof(DateFilter));
return a;
}));
Kinda gross because .Add() isn't fluent, but it does work.

Nhibernate dont update child

i have a Class Map CardMatch which have Employee and Card as reference
Id(x => x.MatchId).GeneratedBy.Sequence("CARDMATCH_SEQ").Column("MATCHID");
References(x => x.Employee).Column("EMPNO");
References(x => x.Card).Column("CARDID").LazyLoad(Laziness.False).Cascade.SaveUpdate();
CardMatch _cm = _rep.getById(1);
_cm.Card.CardLimit = 500;
_rep.Update(_cm);
doesnt update the Child (Card). How can i update the Child?
IMO your FNH mapping is correct. Flush method should be called on ISession instance. The last line is most likely redundand because NH tracks changes on its own.
CardMatch _cm = _rep.getById(1);
_cm.Card.CardLimit = 500;
_rep.Flush(); // save changes

Eager load while using Linq in NHibernate 3

I need help with eager loading in with Linq in NHibernate 3 trunk version.
I have a many-to-many relationship like this:
public class Post
{
public int Id {get;set;}
public IList<Tag> Tags { get;set;}
.
.
.
}
Now I have the following mapping in Fluent NHibernate
public class PostMap:ClassMap<Post>
{
public PostMap()
{
Table("Posts");
Id(x => x.Id);
.
.
HasManyToMany(x => x.Tags)
.Table("PostsTags")
.ParentKeyColumn("PostId")
.ChildKeyColumn("TagId")
.Not.LazyLoad(); // this is not working..
}
}
Now while fetching the posts, I need the Tags also to eager load. I know that it is possible with Criteria API and HQL and the SetFetchMode is what I should use. But is there are way to use SetFetchMode when using Linq?
Support for this went into the trunk sometime ago; the syntax is be something like
var query = session.Query<Post>().Where(bla bla).Fetch(p => p.Tags);
If Tags in turn had another relationship, you can do:
var query = session.Query<Post>().Where(bla bla).Fetch(p => p.Tags).ThenFetch(t => t.SomethingElse);
For me this thread solve problem.
Linq for NHibernate - filtering on <many-to-one> foreign key causes extra lookup
var linqsession = session.Linq<FeedItem>();
linqsession.QueryOptions.RegisterCustomAction(c => c.SetResultTransformer(new DistinctRootEntityResultTransformer()));
var feedItemQuery = from ad in linqsession.Expand("Ads")
where ad.Id == Id
select ad