2016-12-12 26 views
0

我试图找出为什么我收到一个无效的转换异常与NHibernate用下面的代码:NHibernate的无效的转换与定制PrimitiveType

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

这一切automapped用流利。

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

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

NHibernate.Tuple.Entity.PocoEntityTuplizer.SetPropertyValuesWithOptimizer(对象实体,对象[]值) {“无效角色(检查为属性的类型不匹配的映射);的otherclass设定器”}

注意,第二POCO对象正常工作与nHib如果删除EncryptedDbString attibute,即,它将Guid保存为nvarchar没有问题。

显然这里的问题是,它是一个GUID作为字符串的情况下工作,但我不希望它保持为GUID不是字符串中的代码,我不能在这里看到故障点。

好像我缺少一些小东西。我想我错过了泛型的东西,但我只找到了代码片段,而不是像这样的完整示例。

编辑:

好了,我想通了,我认为这是因为

Get(IDataReader rs, int index) 

没有返回的Guid对象。

所以我想你可以在EncryptedStringType Get/Set方法中进行序列化/反序列化。在获取(),你可以改变为:

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

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

,但似乎太可怕了,特别是如果你有现有的数据迁移。

我不希望存储的东西,无论是二进制,作为球队希望能够检查/测试/手动审核通过的列进行加密SQL(这是与文本明显的,但不是二进制)。

在我的POCO字符串支持字段将GUID转换为字符串,并通过简单的get/set方法返回可能是最好的选择,但我不知道如何通过解决方案自动映射或如何混乱它是?

+0

你试过类似[this](http://stackoverflow.com/a/393787/497356)吗?它可能工作。此外,你的意思是“这看起来很可怕,特别是如果你有现有的数据迁移?” –

+0

其实你是对的。我以为我不想在列中出现不必要的json,但我可以只有一个字符串,但是无论如何都会看到它的加密,因为它不会在SQL查询中手动显示/查询无论如何。感谢您的评论,因为它确实消除了对json的需求,并且看到了我的结果答案:) – jimasp

+0

顺便说一句,如果有人读到这个确实想要例如将更复杂的对象存储为二进制而不是字符串,请参阅:http://blog.muonlab.com/2010/04/20/encrypting-data-with-nhibernate/ – jimasp

回答

1

睡了之后,我想我一直在想这是错误的方式。

我现在已经意识到,我在数据库中存储json的沉默是由于我存储了字符串偏向的对象 - 即自然转换为文本字段的东西,而不是完整对象。 myGuid.ToString()给你一个guid字符串,myDateTime.ToString()给你一个日期时间字符串等。

因此,考虑到对象序列化本身并不需要在我的情况下,而只是转换为一个字符串,安德鲁的建议似乎是一个很好的解决方案。

更新代码:

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()并且类型是IConvertible,但是我会把它留作足够的时间!