Fluent NHibernate - How do I create a one to many mapping which has a bridge table in the middle? - fluent-nhibernate

How do I create a one to many mapping which has a bridge table in the middle?
I basically have 3 tables: Items, Tags, and TagsToItems.
Each Item can have many Tags as defined by the TagsToItems table. How do I set up this mapping correctly using Fluent NHibernate?
I've been playing with HasMany but haven't quite figured out how this works with a bridge table.
HasMany(x => x.Tags).Table("TagsToItems").KeyColumn("ItemId");
My latest attempt to solve this problem looks like this:
HasManyToMany(x => x.Tags)
.AsBag()
.Table("TagsToItems")
.ParentKeyColumn("ItemId")
.ChildKeyColumn("TagId")
.Cascade.All()
.Inverse();
However this is throwing the error:
Initializing[Namespace.Item#11]-failed to lazily initialize a
collection of role:
Namespace.DataAccess.NHibernate.Entities.Item.Tags, no session or
session was closed

It turns out that the problem is with using the Tags collection associated to an Item.
The Tags collection could not be lazily initialised because by the time I was trying to use it (in my view) the session scope of the NHibernate session had closed.
I solved this by setting .Not.LazyLoad() on the mapping:
HasManyToMany(x => x.Tags)
.AsBag()
.Table("TagsToItems")
.ParentKeyColumn("ItemId")
.ChildKeyColumn("TagId")
.Not.LazyLoad()
.Cascade.All();

Related

Fluent nHibernate L2 Caching Not Working On HasMany Items

I’m wondering if I have mis-understood how the L2 caching works. I am trying to cache a ‘HasMany(x => x.Posts)’, Bascially I have a topic that has many posts under it - I was under the impression that if I added the following at the top of my topic map
Cache.ReadWrite().IncludeAll();
its caches map and hasManys until either the underlying data changes of app is restarted? I have my L2 cache configured like so
Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("MyAppConString")))
.Cache(c => c.ProviderClass<SysCacheProvider>().UseQueryCache())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<MembershipUserMap>())
Etc…etc..
In my Topic map I have the following (I have removed a load of the normal maps to shorten it), and you can see the HasMany on the posts.
public TopicMap()
{
Cache.ReadWrite().IncludeAll();
Id(x => x.Id);
Map(x => x.Name);
*lots of other normal maps*
References(x => x.Category).Column("Category_Id");
References(x => x.User).Column("MembershipUser_Id");
References(x => x.LastPost).Column("Post_Id").Nullable();
HasMany(x => x.Posts)
.Cascade.AllDeleteOrphan().KeyColumn("Topic_Id")
.Inverse();
*And a few other HasManys*
}
So if I grab all topics, loop over and do the following
Topic.Posts.Count()
Using SqlProfiler I see the get all posts for each topic (First hit), but if I reload the page I still see all the gets posts for topic queries??
Am I missing something?? I thought this should be cached now??
Ayende has a good post explaining the same problem that you have run into.
Basically, what you have there is caching only IDs of Posts, not the posts itself. Notice that you have enabled caching for Topic entities, but you probably didn't do the same for Post entities in PostMap.
Add Cache.ReadWrite() to PostMap also.
Notice that NHibernate won't be able to do the collection caching right in most of the cases if you are using eager loading with Fetch.Join(), like you have in your other question.
Here's a very good overview of the caching and the problems you might have with eager loading collections.

NHibernate: Problem trying to save an entity containing collections

