这个问题与映射接口或抽象类组件有关 我也在尝试映射声明为接口的组件,但我使用的是内置的按代码映射/符合标准的方法。
假设我有一个实体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>()
你知道我如何在不出现异常的情况下映射这个场景吗?
到目前为止,我还没有找到问题本身的解决方案。目前有两种解决方法:
求助于 XML 映射或 FluentNHibernate。这可能仅适用于“有问题的”映射。
使用用户类型而不是组件。这就是我现在正在做的事情。我的类型(散列密码)是不可变的,可以存储为单列,因此用户类型相当简单。
这是我当前使用的用户类型(为了完成)。我使用 PBKDF2 创建安全哈希。请注意,在我的应用程序中,所有数据(盐、哈希和 PBKDF2 迭代计数)都存储在Hash
HashedPassword 的一个属性(简称为 )中。
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);
});