我有以下实体:
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) { }
}
我使用以下Hibernate Conformist映射映射这些实体:
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>();
}));
}
);
}
}
EnumCustomType
是IUserType
,它将C#枚举映射到整数列。
经过NHIBnate参考文档和本博客(specific page)(summary)的大量研究和咨询后,我制定了这个设计和映射。我确信这是我想要的实体设计,但当然可能(可能?)我的映射错误。
当我启动并配置NHibernate时,它会加载映射并且不会抱怨。日志输出没有关于映射的警告。但是,当我创建PersonUser
时,为其所有属性指定值,并将Add
指定给ISession
时,会发生最奇怪的事情:
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)
重要的是,看看生成的SQL:
INSERT INTO Authentication_Users (TenantSK, DisplayName, PublicKey, LoginEnabled, LockedOut, NumberOfFailedLoginAttempts, Username, AuthenticationType, UserType) VALUES (?, ?, ?, ?, ?, ?, ?, ?, '1')
这没有任何意义。它根本不对应于映射。它在一个表的insert语句中有来自三个不同表的列。它有来自PersonUser
和ClientUser
的列(这是不可能的),它是不存在的参数索引的绑定参数,甚至不包括我设置的所有属性!
我已经玩了几个小时了,现在没有任何进展。我在这里完全失败了。它没有任何意义。有人见过这个吗?知道发生了什么事吗?
编辑我忘了提及:我在这里使用了鉴别器,因为我希望能够通过其ID获取通用User
并返回正确的{{1} }或PersonUser
取决于它的类型。
答案 0 :(得分:1)
映射存在一些问题
a)您正在将UserType映射为属性,但您也将其用作鉴别器。但是由于使用了鉴别器,因此用户类型值由nhibernate生成...因此,您可以定义要生成的属性。
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>>();
});
你也不应该在setter上抛出异常......以防止从某处设置UserType
,只需标记auto property setter protected internal
public virtual UserType UserType
{
get;
protected internal set;
}
b)您只在子类中映射基类的Name
属性,并且您尝试将该属性映射到子类表中的不同列。
不知道这是否可能,通常您必须将基类的所有属性映射到基类表,或将属性移动到子类...
要解决此问题,只需从子类映射中删除映射到Name
,然后将其移动到基类映射。
答案 1 :(得分:0)
在MichaC的帮助下,这是最终有效的代码。
实体:
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."); }
}
}
映射:
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>();
}));
}
);
}
}