简体   繁体   English

nHibernate具有自定义PrimitiveType的无效类型转换

[英]nHibernate invalid cast with custom PrimitiveType

I am trying to figure out why I am getting an invalid cast exception with nHibernate with the following code: 我试图找出为什么我在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 that works: 有效的POCO:


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

POCO that fails: 失败的POCO:


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

This is all automapped with Fluent. 这一切都通过Fluent自动映射。

Both the Guid type and string type are nvarchar(500) in the SQL database. 在SQL数据库中,Guid类型和字符串类型均为nvarchar(500)。

As mentioned, the first POCO works fine and encrypts/decrypts as expected, but the second POCO fails, and this is what I see in my logs: 如前所述,第一个POCO可以正常工作,并且可以按预期进行加密/解密,但是第二个POCO失败了,这就是我在日志中看到的内容:

NHibernate.Tuple.Entity.PocoEntityTuplizer.SetPropertyValuesWithOptimizer(Object entity, Object[] values) {"Invalid Cast (check your mapping for property type mismatches); setter of otherclass"} NHibernate.Tuple.Entity.PocoEntityTuplizer.SetPropertyValuesWithOptimizer(对象实体,对象[]值){“无效的转换(检查属性类型不匹配的映射);其他类的设置器”}

Note that the second POCO object works fine with nHib if I remove the EncryptedDbString attibute, ie it has no problems saving the Guid to a nvarchar. 请注意,如果我删除EncryptedDbString属性,则第二个POCO对象可以与nHib一起正常工作,即,将Guid保存到nvarchar中没有问题。

Obviously the issue here is that it's a Guid as the string case works, but I do want it kept as a Guid not a string in the code , and I can't see the point of failure here. 显然这里的问题是它是一个Guid,因为它适用于字符串大小写,但是我确实希望它作为Guid而不是字符串保留在代码中 ,并且我在这里看不到失败的地方。

Seems like I'm missing something small. 好像我缺少一些小东西。 I guess I'm missing something with the generics, but I've only found code snippets out there rather than a full example like this. 我想我在泛型中缺少一些东西,但是我只在那里找到了代码片段,而不是像这样的完整示例。

EDIT: 编辑:

ok, so i figured out it i think it was because the 好的,所以我想通了,我认为是因为

Get(IDataReader rs, int index) 

was not returning a Guid object. 没有返回Guid对象。

so I guess you can serialize/deserialize in the EncryptedStringType Get/Set methods, eg in the Get() you could change to: 所以我想您可以在EncryptedStringType Get / Set方法中序列化/反序列化,例如在Get()中,您可以更改为:

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

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

but that seems horrible, especially if you have existing data to migrate. 但这似乎太可怕了,特别是如果您要迁移现有数据。

i don't want to store stuff as binary either , as the team want to be able to check/test/audit manually via SQL which columns are encrypted (which is obvious with text, but not binary). 我也不想将任何内容存储为二进制 ,因为团队希望能够通过SQL手动检查/测试/审核哪些列已加密(这在文本中很明显,但不是二进制的)。

a string backing field in my POCO that converts the Guid to a string and back again via simple get/set methods might be the best option, but I have no idea how to do that with automapping across the solution or how messy it is? 在我的POCO中将Guid转换为字符串并通过简单的get / set方法再次返回的字符串后备字段可能是最好的选择,但是我不知道如何在整个解决方案中自动映射,或者它有多混乱?

Having slept, I think i've been thinking about this the wrong way. 睡觉后,我想我一直在想这个错误的方式。

I've now realised that my reticence to store json in the database was driven by the fact that I am storing string-biased objects - ie things that naturally convert to text fields, as opposed to full objects. 现在,我意识到我不愿将json存储在数据库中的原因是,我存储的是带字符串偏移的对象-即自然转换为文本字段而不是完整对象的对象。 myGuid.ToString() gives you a guid string, myDateTime.ToString() gives you a datetime string etc. myGuid.ToString()给您一个引导字符串,myDateTime.ToString()给您一个日期时间字符串等。

So given that object serialisation per se isn't needed in my case, but rather just conversion to a string, Andrew's suggestion seems like a good solution. 因此,就我而言,不需要对象序列化本身,而只是转换为字符串,Andrew的建议似乎是一个不错的解决方案。

Updated code: 更新的代码:

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

An improvement would be for the EncryptedStringConvention to have the Accept() method added to pre-check that all the types marked with the EncryptedDbString attribute were convertible. EncryptedStringConvention的一个改进是添加了Accept()方法,以预先检查标记有EncryptedDbString属性的所有类型都是可转换的。 Possibly we could use Convert() and type is IConvertible instead, but I'll leave it as, enough time spent! 可能我们可以使用Convert(),而类型是IConvertible,但是我将保留它足够的时间!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 每个子类的nhibernate表“无效转换”异常 - nhibernate table per subclass “invalid cast” exception NHibernate投影。转换为自定义类型 - NHibernate Projections.Cast to custom type NHibernate:[PropertyAccessException:Invalid Cast(检查映射是否存在属性类型不匹配) - NHibernate: [PropertyAccessException: Invalid Cast (check your mapping for property type mismatches) 使用 nhibernate 查询字节属性导致无效转换错误 - Query a byte property with nhibernate cause invalid cast error NHibernate无效的类型转换-使用查询时属性类型不匹配 - NHibernate Invalid Cast - Property type mismatches when using queries 从UserControl到自定义控件的无效转换 - Invalid Cast from UserControl to custom control System.InvalidCastException:从&#39;DateTime&#39;到&#39;Int32&#39;的无效转换。 冬眠 - System.InvalidCastException: Invalid cast from 'DateTime' to 'Int32'. nhibernate 从HttpContext获取自定义IPrincipal时无效的强制转换 - Invalid cast when getting custom IPrincipal from HttpContext 难过:自定义NHibernate会话管理给出“指定的转换无效”异常(C#) - Stumped: Custom NHibernate Session management gives “Specified cast is not valid” exception (C#) 无效的转换例外-转换无效 - Invalid Cast Exception - cast is not valid
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM