每个子类的NHibernate表导致无意义的INSERT语句 - 我做错了什么?

时间:2014-01-16 07:24:04

标签: nhibernate nhibernate-mapping mapping-by-code discriminator table-per-subclass

我有以下实体:

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

EnumCustomTypeIUserType,它将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语句中有来自三个不同表的列。它有来自PersonUserClientUser的列(这是不可能的),它是不存在的参数索引的绑定参数,甚至不包括我设置的所有属性!

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

编辑我忘了提及:我在这里使用了鉴别器,因为我希望能够通过其ID获取通用User并返回正确的{{1} }或PersonUser取决于它的类型。

2 个答案:

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