Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections;
- using System.Dynamic;
- using System.Linq;
- using System.Linq.Expressions;
- using MongoDB.Bson.IO;
- using MongoDB.Bson.Serialization;
- using MongoDB.Bson.Serialization.Serializers;
- using MongoDB.Bson;
- using MongoDB.Bson.Serialization.IdGenerators;
- using System.Collections.Generic;
- using log4net;
- namespace MongoData
- {
- public class MongoDynamicBsonSerializer : SerializerBase<MongoDynamic>, IBsonIdProvider //, IBsonDocumentSerializer//, IBsonArraySerializer
- {
- private static readonly ILog log= LogManager.GetLogger("DynMongoSer");
- public static MongoDynamicBsonSerializer Instance { get; } = new MongoDynamicBsonSerializer();
- private static readonly IBsonSerializer<string> stringSerializer = new StringSerializer();
- public override MongoDynamic Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
- {
- log.Debug($"Deserializing ... {args.NominalType}");
- var bsonReader = context.Reader;
- Type nominalType = args.NominalType;
- var bsonType = bsonReader.GetCurrentBsonType();
- if (bsonType == BsonType.Null)
- {
- bsonReader.ReadNull();
- return null;
- }
- else if (bsonType == BsonType.Document)
- {
- MongoDynamic md = new MongoDynamic();
- bsonReader.ReadStartDocument();
- // Scan document first to find interfaces and id fields
- Dictionary<string, Type> typeMap = ScanToLoadTypeMapFromInterfaces(context, bsonReader, md, nominalType);
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- var name = bsonReader.ReadName();
- log.Debug($"Reading field {name}");
- if (name == "_id")
- {
- md[name] = bsonReader.ReadObjectId();
- log.Debug($" {name} = {md[name]}");
- }
- else if (name == MongoDynamic.InterfacesField)
- {
- // Read it and ignore it, we already have it
- bsonReader.SkipValue();
- log.Debug(" Skipping int field");
- }
- else if (name == "_t")
- {
- log.Debug(" Skipping _t field, don't need that for interfaces");
- bsonReader.SkipValue();
- }
- else if (name == "Entity")
- {
- log.Debug(" Skipping Entity field");
- // Read it and ignore it, this was a broken earlier attempt at serialization
- bsonReader.SkipValue();
- }
- else if (bsonReader.CurrentBsonType == BsonType.Null)
- {
- bsonReader.ReadNull();
- md[name] = null;
- log.Debug($" {name} = null");
- }
- else
- {
- if (typeMap == null)
- {
- throw new FormatException("No interfaces defined for this dynamic object - can't deserialize [" + md["_id"] + "]");
- }
- // lookup the type for this element according to the interfaces
- Type elementType;
- if (typeMap.TryGetValue(name, out elementType))
- {
- if (elementType.IsArray)
- {
- var subElementType = elementType.GetElementType();
- if (subElementType.IsInterface)
- {
- bsonReader.ReadStartArray();
- var listType = typeof (List<>).MakeGenericType(subElementType);
- var elements = (IList) Activator.CreateInstance(listType);
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- var subargs = new BsonDeserializationArgs {NominalType = subElementType};
- MongoDynamic o = this.Deserialize(context, subargs);
- elements.Add(o.ActLikeAllInterfacesPresent());
- }
- bsonReader.ReadEndArray();
- var array = Array.CreateInstance(subElementType, elements.Count);
- for (int i = 0; i < array.Length; i++)
- {
- try
- {
- array.SetValue(elements[i], i);
- }
- catch (InvalidCastException)
- {
- log.Error(
- $"Cannot cast {elements[i].GetType().Name} to {subElementType}");
- }
- }
- md[name] = array;
- log.Debug($" {name} = {md[name]}");
- }
- else
- {
- md[name] = BsonSerializer.LookupSerializer(elementType).Deserialize(context, args);
- log.Debug($" {name} = {md[name]}");
- }
- }
- else if (elementType.IsInterface)
- {
- //log.Debug("Recursing on an interface " + elementType.Name);
- var subargs = new BsonDeserializationArgs {NominalType = elementType};
- var value = this.Deserialize(context, subargs);
- md[name] = value.ActLikeAllInterfacesPresent();
- log.Debug($" {name} = {md[name]}");
- }
- else
- {
- // Not an interface type, call the normal deserializer
- try
- {
- IBsonSerializer serializer = BsonSerializer.LookupSerializer(elementType);
- var value = serializer.Deserialize(context);
- md[name] = value;
- }
- catch (FormatException fex)
- {
- log.Error($"Unrecoverable error reading a {elementType}", fex);
- log.Error(md.ToStringValues());
- throw;
- }
- catch (Exception ex)
- {
- log.Error($"Could not Deserialize a {elementType}", ex);
- log.Error(md.ToStringValues());
- }
- log.Debug($" {name} = {md[name]}");
- }
- }
- else
- {
- log.Debug("Trying to deserialize field " + name + " which was not found in the type map");
- log.Debug("TypeMap contains " + string.Join(", ", typeMap.Select(x => x.Key + "=" + x.Value)));
- try
- {
- // This is a value that is no longer in the interface, maybe a column you removed
- // not really much we can do with it ... but we need to read it and move on
- var value = BsonSerializer.Deserialize(bsonReader, typeof (object));
- md[name] = value;
- log.Debug($" {name} = {md[name]}");
- }
- catch (Exception ex)
- {
- ex.Data.Add("Explanation",
- "As with all databases, removing elements from the schema is going to cause problems");
- throw;
- }
- }
- }
- }
- bsonReader.ReadEndDocument();
- log.Debug($"--------");
- return md;
- }
- else
- {
- var message = $"Can't deserialize a {nominalType.FullName} from BsonType {bsonType}.";
- throw new FormatException(message);
- }
- }
- static Dictionary<string, Type> ScanToLoadTypeMapFromInterfaces(BsonDeserializationContext context, IBsonReader bsonReader,
- MongoDynamic md, Type nominalType)
- {
- Dictionary<string, Type> typeMap = null;
- var bookMark = bsonReader.GetBookmark();
- if (bsonReader.FindElement(MongoDynamic.InterfacesField))
- {
- bsonReader.ReadStartArray();
- var innerList = new List<string>();
- while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
- {
- innerList.Add(stringSerializer.Deserialize(context));
- }
- bsonReader.ReadEndArray();
- md[MongoDynamic.InterfacesField] = innerList;
- typeMap = md.GetTypeMap();
- log.Debug($" Interfaces = {md.InterfacesAsText}");
- log.Debug($" Properties = {string.Join(", ", typeMap.Keys)}");
- }
- else
- {
- log.Debug($"No 'int' field on this dynamic object - switching to use nominal type {nominalType}");
- var interfaces = nominalType.GetInterfaces();
- List<string> interfaceNames = new List<string>();
- if (nominalType.IsInterface) interfaceNames.Add(nominalType.FullName);
- interfaceNames.AddRange(interfaces.Select(i => i.FullName));
- // What about concrete types? Well, they are deserialized by normal MongoDB deserializer
- md[MongoDynamic.InterfacesField] = interfaceNames;
- typeMap = md.GetTypeMap();
- }
- bsonReader.ReturnToBookmark(bookMark);
- return typeMap;
- }
- public bool GetDocumentId(object document, out object id, out Type idNominalType, out IIdGenerator idGenerator)
- {
- log.Debug("GetDocumentId");
- MongoDynamic x = (MongoDynamic)document;
- id = x._id;
- idNominalType = typeof(ObjectId);
- idGenerator = new ObjectIdGenerator();
- return true;
- }
- public void SetDocumentId(object document, object id)
- {
- MongoDynamic x = (MongoDynamic)document;
- x._id = (ObjectId)id;
- }
- public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args,
- MongoDynamic value)
- {
- var bsonWriter = context.Writer;
- log.Debug("Serialize " + value);
- if (value == null)
- {
- bsonWriter.WriteNull();
- return;
- }
- // ------------------- NEW WAY --- WE KNOW IT'S A MONGODYNAMIC, NO NEED TO GO ALL IDynamicMetaObjectProvider ON IT
- MongoDynamic mongoDynamic = value as MongoDynamic;
- bsonWriter.WriteStartDocument();
- // And now write the fields according to the interface map
- var typeMap = mongoDynamic.GetTypeMap();
- var metaObject = ((IDynamicMetaObjectProvider)value).GetMetaObject(Expression.Constant(value));
- var memberNames = metaObject.GetDynamicMemberNames().ToList();
- if (memberNames.Count == 0)
- {
- bsonWriter.WriteNull();
- return;
- }
- foreach (var memberName in memberNames)
- {
- log.Debug("Examining " + memberName);
- // Get the value
- object memberValue;
- Type memberType;
- if (memberName == "_id")
- {
- memberValue = mongoDynamic._id;
- memberType = typeof(ObjectId);
- }
- else if (memberName == "int")
- {
- memberValue = mongoDynamic.@int;
- memberType = memberValue.GetType(); // A Hashset<string>
- }
- else
- {
- memberValue = mongoDynamic[memberName]; /// Could also use ... Impromptu.InvokeGet(value, memberName);
- // Lookup the intended type for that field because it
- // may be different from the typeof(memberValue) which
- // is likely a Proxy type. If we can't find it, go ahead
- // and use the type of the object - for some reason it's not in the interfaces
- if (!typeMap.TryGetValue(memberName, out memberType))
- memberType = memberValue?.GetType();
- }
- bsonWriter.WriteName(memberName);
- WriteValue(context, args, memberValue, bsonWriter, memberType);
- if (memberName == "Name" && memberType == typeof(string))
- {
- string cleanedName = ITagExtensions.RemoveDiacritics(((string)memberValue).ToLowerInvariant());
- // Normalize the name field, lower case, replace special characters (TODO)
- bsonWriter.WriteName("name");
- WriteValue(context, args, cleanedName, bsonWriter, memberType);
- }
- }
- bsonWriter.WriteEndDocument();
- return;
- }
- private void WriteValue(BsonSerializationContext context, BsonSerializationArgs args, object memberValue,
- IBsonWriter bsonWriter, Type memberType)
- {
- if (memberValue == null)
- bsonWriter.WriteNull();
- else
- {
- if (memberType.IsArray)
- {
- context.Writer.WriteStartArray();
- memberType = memberType.GetElementType();
- foreach (var item in memberValue as IEnumerable)
- {
- // It could be a normal type here, or it could be an interface
- var iid = item as IId;
- var type = item.GetType();
- if (iid != null)
- {
- var mdInner = iid.Entity;
- this.Serialize(context, mdInner);
- }
- else
- {
- var serializer = BsonSerializer.LookupSerializer(memberType);
- serializer.Serialize(context, item);
- }
- }
- context.Writer.WriteEndArray();
- }
- else if (memberType.IsInterface)
- {
- // log.Debug("Serializing " + memberName + " : " + memberValue.GetType().Name + " a " + memberType.Name + " from a " + args.NominalType.Name + " by using another MongoDynamic!");
- // Make it into a MongoDynamic object so we can read it back using only its interface
- MongoDynamic expanded = new MongoDynamic(memberType, memberValue);
- // Recursively call ourself to serialize this embedded interface which will again embed another interfaces field
- // to identify the interface type to load back in
- Serialize(context, args, expanded);
- }
- else
- {
- // log.Debug("Serializing " + memberName + " : " + memberValue.GetType() + " a " + memberType.Name + " from a " + args.NominalType.Name + " using normal lookup");
- var serializer = BsonSerializer.LookupSerializer(memberType);
- serializer.Serialize(context, memberValue);
- }
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement