NHibernate Table Per Subclass results in nonsensical INSERT statement - What am I doing wrong? - nhibernate

I have the following entities:
public abstract class User : IIdentity
{
private readonly UserType _userType;
public virtual int EntitySK { get; set; }
public virtual int TenantSK { get; set; }
public abstract string Name { get; set; }
public virtual PublicKey PublicKey { get; set; }
public abstract string AuthenticationType { get; set; }
public virtual bool IsAuthenticated { get; protected internal set; }
public virtual bool LoginEnabled { get; set; }
public virtual bool LockedOut { get; set; }
public virtual int NumberOfFailedLoginAttempts { get; set; }
// Hibernate requires this constructor
protected User()
{
this._userType = this is PersonUser ? Models.UserType.Person : Models.UserType.Client;
this.LoginEnabled = true;
}
protected User(UserType userType)
{
this._userType = userType;
this.LoginEnabled = true;
}
public virtual UserType UserType
{
get { return this._userType; }
set
{
if(value != this._userType)
throw new InvalidOperationException("Attempted to load " + value + " into " + this._userType + "User.");
}
}
}
public class PersonUser : User
{
public virtual string Domain { get; set; }
public override string Name { get; set; }
public virtual byte[] Password { get; set; }
public virtual byte[] Pepper { get; set; }
public virtual string EmailAddress { get; set; }
public virtual int PersonSK { get; set; }
public override string AuthenticationType { get; set; }
public PersonUser() : base(UserType.Person) { }
}
public class ClientUser : User
{
public override string Name { get; set; }
public virtual string SharedSecret { get; set; }
public virtual ISet<string> Scopes { get; set; }
public virtual ISet<GrantType> AuthorizedGrantTypes { get; set; }
public virtual ISet<Uri> RegisteredRedirectUris { get; set; }
public virtual int AuthorizationCodeValiditySeconds { get; set; }
public virtual int AccessTokenValiditySeconds { get; set; }
public ClientUser() : base(UserType.Client) { }
}
I map these entities using the following Hibernate Conformist mapping:
public class UserMapping : ClassMapping<User>
{
public UserMapping()
{
LogManager.GetLogger().Info("Initialized User mapping.");
this.Table("Authentication_Users");
this.Id(u => u.EntitySK,
m => {
m.Column("UserSK");
m.Generator(Generators.Identity);
m.UnsavedValue(0);
});
this.Property(u => u.TenantSK,
m => {
m.Column("TenantSK");
m.NotNullable(true);
});
this.Property(u => u.PublicKey,
m => {
m.Column("PublicKey");
m.Type<PublicKeyCustomType>();
m.NotNullable(false);
m.Lazy(true);
});
this.Property(u => u.UserType,
m => {
m.Column("UserType");
m.NotNullable(true);
m.Type<EnumCustomType<UserType>>();
});
this.Property(u => u.LoginEnabled,
m => {
m.Column("LoginEnabled");
m.NotNullable(true);
});
this.Property(u => u.LockedOut,
m => {
m.Column("LockedOut");
m.NotNullable(true);
});
this.Property(u => u.NumberOfFailedLoginAttempts,
m => {
m.Column("NumberOfFailedLoginAttempts");
m.NotNullable(true);
});
this.Discriminator(d => d.Column("UserType"));
}
}
public class PersonUserMapping : SubclassMapping<PersonUser>
{
public PersonUserMapping()
{
LogManager.GetLogger().Info("Initialized PersonUser mapping.");
this.DiscriminatorValue((int)UserType.Person);
this.Join(
"PersonUser",
j =>
{
j.Table("Authentication_Users_PersonUsers");
j.Key(m => {
m.Column("UserSK");
m.NotNullable(true);
m.OnDelete(OnDeleteAction.Cascade);
m.Unique(true);
m.Update(false);
});
j.Property(u => u.Domain,
m => {
m.Column("DomainName");
m.NotNullable(false);
});
j.Property(u => u.Name,
m => {
m.Column("Username");
m.NotNullable(true);
});
j.Property(u => u.Password,
m => {
m.Column("Password");
m.NotNullable(false);
m.Lazy(true);
});
j.Property(u => u.Pepper,
m => {
m.Column("Pepper");
m.NotNullable(false);
m.Lazy(true);
});
j.Property(u => u.EmailAddress,
m => {
m.Column("EmailAddress");
m.NotNullable(false);
});
j.Property(u => u.PersonSK,
m => {
m.Column("PersonSK");
m.NotNullable(false);
});
j.Property(u => u.AuthenticationType,
m => {
m.Column("AuthenticationType");
m.NotNullable(true);
});
}
);
}
}
public class ClientUserMapping : SubclassMapping<ClientUser>
{
public ClientUserMapping()
{
LogManager.GetLogger().Info("Initialized ClientUser mapping.");
this.DiscriminatorValue((int)UserType.Client);
this.Join(
"ClientUser",
j =>
{
j.Table("Authentication_Users_ClientUsers");
j.Key(m => {
m.Column("UserSK");
m.NotNullable(true);
m.OnDelete(OnDeleteAction.Cascade);
m.Unique(true);
m.Update(false);
});
j.Property(u => u.Name,
m => {
m.Column("DisplayName");
m.NotNullable(true);
});
j.Property(u => u.SharedSecret,
m => {
m.Column("SharedSecret");
m.NotNullable(true);
});
j.Property(u => u.AuthorizationCodeValiditySeconds,
m => {
m.Column("AuthorizationCodeValiditySeconds");
m.NotNullable(true);
});
j.Property(u => u.AccessTokenValiditySeconds,
m => {
m.Column("AccessTokenValiditySeconds");
m.NotNullable(true);
});
j.Set(u => u.Scopes,
s => {
s.Fetch(CollectionFetchMode.Join);
s.Lazy(CollectionLazy.Lazy);
s.Table("Authentication_Users_ClientUsers_Scopes");
s.Key(m => {
m.Column("UserSK");
m.NotNullable(true);
});
},
r => r.Element(m => {
m.Column("Scope");
m.NotNullable(true);
m.Unique(true);
}));
j.Set(u => u.AuthorizedGrantTypes,
s => {
s.Fetch(CollectionFetchMode.Join);
s.Lazy(CollectionLazy.Lazy);
s.Table("Authentication_Users_ClientUsers_AuthorizedGrantTypes");
s.Key(m => {
m.Column("UserSK");
m.NotNullable(true);
});
},
r => r.Element(m => {
m.Column("GrantType");
m.NotNullable(true);
m.Unique(true);
m.Type<EnumCustomType<GrantType>>();
}));
j.Set(u => u.RegisteredRedirectUris,
s => {
s.Fetch(CollectionFetchMode.Join);
s.Lazy(CollectionLazy.Lazy);
s.Table("Authentication_Users_ClientUsers_RegisteredRedirectUris");
s.Key(m => {
m.Column("UserSK");
m.NotNullable(true);
});
},
r => r.Element(m => {
m.Column("Uri");
m.NotNullable(true);
m.Unique(true);
m.Type<UriCustomType>();
}));
}
);
}
}
The EnumCustomType is an IUserType that maps C# enums to integer columns.
I formulated this design and mapping after much research and consultation with the NHibernate reference documentation and this blog (specific page) (summary). I am sure this is the entity design I want, but of course it's possible (likely?) I got the mapping wrong.
When I start up and configure NHibernate, it loads the mappings and does not complain. The log output has no warnings about the mappings. However, when I create a PersonUser, assign values to all of its properties, and Add it to the ISession, the most peculiar thing happens:
2014-01-16 00:58:34,465 DEBUG NHibernate.AdoNet.AbstractBatcher.Generate() - Building an IDbCommand object for the SqlString: INSERT INTO Authentication_Users (TenantSK, DisplayName, PublicKey, LoginEnabled, LockedOut, NumberOfFailedLoginAttempts, Username, AuthenticationType, UserType) VALUES (?, ?, ?, ?, ?, ?, ?, ?, '1'); select SCOPE_IDENTITY()
2014-01-16 00:58:34,472 DEBUG NHibernate.Persister.Entity.AbstractEntityPersister.Dehydrate() - Dehydrating entity: [Models.PersonUser#<null>]
2014-01-16 00:58:34,475 DEBUG NHibernate.Type.NullableType.NullSafeSet() - binding '0' to parameter: 0
2014-01-16 00:58:34,478 DEBUG NHibernate.Type.NullableType.NullSafeSet() - binding 'nick.williams' to parameter: 1
2014-01-16 00:58:34,482 DEBUG NHibernate.Type.NullableType.NullSafeSet() - binding 'PublicKey' to parameter: 3
2014-01-16 00:58:34,485 DEBUG NHibernate.Type.NullableType.NullSafeSet() - binding 'True' to parameter: 4
2014-01-16 00:58:34,486 DEBUG NHibernate.Type.NullableType.NullSafeSet() - binding 'False' to parameter: 5
2014-01-16 00:58:34,487 DEBUG NHibernate.Type.NullableType.NullSafeSet() - binding '0' to parameter: 6
2014-01-16 00:58:34,488 DEBUG NHibernate.Type.NullableType.NullSafeSet() - binding 'nick.williams' to parameter: 8
NHibernate.PropertyValueException : Error dehydrating property value for Models.PersonUser.Name
----> System.IndexOutOfRangeException : Invalid index 8 for this SqlParameterCollection with Count=8.
at NHibernate.Persister.Entity.AbstractEntityPersister.Dehydrate(Object id, Object[] fields, Object rowId, Boolean[] includeProperty, Boolean[][] includeColumns, Int32 table, IDbCommand statement, ISessionImplementor session, Int32 index)
at NHibernate.Persister.Entity.AbstractEntityPersister.GeneratedIdentifierBinder.BindValues(IDbCommand ps)
at NHibernate.Id.Insert.AbstractReturningDelegate.PerformInsert(SqlCommandInfo insertSQL, ISessionImplementor session, IBinder binder)
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object[] fields, Object obj, ISessionImplementor session)
at NHibernate.Action.EntityIdentityInsertAction.Execute()
at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSaveOrReplicate(Object entity, EntityKey key, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.FireSave(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.Save(Object obj)
Importantly, take a look at that SQL generated:
INSERT INTO Authentication_Users (TenantSK, DisplayName, PublicKey, LoginEnabled, LockedOut, NumberOfFailedLoginAttempts, Username, AuthenticationType, UserType) VALUES (?, ?, ?, ?, ?, ?, ?, ?, '1')
That makes no sense. It in no way corresponds to the mapping at all. It's got columns from three different tables in an insert statement for one table. It has columns from PersonUser AND from ClientUser (which shouldn't be possible), it's binding parameters with nonexistent parameter indexes, and it's not even including all the properties I set!
I've been playing around with this for several hours now without any progress. I'm at a complete loss here. It just doesn't make any sense. Anyone seen this before? Any idea what's going on?
EDIT I forgot to mention: I'm using the discriminator here because I want to be able to get a generic User by its ID and it return the proper PersonUser or ClientUser depending on which type it is.

There are some issues with the mapping
a) You are mapping the UserType as property but you are using it as discriminator, too. But the user type value is generated by nhibernate, because of the discriminator usage... Therefore you can define the property to be generated.
this.Property(u => u.UserType,
m =>
{
m.Column("UserType");
m.NotNullable(true);
m.Generated(PropertyGeneration.Always); // this should fix it for user type
m.Type<EnumCustomType<UserType>>();
});
You should also not throw an exception on the setter... to prevent UserType being set from somewhere, simply mark the auto property setter protected internal
public virtual UserType UserType
{
get;
protected internal set;
}
b) You are mapping the Name attribute of your base class in your sub classes only and you are trying to map that property to different columns within your sub classes' tables.
Don't know if this is even possible, usually you have to map all properties of your base class to the base class table, or move the property into the subclass...
To fix this, simply remove the mappings to Name from your subclass mappings and move it to your base class mapping.

