【问题标题】:NHibernate - map interface or abstract component with mapping-by-code/ConformistNHibernate - 映射接口或抽象组件与按代码映射/一致性
【发布时间】:2013-02-11 15:45:20
【问题描述】:

这个问题与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&lt;HashedPassword&gt;() 设置的类的成员

你知道我怎样才能在不出现异常的情况下映射这个场景吗?


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

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

  2. 使用用户类型而不是组件。这就是我现在正在做的事情。我的类型(散列密码)是不可变的,可以存储为单列,因此用户类型相当简单。

这是我当前使用的用户类型(为了完成)。我使用 PBKDF2 创建安全哈希。请注意,在我的应用程序中,所有数据(盐、哈希和 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);
    });

【问题讨论】:

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


    【解决方案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 迭代计数)都存储在 one 列中以保持类型简单。

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

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

    【讨论】:

      【解决方案2】:

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

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

      你可以试试

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

      【讨论】:

      • 如果我这样做,属性根本不会出现在映射中,即。该组件被映射为&lt;component class="HashedPassword" name="Password" /&gt;
      猜你喜欢
      • 1970-01-01
      • 2018-08-17
      • 1970-01-01
      • 2012-04-09
      • 1970-01-01
      • 2014-04-02
      • 1970-01-01
      • 1970-01-01
      • 2010-12-12
      相关资源
      最近更新 更多