2016-12-12 14 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(); 
    } 

} 

ПОКО, который работает:


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

ПОКО, которая не:


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

Это все automapped с Fluent.

Оба типа и тип строки являются nvarchar (500) в базе данных SQL.

Как уже упоминалось, первый ПОКО работает отлично и шифрует/расшифровывает, как и ожидалось, но второй ПОКО терпит неудачу, и это то, что я вижу в моих журналах:

NHibernate.Tuple.Entity.PocoEntityTuplizer.SetPropertyValuesWithOptimizer (Object объект, Object [] значения) { «Invalid Cast (проверить отображение для несовпадения типа недвижимости); сеттер OtherClass»}

Обратите внимание, что второй объект POCO прекрасно работает с nHib, если я удалю EncryptedDbString атрибут объявления, т.е. не имеет проблем с сохранением Guid на nvarchar.

Очевидно, проблема заключается в том, что это руководство, поскольку строковый регистр работает, но Я хочу, чтобы он хранился в качестве указателя не в строке в коде, и я не вижу здесь точки сбоя.

Похоже, что мне не хватает чего-то маленького. Наверное, я что-то пропустил с помощью дженериков, но я нашел только фрагменты кода, а не полный пример.

EDIT:

нормально, так что я понял, это я думаю, что это произошло потому, что

Get(IDataReader rs, int index) 

не возвращает объект Guid.

поэтому, я думаю, вы можете сериализовать/десериализовать в методах Get/Set EncryptedStringType, например. в Get() можно изменить на:

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

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

, но это кажется ужасным, особенно если у вас есть существующие данные по миграции.

Я не хочу хранить вещи как двоичные либо, так как команда хочет иметь возможность проверять/проверять/проверять вручную через SQL, какие столбцы зашифрованы (что очевидно с текстом, но не с двоичным).

Поле поддержки строк в моем POCO, которое преобразует Guid в строку и обратно через простые методы get/set, может быть лучшим вариантом, но я не знаю, как это сделать с автоматизацией через решение или как беспорядочно это?

+0

Вы пробовали что-то вроде [этого] (http://stackoverflow.com/a/393787/497356)? Это может сработать. Также, что вы подразумеваете под «кажется ужасным, особенно если у вас есть данные для миграции?» –

+0

На самом деле, вы правы. я думал, что я не хочу лишнего json в столбцах, когда у меня может быть только строка, но в любом случае, поскольку она зашифрована, это не делает такой большой разницы, поскольку она не будет отображаться вручную/запрашиваться в SQL-запросе анализатор в любом случае. спасибо за ваш комментарий, так как он устраняет необходимость в json и видит мой ответ: – jimasp

+0

btw, если кто-либо читает это, захочет, например, хранить более сложные объекты как двоичные, а не строки, см. http://blog.muonlab.com/2010/04/20/encrypting-data-with-nhibernate/ – jimasp

ответ

1

Спал, я думаю, что думал об этом неправильно.

Теперь я понял, что моя сдержанность в хранении json в базе данных обусловлена ​​тем фактом, что я храню объектно-ориентированные объекты, т. Е. Вещи, которые, естественно, преобразуются в текстовые поля, а не в полные объекты. myGuid.ToString() дает вам строку guid, myDateTime.ToString() дает вам строку datetime и т. д.

Так что если сериализация объектов сама по себе не нужна в моем случае, а скорее просто преобразование в строку, Предложение Андрея кажется хорошим решением.

Обновленный код:

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(), и вместо этого тип ICON обратим, но я оставлю его как есть, достаточно времени!