Problem with table per class hierarchy and automapping

157 views
Skip to first unread message

Miha V

unread,
Aug 9, 2010, 7:39:00 AM8/9/10
to Fluent NHibernate
Hi!

I'm using most of the default configuration, but I overrode one entity
to be persisted using table-per-class-hierarchy concept.

I have an abstract base class AmountInfo and then have several other
derived classes, which add no other information to this base class.

They are mapped like so on an entity:

public class Employee : EntityBase
{
public virtual ISet<HourlyRate> HourlyRates { get; set;
}

where HourlyRate is defined like

public class HourlyRate : AmountInfo
{}

The configuration I'm using is:
var mapping = AutoMap.AssemblyOf<EntityBase>(new
CustomMappingConfiguration())
.IncludeBase<AmountInfo>()
.Override<AmountInfo>(map => {
map.DiscriminateSubClassesOnColumn("type");
map.SubClass<HourlyRate>("HourlyRate");
map.SubClass<EventHourlyRate>("EventHourlyRate");

map.SubClass<ExternalHourlyRate>("ExternalHourlyRate");

map.SubClass<SchoolHourlyRate>("SchoolHourlyRate");

map.SubClass<IndividualHourlyRate>("IndividualHourlyRate");

map.SubClass<OpenClassTicketPrice>("OpenClassTicketPrice");
map.SubClass<TwoPackagePrice>("TwoPackagePrice");

map.SubClass<ThreePackagePrice>("ThreePackagePrice");
})

.Override<AmountInfo>(map => map.LazyLoad())
.Override<HourlyRate>(map => map.LazyLoad())
.Override<ExternalHourlyRate>(map => map.LazyLoad())
.Override<EventHourlyRate>(map => map.LazyLoad())
.Override<SchoolHourlyRate>(map => map.LazyLoad())
.Override<IndividualHourlyRate>(map => map.LazyLoad())
}

I have ommited other parts of the configuration for brevity.

Now this seems to work for **saving** entities, but not for loading.
When loading, NHibernate does not add where clause to filter elements
based on "type" column. The generated XML fragment looks like:

<set cascade="all" name="HourlyRates" mutable="true">
<key>
<column name="Employee_id" />
</key>
<one-to-many class="Bbis.Core.Models.HourlyRate, Bbis.Core,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</set>

So when loading items, I get the following exception:
NHibernate.WrongClassException: Object with id: 6565 was not of the
specified subclass: Bbis.Core.Models.EventHourlyRate (loading object
was of wrong class [Bbis.Core.Models.HourlyRate]) because it loads all
of the items in the AmountInfo table and does not filter the contents
on the type column.

How can I approach this?

Thanks,
Miha.

Miha V

unread,
Aug 16, 2010, 5:18:55 AM8/16/10
to Fluent NHibernate
Hi!

I'm pretty stuck with this issue. Could anyone comment on it? If
needed, I have a small repro solution ready to share as well.

Thanks,
Miha.

tbushell

unread,
Aug 17, 2010, 2:49:25 PM8/17/10
to Fluent NHibernate
Miha,

(I replied to your post on NHUsers, but here it is again...)

I am also using FNH automapping in my project, and have a similar
requirement to map multiple ILists of the same type in some of my
classes.

Following advice from the FNH mailing list, I ended up with a
solution
almost identical to yours, a base class (though it's not defined as
Abstract), and a derived class for each IList that is a member of the
same class.

This Automaps perfectly well _as is_ - no overrides, no where
clauses,
no mapping files are needed.

I see you are using ISets, not ILists, and am not sure if Automapping
supports ISets. But I know this approach works very well with
ILists.

The other difference I see is that you are using private backing
variables for your ISets. This is a fairly recent addition to FNH
Automapping that was not available to me several months ago when I
started my project.

-Tom Bushell

Miha V

unread,
Aug 17, 2010, 6:51:49 PM8/17/10
to Fluent NHibernate
Tom, thanks for your effort. I tried using IList, but that didn't
change anything (as I replied on the nhusers mailing list). The catch
was that I needed to specify the "WHERE" clauses with Overrides like
this:

.Override<Employee>(map => map.HasMany(m =>
m.NightlyRates).Where("RateType = 'NightlyRate'"))