I need some help.
I'm just starting out with NHibernate and I'm using Fluent for mappings. Everything seemed to work fine until today.
Here is the story:
I have two tables in my db: Store and WorkDay
The first table contains info about the store, and the WorkDay table contains info about the days of week and start/end time when the store is open.
Store contains a Guid StoreID PK column that is referenced in the WorkDay table.
So I have a mapping file for Store where I have a HasMany association with the WorkDay table, and a corresponding POCO for Store.
Now, when I fill in all the necessary data and try to persist it to database, I get an exception telling me that the insert into table WorkDay failed because the StoreID had null value and the table constraint doesn't allow nulls for that column (which is, of course, expected behavior).
I understand the reason for this exception, but I don't know how to solve it.
The reason why the insert fails is because the StoreID gets generated upon insert, but the [b]WorkDay[/b] collection gets saved first, in the time when the StoreID hasn't yet been generated!
So, how do I force NHibernate to generate this ID to pass it to dependent tables? Or is there another solution for this?
Thank you!
Here's the code for StoreMap
public class StoreMap : ClassMap<Store> {
public StoreMap() {
Id(x => x.StoreID)
.GeneratedBy.GuidComb();
Map(x => x.City);
Map(x => x.Description);
Map(x => x.Email);
Map(x => x.Fax);
Map(x => x.ImageData).CustomType("BinaryBlob");
Map(x => x.ImageMimeType);
Map(x => x.Name);
Map(x => x.Phone);
Map(x => x.Street);
Map(x => x.Zip);
HasMany(x => x.WorkDays)
.Inverse().KeyColumn("StoreID").ForeignKeyCascadeOnDelete()
.Cascade.All();
}
}
and this is for the WorkDayMap
public class WorkDayMap : ClassMap<WorkDay>{
public WorkDayMap() {
Id(x => x.WorkDayID)
.GeneratedBy.Identity();
Map(x => x.TimeOpen);
Map(x => x.TimeClose);
References(x => x.Store).Column("StoreID");
References(x => x.Day).Column("DayID");
}
}
NHibernate shouldn't insert the WorkDay first, so there must be an error in your code. Make sure you do all of the following:
Add all WorkDay objects to the WorkDays collection.
Set the Store property on all WorkDay objects to the parent object.
Call session.Save() for the Store but not for the WorkDay objects.
edit: You should also note that ForeignKeyCascadeOnDelete() won't change anything at runtime. This is just an attribute for the hbm2ddl tool. If you want NHibernate to delete removed entries, use Cascade.AllDeleteOrphan().
It probably inserts the Workday before the Store because the first has an identity generator. This forces NH to execute an INSERT statement to generate the ID. guid.comb is generated in memory, NH doesn't need the database.
NH tries to access the db at the latest possible point in time, to avoid unnecessary updates and to make use of batches. Workday is inserted when you call session.Save, Store is inserted when it flushes the session next time (eg. on commit).
You shouldn't use the identity generator (unless you are forced to use it). It is bad for performance anyway.
If it still doesn't work, you probably need to remove the not-null constraint on the foreign key. NH can't resolve it every time. There is some smart algorithm which is also able to cope with recursive references. But there are sometimes cases where it sets a foreign key to null and updates it later even if there would be a solution to avoid it.

NHibernate Issuing Unwanted Orphaning Statements

Imagine that I have a Parent/Child relationship managed by NHibernate.
I'm receiving a Parent object from an MVC postback that edits its properties; I want to save just the parent to the database without having to load the children from the database.
At the time of the save, Parent has a Children property that is null (because it hasn't been loaded; there are, however, valid Children in the database for that parent).
When I save a modified Parent (ID=100), NHibernate is issuing a "SET Child.ParentId = NULL WHERE Child.ParentId = 100" statement. I don't want this to happen because there could be valid children. I shouldn't have to load them from the database before the save just to prevent them from being orphaned.
The fluent mappings look like this (true entity names genericized for this post):
public ParentMapping()
{
Table("Parent");
Id(x => x.Id).Column("Id").GeneratedBy.Identity();
Map(x => x.ParentProperty1).Column("ParentProperty1").Not.Nullable();
HasMany(x => x.Children).Cascade.None();
}
public ChildMapping()
{
Table("Children");
Id(x => x.Id).Column("Id").GeneratedBy.Identity();
Map(x => x.ChildProperty1).Column("ChildProperty1").Not.Nullable();
References(x => x.Parent).Column("Parent_Id").Not.Nullable().Fetch.Select();
}
To summarize, I want to save an updated Parent instance that was retrieved from an earlier ISession (and passed to a browser and back through MVC model minding); its Children property is null, but in reality it's got plenty of children in the database. I don't want NHibernate to issue any modifying statements to the Children table at all.
I've experimented with Cascade.None() and LazyLoad() in the hopes that this nudges NHibernate to behave differently, but no luck.
Any insight would be appreciated. Thanks!
Jeff
You must specify Inverse() on the has many to tell nhibernate not to worry about this side of the collection

Fluent NHibernate - HasMany().WithKeyColumnName

I just got the latest version of Fluent from Google code and it seems some of the mapping has changed since I last used it.
Previously I could Map a relationship using the following when the id I was joining on had a different name in the second table
HasMany(x => x.Roles).WithTableName("tbl_Roles").WithKeyColumn("RoleId");
How is done in the latest release of Fluent?
Thanks
HasMany(x => x.Roles)
.WithTableName("tbl_Roles")
.KeyColumns.Add("RoleId");
Multiple column support was added, so the method signature needed to be improved to make it clear what's happening.
This works for me:
HasMany(x => x.Roles)
.WithTableName("tbl_Roles")
.KeyColumnNames.Add("RoleId");

FluentNHibernate HasMany not filling collection

I have a one to many relationship with the following config
HasMany(x => x.Staff)
.Inverse()
.Cascade.All();
But I get a collection failed to initialize error.
Dont I have to specify the foreignkey here, examples I found do not????
How does it know which is the foreign key?
EDIT: Looking closer at the exception the sql is trying to use field Staff_id
when I have said it is StaffID??
Malcolm
Try
HasMany(x => x.Staff)
.KeyColumnNames.Add("StaffID")
.Inverse()
.Cascade.All();
Staff_id is the auto configure default, although you can set what conventions auto-configure uses.
If you're mapping the collection to an IList<T>, you'll want to add AsBag() or NHibernate will complain about a missing "idx" column. If you want to lazy load the collection add .LazyLoad(). And I usually go with .Cascade.AllDeleteOrphan().