Inheritance Mapping Fluent NHibernate results in unnecessary joins - nhibernate

The issue is when selecting directly on the base model the generated SQL performs an left outer select on the subclasses.
Basemodel.
public class Node
{
public virtual int ID {get;set;}
public virtual string Url {get;set;}
}
public class CMSPage : Node
{
public virtual string FieldA {get;set;}
public virtual string FieldB {get;set;}
}
public class Article : Node
{
public virtual string FieldC {get;set;}
public virtual string FieldD {get;set;}
}
My Mapping is
public class NodeMap : ClassMap<Node>
{
Table("Nodes");
Id(x => x.ID, "Node_ID");
Map(x => x.Url);
}
public class CMSPageMap: SubclassMap<CMSPage>
{
Table("CMSPages");
Map(x => x.FieldA);
Map(x => x.FieldB);
}
public class ArticleMap: SubclassMap<Article>
{
Table("Articles");
Map(x => x.FieldC);
Map(x => x.FieldD);
}
When querying direct on Nodes using ICriteria
var store = session.CreateCriteria(typeof(Node));
store.Add(Restrictions.Eq("Url", filter.Url));
store.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("ID"), "ID")
.Add(Projections.Property("Url"), "Url"));
store.SetResultTransformer(new
AliasToBeanResultTransformer(typeof(Node)));
Node result = store.UniqueResult<Node>();
The sql generated is
SELECT this_.Node_ID as y0_, this_.Url as y7_ FROM Nodes this_
left outer join CMSPages this_1_
on this_.Node_ID = this_1_.Node_ID
left outer join Articles this_2_
on this_.Node_ID = this_2_.Node_ID
WHERE this_.Url = '/' /* #p0 - Url */
How do i prevent the join selects
Ive tried using both Abstract and KeyColumn according to https://www.codeproject.com/Articles/232034/Inheritance-mapping-strategies-in-Fluent-Nhibernat

Subclass mapping purpose is to have a Table per class hierarchy. Means you have one table for any child class. You can find examples on the documentation about all kind of supported inheritance by NHibernate.
I think you need to try "Table per subclass" strategy, using a JoinedSubclassMap<T>. The two subclass tables have primary key associations to the superclass table so the relational model is actually a one-to-one association. Documentation 9.1.2. Table per subclass
If you want to keep the SubclassMap<T> you must use a discriminator. 9.1.3. Table per subclass, using a discriminator.

Related

How to map a string collection from another table in Fluent NHibernate?

