1

这个问题与映射接口或抽象类组件有关 我也在尝试映射声明为接口的组件,但我使用的是内置的按代码映射/符合标准的方法。

假设我有一个实体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. 使用用户类型而不是组件。这就是我现在正在做的事情。我的类型(散列密码)是不可变的,可以存储为单列,因此用户类型相当简单。

这是我当前使用的用户类型(为了完成)。我使用 PBKDF2 创建安全哈希。请注意,在我的应用程序中,所有数据(盐、哈希和 PBKDF2 迭代计数)都存储在HashHashedPassword 的一个属性(简称为 )中。

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

2 回答 2

1

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

可能的解决方法:

  1. 对有问题的类型使用 XML 映射。这支持我的方案没有问题。请参阅问题以获取示例。
  2. 使用 FluentNHibernate。它只支持这种开箱即用的映射,请参见下面的示例。
  3. 使用用户类型而不是组件。这是比较多的工作,但至少它适用于按代码映射。

FluentNH 中所需映射的示例。具体类型作为泛型参数传入。

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

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

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

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

于 2013-02-14T10:43:17.593 回答
0

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

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

你可以试试

comp.Property(x => (x as HashedPassword).Hash)
于 2013-02-11T18:23:56.120 回答