/// <summary> /// Register a property as a DbRef - implement a custom Serialize/Deserialize actions to convert entity to $id, $ref only /// </summary> private static void RegisterDbRefItem(BsonMapper mapper, MemberMapper member, string collection) { // get entity var entity = mapper.GetEntityMapper(member.DataType); member.Serialize = (obj, m) => { // supports null values when "SerializeNullValues = true" if (obj == null) { return(BsonValue.Null); } var idField = entity.Id; // #768 if using DbRef with interface with no ID mapped if (idField == null) { throw new LiteException("There is no _id field mapped in your type: " + member.DataType.FullName); } var id = idField.Getter(obj); var bsonDocument = new BsonDocument { { "$id", m.Serialize(id.GetType(), id, 0) }, { "$ref", collection } }; if (member.DataType.GetTypeInfo().IsInterface || member.DataType.GetTypeInfo().IsAbstract) { bsonDocument["$type"] = obj.GetType().FullName + ", " + obj.GetType().GetTypeInfo().Assembly.GetName().Name; } return(bsonDocument); }; member.Deserialize = (bson, m) => { var idRef = bson.AsDocument["$id"]; return(m.Deserialize(entity.ForType, idRef.IsNull ? bson : // if has no $id object was full loaded (via Include) - so deserialize using normal function member.DataType.GetTypeInfo().IsAbstract || member.DataType.GetTypeInfo().IsInterface ? new BsonDocument { { "_id", idRef }, { "_type", bson.AsDocument["$type"] } } : new BsonDocument { { "_id", idRef } })); // if has $id, deserialize object using only _id object }; }
/// <summary> /// Register a property as a DbRefList - implement a custom Serialize/Deserialize actions to convert entity to $id, $ref only /// </summary> private static void RegisterDbRefList(BsonMapper mapper, MemberMapper member, string collection) { // get entity from list item type var entity = mapper.GetEntityMapper(member.UnderlyingType); member.Serialize = (list, m) => { var result = new BsonArray(); var idField = entity.Id; foreach (var item in (IEnumerable)list) { result.Add(new BsonDocument { { "$id", new BsonValue(idField.Getter(item)) }, { "$ref", collection } }); } return(result); }; member.Deserialize = (bson, m) => { var array = bson.AsArray; if (array.Count == 0) { return(m.Deserialize(member.DataType, array)); } var hasIdRef = array[0].AsDocument == null || array[0].AsDocument["$id"].IsNull; if (hasIdRef) { // if no $id, deserialize as full (was loaded via Include) return(m.Deserialize(member.DataType, array)); } else { // copy array changing $id to _id var arr = new BsonArray(); foreach (var item in array) { arr.Add(new BsonDocument { { "_id", item.AsDocument["$id"] } }); } return(m.Deserialize(member.DataType, arr)); } }; }
/// <summary> /// Register a property mapper as DbRef to serialize/deserialize only document reference _id /// </summary> internal static void RegisterDbRef(BsonMapper mapper, MemberMapper member, string collection) { member.IsDbRef = true; if (member.IsList) { RegisterDbRefList(mapper, member, collection); } else { RegisterDbRefItem(mapper, member, collection); } }
/// <summary> /// Register a property mapper as DbRef to serialize/deserialize only document reference _id /// </summary> internal static void RegisterDbRef(BsonMapper mapper, MemberMapper member, ITypeNameBinder typeNameBinder, string collection) { member.IsDbRef = true; if (member.IsEnumerable) { RegisterDbRefList(mapper, member, typeNameBinder, collection); } else { RegisterDbRefItem(mapper, member, typeNameBinder, collection); } }
/// <summary> /// Register a property as a DbRef - implement a custom Serialize/Deserialize actions to convert entity to $id, $ref only /// </summary> private static void RegisterDbRefItem(BsonMapper mapper, MemberMapper member, string collection) { // get entity var entity = mapper.GetEntityMapper(member.DataType); member.Serialize = (obj, m) => { // supports null values when "SerializeNullValues = true" if (obj == null) { return(BsonValue.Null); } var idField = entity.Id; // #768 if using DbRef with interface with no ID mapped if (idField == null) { throw new LiteException(0, "There is no _id field mapped in your type: " + member.DataType.FullName); } var id = idField.Getter(obj); return(new BsonDocument { { "$id", m.Serialize(id.GetType(), id, 0) }, { "$ref", collection } }); }; member.Deserialize = (bson, m) => { var idRef = bson.IsDocument ? bson["$id"] : BsonValue.Null; var missing = bson.IsDocument ? (bson["$missing"] == true) : false; if (missing) { return(null); } return(m.Deserialize(entity.ForType, idRef.IsNull ? bson : // if has no $id object was full loaded (via Include) - so deserialize using normal function new BsonDocument { { "_id", idRef } })); // if has $id, deserialize object using only _id object }; }
/// <summary> /// Register a property as a DbRef - implement a custom Serialize/Deserialize actions to convert entity to $id, $ref only /// </summary> private static void RegisterDbRefItem(BsonMapper mapper, MemberMapper member, string collection) { // get entity var entity = mapper.GetEntityMapper(member.DataType); member.Serialize = (obj, m) => { var idField = entity.Id; // #768 if using DbRef with interface with no ID mapped if (idField == null) { throw new LiteException("There is no _id field mapped in your type: " + member.DataType.FullName); } var id = idField.Getter(obj); return(new BsonDocument { { "$id", new BsonValue(id) }, { "$ref", collection } }); }; member.Deserialize = (bson, m) => { var idRef = bson.AsDocument["$id"]; return(m.Deserialize(entity.ForType, idRef.IsNull ? bson : // if has no $id object was full loaded (via Include) - so deserialize using normal function new BsonDocument { { "_id", idRef } })); // if has $id, deserialize object using only _id object }; }
/// <summary> /// Use this method to override how your class can be, by default, mapped from entity to Bson document. /// Returns an EntityMapper from each requested Type /// </summary> protected virtual EntityMapper BuildEntityMapper(Type type) { var mapper = new EntityMapper { Members = new List <MemberMapper>(), ForType = type }; var idAttr = typeof(BsonIdAttribute); var ignoreAttr = typeof(BsonIgnoreAttribute); var fieldAttr = typeof(BsonFieldAttribute); var indexAttr = typeof(BsonIndexAttribute); var dbrefAttr = typeof(BsonRefAttribute); var members = this.GetTypeMembers(type); var id = this.GetIdMember(members); foreach (var memberInfo in members) { // checks [BsonIgnore] if (memberInfo.IsDefined(ignoreAttr, true)) { continue; } // checks field name conversion var name = this.ResolveFieldName(memberInfo.Name); // check if property has [BsonField] var field = (BsonFieldAttribute)memberInfo.GetCustomAttributes(fieldAttr, false).FirstOrDefault(); // check if property has [BsonField] with a custom field name if (field != null && field.Name != null) { name = field.Name; } // checks if memberInfo is id field if (memberInfo == id) { name = "_id"; } // test if field name is OK (avoid to check in all instances) - do not test internal classes, like DbRef if (BsonDocument.IsValidFieldName(name) == false) { throw LiteException.InvalidFormat(memberInfo.Name); } // create getter/setter function var getter = Reflection.CreateGenericGetter(type, memberInfo); var setter = Reflection.CreateGenericSetter(type, memberInfo); // check if property has [BsonId] to get with was setted AutoId = true var autoId = (BsonIdAttribute)memberInfo.GetCustomAttributes(idAttr, false).FirstOrDefault(); // get data type var dataType = memberInfo is PropertyInfo ? (memberInfo as PropertyInfo).PropertyType : (memberInfo as FieldInfo).FieldType; // check if datatype is list/array var isList = Reflection.IsList(dataType); // create a property mapper var member = new MemberMapper { AutoId = autoId == null ? true : autoId.AutoId, FieldName = name, MemberName = memberInfo.Name, DataType = dataType, IsList = isList, UnderlyingType = isList ? Reflection.GetListItemType(dataType) : dataType, Getter = getter, Setter = setter }; // check if property has [BsonRef] var dbRef = (BsonRefAttribute)memberInfo.GetCustomAttributes(dbrefAttr, false).FirstOrDefault(); if (dbRef != null && memberInfo is PropertyInfo) { BsonMapper.RegisterDbRef(this, member, dbRef.Collection ?? this.ResolveCollectionName((memberInfo as PropertyInfo).PropertyType)); } // support callback to user modify member mapper if (this.ResolveMember != null) { this.ResolveMember(type, memberInfo, member); } // test if has name and there is no duplicate field if (member.FieldName != null && mapper.Members.Any(x => x.FieldName == name) == false) { mapper.Members.Add(member); } } return(mapper); }
/// <summary> /// Register a property as a DbRefList - implement a custom Serialize/Deserialize actions to convert entity to $id, $ref only /// </summary> private static void RegisterDbRefList(BsonMapper mapper, MemberMapper member, string collection) { // get entity from list item type var entity = mapper.GetEntityMapper(member.UnderlyingType); member.Serialize = (list, m) => { // supports null values when "SerializeNullValues = true" if (list == null) { return(BsonValue.Null); } var result = new BsonArray(); var idField = entity.Id; foreach (var item in (IEnumerable)list) { if (item == null) { continue; } var id = idField.Getter(item); result.Add(new BsonDocument { { "$id", m.Serialize(id.GetType(), id, 0) }, { "$ref", collection } }); } return(result); }; member.Deserialize = (bson, m) => { var array = bson.AsArray; if (array.Count == 0) { return(m.Deserialize(member.DataType, array)); } // copy array changing $id to _id var result = new BsonArray(); foreach (var item in array) { var refId = item.AsDocument["$id"]; // if refId is null was included by "include" query, so "item" is full filled document if (refId.IsNull) { result.Add(item); } else { result.Add(new BsonDocument { { "_id", refId } }); } } return(m.Deserialize(member.DataType, result)); }; }
/// <summary> /// Register a property as a DbRefList - implement a custom Serialize/Deserialize actions to convert entity to $id, $ref only /// </summary> private static void RegisterDbRefList(BsonMapper mapper, MemberMapper member, ITypeNameBinder typeNameBinder, string collection) { // get entity from list item type var entity = mapper.GetEntityMapper(member.UnderlyingType); member.Serialize = (list, m) => { // supports null values when "SerializeNullValues = true" if (list == null) { return(BsonValue.Null); } var result = new BsonArray(); var idField = entity.Id; foreach (var item in (IEnumerable)list) { if (item == null) { continue; } var id = idField.Getter(item); var bsonDocument = new BsonDocument { ["$id"] = m.Serialize(id.GetType(), id, 0), ["$ref"] = collection }; if (member.UnderlyingType != item.GetType()) { bsonDocument["$type"] = typeNameBinder.GetName(item.GetType()); } result.Add(bsonDocument); } return(result); }; member.Deserialize = (bson, m) => { if (bson.IsArray == false) { return(null); } var array = bson.AsArray; if (array.Count == 0) { return(m.Deserialize(member.DataType, array)); } // copy array changing $id to _id var result = new BsonArray(); foreach (var item in array) { if (item.IsDocument == false) { continue; } var doc = item.AsDocument; var idRef = doc["$id"]; var missing = doc["$missing"] == true; var included = doc.ContainsKey("$ref") == false; // if referece document are missing, do not inlcude on output list if (missing) { continue; } // if refId is null was included by "include" query, so "item" is full filled document if (included) { item["_id"] = idRef; if (item.AsDocument.ContainsKey("$type")) { item["_type"] = item["$type"]; } result.Add(item); } else { var bsonDocument = new BsonDocument { ["_id"] = idRef }; if (item.AsDocument.ContainsKey("$type")) { bsonDocument["_type"] = item["$type"]; } result.Add(bsonDocument); } } return(m.Deserialize(member.DataType, result)); }; }
/// <summary> /// Register a property as a DbRef - implement a custom Serialize/Deserialize actions to convert entity to $id, $ref only /// </summary> private static void RegisterDbRefItem(BsonMapper mapper, MemberMapper member, ITypeNameBinder typeNameBinder, string collection) { // get entity var entity = mapper.GetEntityMapper(member.DataType); member.Serialize = (obj, m) => { // supports null values when "SerializeNullValues = true" if (obj == null) { return(BsonValue.Null); } var idField = entity.Id; // #768 if using DbRef with interface with no ID mapped if (idField == null) { throw new LiteException(0, "There is no _id field mapped in your type: " + member.DataType.FullName); } var id = idField.Getter(obj); var bsonDocument = new BsonDocument { ["$id"] = m.Serialize(id.GetType(), id, 0), ["$ref"] = collection }; if (member.DataType != obj.GetType()) { bsonDocument["$type"] = typeNameBinder.GetName(obj.GetType()); } return(bsonDocument); }; member.Deserialize = (bson, m) => { // if not a document (maybe BsonValue.null) returns null if (bson == null || bson.IsDocument == false) { return(null); } var doc = bson.AsDocument; var idRef = doc["$id"]; var missing = doc["$missing"] == true; var included = doc.ContainsKey("$ref") == false; if (missing) { return(null); } if (included) { doc["_id"] = idRef; if (doc.ContainsKey("$type")) { doc["_type"] = bson["$type"]; } return(m.Deserialize(entity.ForType, doc)); } else { return(m.Deserialize(entity.ForType, doc.ContainsKey("$type") ? new BsonDocument { ["_id"] = idRef, ["_type"] = bson["$type"] } : new BsonDocument { ["_id"] = idRef })); // if has $id, deserialize object using only _id object } }; }
/// <summary> /// Use this method to override how your class can be, by default, mapped from entity to Bson document. /// Returns an EntityMapper from each requested Type /// </summary> protected virtual EntityMapper BuildEntityMapper(Type type) { var mapper = new EntityMapper(type); var idAttr = typeof(BsonIdAttribute); var ignoreAttr = typeof(BsonIgnoreAttribute); var fieldAttr = typeof(BsonFieldAttribute); var dbrefAttr = typeof(BsonRefAttribute); var members = this.GetTypeMembers(type); var id = this.GetIdMember(members); foreach (var memberInfo in members) { // checks [BsonIgnore] if (CustomAttributeExtensions.IsDefined(memberInfo, ignoreAttr, true)) { continue; } // checks field name conversion var name = this.ResolveFieldName(memberInfo.Name); // check if property has [BsonField] var field = (BsonFieldAttribute)CustomAttributeExtensions.GetCustomAttributes(memberInfo, fieldAttr, true).FirstOrDefault(); // check if property has [BsonField] with a custom field name if (field != null && field.Name != null) { name = field.Name; } // checks if memberInfo is id field if (memberInfo == id) { name = "_id"; } // create getter/setter function var getter = Reflection.CreateGenericGetter(type, memberInfo); var setter = Reflection.CreateGenericSetter(type, memberInfo); // check if property has [BsonId] to get with was setted AutoId = true var autoId = (BsonIdAttribute)CustomAttributeExtensions.GetCustomAttributes(memberInfo, idAttr, true).FirstOrDefault(); // get data type var dataType = memberInfo is PropertyInfo ? (memberInfo as PropertyInfo).PropertyType : (memberInfo as FieldInfo).FieldType; // check if datatype is list/array var isEnumerable = Reflection.IsEnumerable(dataType); // create a property mapper var member = new MemberMapper { AutoId = autoId == null ? true : autoId.AutoId, FieldName = name, MemberName = memberInfo.Name, DataType = dataType, IsEnumerable = isEnumerable, UnderlyingType = isEnumerable ? Reflection.GetListItemType(dataType) : dataType, Getter = getter, Setter = setter }; // check if property has [BsonRef] var dbRef = (BsonRefAttribute)CustomAttributeExtensions.GetCustomAttributes(memberInfo, dbrefAttr, false).FirstOrDefault(); if (dbRef != null && memberInfo is PropertyInfo) { BsonMapper.RegisterDbRef(this, member, _typeNameBinder, dbRef.Collection ?? this.ResolveCollectionName((memberInfo as PropertyInfo).PropertyType)); } // support callback to user modify member mapper this.ResolveMember?.Invoke(type, memberInfo, member); // test if has name and there is no duplicate field if (member.FieldName != null && mapper.Members.Any(x => x.FieldName.Equals(name, StringComparison.OrdinalIgnoreCase)) == false) { mapper.Members.Add(member); } } return(mapper); }