1+ using System ;
2+ using System . Collections . Concurrent ;
3+ using System . Collections . Generic ;
4+ using System . Linq ;
5+ using System . Reflection ;
6+ using System . Threading ;
7+
8+ namespace LiteDB ;
9+
10+ public partial class BsonMapper
11+ {
12+ /// <summary>
13+ /// Mapping cache between Class/BsonDocument
14+ /// </summary>
15+ private readonly ConcurrentDictionary < Type , EntityMapper > _entities = new ( ) ;
16+
17+ /// <summary>
18+ /// Get property mapper between typed .NET class and BsonDocument - Cache results
19+ /// </summary>
20+ internal EntityMapper GetEntityMapper ( Type type )
21+ {
22+ if ( _entities . TryGetValue ( type , out EntityMapper mapper ) )
23+ {
24+ return mapper ;
25+ }
26+
27+ using var cts = new CancellationTokenSource ( ) ;
28+ mapper = new EntityMapper ( type , cts . Token ) ;
29+ if ( _entities . TryAdd ( type , mapper ) )
30+ {
31+ this . BuildEntityMapper ( mapper ) ;
32+ }
33+ cts . Cancel ( ) ;
34+ cts . Dispose ( ) ;
35+
36+ return mapper ;
37+ }
38+
39+ /// <summary>
40+ /// Use this method to override how your class can be, by default, mapped from entity to Bson document.
41+ /// Returns an EntityMapper from each requested Type
42+ /// </summary>
43+ protected void BuildEntityMapper ( EntityMapper mapper )
44+ {
45+ var idAttr = typeof ( BsonIdAttribute ) ;
46+ var ignoreAttr = typeof ( BsonIgnoreAttribute ) ;
47+ var fieldAttr = typeof ( BsonFieldAttribute ) ;
48+ var dbrefAttr = typeof ( BsonRefAttribute ) ;
49+
50+ var members = this . GetTypeMembers ( mapper . ForType ) ;
51+ var id = this . GetIdMember ( members ) ;
52+
53+ foreach ( var memberInfo in members )
54+ {
55+ // checks [BsonIgnore]
56+ if ( CustomAttributeExtensions . IsDefined ( memberInfo , ignoreAttr , true ) ) continue ;
57+
58+ // checks field name conversion
59+ var name = this . ResolveFieldName ( memberInfo . Name ) ;
60+
61+ // check if property has [BsonField]
62+ var field = ( BsonFieldAttribute ) CustomAttributeExtensions . GetCustomAttributes ( memberInfo , fieldAttr , true )
63+ . FirstOrDefault ( ) ;
64+
65+ // check if property has [BsonField] with a custom field name
66+ if ( field != null && field . Name != null )
67+ {
68+ name = field . Name ;
69+ }
70+
71+ // checks if memberInfo is id field
72+ if ( memberInfo == id )
73+ {
74+ name = "_id" ;
75+ }
76+
77+ // create getter/setter function
78+ var getter = Reflection . CreateGenericGetter ( mapper . ForType , memberInfo ) ;
79+ var setter = Reflection . CreateGenericSetter ( mapper . ForType , memberInfo ) ;
80+
81+ // check if property has [BsonId] to get with was setted AutoId = true
82+ var autoId = ( BsonIdAttribute ) CustomAttributeExtensions . GetCustomAttributes ( memberInfo , idAttr , true )
83+ . FirstOrDefault ( ) ;
84+
85+ // get data type
86+ var dataType = memberInfo is PropertyInfo
87+ ? ( memberInfo as PropertyInfo ) . PropertyType
88+ : ( memberInfo as FieldInfo ) . FieldType ;
89+
90+ // check if datatype is list/array
91+ var isEnumerable = Reflection . IsEnumerable ( dataType ) ;
92+
93+ // create a property mapper
94+ var member = new MemberMapper
95+ {
96+ AutoId = autoId == null ? true : autoId . AutoId ,
97+ FieldName = name ,
98+ MemberName = memberInfo . Name ,
99+ DataType = dataType ,
100+ IsEnumerable = isEnumerable ,
101+ UnderlyingType = isEnumerable ? Reflection . GetListItemType ( dataType ) : dataType ,
102+ Getter = getter ,
103+ Setter = setter
104+ } ;
105+
106+ // check if property has [BsonRef]
107+ var dbRef = ( BsonRefAttribute ) CustomAttributeExtensions . GetCustomAttributes ( memberInfo , dbrefAttr , false )
108+ . FirstOrDefault ( ) ;
109+
110+ if ( dbRef != null && memberInfo is PropertyInfo )
111+ {
112+ BsonMapper . RegisterDbRef ( this , member , _typeNameBinder ,
113+ dbRef . Collection ?? this . ResolveCollectionName ( ( memberInfo as PropertyInfo ) . PropertyType ) ) ;
114+ }
115+
116+ // support callback to user modify member mapper
117+ this . ResolveMember ? . Invoke ( mapper . ForType , memberInfo , member ) ;
118+
119+ // test if has name and there is no duplicate field
120+ // when member is not ignore
121+ if ( member . FieldName != null &&
122+ mapper . Members . Any ( x => x . FieldName . Equals ( name , StringComparison . OrdinalIgnoreCase ) ) == false &&
123+ ! member . IsIgnore )
124+ {
125+ mapper . Members . Add ( member ) ;
126+ }
127+ }
128+ }
129+
130+ /// <summary>
131+ /// Gets MemberInfo that refers to Id from a document object.
132+ /// </summary>
133+ protected virtual MemberInfo GetIdMember ( IEnumerable < MemberInfo > members )
134+ {
135+ return Reflection . SelectMember ( members ,
136+ x => CustomAttributeExtensions . IsDefined ( x , typeof ( BsonIdAttribute ) , true ) ,
137+ x => x . Name . Equals ( "Id" , StringComparison . OrdinalIgnoreCase ) ,
138+ x => x . Name . Equals ( x . DeclaringType . Name + "Id" , StringComparison . OrdinalIgnoreCase ) ) ;
139+ }
140+
141+ /// <summary>
142+ /// Returns all member that will be have mapper between POCO class to document
143+ /// </summary>
144+ protected virtual IEnumerable < MemberInfo > GetTypeMembers ( Type type )
145+ {
146+ var members = new List < MemberInfo > ( ) ;
147+
148+ var flags = this . IncludeNonPublic
149+ ? ( BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance )
150+ : ( BindingFlags . Public | BindingFlags . Instance ) ;
151+
152+ members . AddRange ( type . GetProperties ( flags )
153+ . Where ( x => x . CanRead && x . GetIndexParameters ( ) . Length == 0 )
154+ . Select ( x => x as MemberInfo ) ) ;
155+
156+ if ( this . IncludeFields )
157+ {
158+ members . AddRange ( type . GetFields ( flags ) . Where ( x => ! x . Name . EndsWith ( "k__BackingField" ) && x . IsStatic == false )
159+ . Select ( x => x as MemberInfo ) ) ;
160+ }
161+
162+ return members ;
163+ }
164+
165+ /// <summary>
166+ /// Get best construtor to use to initialize this entity.
167+ /// - Look if contains [BsonCtor] attribute
168+ /// - Look for parameterless ctor
169+ /// - Look for first contructor with parameter and use BsonDocument to send RawValue
170+ /// </summary>
171+ protected virtual CreateObject GetTypeCtor ( EntityMapper mapper )
172+ {
173+ Type type = mapper . ForType ;
174+ List < CreateObject > Mappings = new List < CreateObject > ( ) ;
175+ bool returnZeroParamNull = false ;
176+ foreach ( ConstructorInfo ctor in type . GetConstructors ( ) )
177+ {
178+ ParameterInfo [ ] pars = ctor . GetParameters ( ) ;
179+ // For 0 parameters, we can let the Reflection.CreateInstance handle it, unless they've specified a [BsonCtor] attribute on a different constructor.
180+ if ( pars . Length == 0 )
181+ {
182+ returnZeroParamNull = true ;
183+ continue ;
184+ }
185+
186+ KeyValuePair < string , Type > [ ] paramMap = new KeyValuePair < string , Type > [ pars . Length ] ;
187+ int i ;
188+ for ( i = 0 ; i < pars . Length ; i ++ )
189+ {
190+ ParameterInfo par = pars [ i ] ;
191+ MemberMapper mi = null ;
192+ foreach ( MemberMapper member in mapper . Members )
193+ {
194+ if ( member . MemberName . ToLower ( ) == par . Name . ToLower ( ) && member . DataType == par . ParameterType )
195+ {
196+ mi = member ;
197+ break ;
198+ }
199+ }
200+
201+ if ( mi == null )
202+ {
203+ break ;
204+ }
205+
206+ paramMap [ i ] = new KeyValuePair < string , Type > ( mi . FieldName , mi . DataType ) ;
207+ }
208+
209+ if ( i < pars . Length )
210+ {
211+ continue ;
212+ }
213+
214+ CreateObject toAdd = ( BsonDocument value ) =>
215+ Activator . CreateInstance ( type , paramMap . Select ( x =>
216+ this . Deserialize ( x . Value , value [ x . Key ] ) ) . ToArray ( ) ) ;
217+ if ( ctor . GetCustomAttribute < BsonCtorAttribute > ( ) != null )
218+ {
219+ return toAdd ;
220+ }
221+ else
222+ {
223+ Mappings . Add ( toAdd ) ;
224+ }
225+ }
226+
227+ if ( returnZeroParamNull )
228+ {
229+ return null ;
230+ }
231+
232+ return Mappings . FirstOrDefault ( ) ;
233+ }
234+ }
0 commit comments