I'm wondering though, why your solution was working as expected...

Regards,
Miha

tbushell

unread,
Aug 18, 2010, 12:23:48 PM8/18/10
to Fluent NHibernate
Miha,

As an exercise, I downloaded your sample project (as mentioned in your
NHUsers post)
and modifed the "ConfigureNHibernate" to work the same way my project
does.

As far as I can tell, it _seems_ to be working, and I was able to
comment out _all_ of your CustomMappingConfiguration and conventions
code.

Modified routine (and helper "BuildSchema" method) is appended below.
Please let me know if this is works properly for you.

-Tom

----------------------------------------

private ISessionFactory ConfigureNHibernate()
{
var mapping = AutoMap.AssemblyOf<Employee>()
.Where(t => t.Name == "Employee" ||
t.Name == "HourlyRate" ||
t.Name == "NightlyRate")
.Conventions.Add(
// Do cascading saves on all entities so lists will
be
// automatically saved
DefaultCascade.All(),

// Turn on lazy loading, so will only read data that
is actually
// displayed
DefaultLazy.Always()
);

//var mapping = AutoMap.AssemblyOf<Employee>(new
CustomMappingConfiguration())
// .IncludeBase<Rate>()
// .Conventions.Setup(c =>
// {
//
c.Add<IdGenerationConvention>();
//
c.Add<DefaultStringLengthConvention>();
//
c.Add<IndexableConvention>();
//
c.Add<CascadeAllConvention>();
// //
c.Add<EagerLoadingConvention>();
//
c.Add<DiscriminatorConvention>();
// })
// .Override<Rate>(map =>
map.DiscriminateSubClassesOnColumn("RateType"))
// //.Override<HourlyRate>(map => map.Where("RateType =
'HourlyRate'"))
// //.Override<NightlyRate>(map => map.Where("RateType =
'NightlyRate'"))
// ;
return Fluently.Configure()
/* SQLite database */
.Database(SQLiteConfiguration
.Standard
.UsingFile("db.sqlite3")
.ShowSql()
)
/* SQL Server database */
//.Database(
// MsSqlConfiguration.MsSql2008
// .ConnectionString(connectionString)
// .ShowSql())
/* Use auto mapping */
.Mappings(m => m.AutoMappings
.Add(mapping)
.ExportTo(".\\"))
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();


//var nhConfig = config.BuildConfiguration();

//new SchemaExport(nhConfig).Execute(true, true, false);

//return nhConfig.BuildSessionFactory();
}

private static void BuildSchema(Configuration config)
{
try
{
// this NHibernate tool takes a configuration (with
mapping info)
// and exports a database schema from it
new SchemaExport(config)
.Create(true, true);
}
// This will be thrown if the mapping is bad, e.g. an
overide is missing
catch (MappingException e)
{
throw new ApplicationException(
"\r\n BuildSchema mapping exception:\r\n\r\n"
+ e.Message + "\r\n\r\n", e);
}
}
}

Miha V

unread,
Aug 18, 2010, 2:01:13 PM8/18/10
to Fluent NHibernate
Tom, thanks for your effort. Indeed, your solution works, but not the
way I wanted it to work.

What I wanted to achieve was that I wanted to serialize "hourly rates"
as table-per-class-hierarcy strategy, meaning that you have one table,
say "Rates" which has additional column "type", which defines the type
of the rate ('hourly rate', 'nightly rate', etc).

Your solution (and mine as well, if you remove the overrides) works in
a way that it uses table-per-class strategy, where you get a table for
every "hourly type", meaning that you get a table for hourlyrates,
table for nightlyrates, and so on.

The override '.Override<Rate>(map =>
map.DiscriminateSubClassesOnColumn("RateType"))' defines that I want
to store all the entities deriving from "Rate" in a single table,
where a column named "RateType" defines the type of this entry (row).

And when you store it like that, you get into the problem with
deserialization. The selects are missing a where clause to limit the
"correct entity type". And the solution I found was to add additional
overrides
.Override<Employee>(map => map.HasMany(m =>
m.RegularRates).Where("RateType = 'HourlyRate'"))
.Override<Employee>(map => map.HasMany(m =>
m.NightlyRates).Where("RateType = 'NightlyRate'"))

