NHibernate - 使用map-by-code / Conformist映射接口或抽象组件

时间:2013-02-11 15:45:20

标签: c# nhibernate nhibernate-mapping mapping-by-code

此问题与Mapping interface or abstract class component有关 我也试图映射一个声明为接口的组件,但我正在使用内置的按代码映射/一致的方法。

假设我有一个实体Login(C#):

public class Login
{
    public virtual int Id { get; set; }
    public virtual string UserName { get; set; }
    public virtual IPassword Password { get; set; }
}

我想抽象出密码的存储方式,所以我定义了一个简单的接口IPassword

public interface IPassword
{
    bool Matches(string password);
}

示例实现是HashedPassword

public class HashedPassword : IPassword
{
    public virtual string Hash { get; set; }
    public virtual string Salt { get; set; }

    public virtual bool Matches(string password){ /* [snip] */ }
}

我想将Login.Password映射为一个组件,而不是多对一或一对一的关系。使用XML我会像这样映射:

<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="..." namespace="...">
  <class name="Login">
    <id name="Id">...</id>
    <property name="UserName" .../>
    <component name="Password" class="HashedPassword">
      <property name="Hash" not-null="true" length="32"/>
      <property name="Salt" not-null="true" length="32"/>
    </component>
  </class>
</hibernate-mapping>

这可以按预期工作。

这是我尝试使用NHibernate内置的逐代码映射工具来映射它:

public class LoginMapping : ClassMapping<Login>
{
    public LoginMapping()
    {
        Id(x => x.Id, map => map.Generator(Generators.HighLow));
        Property(x => x.UserName, map => map.Length(32));
        Component(x => x.Password, comp =>
            {
                comp.Class<HashedPassword>();
                comp.Property("Salt", map => map.Length(32));
                comp.Property("Hash", map => map.Length(32));
            });
    }
}

当我使用此映射时,我得到以下异常:

  

NHibernate.MappingException:找不到成员。 IPassword类型中不存在成员'Salt'

虽然Salt不是IPassword的成员,但我使用comp.Class<HashedPassword>()

设置的类的成员

您知道如何在不获取异常的情况下映射此方案吗?


到目前为止,我还没有找到问题本身的解决方案。目前有两种解决方法:

  1. 求助于XML映射或FluentNHibernate。这可能仅适用于“有问题”的映射。

  2. 使用用户类型代替组件。这就是我现在正在做的事情。我的案例中的类型(散列密码)是不可变的,可以存储为单个列,因此用户类型非常简单。

  3. 这是我目前正在使用的用户类型(为了完成)。我使用PBKDF2来创建安全哈希。请注意,在我的应用程序中,所有数据(salt,hash和PBKDF2迭代计数)都存储在HashedPassword的一个属性(简称为Hash)中。

    public abstract class ImmutableValue<T> : IUserType where T : class
    {
        public abstract SqlType[] SqlTypes { get; }
    
        public virtual Type ReturnedType
        {
            get { return typeof (T); }
        }
    
        public bool IsMutable
        {
            get { return false; }
        }
    
        bool IUserType.Equals(object x, object y)
        {
            return InternalEquals(x, y);
        }
    
        protected virtual bool InternalEquals(object x, object y)
        {
            return Equals(x, y);
        }
    
        public virtual int GetHashCode(object x)
        {
            return x == null ? 0 : x.GetHashCode();
        }
    
        public virtual object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            return Load(rs, names, owner);
        }
    
        protected abstract T Load(IDataReader rs, string[] names, object owner);
    
        public virtual void NullSafeSet(IDbCommand cmd, object value, int index)
        {
            Save(cmd, (T) value, index);
        }
    
        protected abstract void Save(IDbCommand cmd, T value, int index);
    
        public virtual object DeepCopy(object value)
        {
            return value;
        }
    
        public virtual object Replace(object original, object target, object owner)
        {
            return original;
        }
    
        public virtual object Assemble(object cached, object owner)
        {
            return cached;
        }
    
        public virtual object Disassemble(object value)
        {
            return value;
        }
    
        protected void SetParameter(IDbCommand cmd, int index, object value)
        {
            var parameter = (IDataParameter) cmd.Parameters[index];
            var parameterValue = value ?? DBNull.Value;
            parameter.Value = parameterValue;
        }
    }
    
    public class HashedPasswordType : ImmutableValue<HashedPassword>
    {
        public override SqlType[] SqlTypes
        {
            get { return new SqlType[] {SqlTypeFactory.GetString(HashedPassword.ContentLength)}; }
        }
    
        protected override HashedPassword Load(IDataReader rs, string[] names, object owner)
        {
            var str = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
            return HashedPassword.FromContent(str);
        }
    
        protected override void Save(IDbCommand cmd, HashedPassword value, int index)
        {
            SetParameter(cmd, index, value == null ? null : value.Hash);
        }
    }
    

    所需的映射比较简单:

    Property(x => x.Password, map =>
        {
            map.Type<HashedPasswordType>();
            map.NotNullable(true);
        });
    

2 个答案:

答案 0 :(得分:1)

由于我还没有找到使用组件映射的解决方案,但我现在将添加变通方法作为答案。

可能的解决方法:

  1. 对相关类型使用XML映射。这支持我的方案没有问题。请参阅问题以获取示例。
  2. 使用FluentNHibernate。它只支持这种开箱即用的映射,请参见下面的示例。
  3. 使用用户类型而不是组件。这是相当多的工作,但至少它将适用于按代码映射。
  4. FluentNH中所需映射的示例。具体类型作为泛型参数传入。

    Component<HashedPassword>(x => x.Password, comp =>
    {
        comp.Map(x => x.Hash);
        comp.Map(x => x.Salt);
    });
    

    问题末尾给出了用户类型的示例。在实际示例中,我使用PBKDF2来创建安全哈希。请注意,在问题中显示的用户类型中,所有数据(salt,hash和PBKDF2迭代计数)都存储在 one 列中,以保持类型简单。

    我目前的结论是,按代码映射根本不支持我要求的内容。我现在正在考虑从映射到代码转移到FluentNH。

    如果有与此问题相关的新闻,我会相应地更新答案和/或提问。

答案 1 :(得分:0)

我对代码映射并不熟悉,但在使用Fluent NHibernate时,我已经取得了成功

Map(x => (x as HashedPassword).Hash)

你可以尝试

comp.Property(x => (x as HashedPassword).Hash)