| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653 |
- /* Copyright 2010-2016 MongoDB Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- #if NET45
- using System.Runtime.Serialization;
- #endif
- using MongoDB.Bson.IO;
- using MongoDB.Bson.Serialization.Conventions;
- namespace MongoDB.Bson.Serialization
- {
- /// <summary>
- /// Represents a mapping between a class and a BSON document.
- /// </summary>
- public class BsonClassMap
- {
- // private static fields
- private readonly static Dictionary<Type, BsonClassMap> __classMaps = new Dictionary<Type, BsonClassMap>();
- private readonly static Queue<Type> __knownTypesQueue = new Queue<Type>();
- private static readonly MethodInfo __getUninitializedObjectMethodInfo =
- typeof(string)
- .GetTypeInfo()
- .Assembly
- .GetType("System.Runtime.Serialization.FormatterServices")
- .GetTypeInfo()
- ?.GetMethod("GetUninitializedObject", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
- private static int __freezeNestingLevel = 0;
- // private fields
- private readonly Type _classType;
- private readonly List<BsonCreatorMap> _creatorMaps;
- private readonly IConventionPack _conventionPack;
- private readonly bool _isAnonymous;
- private readonly List<BsonMemberMap> _allMemberMaps; // includes inherited member maps
- private readonly ReadOnlyCollection<BsonMemberMap> _allMemberMapsReadonly;
- private readonly List<BsonMemberMap> _declaredMemberMaps; // only the members declared in this class
- private readonly BsonTrie<int> _elementTrie;
- private bool _frozen; // once a class map has been frozen no further changes are allowed
- private BsonClassMap _baseClassMap; // null for class object and interfaces
- private volatile IDiscriminatorConvention _discriminatorConvention;
- private Func<object> _creator;
- private string _discriminator;
- private bool _discriminatorIsRequired;
- private bool _hasRootClass;
- private bool _isRootClass;
- private BsonMemberMap _idMemberMap;
- private bool _ignoreExtraElements;
- private bool _ignoreExtraElementsIsInherited;
- private BsonMemberMap _extraElementsMemberMap;
- private int _extraElementsMemberIndex = -1;
- private List<Type> _knownTypes = new List<Type>();
- // constructors
- /// <summary>
- /// Initializes a new instance of the BsonClassMap class.
- /// </summary>
- /// <param name="classType">The class type.</param>
- public BsonClassMap(Type classType)
- {
- _classType = classType;
- _creatorMaps = new List<BsonCreatorMap>();
- _conventionPack = ConventionRegistry.Lookup(classType);
- _isAnonymous = IsAnonymousType(classType);
- _allMemberMaps = new List<BsonMemberMap>();
- _allMemberMapsReadonly = _allMemberMaps.AsReadOnly();
- _declaredMemberMaps = new List<BsonMemberMap>();
- _elementTrie = new BsonTrie<int>();
- Reset();
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="BsonClassMap"/> class.
- /// </summary>
- /// <param name="classType">Type of the class.</param>
- /// <param name="baseClassMap">The base class map.</param>
- public BsonClassMap(Type classType, BsonClassMap baseClassMap)
- : this(classType)
- {
- _baseClassMap = baseClassMap;
- }
- // public properties
- /// <summary>
- /// Gets all the member maps (including maps for inherited members).
- /// </summary>
- public ReadOnlyCollection<BsonMemberMap> AllMemberMaps
- {
- get { return _allMemberMapsReadonly; }
- }
- /// <summary>
- /// Gets the base class map.
- /// </summary>
- public BsonClassMap BaseClassMap
- {
- get { return _baseClassMap; }
- }
- /// <summary>
- /// Gets the class type.
- /// </summary>
- public Type ClassType
- {
- get { return _classType; }
- }
- /// <summary>
- /// Gets the constructor maps.
- /// </summary>
- public IEnumerable<BsonCreatorMap> CreatorMaps
- {
- get { return _creatorMaps; }
- }
- /// <summary>
- /// Gets the conventions used for auto mapping.
- /// </summary>
- public IConventionPack ConventionPack
- {
- get { return _conventionPack; }
- }
- /// <summary>
- /// Gets the declared member maps (only for members declared in this class).
- /// </summary>
- public IEnumerable<BsonMemberMap> DeclaredMemberMaps
- {
- get { return _declaredMemberMaps; }
- }
- /// <summary>
- /// Gets the discriminator.
- /// </summary>
- public string Discriminator
- {
- get { return _discriminator; }
- }
- /// <summary>
- /// Gets whether a discriminator is required when serializing this class.
- /// </summary>
- public bool DiscriminatorIsRequired
- {
- get { return _discriminatorIsRequired; }
- }
- /// <summary>
- /// Gets the member map of the member used to hold extra elements.
- /// </summary>
- public BsonMemberMap ExtraElementsMemberMap
- {
- get { return _extraElementsMemberMap; }
- }
- /// <summary>
- /// Gets whether this class map has any creator maps.
- /// </summary>
- public bool HasCreatorMaps
- {
- get { return _creatorMaps.Count > 0; }
- }
- /// <summary>
- /// Gets whether this class has a root class ancestor.
- /// </summary>
- public bool HasRootClass
- {
- get { return _hasRootClass; }
- }
- /// <summary>
- /// Gets the Id member map (null if none).
- /// </summary>
- public BsonMemberMap IdMemberMap
- {
- get { return _idMemberMap; }
- }
- /// <summary>
- /// Gets whether extra elements should be ignored when deserializing.
- /// </summary>
- public bool IgnoreExtraElements
- {
- get { return _ignoreExtraElements; }
- }
- /// <summary>
- /// Gets whether the IgnoreExtraElements value should be inherited by derived classes.
- /// </summary>
- public bool IgnoreExtraElementsIsInherited
- {
- get { return _ignoreExtraElementsIsInherited; }
- }
- /// <summary>
- /// Gets whether this class is anonymous.
- /// </summary>
- public bool IsAnonymous
- {
- get { return _isAnonymous; }
- }
- /// <summary>
- /// Gets whether the class map is frozen.
- /// </summary>
- public bool IsFrozen
- {
- get { return _frozen; }
- }
- /// <summary>
- /// Gets whether this class is a root class.
- /// </summary>
- public bool IsRootClass
- {
- get { return _isRootClass; }
- }
- /// <summary>
- /// Gets the known types of this class.
- /// </summary>
- public IEnumerable<Type> KnownTypes
- {
- get { return _knownTypes; }
- }
- // internal properties
- /// <summary>
- /// Gets the element name to member index trie.
- /// </summary>
- internal BsonTrie<int> ElementTrie
- {
- get { return _elementTrie; }
- }
- /// <summary>
- /// Gets the member index of the member used to hold extra elements.
- /// </summary>
- internal int ExtraElementsMemberMapIndex
- {
- get { return _extraElementsMemberIndex; }
- }
- // public static methods
- /// <summary>
- /// Gets the type of a member.
- /// </summary>
- /// <param name="memberInfo">The member info.</param>
- /// <returns>The type of the member.</returns>
- public static Type GetMemberInfoType(MemberInfo memberInfo)
- {
- if (memberInfo == null)
- {
- throw new ArgumentNullException("memberInfo");
- }
- if (memberInfo is FieldInfo)
- {
- return ((FieldInfo)memberInfo).FieldType;
- }
- else if (memberInfo is PropertyInfo)
- {
- return ((PropertyInfo)memberInfo).PropertyType;
- }
- throw new NotSupportedException("Only field and properties are supported at this time.");
- }
- /// <summary>
- /// Gets all registered class maps.
- /// </summary>
- /// <returns>All registered class maps.</returns>
- public static IEnumerable<BsonClassMap> GetRegisteredClassMaps()
- {
- BsonSerializer.ConfigLock.EnterReadLock();
- try
- {
- return __classMaps.Values.ToList(); // return a copy for thread safety
- }
- finally
- {
- BsonSerializer.ConfigLock.ExitReadLock();
- }
- }
- /// <summary>
- /// Checks whether a class map is registered for a type.
- /// </summary>
- /// <param name="type">The type to check.</param>
- /// <returns>True if there is a class map registered for the type.</returns>
- public static bool IsClassMapRegistered(Type type)
- {
- if (type == null)
- {
- throw new ArgumentNullException("type");
- }
- BsonSerializer.ConfigLock.EnterReadLock();
- try
- {
- return __classMaps.ContainsKey(type);
- }
- finally
- {
- BsonSerializer.ConfigLock.ExitReadLock();
- }
- }
- /// <summary>
- /// Looks up a class map (will AutoMap the class if no class map is registered).
- /// </summary>
- /// <param name="classType">The class type.</param>
- /// <returns>The class map.</returns>
- public static BsonClassMap LookupClassMap(Type classType)
- {
- if (classType == null)
- {
- throw new ArgumentNullException("classType");
- }
- BsonSerializer.ConfigLock.EnterReadLock();
- try
- {
- BsonClassMap classMap;
- if (__classMaps.TryGetValue(classType, out classMap))
- {
- if (classMap.IsFrozen)
- {
- return classMap;
- }
- }
- }
- finally
- {
- BsonSerializer.ConfigLock.ExitReadLock();
- }
- BsonSerializer.ConfigLock.EnterWriteLock();
- try
- {
- BsonClassMap classMap;
- if (!__classMaps.TryGetValue(classType, out classMap))
- {
- // automatically create a classMap for classType and register it
- var classMapDefinition = typeof(BsonClassMap<>);
- var classMapType = classMapDefinition.MakeGenericType(classType);
- classMap = (BsonClassMap)Activator.CreateInstance(classMapType);
- classMap.AutoMap();
- RegisterClassMap(classMap);
- }
- return classMap.Freeze();
- }
- finally
- {
- BsonSerializer.ConfigLock.ExitWriteLock();
- }
- }
- /// <summary>
- /// Creates and registers a class map.
- /// </summary>
- /// <typeparam name="TClass">The class.</typeparam>
- /// <returns>The class map.</returns>
- public static BsonClassMap<TClass> RegisterClassMap<TClass>()
- {
- return RegisterClassMap<TClass>(cm => { cm.AutoMap(); });
- }
- /// <summary>
- /// Creates and registers a class map.
- /// </summary>
- /// <typeparam name="TClass">The class.</typeparam>
- /// <param name="classMapInitializer">The class map initializer.</param>
- /// <returns>The class map.</returns>
- public static BsonClassMap<TClass> RegisterClassMap<TClass>(Action<BsonClassMap<TClass>> classMapInitializer)
- {
- var classMap = new BsonClassMap<TClass>(classMapInitializer);
- RegisterClassMap(classMap);
- return classMap;
- }
- /// <summary>
- /// Registers a class map.
- /// </summary>
- /// <param name="classMap">The class map.</param>
- public static void RegisterClassMap(BsonClassMap classMap)
- {
- if (classMap == null)
- {
- throw new ArgumentNullException("classMap");
- }
- BsonSerializer.ConfigLock.EnterWriteLock();
- try
- {
- // note: class maps can NOT be replaced (because derived classes refer to existing instance)
- __classMaps.Add(classMap.ClassType, classMap);
- BsonSerializer.RegisterDiscriminator(classMap.ClassType, classMap.Discriminator);
- }
- finally
- {
- BsonSerializer.ConfigLock.ExitWriteLock();
- }
- }
- // public methods
- /// <summary>
- /// Automaps the class.
- /// </summary>
- public void AutoMap()
- {
- if (_frozen) { ThrowFrozenException(); }
- AutoMapClass();
- }
- /// <summary>
- /// Creates an instance of the class.
- /// </summary>
- /// <returns>An object.</returns>
- public object CreateInstance()
- {
- if (!_frozen) { ThrowNotFrozenException(); }
- var creator = GetCreator();
- return creator.Invoke();
- }
- /// <summary>
- /// Freezes the class map.
- /// </summary>
- /// <returns>The frozen class map.</returns>
- public BsonClassMap Freeze()
- {
- BsonSerializer.ConfigLock.EnterReadLock();
- try
- {
- if (_frozen)
- {
- return this;
- }
- }
- finally
- {
- BsonSerializer.ConfigLock.ExitReadLock();
- }
- BsonSerializer.ConfigLock.EnterWriteLock();
- try
- {
- if (!_frozen)
- {
- __freezeNestingLevel++;
- try
- {
- var baseType = _classType.GetTypeInfo().BaseType;
- if (baseType != null)
- {
- if (_baseClassMap == null)
- {
- _baseClassMap = LookupClassMap(baseType);
- }
- _discriminatorIsRequired |= _baseClassMap._discriminatorIsRequired;
- _hasRootClass |= (_isRootClass || _baseClassMap.HasRootClass);
- _allMemberMaps.AddRange(_baseClassMap.AllMemberMaps);
- if (_baseClassMap.IgnoreExtraElements && _baseClassMap.IgnoreExtraElementsIsInherited)
- {
- _ignoreExtraElements = true;
- _ignoreExtraElementsIsInherited = true;
- }
- }
- _allMemberMaps.AddRange(_declaredMemberMaps);
- if (_idMemberMap == null)
- {
- // see if we can inherit the idMemberMap from our base class
- if (_baseClassMap != null)
- {
- _idMemberMap = _baseClassMap.IdMemberMap;
- }
- }
- else
- {
- if (_idMemberMap.ClassMap == this)
- {
- // conventions could have set this to an improper value
- _idMemberMap.SetElementName("_id");
- }
- }
- if (_extraElementsMemberMap == null)
- {
- // see if we can inherit the extraElementsMemberMap from our base class
- if (_baseClassMap != null)
- {
- _extraElementsMemberMap = _baseClassMap.ExtraElementsMemberMap;
- }
- }
- _extraElementsMemberIndex = -1;
- for (int memberIndex = 0; memberIndex < _allMemberMaps.Count; ++memberIndex)
- {
- var memberMap = _allMemberMaps[memberIndex];
- int conflictingMemberIndex;
- if (!_elementTrie.TryGetValue(memberMap.ElementName, out conflictingMemberIndex))
- {
- _elementTrie.Add(memberMap.ElementName, memberIndex);
- }
- else
- {
- var conflictingMemberMap = _allMemberMaps[conflictingMemberIndex];
- var fieldOrProperty = (memberMap.MemberInfo is FieldInfo) ? "field" : "property";
- var conflictingFieldOrProperty = (conflictingMemberMap.MemberInfo is FieldInfo) ? "field" : "property";
- var conflictingType = conflictingMemberMap.MemberInfo.DeclaringType;
- string message;
- if (conflictingType == _classType)
- {
- message = string.Format(
- "The {0} '{1}' of type '{2}' cannot use element name '{3}' because it is already being used by {4} '{5}'.",
- fieldOrProperty, memberMap.MemberName, _classType.FullName, memberMap.ElementName, conflictingFieldOrProperty, conflictingMemberMap.MemberName);
- }
- else
- {
- message = string.Format(
- "The {0} '{1}' of type '{2}' cannot use element name '{3}' because it is already being used by {4} '{5}' of type '{6}'.",
- fieldOrProperty, memberMap.MemberName, _classType.FullName, memberMap.ElementName, conflictingFieldOrProperty, conflictingMemberMap.MemberName, conflictingType.FullName);
- }
- throw new BsonSerializationException(message);
- }
- if (memberMap == _extraElementsMemberMap)
- {
- _extraElementsMemberIndex = memberIndex;
- }
- }
- // mark this classMap frozen before we start working on knownTypes
- // because we might get back to this same classMap while processing knownTypes
- foreach (var creatorMap in _creatorMaps)
- {
- creatorMap.Freeze();
- }
- foreach (var memberMap in _declaredMemberMaps)
- {
- memberMap.Freeze();
- }
- _frozen = true;
- // use a queue to postpone processing of known types until we get back to the first level call to Freeze
- // this avoids infinite recursion when going back down the inheritance tree while processing known types
- foreach (var knownType in _knownTypes)
- {
- __knownTypesQueue.Enqueue(knownType);
- }
- // if we are back to the first level go ahead and process any queued known types
- if (__freezeNestingLevel == 1)
- {
- while (__knownTypesQueue.Count != 0)
- {
- var knownType = __knownTypesQueue.Dequeue();
- LookupClassMap(knownType); // will AutoMap and/or Freeze knownType if necessary
- }
- }
- }
- finally
- {
- __freezeNestingLevel--;
- }
- }
- }
- finally
- {
- BsonSerializer.ConfigLock.ExitWriteLock();
- }
- return this;
- }
- /// <summary>
- /// Gets a member map (only considers members declared in this class).
- /// </summary>
- /// <param name="memberName">The member name.</param>
- /// <returns>The member map (or null if the member was not found).</returns>
- public BsonMemberMap GetMemberMap(string memberName)
- {
- if (memberName == null)
- {
- throw new ArgumentNullException("memberName");
- }
- // can be called whether frozen or not
- return _declaredMemberMaps.Find(m => m.MemberName == memberName);
- }
- /// <summary>
- /// Gets the member map for a BSON element.
- /// </summary>
- /// <param name="elementName">The name of the element.</param>
- /// <returns>The member map.</returns>
- public BsonMemberMap GetMemberMapForElement(string elementName)
- {
- if (elementName == null)
- {
- throw new ArgumentNullException("elementName");
- }
- if (!_frozen) { ThrowNotFrozenException(); }
- int memberIndex;
- if (!_elementTrie.TryGetValue(elementName, out memberIndex))
- {
- return null;
- }
- var memberMap = _allMemberMaps[memberIndex];
- return memberMap;
- }
- /// <summary>
- /// Creates a creator map for a constructor and adds it to the class map.
- /// </summary>
- /// <param name="constructorInfo">The constructor info.</param>
- /// <returns>The creator map (so method calls can be chained).</returns>
- public BsonCreatorMap MapConstructor(ConstructorInfo constructorInfo)
- {
- if (constructorInfo == null)
- {
- throw new ArgumentNullException("constructorInfo");
- }
- EnsureMemberInfoIsForThisClass(constructorInfo);
- if (_frozen) { ThrowFrozenException(); }
- var creatorMap = _creatorMaps.FirstOrDefault(m => m.MemberInfo == constructorInfo);
- if (creatorMap == null)
- {
- var @delegate = new CreatorMapDelegateCompiler().CompileConstructorDelegate(constructorInfo);
- creatorMap = new BsonCreatorMap(this, constructorInfo, @delegate);
- _creatorMaps.Add(creatorMap);
- }
- return creatorMap;
- }
- /// <summary>
- /// Creates a creator map for a constructor and adds it to the class map.
- /// </summary>
- /// <param name="constructorInfo">The constructor info.</param>
- /// <param name="argumentNames">The argument names.</param>
- /// <returns>The creator map (so method calls can be chained).</returns>
- public BsonCreatorMap MapConstructor(ConstructorInfo constructorInfo, params string[] argumentNames)
- {
- var creatorMap = MapConstructor(constructorInfo);
- creatorMap.SetArguments(argumentNames);
- return creatorMap;
- }
- /// <summary>
- /// Creates a creator map and adds it to the class.
- /// </summary>
- /// <param name="delegate">The delegate.</param>
- /// <returns>The factory method map (so method calls can be chained).</returns>
- public BsonCreatorMap MapCreator(Delegate @delegate)
- {
- if (@delegate == null)
- {
- throw new ArgumentNullException("delegate");
- }
- if (_frozen) { ThrowFrozenException(); }
- var creatorMap = new BsonCreatorMap(this, null, @delegate);
- _creatorMaps.Add(creatorMap);
- return creatorMap;
- }
- /// <summary>
- /// Creates a creator map and adds it to the class.
- /// </summary>
- /// <param name="delegate">The delegate.</param>
- /// <param name="argumentNames">The argument names.</param>
- /// <returns>The factory method map (so method calls can be chained).</returns>
- public BsonCreatorMap MapCreator(Delegate @delegate, params string[] argumentNames)
- {
- var creatorMap = MapCreator(@delegate);
- creatorMap.SetArguments(argumentNames);
- return creatorMap;
- }
- /// <summary>
- /// Creates a member map for the extra elements field and adds it to the class map.
- /// </summary>
- /// <param name="fieldName">The name of the extra elements field.</param>
- /// <returns>The member map (so method calls can be chained).</returns>
- public BsonMemberMap MapExtraElementsField(string fieldName)
- {
- if (fieldName == null)
- {
- throw new ArgumentNullException("fieldName");
- }
- if (_frozen) { ThrowFrozenException(); }
- var fieldMap = MapField(fieldName);
- SetExtraElementsMember(fieldMap);
- return fieldMap;
- }
- /// <summary>
- /// Creates a member map for the extra elements member and adds it to the class map.
- /// </summary>
- /// <param name="memberInfo">The member info for the extra elements member.</param>
- /// <returns>The member map (so method calls can be chained).</returns>
- public BsonMemberMap MapExtraElementsMember(MemberInfo memberInfo)
- {
- if (memberInfo == null)
- {
- throw new ArgumentNullException("memberInfo");
- }
- if (_frozen) { ThrowFrozenException(); }
- var memberMap = MapMember(memberInfo);
- SetExtraElementsMember(memberMap);
- return memberMap;
- }
- /// <summary>
- /// Creates a member map for the extra elements property and adds it to the class map.
- /// </summary>
- /// <param name="propertyName">The name of the property.</param>
- /// <returns>The member map (so method calls can be chained).</returns>
- public BsonMemberMap MapExtraElementsProperty(string propertyName)
- {
- if (propertyName == null)
- {
- throw new ArgumentNullException("propertyName");
- }
- if (_frozen) { ThrowFrozenException(); }
- var propertyMap = MapProperty(propertyName);
- SetExtraElementsMember(propertyMap);
- return propertyMap;
- }
- /// <summary>
- /// Creates a creator map for a factory method and adds it to the class.
- /// </summary>
- /// <param name="methodInfo">The method info.</param>
- /// <returns>The creator map (so method calls can be chained).</returns>
- public BsonCreatorMap MapFactoryMethod(MethodInfo methodInfo)
- {
- if (methodInfo == null)
- {
- throw new ArgumentNullException("methodInfo");
- }
- EnsureMemberInfoIsForThisClass(methodInfo);
- if (_frozen) { ThrowFrozenException(); }
- var creatorMap = _creatorMaps.FirstOrDefault(m => m.MemberInfo == methodInfo);
- if (creatorMap == null)
- {
- var @delegate = new CreatorMapDelegateCompiler().CompileFactoryMethodDelegate(methodInfo);
- creatorMap = new BsonCreatorMap(this, methodInfo, @delegate);
- _creatorMaps.Add(creatorMap);
- }
- return creatorMap;
- }
- /// <summary>
- /// Creates a creator map for a factory method and adds it to the class.
- /// </summary>
- /// <param name="methodInfo">The method info.</param>
- /// <param name="argumentNames">The argument names.</param>
- /// <returns>The creator map (so method calls can be chained).</returns>
- public BsonCreatorMap MapFactoryMethod(MethodInfo methodInfo, params string[] argumentNames)
- {
- var creatorMap = MapFactoryMethod(methodInfo);
- creatorMap.SetArguments(argumentNames);
- return creatorMap;
- }
- /// <summary>
- /// Creates a member map for a field and adds it to the class map.
- /// </summary>
- /// <param name="fieldName">The name of the field.</param>
- /// <returns>The member map (so method calls can be chained).</returns>
- public BsonMemberMap MapField(string fieldName)
- {
- if (fieldName == null)
- {
- throw new ArgumentNullException("fieldName");
- }
- if (_frozen) { ThrowFrozenException(); }
- var fieldInfo = _classType.GetTypeInfo().GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
- if (fieldInfo == null)
- {
- var message = string.Format("The class '{0}' does not have a field named '{1}'.", _classType.FullName, fieldName);
- throw new BsonSerializationException(message);
- }
- return MapMember(fieldInfo);
- }
- /// <summary>
- /// Creates a member map for the Id field and adds it to the class map.
- /// </summary>
- /// <param name="fieldName">The name of the Id field.</param>
- /// <returns>The member map (so method calls can be chained).</returns>
- public BsonMemberMap MapIdField(string fieldName)
- {
- if (fieldName == null)
- {
- throw new ArgumentNullException("fieldName");
- }
- if (_frozen) { ThrowFrozenException(); }
- var fieldMap = MapField(fieldName);
- SetIdMember(fieldMap);
- return fieldMap;
- }
- /// <summary>
- /// Creates a member map for the Id member and adds it to the class map.
- /// </summary>
- /// <param name="memberInfo">The member info for the Id member.</param>
- /// <returns>The member map (so method calls can be chained).</returns>
- public BsonMemberMap MapIdMember(MemberInfo memberInfo)
- {
- if (memberInfo == null)
- {
- throw new ArgumentNullException("memberInfo");
- }
- if (_frozen) { ThrowFrozenException(); }
- var memberMap = MapMember(memberInfo);
- SetIdMember(memberMap);
- return memberMap;
- }
- /// <summary>
- /// Creates a member map for the Id property and adds it to the class map.
- /// </summary>
- /// <param name="propertyName">The name of the Id property.</param>
- /// <returns>The member map (so method calls can be chained).</returns>
- public BsonMemberMap MapIdProperty(string propertyName)
- {
- if (propertyName == null)
- {
- throw new ArgumentNullException("propertyName");
- }
- if (_frozen) { ThrowFrozenException(); }
- var propertyMap = MapProperty(propertyName);
- SetIdMember(propertyMap);
- return propertyMap;
- }
- /// <summary>
- /// Creates a member map for a member and adds it to the class map.
- /// </summary>
- /// <param name="memberInfo">The member info.</param>
- /// <returns>The member map (so method calls can be chained).</returns>
- public BsonMemberMap MapMember(MemberInfo memberInfo)
- {
- if (memberInfo == null)
- {
- throw new ArgumentNullException("memberInfo");
- }
- if (!(memberInfo is FieldInfo) && !(memberInfo is PropertyInfo))
- {
- throw new ArgumentException("MemberInfo must be either a FieldInfo or a PropertyInfo.", "memberInfo");
- }
- EnsureMemberInfoIsForThisClass(memberInfo);
- if (_frozen) { ThrowFrozenException(); }
- var memberMap = _declaredMemberMaps.Find(m => m.MemberInfo == memberInfo);
- if (memberMap == null)
- {
- memberMap = new BsonMemberMap(this, memberInfo);
- _declaredMemberMaps.Add(memberMap);
- }
- return memberMap;
- }
- /// <summary>
- /// Creates a member map for a property and adds it to the class map.
- /// </summary>
- /// <param name="propertyName">The name of the property.</param>
- /// <returns>The member map (so method calls can be chained).</returns>
- public BsonMemberMap MapProperty(string propertyName)
- {
- if (propertyName == null)
- {
- throw new ArgumentNullException("propertyName");
- }
- if (_frozen) { ThrowFrozenException(); }
- var propertyInfo = _classType.GetTypeInfo().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
- if (propertyInfo == null)
- {
- var message = string.Format("The class '{0}' does not have a property named '{1}'.", _classType.FullName, propertyName);
- throw new BsonSerializationException(message);
- }
- return MapMember(propertyInfo);
- }
- /// <summary>
- /// Resets the class map back to its initial state.
- /// </summary>
- public void Reset()
- {
- if (_frozen) { ThrowFrozenException(); }
- _creatorMaps.Clear();
- _creator = null;
- _declaredMemberMaps.Clear();
- _discriminator = _classType.Name;
- _discriminatorIsRequired = false;
- _extraElementsMemberMap = null;
- _idMemberMap = null;
- _ignoreExtraElements = true; // TODO: should this really be false?
- _ignoreExtraElementsIsInherited = false;
- _isRootClass = false;
- _knownTypes.Clear();
- }
- /// <summary>
- /// Sets the creator for the object.
- /// </summary>
- /// <param name="creator">The creator.</param>
- /// <returns>The class map (so method calls can be chained).</returns>
- public BsonClassMap SetCreator(Func<object> creator)
- {
- _creator = creator;
- return this;
- }
- /// <summary>
- /// Sets the discriminator.
- /// </summary>
- /// <param name="discriminator">The discriminator.</param>
- public void SetDiscriminator(string discriminator)
- {
- if (discriminator == null)
- {
- throw new ArgumentNullException("discriminator");
- }
- if (_frozen) { ThrowFrozenException(); }
- _discriminator = discriminator;
- }
- /// <summary>
- /// Sets whether a discriminator is required when serializing this class.
- /// </summary>
- /// <param name="discriminatorIsRequired">Whether a discriminator is required.</param>
- public void SetDiscriminatorIsRequired(bool discriminatorIsRequired)
- {
- if (_frozen) { ThrowFrozenException(); }
- _discriminatorIsRequired = discriminatorIsRequired;
- }
- /// <summary>
- /// Sets the member map of the member used to hold extra elements.
- /// </summary>
- /// <param name="memberMap">The extra elements member map.</param>
- public void SetExtraElementsMember(BsonMemberMap memberMap)
- {
- if (memberMap == null)
- {
- throw new ArgumentNullException("memberMap");
- }
- EnsureMemberMapIsForThisClass(memberMap);
- if (_frozen) { ThrowFrozenException(); }
- if (memberMap.MemberType != typeof(BsonDocument) && !typeof(IDictionary<string, object>).GetTypeInfo().IsAssignableFrom(memberMap.MemberType))
- {
- var message = string.Format("Type of ExtraElements member must be BsonDocument or implement IDictionary<string, object>.");
- throw new InvalidOperationException(message);
- }
- _extraElementsMemberMap = memberMap;
- }
- /// <summary>
- /// Adds a known type to the class map.
- /// </summary>
- /// <param name="type">The known type.</param>
- public void AddKnownType(Type type)
- {
- if (!_classType.GetTypeInfo().IsAssignableFrom(type))
- {
- string message = string.Format("Class {0} cannot be assigned to Class {1}. Ensure that known types are derived from the mapped class.", type.FullName, _classType.FullName);
- throw new ArgumentNullException("type", message);
- }
- if (_frozen) { ThrowFrozenException(); }
- _knownTypes.Add(type);
- }
- /// <summary>
- /// Sets the Id member.
- /// </summary>
- /// <param name="memberMap">The Id member (null if none).</param>
- public void SetIdMember(BsonMemberMap memberMap)
- {
- if (memberMap != null)
- {
- EnsureMemberMapIsForThisClass(memberMap);
- }
- if (_frozen) { ThrowFrozenException(); }
- _idMemberMap = memberMap;
- }
- /// <summary>
- /// Sets whether extra elements should be ignored when deserializing.
- /// </summary>
- /// <param name="ignoreExtraElements">Whether extra elements should be ignored when deserializing.</param>
- public void SetIgnoreExtraElements(bool ignoreExtraElements)
- {
- if (_frozen) { ThrowFrozenException(); }
- _ignoreExtraElements = ignoreExtraElements;
- }
- /// <summary>
- /// Sets whether the IgnoreExtraElements value should be inherited by derived classes.
- /// </summary>
- /// <param name="ignoreExtraElementsIsInherited">Whether the IgnoreExtraElements value should be inherited by derived classes.</param>
- public void SetIgnoreExtraElementsIsInherited(bool ignoreExtraElementsIsInherited)
- {
- if (_frozen) { ThrowFrozenException(); }
- _ignoreExtraElementsIsInherited = ignoreExtraElementsIsInherited;
- }
- /// <summary>
- /// Sets whether this class is a root class.
- /// </summary>
- /// <param name="isRootClass">Whether this class is a root class.</param>
- public void SetIsRootClass(bool isRootClass)
- {
- if (_frozen) { ThrowFrozenException(); }
- _isRootClass = isRootClass;
- }
- /// <summary>
- /// Removes a creator map for a constructor from the class map.
- /// </summary>
- /// <param name="constructorInfo">The constructor info.</param>
- public void UnmapConstructor(ConstructorInfo constructorInfo)
- {
- if (constructorInfo == null)
- {
- throw new ArgumentNullException("constructorInfo");
- }
- EnsureMemberInfoIsForThisClass(constructorInfo);
- if (_frozen) { ThrowFrozenException(); }
- var creatorMap = _creatorMaps.FirstOrDefault(m => m.MemberInfo == constructorInfo);
- if (creatorMap != null)
- {
- _creatorMaps.Remove(creatorMap);
- }
- }
- /// <summary>
- /// Removes a creator map for a factory method from the class map.
- /// </summary>
- /// <param name="methodInfo">The method info.</param>
- public void UnmapFactoryMethod(MethodInfo methodInfo)
- {
- if (methodInfo == null)
- {
- throw new ArgumentNullException("methodInfo");
- }
- EnsureMemberInfoIsForThisClass(methodInfo);
- if (_frozen) { ThrowFrozenException(); }
- var creatorMap = _creatorMaps.FirstOrDefault(m => m.MemberInfo == methodInfo);
- if (creatorMap != null)
- {
- _creatorMaps.Remove(creatorMap);
- }
- }
- /// <summary>
- /// Removes the member map for a field from the class map.
- /// </summary>
- /// <param name="fieldName">The name of the field.</param>
- public void UnmapField(string fieldName)
- {
- if (fieldName == null)
- {
- throw new ArgumentNullException("fieldName");
- }
- if (_frozen) { ThrowFrozenException(); }
- var fieldInfo = _classType.GetTypeInfo().GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
- if (fieldInfo == null)
- {
- var message = string.Format("The class '{0}' does not have a field named '{1}'.", _classType.FullName, fieldName);
- throw new BsonSerializationException(message);
- }
- UnmapMember(fieldInfo);
- }
- /// <summary>
- /// Removes a member map from the class map.
- /// </summary>
- /// <param name="memberInfo">The member info.</param>
- public void UnmapMember(MemberInfo memberInfo)
- {
- if (memberInfo == null)
- {
- throw new ArgumentNullException("memberInfo");
- }
- EnsureMemberInfoIsForThisClass(memberInfo);
- if (_frozen) { ThrowFrozenException(); }
- var memberMap = _declaredMemberMaps.Find(m => m.MemberInfo == memberInfo);
- if (memberMap != null)
- {
- _declaredMemberMaps.Remove(memberMap);
- if (_idMemberMap == memberMap)
- {
- _idMemberMap = null;
- }
- if (_extraElementsMemberMap == memberMap)
- {
- _extraElementsMemberMap = null;
- }
- }
- }
- /// <summary>
- /// Removes the member map for a property from the class map.
- /// </summary>
- /// <param name="propertyName">The name of the property.</param>
- public void UnmapProperty(string propertyName)
- {
- if (propertyName == null)
- {
- throw new ArgumentNullException("propertyName");
- }
- if (_frozen) { ThrowFrozenException(); }
- var propertyInfo = _classType.GetTypeInfo().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
- if (propertyInfo == null)
- {
- var message = string.Format("The class '{0}' does not have a property named '{1}'.", _classType.FullName, propertyName);
- throw new BsonSerializationException(message);
- }
- UnmapMember(propertyInfo);
- }
- // internal methods
- /// <summary>
- /// Gets the discriminator convention for the class.
- /// </summary>
- /// <returns>The discriminator convention for the class.</returns>
- internal IDiscriminatorConvention GetDiscriminatorConvention()
- {
- // return a cached discriminator convention when possible
- var discriminatorConvention = _discriminatorConvention;
- if (discriminatorConvention == null)
- {
- // it's possible but harmless for multiple threads to do the initial lookup at the same time
- discriminatorConvention = BsonSerializer.LookupDiscriminatorConvention(_classType);
- _discriminatorConvention = discriminatorConvention;
- }
- return discriminatorConvention;
- }
- // private methods
- private void AutoMapClass()
- {
- new ConventionRunner(_conventionPack).Apply(this);
- OrderMembers();
- foreach (var memberMap in _declaredMemberMaps)
- {
- TryFindShouldSerializeMethod(memberMap);
- }
- }
- private void OrderMembers()
- {
- // only auto map properties declared in this class (and not in base classes)
- var hasOrderedElements = false;
- var hasUnorderedElements = false;
- foreach (var memberMap in _declaredMemberMaps)
- {
- if (memberMap.Order != int.MaxValue)
- {
- hasOrderedElements |= true;
- }
- else
- {
- hasUnorderedElements |= true;
- }
- }
- if (hasOrderedElements)
- {
- if (hasUnorderedElements)
- {
- // split out the unordered elements and add them back at the end (because Sort is unstable, see online help)
- var unorderedElements = new List<BsonMemberMap>(_declaredMemberMaps.Where(pm => pm.Order == int.MaxValue));
- _declaredMemberMaps.RemoveAll(m => m.Order == int.MaxValue);
- _declaredMemberMaps.Sort((x, y) => x.Order.CompareTo(y.Order));
- _declaredMemberMaps.AddRange(unorderedElements);
- }
- else
- {
- _declaredMemberMaps.Sort((x, y) => x.Order.CompareTo(y.Order));
- }
- }
- }
- private void TryFindShouldSerializeMethod(BsonMemberMap memberMap)
- {
- // see if the class has a method called ShouldSerializeXyz where Xyz is the name of this member
- var shouldSerializeMethod = GetShouldSerializeMethod(memberMap.MemberInfo);
- if (shouldSerializeMethod != null)
- {
- memberMap.SetShouldSerializeMethod(shouldSerializeMethod);
- }
- }
- private void EnsureMemberInfoIsForThisClass(MemberInfo memberInfo)
- {
- if (memberInfo.DeclaringType != _classType)
- {
- var message = string.Format(
- "The memberInfo argument must be for class {0}, but was for class {1}.",
- _classType.Name,
- memberInfo.DeclaringType.Name);
- throw new ArgumentOutOfRangeException("memberInfo", message);
- }
- }
- private void EnsureMemberMapIsForThisClass(BsonMemberMap memberMap)
- {
- if (memberMap.ClassMap != this)
- {
- var message = string.Format(
- "The memberMap argument must be for class {0}, but was for class {1}.",
- _classType.Name,
- memberMap.ClassMap.ClassType.Name);
- throw new ArgumentOutOfRangeException("memberMap", message);
- }
- }
- private Func<object> GetCreator()
- {
- if (_creator == null)
- {
- Expression body;
- var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
- var classTypeInfo = _classType.GetTypeInfo();
- ConstructorInfo defaultConstructor = classTypeInfo.GetConstructors(bindingFlags)
- .Where(c => c.GetParameters().Length == 0)
- .SingleOrDefault();
- #if ENABLE_IL2CPP
- _creator = () => defaultConstructor.Invoke(null);
- #else
- if (defaultConstructor != null)
- {
- // lambdaExpression = () => (object) new TClass()
- body = Expression.New(defaultConstructor);
- }
- else if (__getUninitializedObjectMethodInfo != null)
- {
- // lambdaExpression = () => FormatterServices.GetUninitializedObject(classType)
- body = Expression.Call(__getUninitializedObjectMethodInfo, Expression.Constant(_classType));
- }
- else
- {
- var message = $"Type '{_classType.GetType().Name}' does not have a default constructor.";
- throw new BsonSerializationException(message);
- }
- var lambdaExpression = Expression.Lambda<Func<object>>(body);
- _creator = lambdaExpression.Compile();
- #endif
- }
- return _creator;
- }
- private Func<object, bool> GetShouldSerializeMethod(MemberInfo memberInfo)
- {
- var shouldSerializeMethodName = "ShouldSerialize" + memberInfo.Name;
- var shouldSerializeMethodInfo = _classType.GetTypeInfo().GetMethod(shouldSerializeMethodName, new Type[] { });
- if (shouldSerializeMethodInfo != null &&
- shouldSerializeMethodInfo.IsPublic &&
- shouldSerializeMethodInfo.ReturnType == typeof(bool))
- {
- // lambdaExpression = (obj) => ((TClass) obj).ShouldSerializeXyz()
- var objParameter = Expression.Parameter(typeof(object), "obj");
- var lambdaExpression = Expression.Lambda<Func<object, bool>>(Expression.Call(Expression.Convert(objParameter, _classType), shouldSerializeMethodInfo), objParameter);
- return lambdaExpression.Compile();
- }
- else
- {
- return null;
- }
- }
- private bool IsAnonymousType(Type type)
- {
- // don't test for too many things in case implementation details change in the future
- var typeInfo = type.GetTypeInfo();
- return
- typeInfo.GetCustomAttributes<CompilerGeneratedAttribute>(false).Any() &&
- typeInfo.IsGenericType &&
- type.Name.Contains("Anon"); // don't check for more than "Anon" so it works in mono also
- }
- private void ThrowFrozenException()
- {
- var message = string.Format("Class map for {0} has been frozen and no further changes are allowed.", _classType.FullName);
- throw new InvalidOperationException(message);
- }
- private void ThrowNotFrozenException()
- {
- var message = string.Format("Class map for {0} has been not been frozen yet.", _classType.FullName);
- throw new InvalidOperationException(message);
- }
- }
- /// <summary>
- /// Represents a mapping between a class and a BSON document.
- /// </summary>
- /// <typeparam name="TClass">The class.</typeparam>
- public class BsonClassMap<TClass> : BsonClassMap
- {
- // constructors
- /// <summary>
- /// Initializes a new instance of the BsonClassMap class.
- /// </summary>
- public BsonClassMap()
- : base(typeof(TClass))
- {
- }
- /// <summary>
- /// Initializes a new instance of the BsonClassMap class.
- /// </summary>
- /// <param name="classMapInitializer">The class map initializer.</param>
- public BsonClassMap(Action<BsonClassMap<TClass>> classMapInitializer)
- : base(typeof(TClass))
- {
- classMapInitializer(this);
- }
- // public methods
- /// <summary>
- /// Creates an instance.
- /// </summary>
- /// <returns>An instance.</returns>
- public new TClass CreateInstance()
- {
- return (TClass)base.CreateInstance();
- }
- /// <summary>
- /// Gets a member map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="memberLambda">A lambda expression specifying the member.</param>
- /// <returns>The member map.</returns>
- public BsonMemberMap GetMemberMap<TMember>(Expression<Func<TClass, TMember>> memberLambda)
- {
- var memberName = GetMemberNameFromLambda(memberLambda);
- return GetMemberMap(memberName);
- }
- /// <summary>
- /// Creates a creator map and adds it to the class map.
- /// </summary>
- /// <param name="creatorLambda">Lambda expression specifying the creator code and parameters to use.</param>
- /// <returns>The member map.</returns>
- public BsonCreatorMap MapCreator(Expression<Func<TClass, TClass>> creatorLambda)
- {
- if (creatorLambda == null)
- {
- throw new ArgumentNullException("creatorLambda");
- }
- IEnumerable<MemberInfo> arguments;
- var @delegate = new CreatorMapDelegateCompiler().CompileCreatorDelegate(creatorLambda, out arguments);
- var creatorMap = MapCreator(@delegate);
- creatorMap.SetArguments(arguments);
- return creatorMap;
- }
- /// <summary>
- /// Creates a member map for the extra elements field and adds it to the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="fieldLambda">A lambda expression specifying the extra elements field.</param>
- /// <returns>The member map.</returns>
- public BsonMemberMap MapExtraElementsField<TMember>(Expression<Func<TClass, TMember>> fieldLambda)
- {
- var fieldMap = MapField(fieldLambda);
- SetExtraElementsMember(fieldMap);
- return fieldMap;
- }
- /// <summary>
- /// Creates a member map for the extra elements member and adds it to the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="memberLambda">A lambda expression specifying the extra elements member.</param>
- /// <returns>The member map.</returns>
- public BsonMemberMap MapExtraElementsMember<TMember>(Expression<Func<TClass, TMember>> memberLambda)
- {
- var memberMap = MapMember(memberLambda);
- SetExtraElementsMember(memberMap);
- return memberMap;
- }
- /// <summary>
- /// Creates a member map for the extra elements property and adds it to the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="propertyLambda">A lambda expression specifying the extra elements property.</param>
- /// <returns>The member map.</returns>
- public BsonMemberMap MapExtraElementsProperty<TMember>(Expression<Func<TClass, TMember>> propertyLambda)
- {
- var propertyMap = MapProperty(propertyLambda);
- SetExtraElementsMember(propertyMap);
- return propertyMap;
- }
- /// <summary>
- /// Creates a member map for a field and adds it to the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="fieldLambda">A lambda expression specifying the field.</param>
- /// <returns>The member map.</returns>
- public BsonMemberMap MapField<TMember>(Expression<Func<TClass, TMember>> fieldLambda)
- {
- return MapMember(fieldLambda);
- }
- /// <summary>
- /// Creates a member map for the Id field and adds it to the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="fieldLambda">A lambda expression specifying the Id field.</param>
- /// <returns>The member map.</returns>
- public BsonMemberMap MapIdField<TMember>(Expression<Func<TClass, TMember>> fieldLambda)
- {
- var fieldMap = MapField(fieldLambda);
- SetIdMember(fieldMap);
- return fieldMap;
- }
- /// <summary>
- /// Creates a member map for the Id member and adds it to the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="memberLambda">A lambda expression specifying the Id member.</param>
- /// <returns>The member map.</returns>
- public BsonMemberMap MapIdMember<TMember>(Expression<Func<TClass, TMember>> memberLambda)
- {
- var memberMap = MapMember(memberLambda);
- SetIdMember(memberMap);
- return memberMap;
- }
- /// <summary>
- /// Creates a member map for the Id property and adds it to the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="propertyLambda">A lambda expression specifying the Id property.</param>
- /// <returns>The member map.</returns>
- public BsonMemberMap MapIdProperty<TMember>(Expression<Func<TClass, TMember>> propertyLambda)
- {
- var propertyMap = MapProperty(propertyLambda);
- SetIdMember(propertyMap);
- return propertyMap;
- }
- /// <summary>
- /// Creates a member map and adds it to the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="memberLambda">A lambda expression specifying the member.</param>
- /// <returns>The member map.</returns>
- public BsonMemberMap MapMember<TMember>(Expression<Func<TClass, TMember>> memberLambda)
- {
- var memberInfo = GetMemberInfoFromLambda(memberLambda);
- return MapMember(memberInfo);
- }
- /// <summary>
- /// Creates a member map for the Id property and adds it to the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="propertyLambda">A lambda expression specifying the Id property.</param>
- /// <returns>The member map.</returns>
- public BsonMemberMap MapProperty<TMember>(Expression<Func<TClass, TMember>> propertyLambda)
- {
- return MapMember(propertyLambda);
- }
- /// <summary>
- /// Removes the member map for a field from the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="fieldLambda">A lambda expression specifying the field.</param>
- public void UnmapField<TMember>(Expression<Func<TClass, TMember>> fieldLambda)
- {
- UnmapMember(fieldLambda);
- }
- /// <summary>
- /// Removes a member map from the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="memberLambda">A lambda expression specifying the member.</param>
- public void UnmapMember<TMember>(Expression<Func<TClass, TMember>> memberLambda)
- {
- var memberInfo = GetMemberInfoFromLambda(memberLambda);
- UnmapMember(memberInfo);
- }
- /// <summary>
- /// Removes a member map for a property from the class map.
- /// </summary>
- /// <typeparam name="TMember">The member type.</typeparam>
- /// <param name="propertyLambda">A lambda expression specifying the property.</param>
- public void UnmapProperty<TMember>(Expression<Func<TClass, TMember>> propertyLambda)
- {
- UnmapMember(propertyLambda);
- }
- // private static methods
- private static MethodInfo[] GetPropertyAccessors(PropertyInfo propertyInfo)
- {
- return propertyInfo.GetAccessors(true);
- }
- private static MemberInfo GetMemberInfoFromLambda<TMember>(Expression<Func<TClass, TMember>> memberLambda)
- {
- var body = memberLambda.Body;
- MemberExpression memberExpression;
- switch (body.NodeType)
- {
- case ExpressionType.MemberAccess:
- memberExpression = (MemberExpression)body;
- break;
- case ExpressionType.Convert:
- var convertExpression = (UnaryExpression)body;
- memberExpression = (MemberExpression)convertExpression.Operand;
- break;
- default:
- throw new BsonSerializationException("Invalid lambda expression");
- }
- var memberInfo = memberExpression.Member;
- if (memberInfo is PropertyInfo)
- {
- if (memberInfo.DeclaringType.GetTypeInfo().IsInterface)
- {
- memberInfo = FindPropertyImplementation((PropertyInfo)memberInfo, typeof(TClass));
- }
- }
- else if (!(memberInfo is FieldInfo))
- {
- throw new BsonSerializationException("Invalid lambda expression");
- }
- return memberInfo;
- }
- private static string GetMemberNameFromLambda<TMember>(Expression<Func<TClass, TMember>> memberLambda)
- {
- return GetMemberInfoFromLambda(memberLambda).Name;
- }
- private static PropertyInfo FindPropertyImplementation(PropertyInfo interfacePropertyInfo, Type actualType)
- {
- var interfaceType = interfacePropertyInfo.DeclaringType;
- #if NETSTANDARD1_5 || NETSTANDARD1_6
- var actualTypeInfo = actualType.GetTypeInfo();
- var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
- var actualTypePropertyInfos = actualTypeInfo.GetMembers(bindingFlags).OfType<PropertyInfo>();
- var explicitlyImplementedPropertyName = $"{interfacePropertyInfo.DeclaringType.FullName}.{interfacePropertyInfo.Name}".Replace("+", ".");
- var explicitlyImplementedPropertyInfo = actualTypePropertyInfos
- .Where(p => p.Name == explicitlyImplementedPropertyName)
- .SingleOrDefault();
- if (explicitlyImplementedPropertyInfo != null)
- {
- return explicitlyImplementedPropertyInfo;
- }
- var implicitlyImplementedPropertyInfo = actualTypePropertyInfos
- .Where(p => p.Name == interfacePropertyInfo.Name && p.PropertyType == interfacePropertyInfo.PropertyType)
- .SingleOrDefault();
- if (implicitlyImplementedPropertyInfo != null)
- {
- return implicitlyImplementedPropertyInfo;
- }
- throw new BsonSerializationException($"Unable to find property info for property: '{interfacePropertyInfo.Name}'.");
- #else
- // An interface map must be used because because there is no
- // other officially documented way to derive the explicitly
- // implemented property name.
- var interfaceMap = actualType.GetInterfaceMap(interfaceType);
- var interfacePropertyAccessors = GetPropertyAccessors(interfacePropertyInfo);
- var actualPropertyAccessors = interfacePropertyAccessors.Select(interfacePropertyAccessor =>
- {
- var index = Array.IndexOf<MethodInfo>(interfaceMap.InterfaceMethods, interfacePropertyAccessor);
- return interfaceMap.TargetMethods[index];
- });
- // Binding must be done by accessor methods because interface
- // maps only map accessor methods and do not map properties.
- return actualType.GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
- .Single(propertyInfo =>
- {
- // we are looking for a property that implements all the required accessors
- var propertyAccessors = GetPropertyAccessors(propertyInfo);
- return actualPropertyAccessors.All(x => propertyAccessors.Contains(x));
- });
- #endif
- }
- }
- }
|