which define that when querying for RegularRates, include a SQL where
condition on RateType = 'HourlyRate'. I'm sort of surprised that this
is necessary, because the "saving" part works as expected and loading
doesnt. :(

I also received answers on nhusers mailing list telling me that I need
to specify the WHERE condition. The new overrides are also reflected
in HBM files (of course) like so:

<set cascade="all" name="NightlyRates" where="RateType =
'NightlyRate'" mutable="true">
<key>
<column name="Employee_id" />
</key>
<one-to-many class="NHibernatCollections.Models.NightlyRate,
NHibernatCollections, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null" />
</set>

Note the where attribute.

Anyway, thanks for your effort and help.

Regards,
Miha

tbushell

unread,
Aug 19, 2010, 10:47:33 AM8/19/10
to Fluent NHibernate

> What I wanted to achieve was that I wanted to serialize "hourly rates"
> as table-per-class-hierarcy strategy,

My mistake - on re-reading your first post, I now see you mentioned
that explicitly.

I must confess to a sort of "willful ignorance" about the actual
tables that FNH maps for me. I usually take a quick look at the
generated tables, and say "OK, I get it", but as long as the data in
my object model is persisted properly, and there are no obvious
performance problems, I'm happy with whatever it gives me.

In your case, you are obviously going to extra effort to get a table-
per-class-hierarchy. What are the advantages of this approach?

-Tom

Miha V

unread,
Aug 19, 2010, 3:29:59 PM8/19/10
to Fluent NHibernate
Well,

it "feels" weird to have many tables basically for the same type of
data. The derived classes are already a workaround if you ask me, and
when you do reporting (directly with SQL), it is easier to understand
what's going on and also easier to write reports against such model.
Because derived classes don't introduce new fields or anything into
the picture, you're basically just saving extra data for NHibernate to
figure out what goes into specific collection.

rgds,
Miha.

James Gregory

unread,
Aug 19, 2010, 3:36:50 PM8/19/10
to fluent-n...@googlegroups.com
Why are the classes a workaround? Are you designing a domain model or a database?

--
You received this message because you are subscribed to the Google Groups "Fluent NHibernate" group.
To post to this group, send email to fluent-n...@googlegroups.com.
To unsubscribe from this group, send email to fluent-nhibern...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/fluent-nhibernate?hl=en.


Miha V

unread,
Aug 20, 2010, 6:40:20 AM8/20/10
to Fluent NHibernate
James,

in OO terms, I can have:

public IList<Rate> HourlyRate { get; set;}
public IList<Rate> OtherRates { get; set; }

and this works perfectly well. Rate is a type and with a colletcion, I
specify a set (or a list in this case) of entities (or values if we'd
be talking about value object). Indeed, when talking about value
objects, the proper way would be to model Rate as a struct, but I was
unable to serialize it as such (I got error that NHibernate has a non-
mapped association). Event though the Rate struct was inside the
namespace that I configured for Automapping.

Regards,
Miha.

ps: and in most cases, when we model our domain, we have to (IMHO)
think about persistence (and ususally that means tables) as well -
unless a domain is a fairly trivial one.

Nick Webb

unread,
Aug 20, 2010, 10:20:21 AM8/20/10
to fluent-n...@googlegroups.com
Nhibernate cannot support structs.  It could never validate the proxy class it's trying to map to it, as it would get a new instance of struct every time it compared the two.  

Unless I've missed something, your best option is a simple class mapped as a component.

tbushell

unread,
Aug 20, 2010, 12:58:38 PM8/20/10
to Fluent NHibernate
James,

On Aug 20, 6:40 am, Miha V <miha.valen...@gmail.com> wrote:
> in OO terms, I can have:
>
> public IList<Rate> HourlyRate { get; set;}
> public IList<Rate> OtherRates { get; set; }
>
> and this works perfectly well. Rate is a type and with a colletcion, I
> specify a set (or a list in this case) of entities

I think Miha is saying that the empty derived classes are a workaround
needed to make Automapping work - they are not required by the object
model at all. They are an indication of a "leaky abstraction" - the
underlying relational model is leaking into the object model.

As someone who was also bitten by this (see Nov 2009 thread)
http://groups.google.com/group/fluent-nhibernate/browse_thread/thread/945b8bb9ddeebd4e/f59b9751ec0a6555
I totally agree with him.

Where Miha and I might differ is that I'm quite willing to compromise
the relational model to preserve the object model, at least in the
early stages of development.

I'm confident that FNH is so powerfull I'll be able optimize any "bad"
automappings via overrides, etc - though I have not needed to, after
almost a year of development on my first (and only, so far! :) FNH
project.

You and I had a little back and forth on this issue in the above
mentioned thread. At that time, you politely, but firmly, explained
that this is NOT your philosophy - you design your object model with
an eye to how it will be mapped to the relational DB.

I respect that - it's your project. Though it's probably more obvious
to you how FNH is going to map between the object model and the
relational model than to some of the rest of us (uh...well...to me at
least).

You also said that it would be difficult to modify FNH to handle this
case. But would it be possible (without a lot of work), to issue a
warning at runtime that putting ILists of the same type in the same
class is not going to work as expected? If so, I'll submit a feature
request.

The really cool thing about FNH Automapping is that it DOES allow
someone like me to be (mostly) willfully ignorant about the underlying
DB - this is one of the few leaky abstractions that I'm aware of.

-Tom

Nick Webb

unread,
Aug 20, 2010, 1:19:54 PM8/20/10
to fluent-n...@googlegroups.com
To toss in my $0.02, also having had this issue come up, I offer that one can easily handle the mappings in this case by specifying the columns for each collection of the same type.  In my case, I had a convention handling the first, and only the second required an override on the column name be set - this resolved the issue.

However, I disagree with the concept that one can build an object model and expect nhibernate to figure out how to make such a model work in a database any more than one could design an object model without an eye to how it will be used throughout the domain. 

Miha V

unread,
Aug 20, 2010, 1:21:33 PM8/20/10
to Fluent NHibernate
Nick, I managed a workaround (described earlier) with derived classes
and table-per-class-hierarachy strategy. Component wouldn't work
AFAIK, as there is a collection of "value" types stored (collection of
rates).

rgds,
Miha.

Nick Webb

unread,
Aug 20, 2010, 1:32:19 PM8/20/10
to fluent-n...@googlegroups.com
That works!

tbushell

unread,
Aug 20, 2010, 2:42:31 PM8/20/10
to Fluent NHibernate


On Aug 19, 3:29 pm, Miha V <miha.valen...@gmail.com> wrote:

> it "feels" weird to have many tables basically for the same type of
> data.

Yeah, I had the same reaction when I looked at the generated tables.

But in my case, it wasn't a problem, since I have no intention of
accessing the DB directly with standard SQL tools. I see now that you
have different requirements.

Another question, which speaks to my simple (as opposed to willful)
ignorance about the performance tradeoffs of different relational
models...

Are there significant (i.e. +/- 30%) performance advantages/
disadvantages among the various approaches?

-Tom

Miha V

unread,
Aug 20, 2010, 2:48:32 PM8/20/10
to Fluent NHibernate
I don't know, but I guess that not as much as 30%. Also, it _depends_
(doesn't it always? :)) on what kind of db backend you're using and
what kind of joins are being made.

rgds,
Miha.

tbushell

unread,
Aug 20, 2010, 3:03:15 PM8/20/10
to Fluent NHibernate


On Aug 20, 1:19 pm, Nick Webb <nwe...@gmail.com> wrote:
> To toss in my $0.02, also having had this issue come up, I offer that one
> can easily handle the mappings in this case by specifying the columns for
> each collection of the same type.  In my case, I had a convention handling
> the first, and only the second required an override on the column name be
> set - this resolved the issue.

Could you post some code snippets of the convention and the override
that illustrate this approach?


> However, I disagree with the concept that one can build an object model and
> expect nhibernate to figure out how to make such a model work in a database

Well, I certainly don't expect it to be able to map every concievable
object model to a database. But it already does an amazingly good job
of auto mapping many standard OO constructs - far better than any
other tool I'm aware of.

Maybe this is just a happy accident, but it was the main reason I
chose to use FNH over the alternatives.

-Tom
Reply all
Reply to author
Forward
0 new messages