With MichaC's help, this was the code that finally worked.
Entities:
public abstract class User : IIdentity
{
// necessary because the property is read-only
private readonly UserType _userType;
// necessary because the property needs to default to true
private bool _loginEnabled = true;
//-------- These properties are mapped to database columns --------//
public virtual int ObjectId { get; set; }
public virtual int? TenantId { get; set; }
public virtual PublicKey PublicKey { get; set; }
public virtual bool LoginEnabled { get { return this._loginEnabled; } set { this._loginEnabled = value; } }
public virtual bool LockedOut { get; set; }
public virtual int NumberOfFailedLoginAttempts { get; set; }
//-------- These properties are NOT mapped to database columns --------//
public abstract string Name { get; set; }
public abstract string AuthenticationType { get; set; }
public virtual bool IsAuthenticated { get; protected internal set; }
public virtual UserType UserType
{
get { return this._userType; }
set { throw new InvalidOperationException("Property UserType is read-only."); }
}
...
}
public class PersonUser : User
{
//-------- These properties are mapped to database columns --------//
public virtual string Domain { get; set; }
protected internal virtual string Username { get { return this.Name; } set { this.Name = value; } }
public virtual byte[] Password { get; set; }
public virtual byte[] Pepper { get; set; }
public virtual string EmailAddress { get; set; }
public virtual int PersonSK { get; set; }
protected internal virtual string AuthenticationStrategy
{
get { return this.AuthenticationType; }
set { this.AuthenticationType = value; }
}
//-------- These properties are NOT mapped to database columns --------//
public override string Name { get; set; }
public override string AuthenticationType { get; set; }
}
public class ClientUser : User
{
//-------- These properties are mapped to database columns --------//
protected internal virtual string DisplayName { get { return this.Name; } set { this.Name = value; } }
public virtual string SharedSecret { get; set; }
public virtual ISet<string> Scopes { get; set; }
public virtual ISet<GrantType> AuthorizedGrantTypes { get; set; }
public virtual ISet<Uri> RegisteredRedirectUris { get; set; }
public virtual int AuthorizationCodeValiditySeconds { get; set; }
public virtual int AccessTokenValiditySeconds { get; set; }
//-------- These properties are NOT mapped to database columns --------//
public override string Name { get; set; }
public override string AuthenticationType
{
get { return AuthorizationHeaderProtocol.SignatureClientCredentials; }
set { throw new InvalidOperationException("Cannot change the authentication type for a ClientUser."); }
}
}
Mappings:
public class UserMapping : ClassMapping<User>
{
public UserMapping()
{
LogManager.GetLogger().Info("Initialized User mapping.");
this.Table("Authentication_Users");
this.Id(u => u.ObjectId,
m => {
m.Column("UserId");
m.Generator(Generators.Identity);
m.UnsavedValue(0);
});
this.Property(u => u.TenantId,
m => {
m.Column("TenantId");
m.NotNullable(false);
});
this.Property(u => u.PublicKey,
m => {
m.Column("PublicKey");
m.Type<PublicKeyCustomType>();
m.NotNullable(false);
m.Lazy(true);
});
this.Property(u => u.LoginEnabled,
m => {
m.Column("LoginEnabled");
m.NotNullable(true);
});
this.Property(u => u.LockedOut,
m => {
m.Column("LockedOut");
m.NotNullable(true);
});
this.Property(u => u.NumberOfFailedLoginAttempts,
m => {
m.Column("NumberOfFailedLoginAttempts");
m.NotNullable(true);
});
this.Discriminator(d => d.Column("UserType"));
}
}
public class PersonUserMapping : SubclassMapping<PersonUser>
{
public PersonUserMapping()
{
LogManager.GetLogger().Info("Initialized PersonUser mapping.");
this.DiscriminatorValue((int)UserType.Person);
this.Join(
"PersonUser",
j =>
{
j.Table("Authentication_Users_PersonUsers");
j.Key(m => {
m.Column("UserId");
m.NotNullable(true);
m.OnDelete(OnDeleteAction.Cascade);
m.Unique(true);
m.Update(false);
});
j.Property(u => u.Domain,
m => {
m.Column("DomainName");
m.NotNullable(false);
});
j.Property("Username", // protected internal, see NH-3485
m => {
m.Column("Username");
m.NotNullable(true);
});
j.Property(u => u.Password,
m => {
m.Column("Password");
m.NotNullable(false);
m.Lazy(true);
});
j.Property(u => u.Pepper,
m => {
m.Column("Pepper");
m.NotNullable(false);
m.Lazy(true);
});
j.Property(u => u.EmailAddress,
m => {
m.Column("EmailAddress");
m.NotNullable(false);
});
j.Property(u => u.PersonSK,
m => {
m.Column("PersonSK");
m.NotNullable(false);
});
j.Property("AuthenticationStrategy", // protected internal, see NH-3485
m => {
m.Column("AuthenticationType");
m.NotNullable(true);
});
}
);
}
}
public class ClientUserMapping : SubclassMapping<ClientUser>
{
public ClientUserMapping()
{
LogManager.GetLogger().Info("Initialized ClientUser mapping.");
this.DiscriminatorValue((int)UserType.Client);
this.Join(
"ClientUser",
j =>
{
j.Table("Authentication_Users_ClientUsers");
j.Key(m => {
m.Column("UserId");
m.NotNullable(true);
m.OnDelete(OnDeleteAction.Cascade);
m.Unique(true);
m.Update(false);
});
j.Property("DisplayName", // protected internal, see NH-3485
m => {
m.Column("DisplayName");
m.NotNullable(true);
});
j.Property(u => u.SharedSecret,
m => {
m.Column("SharedSecret");
m.NotNullable(true);
});
j.Property(u => u.AuthorizationCodeValiditySeconds,
m => {
m.Column("AuthorizationCodeValiditySeconds");
m.NotNullable(true);
});
j.Property(u => u.AccessTokenValiditySeconds,
m => {
m.Column("AccessTokenValiditySeconds");
m.NotNullable(true);
});
j.Set(u => u.Scopes,
s => {
s.Fetch(CollectionFetchMode.Join);
s.Lazy(CollectionLazy.Lazy);
s.Table("Authentication_Users_ClientUsers_Scopes");
s.Key(m => {
m.Column("UserId");
m.NotNullable(true);
});
},
r => r.Element(m => {
m.Column("Scope");
m.NotNullable(true);
m.Unique(true);
}));
j.Set(u => u.AuthorizedGrantTypes,
s => {
s.Fetch(CollectionFetchMode.Join);
s.Lazy(CollectionLazy.Lazy);
s.Table("Authentication_Users_ClientUsers_AuthorizedGrantTypes");
s.Key(m => {
m.Column("UserId");
m.NotNullable(true);
});
},
r => r.Element(m => {
m.Column("GrantType");
m.NotNullable(true);
m.Unique(true);
m.Type<EnumCustomType<GrantType>>();
}));
j.Set(u => u.RegisteredRedirectUris,
s => {
s.Fetch(CollectionFetchMode.Join);
s.Lazy(CollectionLazy.Lazy);
s.Table("Authentication_Users_ClientUsers_RegisteredRedirectUris");
s.Key(m => {
m.Column("UserId");
m.NotNullable(true);
});
},
r => r.Element(m => {
m.Column("Uri");
m.NotNullable(true);
m.Unique(true);
m.Type<UriCustomType>();
}));
}
);
}
}

