【问题标题】:nHibernate invalid cast with custom PrimitiveType使用自定义 PrimitiveType 的 nHibernate 无效转换
【发布时间】:2016-12-12 17:56:09
【问题描述】:

我正在尝试使用以下代码找出为什么我会使用 nHibernate 得到一个无效的强制转换异常:

AutoMap.Source(new TypeSource(recordDescriptors))
    .Conventions.Add(new EncryptedStringConvention());

.

[AttributeUsage(AttributeTargets.Property)]
public class EncryptedDbString : Attribute { }

.

public class EncryptedStringConvention : IPropertyConvention {
    public void Apply(IPropertyInstance instance) {
        if (!instance.Property.MemberInfo.IsDefined(typeof(EncryptedDbString), false))
            return;

        var propertyType = instance.Property.PropertyType;
        var generic = typeof(EncryptedStringType<>);
        var specific = generic.MakeGenericType(propertyType);
        instance.CustomType(specific);
    }
}

.

[Serializable]
public class EncryptedStringType<T> : PrimitiveType
{
    const int MaxStringLen = 1000000000;
    public EncryptedStringType() : this(new StringSqlType(MaxStringLen)) { }
    public EncryptedStringType(SqlType sqlType) : base(sqlType) { }

    public override string Name {
        get { return typeof(T).Name; }
    }

    public override Type ReturnedClass {
        get { return typeof(T); }
    }

    public override Type PrimitiveClass {
        get { return typeof(T); }
    }

    public override object DefaultValue {
        get { return default(T); }
    }

    public override object Get(IDataReader rs, string name) {
        return Get(rs, rs.GetOrdinal(name));
    }

    public override void Set(IDbCommand cmd, object value, int index) {
        if (cmd == null) throw new ArgumentNullException("cmd");
        if (value == null) {
            ((IDataParameter)cmd.Parameters[index]).Value = null;
        }
        else {
            ((IDataParameter)cmd.Parameters[index]).Value = Encryptor.EncryptString((string)value);
        }
    }

    public override object Get(IDataReader rs, int index) {
        if (rs == null) throw new ArgumentNullException("rs");
        var encrypted = rs[index] as string;
        if (encrypted == null) return null;
        return Encryptor.DecryptString(encrypted);
    }

    public override object FromStringValue(string xml) {
        // i don't think this method actually gets called for string (i.e. non-binary) storage 
        throw new NotImplementedException();
    }

    public override string ObjectToSQLString(object value, Dialect dialect) {
        // i don't think this method actually gets called for string (i.e. non-binary) storage 
        throw new NotImplementedException();
    }

}

有效的 POCO:


public class someclass {
   public virtual string id {get;set;}
   [EncryptedDbString]
   public virtual string abc {get;set;}
}

失败的POCO:


public class otherclass {
   public virtual string id {get;set;}
   [EncryptedDbString]
   public virtual Guid def {get;set;}
}

这一切都是用 Fluent 自动映射的。

在SQL数据库中Guid类型和字符串类型都是nvarchar(500)。

如前所述,第一个 POCO 工作正常并按预期加密/解密,但第二个 POCO 失败,这是我在日志中看到的:

NHibernate.Tuple.Entity.PocoEntityTuplizer.SetPropertyValuesWithOptimizer(对象实体,对象 [] 值) {"Invalid Cast (检查你的映射是否有属性类型不匹配);其他类的设置器"}

请注意,如果我删除 EncryptedDbString 属性,第二个 POCO 对象可以与 nHib 一起正常工作,即将 Guid 保存到 nvarchar 时没有问题。

很明显,这里的问题是它是一个 Guid,因为字符串案例有效,但是 我确实希望它在代码中保留为 Guid 而不是字符串,我看不出有什么意义这里失败了。

好像我错过了一些小东西。我想我在泛型方面遗漏了一些东西,但我只找到了代码 sn-ps,而不是像这样的完整示例。

编辑:

好的,所以我想通了,我认为这是因为

Get(IDataReader rs, int index) 

没有返回 Guid 对象。

所以我猜你可以在 EncryptedStringType Get/Set 方法中序列化/反序列化,例如在 Get() 中,您可以更改为:

if (typeof(T) == typeof(string))
    return decrypted;

var obj = JsonConvert.DeserializeObject(decrypted);
return obj;

但这看起来很可怕,尤其是在您有现有数据要迁移的情况下。

我也不想将内容存储为二进制,因为团队希望能够通过 SQL 手动检查/测试/审计哪些列是加密的(这在文本中很明显,但是不是二进制)。

我的 POCO 中将 Guid 转换为字符串并通过简单的 get/set 方法再次返回的字符串支持字段可能是最佳选择,但我不知道如何通过解决方案中的自动映射来做到这一点,也不知道它有多混乱是吗?

【问题讨论】:

  • 你试过this之类的吗?它可能会起作用。另外,“这看起来很可怕,尤其是在您有现有数据要迁移的情况下”是什么意思?
  • 其实,你是对的。我在想当我可以有一个字符串时我不想在列中出现不必要的 json,但是无论如何看到它是加密的,它并没有太大的区别,因为它不会在 SQL 查询中手动可见/可查询无论如何分析仪。感谢您的评论,因为它确实消除了对 json 的需求,并查看我的结果答案:)
  • 顺便说一句,如果有人读到这里确实想要例如将更复杂的对象存储为二进制而不是字符串,请参阅:blog.muonlab.com/2010/04/20/encrypting-data-with-nhibernate

标签: c# encryption nhibernate fluent-nhibernate orchardcms


【解决方案1】:

睡着了,我想我一直在想这个错误的方式。

我现在意识到,我之所以不愿在数据库中存储 json,是因为我存储的是字符串偏向的对象——即自然转换为文本字段的东西,而不是完整的对象。 myGuid.ToString() 给你一个 guid 字符串,myDateTime.ToString() 给你一个日期时间字符串等等。

因此,鉴于我的情况不需要对象序列化本身,而只是转换为字符串,Andrew 的建议似乎是一个很好的解决方案。

更新代码:

public override void Set(IDbCommand cmd, object value, int index) {

    var prm = ((IDataParameter) cmd.Parameters[index]);
    if (cmd == null) throw new ArgumentNullException("cmd");
    if (value == null) {
        prm.Value = null;
        return;
    }

    string str;
    try {
        // guid becomes a simple guid string, datetime becomes a simple     
        // datetime string etc. (ymmv per type)
        // note that it will use the currentculture by 
        // default - which is what we want for a datetime anyway
        str = TypeDescriptor.GetConverter(typeof(T)).ConvertToString(value);
    }
    catch (NotSupportedException) {
        throw new NotSupportedException("Unconvertible type " + typeof(T) + " with EncryptedDbString attribute");
    }

    prm.Value = Encryptor.EncryptString(str);

}

public override object Get(IDataReader rs, int index) {

    if (rs == null) throw new ArgumentNullException("rs");
    var encrypted = rs[index] as string;
    if (encrypted == null) return null;

    var decrypted = Encryptor.DecryptString(encrypted);

    object obj;
    try {
        obj = (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(decrypted);
    }
    catch (NotSupportedException) {
        throw new NotSupportedException("Unconvertible type " + typeof(T) + " with EncryptedDbString attribute");
    }
    catch (FormatException) {
        // consideration - this will log the unencrypted text
        throw new FormatException(string.Format("Cannot convert string {0} to type {1}", decrypted, typeof(T)));
    }

    return obj;
}

EncryptedStringConvention 的一项改进是添加 Accept() 方法来预先检查所有标记有 EncryptedDbString 属性的类型是否可转换。可能我们可以使用 Convert() 并且 type 是 IConvertible ,但我会保留它,花费足够的时间!

【讨论】:

    猜你喜欢
    • 2013-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-02
    • 2020-01-02
    • 2011-03-18
    • 2012-07-01
    • 1970-01-01
    相关资源
    最近更新 更多