One-to-one relations within nhibernate can be lazyloaded either "false" or "proxy". I was wondering if anyone knows a way to do a lazy one-to-one mapping.
I worked out a hack to achieve the same result by using a lazy set mapped to a private field, and having the public property return the first result of that set. It works, but isn't the cleanest code...
Thanks in advance!
Lazy loading of one-to-one isn't supported unless the association is mandatory. See here for the reasoning.
It boils down to the fact that in order to decide if the other side of the relationship exists (N)Hibernate has to go to the database. Since you've already taken the database hit, you might as well load the full object.
While there are cases where hitting the DB just to see if the related object exists without actually loading the object makes sense (if the related object is very "heavy"), it isn't currently supported in NHibernate.
As far as I know, there isn't a non-hacky way to lazy load a one-to-one. I hope I'm wrong, but last time I checked it was the case.
There is way thought. It's described here in details :
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTest" namespace="NHibernateTest">
<class name="Person" >
<id name="PersonID" type="Int32">
<generator class="identity" />
</id>
<property name="LastName" type="String" length="50" />
<property name="FirstName" type="String" length="50" />
<many-to-one name="Photo" class="PersonPhoto" />
</class>
<class name="PersonPhoto">
<id name="PersonID" type="Int32">
<generator class="foreign">
<param name="property">Owner</param>
</generator>
</id>
<property name="Photo" type="BinaryBlob" />
<one-to-one name="Owner" class="Person" constrained="true" />
</class>
</hibernate-mapping>
I tried the example used by Artem Tikhomirov above. I kept getting an error that the Photo column does not exist. After looking at this, I figured out that the mapping was off a little. When I changed the many-to-one mapping to specify the column name like this:
many-to-one name="Photo" column="PersonID" class="PersonPhoto" unique="true"
I got it to work. I hope this helps someone :o)
After reading the answers here, I´ve manage to get it to work.
I´m just going to add this example because I´m using a One to One relation with Constrained= False and because it´s a Mapping by Code Example
Two Classes:
public class Pedido:BaseModel
{
public virtual BuscaBase Busca { get; set; }
}
public class BuscaBase : BaseModel
{
public virtual Pedido Pedido { get; set; }
}
Mappings:
public class PedidoMap : ClassMapping<Pedido>
{
public PedidoMap()
{
Id(x => x.Id, x => x.Generator(Generators.Identity));
ManyToOne(x => x.Busca, m =>
{
m.Cascade(Cascade.DeleteOrphans);
m.NotNullable(true); m.Unique(true);
m.Class(typeof(BuscaBase));
});
}
}
public class BuscaBaseMap : ClassMapping<BuscaBase>
{
public BuscaBaseMap()
{
Id(x => x.Id, x => x.Generator(Generators.Sequence, g => g.Params(new { sequence = "buscaefetuada_id_seq" })));
OneToOne(x => x.Pedido, m =>
{
m.Lazy(LazyRelation.NoProxy);
m.Constrained(false);
m.Cascade(Cascade.None);
m.Class(typeof(Pedido));
});
}
}
Note: I was Using for the one-to-one mapping m.PropertyReference(typeof(Pedido).GetProperty("Busca")); but this does't work for the lazy loading. You have to specify the relation using the Class
A quick brief about the Constrained = False used in here, the "Pedido" object might not exist in "BuscaBase" object.
What worked for me is the following (very similar to #Daniel) but I found that it was necessary to specify the LazyRelation.NoProxy on both ends of the mapping.
public class Person
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual PersonDetails Details { get; set; }
public class PersonMap : ClassMapping<Person>
{
public PersonMap()
{
Id(x => x.Id, m =>
{
m.Generator(Generators.Native);
});
Property(x => x.Name);
OneToOne(x => x.Details, m =>
{
m.Lazy(LazyRelation.NoProxy);
m.Cascade(Cascade.Persist);
});
}
}
}
public class PersonDetails
{
public virtual int Id { get; set; }
public virtual string ExtraDetails { get; set; }
public virtual Person Person { get; set; }
public class PersonDetailsMap : ClassMapping<PersonDetails>
{
public PersonDetailsMap()
{
Id(x => x.Id, m =>
{
m.Generator(Generators.Native);
});
Property(x => x.ExtraDetails);
ManyToOne(x => x.Person, m =>
{
m.Lazy(LazyRelation.NoProxy);
m.Unique(true);
m.NotNullable(true);
});
}
}
}
using var session = NhHelper.OpenSession();
var person1 = new Person();
person1.Name = "A";
var person1Details = new PersonDetails();
person1Details.ExtraDetails = "A details";
person1.Details = person1Details;
person1Details.Person = person1;
session.Save(person1);
//because of PersonMapping's Cascade.Persist it is not necessary to manually save person1Details object.
using var session = NhHelper.OpenSession();
foreach(var person in session.Query<Person>()) {
Console.WriteLine(person.Name); //<-- does not load PersonDetails unless it's property is accessed
}
NHibernate 5.3.5
Npgsql 5.0.3 (Postgresql Db).
Related
I have used NHibernate for quite some time now but still struggle to do some "simple" stuff. I am trying to work with a many-to-many relationship between my entity ServiceProvider and Features.
Basically every SERVICE_PROVIDERS can have different features which must be present in my table FEATURES.
These are my mapping files:
ServiceProviders.hbm.xml,
<class name="App.Domain.ServiceProvider, App.Domain" table="ServiceProviders">
<id name="Code" type="System.Guid" unsaved-value="00000000-0000-0000-0000-000000000000">
<column name="ServiceProviderCode" />
<generator class="guid.comb" />
</id>
<property name="Description" type="AnsiString">
<column name="Description" length="150" not-null="true" />
</property>
<set name="Features" table="ServiceProvidersFeatures" access="field.pascalcase-underscore" cascade="save-update" optimistic-lock="false">
<key column="ServiceProviderCode"></key>
<many-to-many class="App.Domain.Feature" column="FeatureCode" not-found="exception" />
</set>
</class>
Features,
<class name="App.Domain.Feature, App.Domain" table="Features">
<id name="Code" type="System.Guid" unsaved-value="00000000-0000-0000-0000-000000000000">
<column name="FeatureCode" />
<generator class="guid.comb" />
</id>
<property name="Description" type="AnsiString">
<column name="Description" length="150" not-null="true" />
</property>
<set name="ServiceProviders" table="ServiceProvidersFeatures" cascade="none" inverse="true" lazy="true" access="field.pascalcase-underscore" optimistic-lock="false" mutable="false">
<key column="FeatureCode"></key>
<many-to-many class="App.Domain.ServiceProvider" column="ServiceProviderCode" not-found="ignore" />
</set>
</class>
And these are the 2 main classes:
ServiceProvider.cs
public class ServiceProvider
{
public ServiceProvider()
{
this._Features = new HashSet<Feature>();
}
public virtual Guid Code { get; protected set; }
public virtual string Description { get; set; }
private ICollection<Feature> _Features = null;
public virtual ReadOnlyCollection<Feature> Features
{
get { return (new List<Feature>(_Features).AsReadOnly()); }
}
}
Feature.cs
public class Feature
{
public Feature()
{
this._ServiceProviders = new HashSet<ServiceProvider>();
}
public virtual Guid Code { get; protected set; }
public virtual string Description { get; set; }
private ICollection<ServiceProvider> _ServiceProviders = null;
public virtual ReadOnlyCollection<ServiceProvider> ServiceProviders
{
get { return (new List<ServiceProvider>(_ServiceProviders).AsReadOnly()); }
}
}
What I am trying to do is fetch all the services-providers (with all the features) where the description starts with a certain string, and they have at least one feature specified (param).
I reckon I need a subquery but I don't know how to build the QueryOver. It should be something like this:
var serviceProviders =
session.QueryOver<App.Domain.ServiceProvider>()
.Inner.JoinAlias(x => x.Features, () => features)
.WhereRestrictionOn(f => f.Description).IsLike("%" + "test" + "%")
.AndRestrictionOn(() => features.Code).IsIn(<SubQuery>)
.List();
I've come up with an extension method with allows me to build expressions:
public static class ServiceProviderSearch
{
public static IQueryOver<App.Domain.ServiceProvider, App.Domain.ServiceProvider> AttachWhereForSearchText(this IQueryOver<App.Domain.ServiceProvider, App.Domain.ServiceProvider> mainQuery, string searchText)
{
if (!string.IsNullOrEmpty(searchText))
{
ICriterion filterSearchText = Expression.Disjunction()
.Add(Restrictions.On<App.Domain.ServiceProvider>(f => f.Description).IsLike(searchText, MatchMode.Anywhere))
.Add(Restrictions.On<App.Domain.ServiceProvider>(f => f.ExtendedDescription).IsLike(searchText, MatchMode.Anywhere));
mainQuery.Where(filterSearchText);
}
return (mainQuery);
}
public static IQueryOver<App.Domain.ServiceProvider, App.Domain.ServiceProvider> AttachWhereForFeatures(this IQueryOver<App.Domain.ServiceProvider, App.Domain.ServiceProvider> mainQuery, App.Domain.ServiceProvider serviceProvider, App.Domain.Feature features, Guid[] listOfFeatures)
{
if ((listOfFeatures != null) && (listOfFeatures.Count() > 0))
{
mainQuery
.Inner.JoinAlias(() => serviceProvider.Features, () => features);
mainQuery.WithSubquery.WhereProperty(() => features.Code)
.In(
QueryOver.Of<App.Domain.Feature>()
.WhereRestrictionOn(f => f.Code)
.IsIn(listOfFeatures)
.Select(f => f.Code)
);
}
return (mainQuery);
}
}
So now I can write something like this:
App.Domain.ServiceProvider serviceProvider = null;
App.Domain.Feature features = null;
App.Domain.Language languages = null;
App.Domain.Service services = null;
Guid[] selectedFeatures = {};
var serviceProviders = Session.QueryOver(() => serviceProvider);
serviceProviders
.AttachWhereForSearchText(<searchText>)
.AttachWhereForFeatures(serviceProvider, features, selectedFeatures);
Results = serviceProviders
.TransformUsing(Transformers.DistinctRootEntity)
.Take(<pageSize>)
.Skip((<page> - 1) * <pageSize>)
.ToList<App.Domain.ServiceProvider>();
Inspiration from this answer.
There are couple of things I would change in your snippet. First, you don't need to specify the % in your IsLike method: NHibernate does that automatically.
Second, you can construct the subquery in the following manner:
var subquery =
session.QueryOver<App.Domain.Feature>()
.WhereRestrictionOn(f => f.Description).IsLike("test", MatchMode.Anywhere)
.Select(f => f.Code);
You can plug this in your main query:
var serviceProviders =
session.QueryOver<App.Domain.ServiceProvider>()
.WithSubquery.WhereProperty(s => s.Code).In(subquery)
.List();
Or you can even try:
var serviceProviders =
session.QueryOver<App.Domain.ServiceProvider>()
.JoinQueryOver<App.Domain.Feature>(s => s.Features)
.WhereRestrictionOn(f => f.Description).IsLike("test", MatchMode.Anywhere)
.List<App.Domain.ServiceProvider>();
Depending on your lazy settings, you can then initialize the Features collection with,
serviceProviders.ToList()
.ForEach(service => NHibernateUtil.Initialize(service.Features));
I want to get ClassA.ClassBCollection property filtered and paged. I need to change filtering dynamically.
The default querying will result in something like:
select * from ClassA
left outer join ClassB
on id == FK_ClassB
Can I customize querying of nhibernate set somehow?
Mappings:
<class name="ClassA">
<property name="Name" />
<set name="ClassBCollection">
<key column="FK_ClassB" on-delete="cascade" />
<one-to-many class="ClassB" />
</set>
</class>
<class name="ClassB">
<property name="Something"/>
</class>
If I do understand your question...
Can I customize querying of nhibernate set somehow?
...correctly, the answer is NO.
I mean, if you think about getting the instance of ClassA and doing some paging and filtering over its <set> collection. That would be always done in memory. (What we can do with mapping I appended at the end).
we can change the approach
In this case, when you need a filter and paging over the collection items, I would strongly recommend to go the other way. Create Criteria (QueryOver, HQL) not over the ClassA but over the ClassB.
First of all we have to extend ClassB mapping:
<class name="ClassB">
<property name="Something" />
<many-to-one name="ClassA" column="FK_ClassB" fetch="join" />
</class>
And then create a Criteria like this
var criteria = NHSession.Current.CreateCriteria<ClassB>();
criteria
.Add(new InExpression("ClassA", new object[] {1})) // id of one or more ClassA
.AddOrder(new Order("Something", true)) // Order By
.SetFirstResult(2) // Skip
.SetMaxResults(10); // Take
var list = criteria.List<ClassB>();
Because we used mapping of ClassA fetch="join" the resulting SQL statement will be very similar to the first snippet in this question.
So this way, we can achieve the desired SQL Select, but we cannot use ClassA.ClassBCollection directly. We did it this way...
NOTE:
Filters / paging which we can influence on the <set> mapping are static filter in the where clause and style of fetching values.
Where clause will always be evaluated when loading ClassBCollection as a property of the ClassA. It could be like where="IsActive=true"
In case that ClassA can have a lot of items in ClassBCollection, we can manage how they will be loaed. Very effective way is attribute batch-size documented here
Can I customize querying of nhibernate set somehow?
I'm not entirely sure what this means. If you meant, can I query and use WHERE clauses over the collection, the answer is yes. Here's how:
[TestFixture]
public class StackOverflowQuestion13496270Tests
{
public ISession session;
[SetUp]
public void SetUp()
{
session = // Get the current NHibernate session
}
[Test]
public void Query_ClassA()
{
var results = session.Query<ClassA>()
.Where( x => x.ClassBCollection.Any( y => y.Name == "Bob" ) )
.Fetch( x => x.ClassBCollection )
.Skip( 0 )
.Take( 50 )
.ToList();
}
[Test]
public void Query_ClassB()
{
var results = session.Query<ClassB>()
.Where( x => x.Name == "Bob" )
.Fetch( x => x.ClassAParent )
.Skip( 0 )
.Take( 50 )
.ToList();
}
public class ClassA
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<ClassB> ClassBCollection { get; set; }
}
public class ClassB
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
// Add this and the appropriate mapping modifications to be able to navigate back to the parent
public virtual ClassA ClassAParent { get; set; }
}
}
I have an entity where a composite id is used. I changed to code to make use of wrapping the composite id in a seperate key class. I expected that with Linq I could do a comparison on key object and with the Criteria API to use Restrictions.IdEq but both fail. I need to explicitly compare the key values to make it work.
I cannot find any documentation if this should work so for the moment I am stuck with direct comparisons but this means that when I alter the key that I also need to update the query code which is obviously not what I would want.
As a side note, I tried this with NHibernate 3.0.0 Alpha 2 and 3.
Domain
Mapping
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Cwc.Pulse.Dal"
namespace="Cwc.Pulse.Dal">
<class name="AddonStatus">
<composite-id name="Id">
<key-many-to-one name="Context" column="Context_Id" class="Context" />
<key-property name="AddonType" column="Addon_Id"/>
</composite-id>
<property name="Status" />
</class>
</hibernate-mapping>
Class
public class AddonStatus
{
public virtual string Status { get; set; }
public virtual Key Id { get; protected set; }
public AddonStatus()
{
Id = new Key();
}
public class Key
{
public virtual Context Context { get; set; }
public virtual AddonType AddonType { get; set; }
public override int GetHashCode()
{
return ContextId.GetHashCode() ^ AddonType.GetHashCode();
}
public override bool Equals(object obj)
{
if (this == obj) return true;
var o = obj as Key;
if (null == o) return false;
return Context == o.Context && AddonType == o.AddonType;
}
}
}
Working queries
The queries below work and as you can see I compare the key values explicitly. I do not compare the key object.
Linq
from status
in session.Query<AddonStatus>()
where status.Id.Context == context && status.Id.AddonType == addonType
select status
Criteria API
session.CreateCriteria<AddonStatus>()
.Add(Restrictions.Eq("Id.Context", context))
.Add(Restrictions.Eq("Id.AddonType", addonType))
Expected to work but dont
I expect the following queries to work. Either in efficiently for linq in memory instead of the database but I expect the criteria api to be smart enough to handle such composite id´s in queries.
Both linq and criteria api queries make use of a Key object comparison.
var key = new AddonStatus.Key
{
Context = context,
AddonType = addonType
};
Linq
from status
in session.Query<AddonStatus>()
where status.Id == key
select status
Criteria API
session.CreateCriteria<AddonStatus>()
.Add(Restrictions.IdEq(key))
So if anyone has such a scenario working then what am I doing wrong?
Not directly an answer to your question, but it may be useful to you anyway. You could avoid the (explicit) composite key by mapping the AddonStatus as composite-element on the owner (most probably the Context):
<class name="Context">
<map name="AddonStates" table="AddonStatus">
<key column="Context_Id" /> <!-- Foreign key to the Context -->
<index column="Addon_Id" /> <!-- Dictionary key -->
<composite-element>
<property name="Status" /> <!-- data -->
</composite-element>
</map>
</class>
In the class Context is looks like this:
class Context
{
IDictionary<AddonType, AddonStatus> AddonStates { get; private set; }
}
This results and pretty the same database structure, but it is different to work with. I can't say if this is what you actually want, but it just looks like it.
Interestingly, I'm getting almost the exact opposite of this behavior in 2.1.2.
My mapping (simplified):
<!-- Subscriber class -->
<class name="Subscriber" >
<composite-id name="SubscriberKey" class="SubscriberKey">
<key-property name="Request" column="RequestID" type="int"/>
<key-many-to-one name="User" column="UserID" class="User" not-found="ignore" />
</composite-id>
<!-- User class - note that this goes to a different schema,
and is not mutable. Who knows if that's important... -->
<class name="User" schema="AnotherDb.dbo" mutable="false">
<id name="Id" column="UserID" type="int">
<generator class="native" />
</id>
<property name="FirstName" column="FirstName" type="string" />
<property name="LastName" column="LastName" type="string" />
goes to:
public class User
{
public virtual int? Id {get; protected set;}
public virtual string FirstName { get; protected set; }
public virtual string LastName { get; protected set; }
public User() { }
}
public class Subscriber
{
public virtual SubscriberKey SubscriberKey { get; set; }
public virtual User User { get; set; }
public Subscriber() { }
}
public class SubscriberKey
{
public override bool Equals(object obj)
{
if (obj is SubscriberKey && obj != null)
return ((SubscriberKey)obj).Request == Request
&& ((SubscriberKey)obj).User.Id == User.Id;
return false;
}
public override int GetHashCode()
{
return (Request.ToString() + User.Id.ToString()).GetHashCode();
}
public virtual int Request { get; set; }
public virtual User User { get; set; }
public SubscriberKey() { }
}
Things which work:
CreateCriteria<Subscriber>()
.Add(Restrictions.IdEq(keyInstance))
.UniqueResult<Subscriber>();
CreateCriteria<Subscriber>()
.Add(Restrictions.Eq("SubscriberKey.User.Id", aUserID))
.Add(Restrictions.Eq("SubscriberKey.Request", aRequestID))
.UniqueResult<Subscriber>();
Things which don't work:
Get<Subscriber>(keyInstance);
I'm thinking this is an inconsistency between their various ID-equaling query forms. When I get time, I'll be building a minimal unit test to submit as a bug example. I'd be interested in any / all thoughts anyone might have on this...
edit: Heeey, I figured it out!
Things which do work, now that I've read this
Get<Subscriber>(new SubscriberKey() {
User = Load<User>(aUserID), // the important part!
Request = aRequestID
});
This will create a proxy object for the User key, without hitting the database (unless necessary). If you swap Load<User> for Get<User>, you'll immediately hit the database to populate the object, rather than respecting your lazy-loading properties. Use Load.
And things like this are precisely why people suggest the (type)Repository pattern - I can do this behind the scenes: Get<>(new SK(){User=Load<>(key.User.Id)}, and still Get(key) by a single key, identical to every other object.
Using Fluent NHibernate, I cannot seem to devise the necessary automapping conventions for the following (seemingly simple and common) use-case:
public class MyClass
{
private int _specialIdentityField
private string _firstname;
public Id { get { return _specialIdentityField; }; }
public virtual string Firstname
{
get
{
return _firstname;
}
set
{
_firstname = value;
}
}
}
public class OtherClass
{
private int _specialIdentityField
private string _lastname;
public Id { get { return _specialIdentityField; }; }
public virtual string Lastname
{
get
{
return _lastname;
}
set
{
_lastname = value;
}
}
}
The desired mappings are like so:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="field.camelcase-underscore" auto-import="true" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="MyClass" table="`MyClass`">
<id name="_specialIdentityField" type="System.Int32" access=field>
<column name="Id" />
<generator class="identity" />
</id>
<property name="Firstname" type="System.String">
<column name="Firstname" />
</property>
</class>
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="OtherClass" table="`OtherClass`">
<id name="_specialIdentityField" type="System.Int32" access=field>
<column name="Id" />
<generator class="identity" />
</id>
<property name="Lastname" type="System.String">
<column name="Lastname" />
</property>
</class>
</hibernate-mapping>
Basically the rules are:
everything is field-camelcase-underscore as an access type EXCEPT identity
identity is a fixed-name field in every class (name=_someSpecialIdentityField)
identity access is always field-only and bears no relation to the name of the RO property that surrounds it
The part of this that is completely tripping me up is the convention-mapping of the identity-related elements (clearly the convention-mapping of the properties is completely standard fare). The issue I am having is how to tell FNH conventions that my identity field is a fixed-name. All of the convention overrides that I can find seem to assume there will always be some relationship between the property that represents identity and the name of its underlying backing field (e.g. I can set a 'custom prefix' for the backing field, but cannot seem to see how I can just say "this is always the name of the backing field").
Its obvious to me how to accomplish this with explicit mapping (and for that matter, with XML mapping files) but not obvious at all to me how to accomplish this with convention-based (Automapping) mapping in FNH.
This can't be an atypical use-case so I must just be overlooking something terribly obvious. Thoughts from any FNH gurus appreciated!
EDIT: Take a look at the IAutomappingConfiguration interface. Create you own implementation or override the DefaultAutomappingConfiguration class.
public virtual bool IsId(Member member)
{
return member.Name.Equals("id", StringComparison.InvariantCultureIgnoreCase);
}
then you add it to the initialization:
Fluently.Configure( Configuration )
.Mappings( cfg =>
{ cfg.AutoMappings.Add( IAutomappingConfigurationInstance )}
===============================================
Hi Steve, I think I know what you are trying to do. I use Proteus and FNH automapping. To do the trick with the id I created a wrapper around Proteus which do two things:
1) Maps the ID
2) Hides the Setter for the id
public abstract class EntityObject<TEntity> : IdentityPersistenceBase<TEntity, Guid>, IEntity
where TEntity : class, IEntity
{
public virtual Guid Id
{
get { return _persistenceId; }
}
Guid IIdentifiedEntity<Guid>.Id
{
get { return _persistenceId; }
set { _persistenceId = value; }
}
public virtual int Version
{
get { return _persistenceVersion; }
set { _persistenceVersion = value; }
}
}
And to avoid the property IsTransient to be persisted + other stuff you can create MappingAlternation:
public class EntityAlteration : IAutoMappingAlteration
{
public void Alter( AutoPersistenceModel model )
{
model.OverrideAll( map =>
{
Type recordType = map.GetType().GetGenericArguments().Single();
if( recordType.BaseType.Name == "EntityObject`1" )
{
Type changeType = typeof( Change<> ).MakeGenericType( recordType );
var change = ( IChange )Activator.CreateInstance( changeType );
change.Go( map );
}
} );
}
}
interface IChange
{
void Go( object mapObject );
}
class Change<TRecord> : IChange where TRecord : EntityObject<TRecord>
{
void IChange.Go( object mapObject )
{
var map = ( AutoMapping<TRecord> )mapObject;
map.Id( x => x.Id ).GeneratedBy.Guid().Access.Property();
map.IgnoreProperty( x => x.IsTransient );
}
}
PS: I am really missing the times when you were active in the online space. It was exciting Summer and Authumn 2 years ago...
Given the following scenario, I want map the type hierarchy to the database schema using Fluent NHibernate.
I am using NHibernate 2.0
Type Hierarchy
public abstract class Item
{
public virtual int ItemId { get; set; }
public virtual string ItemType { get; set; }
public virtual string FieldA { get; set; }
}
public abstract class SubItem : Item
{
public virtual string FieldB { get; set; }
}
public class ConcreteItemX : SubItem
{
public virtual string FieldC { get; set; }
}
public class ConcreteItemY : Item
{
public virtual string FieldD { get; set; }
}
See image
The Item and SubItem classes are abstract.
Database Schema
+----------+ +---------------+ +---------------+
| Item | | ConcreteItemX | | ConcreteItemY |
+==========+ +===============+ +===============+
| ItemId | | ItemId | | ItemId |
| ItemType | | FieldC | | FieldD |
| FieldA | +---------------+ +---------------+
| FieldB |
+----------+
See image
The ItemType field determines the concrete type.
Each record in the ConcreteItemX table has a single corresponding record in the Item table; likewise for the ConcreteItemY table.
FieldB is always null if the item type is ConcreteItemY.
The Mapping (so far)
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
WithTable("Item");
Id(x => x.ItemId, "ItemId");
Map(x => x.FieldA, "FieldA");
JoinedSubClass<ConcreteItemX>("ItemId", MapConcreteItemX);
JoinedSubClass<ConcreteItemY>("ItemId", MapConcreteItemY);
}
private static void MapConcreteItemX(JoinedSubClassPart<ConcreteItemX> part)
{
part.WithTableName("ConcreteItemX");
part.Map(x => x.FieldC, "FieldC");
}
private static void MapConcreteItemY(JoinedSubClassPart<ConcreteItemY> part)
{
part.WithTableName("ConcreteItemX");
part.Map(x => x.FieldD, "FieldD");
}
}
FieldB is not mapped.
The Question
How do I map the FieldB property of the SubItem class using Fluent NHibernate?
Is there any way I can leverage DiscriminateSubClassesOnColumn using the ItemType field?
Addendum
I am able to achieve the desired result using an hbm.xml file:
<class name="Item" table="Item">
<id name="ItemId" type="Int32" column="ItemId">
<generator class="native"/>
</id>
<discriminator column="ItemType" type="string"/>
<property name="FieldA" column="FieldA"/>
<subclass name="ConcreteItemX" discriminator-value="ConcreteItemX">
<!-- Note the FieldB mapping here -->
<property name="FieldB" column="FieldB"/>
<join table="ConcreteItemX">
<key column="ItemId"/>
<property name="FieldC" column="FieldC"/>
</join>
</subclass>
<subclass name="ConcreteItemY" discriminator-value="ConcreteItemY">
<join table="ConcreteItemY">
<key column="ItemId"/>
<property name="FieldD" column="FieldD"/>
</join>
</subclass>
</class>
How do I accomplish the above mapping using Fluent NHibernate?
Is it possible to mix table-per-class-hierarchy with table-per-subclass using Fluent NHibernate?
I know this is really old, but it is now pretty simple to set up fluent to generate the exact mapping you initially desired. Since I came across this post when searching for the answer, I thought I'd post it.
You just create your ClassMap for the base class without any reference to your subclasses:
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
this.Table("Item");
this.DiscriminateSubClassesOnColumn("ItemType");
this.Id(x => x.ItemId, "ItemId");
this.Map(x => x.FieldA, "FieldA");
}
}
Then map your abstract subclass like this:
public class SubItemMap: SubclassMap<SubItemMap>
{
public SubItemMap()
{
this.Map(x => x.FieldB);
}
}
Then map your concrete subclasses like so:
public class ConcreteItemXMap : SubclassMap<ConcreteItemX>
{
public ConcretItemXMap()
{
this.Join("ConcreteItemX", x =>
{
x.KeyColumn("ItemID");
x.Map("FieldC")
});
}
}
Hopefully this helps somebody else looking for this type of mapping with fluent.
Well, I'm not sure that it's quite right, but it might work... If anyone can do this more cleanly, I'd love to see it (seriously, I would; this is an interesting problem).
Using the exact class definitions you gave, here are the mappings:
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
Id(x => x.ItemId);
Map(x => x.ItemType);
Map(x => x.FieldA);
AddPart(new ConcreteItemYMap());
}
}
public class SubItemMap : ClassMap<SubItem>
{
public SubItemMap()
{
WithTable("Item");
// Get the base map and "inherit" the mapping parts
ItemMap baseMap = new ItemMap();
foreach (IMappingPart part in baseMap.Parts)
{
// Skip any sub class parts... yes this is ugly
// Side note to anyone reading this that might know:
// Can you use GetType().IsSubClassOf($GenericClass$)
// without actually specifying the generic argument such
// that it will return true for all subclasses, regardless
// of the generic type?
if (part.GetType().BaseType.Name == "JoinedSubClassPart`1")
continue;
AddPart(part);
}
Map(x => x.FieldB);
AddPart(new ConcreteItemXMap());
}
}
public class ConcreteItemXMap : JoinedSubClassPart<ConcreteItemX>
{
public ConcreteItemXMap()
: base("ItemId")
{
WithTableName("ConcreteItemX");
Map(x => x.FieldC);
}
}
public class ConcreteItemYMap : JoinedSubClassPart<ConcreteItemY>
{
public ConcreteItemYMap()
: base("ItemId")
{
WithTableName("ConcreteItemY");
Map(x => x.FieldD);
}
}
Those mappings produce two hbm.xml files like so (some extraneous data removed for clarity):
<class name="Item" table="`Item`">
<id name="ItemId" column="ItemId" type="Int32">
<generator class="identity" />
</id>
<property name="FieldA" type="String">
<column name="FieldA" />
</property>
<property name="ItemType" type="String">
<column name="ItemType" />
</property>
<joined-subclass name="ConcreteItemY" table="ConcreteItemY">
<key column="ItemId" />
<property name="FieldD">
<column name="FieldD" />
</property>
</joined-subclass>
</class>
<class name="SubItem" table="Item">
<id name="ItemId" column="ItemId" type="Int32">
<generator class="identity" />
</id>
<property name="FieldB" type="String">
<column name="FieldB" />
</property>
<property name="ItemType" type="String">
<column name="ItemType" />
</property>
<property name="FieldA" type="String">
<column name="FieldA" />
</property>
<joined-subclass name="ConcreteItemX" table="ConcreteItemX">
<key column="ItemId" />
<property name="FieldC">
<column name="FieldC" />
</property>
</joined-subclass>
</class>
It's ugly, but it looks like it might generate a usable mapping file and it's Fluent! :/
You might be able to tweak the idea some more to get exactly what you want.
This is how I resolved my inheritance problem:
public static class DataObjectBaseExtension
{
public static void DefaultMap<T>(this ClassMap<T> DDL) where T : IUserAuditable
{
DDL.Map(p => p.AddedUser).Column("AddedUser");
DDL.Map(p => p.UpdatedUser).Column("UpdatedUser");
}
}
You can then add this to your superclass map constructor:
internal class PatientMap : ClassMap<Patient>
{
public PatientMap()
{
Id(p => p.GUID).Column("GUID");
Map(p => p.LocalIdentifier).Not.Nullable();
Map(p => p.DateOfBirth).Not.Nullable();
References(p => p.Sex).Column("RVSexGUID");
References(p => p.Ethnicity).Column("RVEthnicityGUID");
this.DefaultMap();
}
}
The line of code: if (part.GetType().BaseType.Name == "JoinedSubClassPart1")
can be rewritten as follows:
part.GetType().BaseType.IsGenericType && part.GetType().BaseType.GetGenericTypeDefinition() == typeof(JoinedSubClassPart<>)