Related

NHibernate update all child rows when adding a new child

I'm struggling with a problem related to parent-child mapping.
I think I did everything correctly, but every time I add a child and save the parent, nhibernate execute an insert and then an update for all the rows in the child list.
here is my classes and mapping
public class Cliente : EntityBase
{
public virtual string Cognome { get; set; }
public virtual string Nome { get; set; }
public virtual int AttivitaId { get; set; }
public virtual DateTime? DittaAssunzioneData { get; set; }
public virtual decimal Stipendio { get; set; }
public virtual IList<ClienteChiusura> Chiusure { get; protected set; }
public virtual void AddTelefonata(Telefonata telefonata)
{
if (Telefonate == null)
Telefonate = new List<Telefonata>();
//NHibernate impedence
telefonata.Cliente = this;
Telefonate.Add(telefonata);
}
public virtual void RemoveTelefonata(int telefonataId)
{
if (Telefonate == null)
return;
var telefonata = Telefonate.SingleOrDefault(x => x.Id == telefonataId);
if (telefonata != null)
{
Telefonate.Remove(telefonata);
telefonata.Cliente = null;
}
}
}
public class Telefonata : EntityBase
{
public virtual Cliente Cliente { get; set; }
public virtual string Descrizione { get; set; }
public virtual DateTime? Data { get; set; }
public virtual bool Ingresso { get; set; }
public virtual string Utente { get; set; }
public virtual DateTime? UtenteData { get; set; }
public virtual TelefonataTipo TelefonataTipo { get; set; }
}
public class TelefonataTipo : EntityBase
{
public virtual string Nome { get; set; }
}
public abstract class EntityBase
{
public virtual int Id { get; private set; }
//ctor
protected EntityBase()
{ }
public override int GetHashCode()
{
return Id.GetHashCode();
}
public override bool Equals(object obj)
{
return (obj != null && obj.GetType() == GetType() && ((EntityBase)obj).Id == Id);
}
public static bool operator ==(EntityBase entity1, EntityBase entity2)
{
//cast come object altrimenti ho un loop ricorsivo
if ((object)entity1 == null && (object)entity2 == null)
return true;
if ((object)entity1 == null || (object)entity2 == null)
return false;
if (entity1.GetType() != entity2.GetType())
return false;
if (entity1.Id != entity2.Id)
return false;
return true;
}
public static bool operator !=(EntityBase entity1, EntityBase entity2)
{
return (!(entity1 == entity2));
}
}
And the mapping is as follow
public class ClienteMapping : MappingBase<Evoltel.PuntoQuinto.Domain.Entities.Cliente.Cliente>
{
public ClienteMapping()
: base("CLIENTI")
{
Id(x => x.Id, c => { c.Column("CLIENTE_ID"); c.Generator(Generators.Native, g => g.Params(new { sequence = "GEN_CLIENTI_ID" })); });
Property(x => x.Cognome, c => c.Column("COGNOME"));
Property(x => x.Nome, c => c.Column("NOME"));
Property(x => x.AttivitaId, c => c.Column("ATTIVITA_ID"));
Property(x => x.DittaAssunzioneData, c => c.Column("D_ASSUNZIONE_DATA"));
Property(x => x.Stipendio, c => c.Column("D_STIPENDIO"));
Bag(x => x.Telefonate, map =>
{
map.Inverse(true);
map.Table("TELEFONATE");
map.Cascade(Cascade.All | Cascade.DeleteOrphans);
map.Key(k => k.Column("CLIENTE_ID"));
map.Lazy(CollectionLazy.NoLazy);
}, r => r.OneToMany());
}
public class TelefonataMapping : MappingBase<Evoltel.PuntoQuinto.Domain.Entities.Cliente.Telefonata>
{
public TelefonataMapping()
: base("TELEFONATE")
{
Id(x => x.Id, c => { c.Column("TELEFONATA_ID"); c.Generator(Generators.Native, g => g.Params(new { sequence = "GEN_TELEFONATE_ID" })); });
ManyToOne(x => x.Cliente, c => c.Column("CLIENTE_ID"));
Property(x => x.Descrizione, c => c.Column("DESC_LUNGA"));
Property(x => x.Data, c => c.Column("TELEFONATA_DATA"));
Property(x => x.Ingresso, c => { c.Column("INGRESSO"); c.Type<TrueFalseType>(); });
Property(x => x.Utente, c => c.Column("UTENTE_NOME"));
Property(x => x.UtenteData, c => c.Column("UTENTE_DATA"));
ManyToOne(x => x.TelefonataTipo, c => { c.Column("TELEFONATA_TIPO_ID"); c.NotFound(NotFoundMode.Ignore); });
}
}
public abstract class MappingBase<T> : ClassMapping<T> where T : EntityBase
{
protected MappingBase(string tableName)
{
Table(tableName);
Lazy(false);
DynamicUpdate(true);
}
}
If I do not use Cascade.All the new Child is not saved (and for what I understood of NHibernate it's correct)
What is that I'm not seeing?

After updating to nHibernate 4, cannot use ManyToMany Mappings. Could not determine type

After updating to nHibernate (4.0.2.4000 via nuget), the many to many mappings that previously worked now cause mapping exception "Could not determine type for: nHibernateManyToMany.IRole, nHibernateManyToMany, for columns: NHibernate.Mapping.Column(id)"
Seems to be only for many to many, and when the List is a interface (i.e. List<Role> vs List<IRole>).
Example code that now fails:
class Program
{
static void Main(string[] args)
{
var configuration = new Configuration().SetProperty(Environment.ReleaseConnections, "on_close")
.SetProperty(Environment.Dialect, typeof(SQLiteDialect).AssemblyQualifiedName)
.SetProperty(Environment.ConnectionDriver, typeof(SQLite20Driver).AssemblyQualifiedName)
.SetProperty(Environment.CollectionTypeFactoryClass, typeof(DefaultCollectionTypeFactory).AssemblyQualifiedName)
.SetProperty(Environment.CommandTimeout, "0");
var mapper = new ModelMapper();
mapper.AddMappings(new[] { typeof(EmployeeMapping), typeof(RoleMapping) });
var hbmMapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
hbmMapping.autoimport = false;
configuration.AddMapping(hbmMapping);
// this line will fail
var factory = configuration.BuildSessionFactory();
}
}
public class Employee
{
public virtual int Id { get; set; }
public virtual List<IRole> Roles { get; set; }
}
public interface IRole
{
int Id { get; set; }
string Description { get; set; }
}
public class Role : IRole
{
public virtual int Id { get; set; }
public virtual string Description { get; set; }
}
public class EmployeeMapping : ClassMapping<Employee>
{
public EmployeeMapping()
{
Id(c => c.Id, x =>
{
x.Type(NHibernateUtil.Int32);
x.Generator(Generators.Identity);
x.Column("EmployeeId");
});
Bag(x => x.Roles, m =>
{
m.Table("EmployeeRole");
m.Key(km =>
{
km.Column("EmployeeId");
km.NotNullable(true);
km.ForeignKey("FK_Role_Employee");
});
m.Lazy(CollectionLazy.Lazy);
}, er => er.ManyToMany(m =>
{
m.Class(typeof(Role));
m.Column("RoleId");
}));
}
}
public class RoleMapping : ClassMapping<Role>
{
public RoleMapping()
{
Id(c => c.Id, x =>
{
x.Type(NHibernateUtil.Int32);
x.Generator(Generators.Identity);
x.Column("RoleId");
});
Property(x => x.Description, c =>
{
c.Length(50);
c.NotNullable(true);
});
}
}
Any help or suggestions about where we could look for details on how this has changed since v3 would be appreciated.
Turned out to be a bug in nHibernate.
A pull request has been submitted here https://github.com/nhibernate/nhibernate-core/pull/385

nHibernate map a filtered bag to a single property

I need to map an etity to a mater tabele of a DB.
This Entity has OneToMany with another.
I need to map a Collection othe the master entity to all rows of the Child table.
But I also need to map a Property with a single row getted from the child table and filtered by a criteria that return always only one row.
Someting like a Component but in a filtered child table.
This is My Mapping:
public class Test
{
public virtual string Id { get; set; }
public virtual string Description { get; set; }
public virtual IList<TestItem> Items { get; set; }
public virtual TestItem Item { get; set; }
public Test()
{
Items = new List<TestItem>();
}
}
public class TestItem
{
public virtual string Id { get; set; }
public virtual Test Test { get; set; }
public virtual string ItemCode { get; set; }
public virtual string ItemData { get; set; }
}
public class TestMap : ClassMapping<Test>
{
public TestMap()
{
Id(x => x.Id, m => m.Column("IDTest"));
//IPOTETICAL
SomeComponent(x => x.Item, c => // How to map a filtered collection to a single property??
{
c.Key(k =>
{
k.NotNullable(true);
k.Column("IDTest");
});
**// This Is the filter**
c.Filter("itemsFilter", f => f.Condition("ItemCode = :itemsCodeValue"));
}, r => r.OneToMany(m =>
{
m.NotFound(NotFoundMode.Exception);
m.Class(typeof(TestItem));
}));
Bag(x => x.Items, c => // All Child Rows
{
c.Key(k =>
{
k.NotNullable(true);
k.Column("IDTest");
});
c.Cascade(Cascade.All | Cascade.DeleteOrphans);
c.Lazy(CollectionLazy.NoLazy);
c.Inverse(true);
}, r => r.OneToMany(m =>
{
m.NotFound(NotFoundMode.Exception);
m.Class(typeof(TestItem));
}));
}
}
public class TestItemMap : ClassMapping<TestItem>
{
public TestItemMap()
{
Id(x => x.Id, m => m.Column("IDTestItem"));
ManyToOne(x => x.Test, m =>
{
m.Column("IDTest");
m.NotNullable(false);
m.Lazy(LazyRelation.NoLazy);
});
Property(x => x.ItemCode);
Property(x => x.ItemData);
}
}
I found something about DynamicComponent but I do not know if it is for me...
http://notherdev.blogspot.it/2012/01/mapping-by-code-dynamic-component.html
Thank You!!

How to Use AutoMapper to Map to Models With Inheritance and Nested Models

I have an Address entity with 2 sub types. Here's my simplified code:
public class Address {
public string Street1 { get; set; }
public string Country { get; set; }
}
public class UsAddress : Address {
public string State { get; set; }
}
public class CandianAddress : Address {
public string Providence { get; set; }
}
Here's my simplified view models:
public class LocationModel {
public string Street1 { get; set; }
}
public class UsLocationModel : LocationModel {
public string State { get; set; }
}
public class CaLocationModel : LocationModel {
public string Providence { get; set; }
}
public class AddressModel {
public int? Country { get; set; }
public UsLocationModel UsLocation { get; set; }
public CaLocationModel CaLocation { get; set; }
}
Here's my simplified AutoMapper config:
Mapper.CreateMap<Address, AddressModel>()
.Include<UsAddress, AddressModel>()
.Include<CanadianAddress, AddressModel>();
Mapper.CreateMap<UsAddress, AddressModel>();
Mapper.CreateMap<CanadianAddress, AddressModel>();
Mapper.CreateMap<Address, LocationModel>()
.Include<UsAddress, USLocationModel>()
.Include<CanadianAddress, CALocationModel>();
Mapper.CreateMap<UsAddress, USLocationModel>();
Mapper.CreateMap<CanadianAddress, CALocationModel>();
I can't figure out how to resolve the UsLocation and CaLocation properties on AddressModel...
I figured it out. Here's a simplified version of my Automapper config:
Mapper.CreateMap<Address, AddressModel>()
.Include<UsAddress, AddressModel>()
.Include<CanadianAddress, AddressModel>()
.ForMember(x => x.USLocation, a => a.Ignore())
.ForMember(x => x.CALocation, a => a.Ignore())
.ForMember(x => x.Country, a => a.ResolveUsing<HaveIdValueResolver<Country, int>>().FromMember(x => x.Country);
Mapper.CreateMap<UsAddress, AddressModel>()
.ForMember(x => x.USLocation, a => a.MapFrom(Mapper.Map<UsAddress, USLocationModel>))
.ForMember(x => x.CALocation, a => a.Ignore())
.ForMember(x => x.Country, a => a.ResolveUsing<HaveIdValueResolver<Country, int>>().FromMember(x => x.Country));
Mapper.CreateMap<CanadianAddress, AddressModel>()
.ForMember(x => x.USLocation, a => a.Ignore())
.ForMember(x => x.CALocation, a => a.MapFrom(Mapper.Map<CanadianAddress, CALocationModel>))
.ForMember(x => x.Country, a => a.ResolveUsing<HaveIdValueResolver<Country, int>>().FromMember(x => x.Country));
Mapper.CreateMap<Address, LocationModel>()
.Include<UsAddress, USLocationModel>()
.Include<CanadianAddress, CALocationModel>();
Mapper.CreateMap<UsAddress, USLocationModel>();
Mapper.CreateMap<CanadianAddress, CALocationModel>();
Here's a simplified sample test:
var usAddress = new FakeUsAddress("111 L St", new FakeState(id: 17));
var addressModel = Mapper.Map<UsAddress, AddressModel>(usAddress);
Assert.IsNotNull(addressModel.USLocationModel);
Assert.IsNull(addressModel.CALocationModel);
Assert.AreEqual(usAddress.Street1, addressModel.USLocationModel.Street1);
Assert.AreEqual(usAddress.State.Id, addressModel.USLocationModel.State);
Assert.AreEqual(usAddress.Country.Id, addressModel.USLocationModel.Country);

Nhibernate Conformist Mapping "Unable to determine type..."

The class:
public class SOPProcess : ISOPProcess
{
public virtual Guid Id { get; set; }
public virtual SOP SOP { get; set; }
public virtual ProcessType Type { get; set; }
public virtual SOPProcessInput Input { get; set; }
public virtual SOPProcessOutput Output { get; set; }
public virtual SOPProcessMeasures Measures { get; set; }
public virtual decimal YieldFactor { get; set; }
public virtual SOPProcess PreviousProcess { get; set; }
public virtual SOPProcess NextProcess { get; set; }
}
The Mapping:
public class SOPProcessMap : ClassMapping<SOPProcess>
{
public SOPProcessMap()
{
Id(s => s.Id, i => i.Generator(Generators.GuidComb));
Property(s => s.YieldFactor);
ManyToOne(s => s.SOP, m =>
{
m.Column("SopId");
m.Cascade(Cascade.All);
});
ManyToOne(s => s.Type, m =>
{
m.Column("ProcessTypeId");
m.Cascade(Cascade.All);
});
ManyToOne(s => s.NextProcess, m =>
{
m.Column("NextProcessId");
m.Cascade(Cascade.All);
});
ManyToOne(s => s.PreviousProcess, m =>
{
m.Column("PreviousProcessId");
m.Cascade(Cascade.All);
});
}
}
The Error:
NHibernate.MappingException: Could not determine type for: MES.ProcessManager.SOP.SOPProcess, MES.ProcessManager, for columns: NHibernate.Mapping.Column(id)
I hope it's something simple, this is my first project using the Conformist mapping, so maybe I'm just overlooking something.
From our discussion on the nhusers mailing list.
I ran across the same problems.
You haven't defined the type of relationship. See the line action => action.OneToMany()); in the mapping below.
public class SportMap : ClassMapping<Sport>
{
public SportMap()
{
Id(x => x.Id, map =>
{
map.Column("Id");
map.Generator(Generators.GuidComb);
});
Property(x => x.Name, map =>
{
map.NotNullable(true);
map.Length(50);
});
Bag(x => x.Positions, map =>
{
map.Key(k => k.Column(col => col.Name("SportId")));
map.Cascade(Cascade.All | Cascade.DeleteOrphans);
},
action => action.OneToMany());
Property(x => x.CreateDate);
Property(x => x.CreateUser);
Property(x => x.LastUpdateDate);
Property(x => x.LastUpdateUser);
}
}
It turned out that the problem was in my Set mappings in other classes. If you don't specify the action for the mapping, it throws this (misleading) error.