2

我有以下实体:

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是将IUserTypeC# 枚举映射到整数列的一个。

经过大量研究和咨询 NHibernate 参考文档和此博客(特定页面)(摘要)后,我制定了此设计和映射。我确信这是我想要的实体设计,但当然有可能(可能?)我弄错了映射。

当我启动并配置 NHibernate 时,它​​会加载映射并且不会抱怨。日志输出没有关于映射的警告。但是,当我创建 a 时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')

这是没有意义的。它根本不对应于映射。它在一个表的插入语句中从三个不同的表中获取列。它有来自PersonUserAND 的列ClientUser(这应该是不可能的),它使用不存在的参数索引绑定参数,甚至不包括我设置的所有属性!

我已经玩了几个小时了,没有任何进展。我在这里完全不知所措。这没有任何意义。有人见过这个吗?知道发生了什么吗?

编辑我忘了提:我在这里使用鉴别器是因为我希望能够User通过它的 ID 获得一个泛型,它返回正确的PersonUserClientUser取决于它是哪种类型。

4

2 回答 2

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>>();
    });

您也不应该在设置器上抛出异常......为了防止UserType从某个地方设置,只需将自动属性设置器标记为受保护的内部

    public virtual UserType UserType
    {
        get;
        protected internal set;
    }

b)您Name仅在子类中映射基类的属性,并且试图将该属性映射到子类表中的不同列。不知道这是否可能,通常您必须将基类的所有属性映射到基类表,或者将属性移动到子类中......

要解决此问题,只需Name从子类映射中删除映射并将其移至基类映射。

于 2014-01-16T12:16:28.157 回答
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>();
                    }));
            }
        );
    }
}
于 2014-01-16T18:55:41.833 回答