2013-05-11 6 views
2

У меня есть много неизменяемых классов типа значений, например EmailAddress, которые гарантируют, что действительный недействительный экземпляр действителен. Я бы хотел, чтобы сериализация этих типов объектов была просто стандартным строковым представлением ("[email protected]") при сохранении с использованием MongoDB C# Driver.Сериализация неизменяемых типов значений с помощью драйвера Mongo C#

Я попытался реализовать IBsonSerilizer, однако он будет разрешать только объекты или массивы на уровне корня. Я смог реализовать надлежащую Json Serilization с Json.NET, есть ли другой подход, который я должен принять?

ответ

5

Я предполагаю, что вы имеете в виду класс EmailAddress что-то вроде этого:

[BsonSerializer(typeof(EmailAddressSerializer))] 
public class EmailAddress 
{ 
    private string _value; 

    public EmailAddress(string value) 
    { 
     _value = value; 
    } 

    public string Value 
    { 
     get { return _value; } 
    } 
} 

Я использовал атрибут, чтобы связать класс EmailAddress к пользовательскому сериализатором, который может быть реализован так:

public class EmailAddressSerializer : BsonBaseSerializer 
{ 
    public override object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options) 
    { 
     if (bsonReader.GetCurrentBsonType() == BsonType.Null) 
     { 
      bsonReader.ReadNull(); 
      return null; 
     } 
     else 
     { 
      var value = bsonReader.ReadString(); 
      return new EmailAddress(value); 
     } 
    } 

    public override void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options) 
    { 
     if (value == null) 
     { 
      bsonWriter.WriteNull(); 
     } 
     else 
     { 
      var emailAddress = (EmailAddress)value; 
      bsonWriter.WriteString(emailAddress.Value); 
     } 
    } 
} 

Вы не можете сериализовать EmailAddress в качестве корневого документа (потому что это не документ ...). Но вы можете использовать EmailAddress, встроенный в другой документ. Например:

public class Person 
{ 
    public int Id { get; set; } 
    public EmailAddress EmailAddress { get; set; } 
} 

Что вы могли бы проверить с помощью кода вроде следующего:

var person = new Person { Id = 1, EmailAddress = new EmailAddress("[email protected]") }; 
var json = person.ToJson(); 
var rehyrdated = BsonSerializer.Deserialize<Person>(json); 

В результате JSON/BSON документ:

{ "_id" : 1, "EmailAddress" : "[email protected]" } 
+1

Благодарим Вас за время, чтобы установить меня прямо. Ваше право, в моем модульном тесте, я пытался его сериализовать напрямую, поэтому я столкнулся с этой ошибкой, я скорректировал тест для сериализации родительского объекта с свойством этого типа. – Gent

+0

Это работает для сериализации, но как искать Лица с определенным электронным письмом с помощью Linq? Mongo выбрасывает исключение из свойства Value, потому что его не существует. –

0

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

Предположим, что у вас есть класс, как:

public class Person 
{ 
    public string FirstName { get; } 
    public string LastName { get; } 
    public string FullName => FirstName + LastName; 
    public ImmutablePocoSample(string lastName) 
    { 
     LastName = lastName; 
    } 

    public ImmutablePocoSample(string firstName, string lastName) 
    { 
     FirstName = firstName; 
     LastName = lastName; 
    } 
} 

Вот код конвенции:

/// <summary> 
/// A convention that map all read only properties for which a matching constructor is found. 
/// Also matching constructors are mapped. 
/// </summary> 
public class ImmutablePocoConvention : ConventionBase, IClassMapConvention 
{ 
    private readonly BindingFlags _bindingFlags; 

    public ImmutablePocoConvention() 
      : this(BindingFlags.Instance | BindingFlags.Public) 
    { } 

    public ImmutablePocoConvention(BindingFlags bindingFlags) 
    { 
     _bindingFlags = bindingFlags | BindingFlags.DeclaredOnly; 
    } 

    public void Apply(BsonClassMap classMap) 
    { 
     var readOnlyProperties = classMap.ClassType.GetTypeInfo() 
      .GetProperties(_bindingFlags) 
      .Where(p => IsReadOnlyProperty(classMap, p)) 
      .ToList(); 

     foreach (var constructor in classMap.ClassType.GetConstructors()) 
     { 
      // If we found a matching constructor then we map it and all the readonly properties 
      var matchProperties = GetMatchingProperties(constructor, readOnlyProperties); 
      if (matchProperties.Any()) 
      { 
       // Map constructor 
       classMap.MapConstructor(constructor); 

       // Map properties 
       foreach (var p in matchProperties) 
        classMap.MapMember(p); 
      } 
     } 
    } 

    private static List<PropertyInfo> GetMatchingProperties(ConstructorInfo constructor, List<PropertyInfo> properties) 
    { 
     var matchProperties = new List<PropertyInfo>(); 

     var ctorParameters = constructor.GetParameters(); 
     foreach (var ctorParameter in ctorParameters) 
     { 
      var matchProperty = properties.FirstOrDefault(p => ParameterMatchProperty(ctorParameter, p)); 
      if (matchProperty == null) 
       return new List<PropertyInfo>(); 

      matchProperties.Add(matchProperty); 
     } 

     return matchProperties; 
    } 


    private static bool ParameterMatchProperty(ParameterInfo parameter, PropertyInfo property) 
    { 
     return string.Equals(property.Name, parameter.Name, System.StringComparison.InvariantCultureIgnoreCase) 
       && parameter.ParameterType == property.PropertyType; 
    } 

    private static bool IsReadOnlyProperty(BsonClassMap classMap, PropertyInfo propertyInfo) 
    { 
     // we can't read 
     if (!propertyInfo.CanRead) 
      return false; 

     // we can write (already handled by the default convention...) 
     if (propertyInfo.CanWrite) 
      return false; 

     // skip indexers 
     if (propertyInfo.GetIndexParameters().Length != 0) 
      return false; 

     // skip overridden properties (they are already included by the base class) 
     var getMethodInfo = propertyInfo.GetMethod; 
     if (getMethodInfo.IsVirtual && getMethodInfo.GetBaseDefinition().DeclaringType != classMap.ClassType) 
      return false; 

     return true; 
    } 
} 

Вы можете зарегистрировать я с помощью:

ConventionRegistry.Register(
    nameof(ImmutablePocoConvention), 
    new ConventionPack { new ImmutablePocoConvention() }, 
    _ => true);