I have an entity:
public class Foo
{
public virtual int Id;
public virtual IEnumberable<string> Bars;
}
And its mapping:
public class FooMapping : ClassMap<Foo>
{
public FooMapping()
{
Table("foo_table_in_database");
Id(x => x.Id, "Id");
HasMany(x => x.Bars)
.AsList()
.Table("bars_table_in_db")
.Element("BarId", m =>
{
m.Type<string>();
});
}
}
And an exception returned inside the entity insteaf of the expected result :(
base = {"could not initialize a collection: [Loya.Services.CouponsWeb.Promotion.LoyCouponCustomerGroups#2][SQL: SELECT loycouponc0_.Promotion_id as Promotion3_0_, loycouponc0_.LoyCustomerGroupId as LoyCusto1_0_, loycouponc0_.Index as Index0_ FROM loy_promotion__cu...
Database tables :
foo_table : *Id, other properties
bar_table : *FooId, *BarId
My aim is to get a List of BarId's (strings) in my Foo.
How do I map it properly?
I think you might need to specify the KeyColumn. I do something similar in one of my solutions and I would map the entities above as follows...
mapping.HasMany(x => x.Bars)
.Schema("Schema")
.Table("FooBars")
.Element("Bar")
.KeyColumn("FooID")
.ForeignKeyConstraintName("FK_FooBar_Foo")
.Not.Inverse()
.Cascade.All()
.Fetch.Select();
This will give a table called Schema.FooBars. It will have a FooID column (which is a foreign key back to the Foo table) and a Bar column which contains the value in your Bars collection.

Set identity seed in fluentnhibernate

Using NHibernate you can set an identity seed like so:
<column name="Column1" not-null="true" sql-type="int IDENTITY(1,1000)"/>
The FluentNHibernate IdentityPart has CustomType and SqlCustomType methods, neither does it for me though. Is there a way to fluently set an identity seed?
More info:
When I do this: Map(x => x.Id).Column("CustomerId").CustomSqlType("int IDENTITY(1,1000)");
I get this error: The entity 'Customer' doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id).
When I do this: Id(x => x.Id).Column("CustomerId").CustomSqlType("int IDENTITY(1,1000)");
I get this error: More than one column IDENTITY constraint specified for column 'CustomerId', table 'Customer'
Using FluentNHibernate 1.2.0.712.
I was able to duplicate that xml by doing something like this:
Map(x => x.LoginName, "Column1").CustomSqlType("int IDENTITY(1,1000)");
Edit:
If you can't achieve what you are wanting maybe you should explicitly map this using xml for now.
There is the article at the link below about implementing custom identity generator (see: Part 1: Inheriting from TableGenerator class) but the example throws the exception for SQLite database ("SQLite errorr no such table: hibernate_unique_key"). Thus as regard SQLite there is no possibility to gain current id key from a table. It uses class TableGenerator from NHibernate API (NHibernate.Id);
http://nhforge.org/wikis/howtonh/creating-a-custom-id-generator-for-nhibernate.aspx
To avoid the exception I implemented another solution (especially the way of getting current Id). It takes advantage of Fluent-NHibernate API (GeneratedBy.Custom()). Look at the following source code:
public class MyAutoincrement<T> : IIdentifierGenerator where T : IId
{
#region IIdentifierGenerator Members
public object Generate(ISessionImplementor session, object obj)
{
NHibernate.ISession s = (NHibernate.ISession)session;
int seedValue = 1000;
int maxId = -1;//start autoincrement from zero! (fluent nhibernate start from 1 as default)
List<T> recs = s.Query<T>().ToList<T>();
if (recs.Count > 0)
{
maxId = recs.Max(x => x.getId());
}
return seedValue + maxId + 1;
}
#endregion
}
//Interface for access to current Id of table
public interface IId
{
int getId();
}
//Entity
public class MyEntity : IId
{
public virtual int Id { get; protected set; }
public virtual string MyField1 { get; set; }
public virtual string MyField2 { get; set; }
#region IId Members
public virtual int getId()
{
return this.Id;
}
#endregion
}
//Entity Mapping
public class MyEntityMap : ClassMap<MyEntity>
{
public MyEntityMap()
{
Id(x => x.Id).GeneratedBy.Custom<MyAutoincrement<MyEntity>>();
Map(x => x.MyField1);
Map(x => x.MyField1);
}
}
It works with SQLite database and involves custom identity seed.
Regards
Bronek

Loading a base class through nhibernate incorrectly uses mappings from derived classes

I have a scenario where I have a base class as one entity, then another entity that derives from the other base class. Both have meaning in my domain and can be used separately.
public class MyBaseClass
{
int ID { get; set; }
string Name { get; set; }
}
public class MyChildClass
{
string AdditionalField { get; set; }
}
I have both mapped using Fluent nHibernate using ClassMap like this:
public class MyBaseClassMap : ClassMap<MyBaseClass>
{
Id("MyBaseClassID");
Map(x => x.Name);
}
public class MyChildClassMap : SubclassMap<MyChildClass>
{
Map(x => x.AdditionalField);
}
What is happening is when I try to fetch a copy of the base class, its using the mapping for the child class. Its as if it doesn't know the the difference between the base and child class, or its choosing the wrong mapping for it. I confirmed this by watching the SQL statement and its joining to the child table and fetching the additional column. Any way to get it to use the right map?
That's the 'nature' of NHibernate.
The behaviour you're describing, is called 'polymorphic queries'.
Since MyChildClass is a MyBaseClass, the MyChildClass instances are retrieved as well.
If you want to avoid this behaviour, you can maybe have a look at the answers in this topic. (I've never 'disabled' the polymorphic query ability).

HasMany<MySubtype> mapping returns supertype and all descendants?

I'm having a problem getting FluentNHibernate's HasMany<> mapping to behave as I would expect.
I have a class hierarchy of Child is a descendant of Parent, defined using table-per-hierarchy, discriminated by the class name in a column named 'TYPE'. An unrelated class Group contains a collection of Child elements.
So, the Group class is defined as:
public class Group
{
public virtual IList<Child> Children {get; protected set;}
public Group()
{
Children = new List<Children>();
}
}
My map in Fluent NHibernate looks like this:
public class GroupMap : SubclassMap<Group>
{
public GroupMap()
{
HasMany<Child>(x => x.Children)
.Not.LazyLoad()
.Inverse()
.Cascade.All();
}
}
I would expect that using Group.Children would result in a collection of objects of type Child, but it is returning a collection that contains objects of either type Parent or Child.
Looking at the SQL generated, the select statement is not differentiating based on the TYPE column in the table that holds the Parent and Child objects.
Changing my mapping to:
public class GroupMap : SubclassMap<Group>
{
public GroupMap()
{
HasMany<Child>(x => x.Children)
.Inverse()
.Not.LazyLoad()
.Where("Type = 'Child'")
.Cascade.All();
}
}
solves the problem, but it seems like a hack to me. Shouldn't the declaration "HasMany' infer that the results should be only of type Child?
Am I mapping my collections incorrectly?
The short answer is I've moved away from using FluentNHibernate mappings.
I'm back to using plain .hbm.xml mappings and much happier as a result.

NHibernate: set id to interface mapping

I try to write a (fluent) mapping against an interface
public interface IOrderDiscount : IDomainObject<long>
where
public interface IDomainObject<IdT> : IDomainObject
{
IdT Id { get; }
}
like so (and all other thinkable varieties of access strategies)
Id(d => d.Id, "DiscountId")
.GeneratedBy.HiLo("9")
.WithUnsavedValue(0)
.Access.AsReadOnlyPropertyThroughCamelCaseField();
but all I get are variations of
Could not find field 'id' in class 'IOrderDiscount'
My base class implements this as
public virtual IdT Id { get; protected set; }
but event using a backing field does not change a thing.
So I am left to wonder, how I could get this to work...
Anyone with an idea?
Specify the custom column name via the Column method instead:
Id(d => d.Id)
.Column("DiscountId")
.GeneratedBy.HiLo("9")
.WithUnsavedValue(0)
.Access.AsReadOnlyPropertyThroughCamelCaseField();