@@ -13,85 +13,66 @@ internal static class TEHelpers
13
13
private static readonly Regex invalidPropertyNameRegex = new Regex ( InvalidPropertyNamePattern , RegexOptions . Compiled ) ;
14
14
private const string InvalidPropertyNamePattern = @"[^\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\p{Nd}_]" ;
15
15
16
- internal static T CreateTheInstanceWithTheDefaultConstructor < T > ( Table table , InstanceCreationOptions creationOptions )
16
+ internal static T CreateInstanceAndInitializeWithValuesFromTheTable < T > ( Table table , InstanceCreationOptions creationOptions )
17
17
{
18
18
var instance = ( T ) Activator . CreateInstance ( typeof ( T ) ) ;
19
19
LoadInstanceWithKeyValuePairs ( table , instance , creationOptions ) ;
20
20
return instance ;
21
21
}
22
22
23
- internal static T CreateTheInstanceWithTheValuesFromTheTable < T > ( Table table , InstanceCreationOptions creationOptions )
23
+ internal static T ConstructInstanceWithValuesFromTheTable < T > ( Table table , InstanceCreationOptions creationOptions )
24
24
{
25
- var constructor = GetConstructorMatchingToColumnNames < T > ( table ) ;
26
- if ( constructor == null )
27
- throw new MissingMethodException ( $ "Unable to find a suitable constructor to create instance of { typeof ( T ) . Name } ") ;
25
+ creationOptions ??= new InstanceCreationOptions ( ) ;
26
+ creationOptions . AssumeInstanceIsAlreadyCreated = false ;
28
27
29
- var membersThatNeedToBeSet = GetMembersThatNeedToBeSet ( table , typeof ( T ) ) ;
28
+ var ( memberHandlers , constructorInfo ) = GetMembersThatNeedToBeSet ( table , typeof ( T ) , creationOptions ) ;
30
29
31
- var constructorParameters = constructor . GetParameters ( ) ;
32
- var parameterValues = new object [ constructorParameters . Length ] ;
30
+ VerifyAllColumn ( table , creationOptions , memberHandlers . Select ( m => m . MemberName ) ) ;
33
31
34
- var members = new List < string > ( constructorParameters . Length ) ;
35
- for ( var parameterIndex = 0 ; parameterIndex < constructorParameters . Length ; parameterIndex ++ )
32
+ var parameters = constructorInfo . GetParameters ( ) ;
33
+ var parameterValues = new object [ parameters . Length ] ;
34
+
35
+ for ( var i = 0 ; i < parameters . Length ; ++ i )
36
36
{
37
- var parameter = constructorParameters [ parameterIndex ] ;
38
- var parameterName = parameter . Name ;
39
- var member = ( from m in membersThatNeedToBeSet
40
- where string . Equals ( m . MemberName , parameterName , StringComparison . OrdinalIgnoreCase )
41
- select m ) . FirstOrDefault ( ) ;
42
- if ( member != null )
43
- {
44
- members . Add ( member . MemberName ) ;
45
- parameterValues [ parameterIndex ] = member . GetValue ( ) ;
46
- }
47
- else if ( parameter . HasDefaultValue )
48
- parameterValues [ parameterIndex ] = parameter . DefaultValue ;
37
+ var parameter = parameters [ i ] ;
38
+ var matchingHandler = memberHandlers . FirstOrDefault ( memberHandler => memberHandler . MatchesParameter ( parameter ) ) ;
39
+ parameterValues [ i ] = matchingHandler ? . GetValue ( ) ?? ( parameter . HasDefaultValue ? parameter . DefaultValue : null ) ;
40
+
41
+ memberHandlers . Remove ( matchingHandler ) ;
49
42
}
50
43
51
- VerifyAllColumn ( table , creationOptions , members ) ;
52
- return ( T ) constructor . Invoke ( parameterValues ) ;
44
+ var instance = ( T ) constructorInfo . Invoke ( parameterValues ) ;
45
+ memberHandlers . ForEach ( x => x . Setter ( instance , x . GetValue ( ) ) ) ;
46
+
47
+ return instance ;
53
48
}
54
49
55
50
internal static bool ThisTypeHasADefaultConstructor < T > ( )
56
51
{
57
52
return typeof ( T ) . GetConstructors ( ) . Any ( c => c . GetParameters ( ) . Length == 0 ) ;
58
53
}
59
54
60
- internal static ConstructorInfo GetConstructorMatchingToColumnNames < T > ( Table table )
61
- {
62
- var projectedPropertyNames = from property in typeof ( T ) . GetProperties ( )
63
- from row in table . Rows
64
- where IsMemberMatchingToColumnName ( property , row . Id ( ) )
65
- select property . Name ;
66
-
67
- return ( from constructor in typeof ( T ) . GetConstructors ( )
68
- where ! projectedPropertyNames . Except (
69
- from parameter in constructor . GetParameters ( )
70
- select parameter . Name , StringComparer . OrdinalIgnoreCase ) . Any ( )
71
- select constructor ) . FirstOrDefault ( ) ;
72
- }
73
-
74
55
internal static bool IsMemberMatchingToColumnName ( MemberInfo member , string columnName )
75
56
{
76
57
return member . Name . MatchesThisColumnName ( columnName )
77
58
|| IsMatchingAlias ( member , columnName ) ;
78
59
}
79
60
80
- internal static bool MatchesThisColumnName ( this string propertyName , string columnName )
61
+ private static bool MatchesThisColumnName ( this string propertyName , string columnName )
81
62
{
82
63
var normalizedColumnName = NormalizePropertyNameToMatchAgainstAColumnName ( RemoveAllCharactersThatAreNotValidInAPropertyName ( columnName ) ) ;
83
64
var normalizedPropertyName = NormalizePropertyNameToMatchAgainstAColumnName ( propertyName ) ;
84
65
85
66
return normalizedPropertyName . Equals ( normalizedColumnName , StringComparison . OrdinalIgnoreCase ) ;
86
67
}
87
68
88
- internal static string RemoveAllCharactersThatAreNotValidInAPropertyName ( string name )
69
+ private static string RemoveAllCharactersThatAreNotValidInAPropertyName ( string name )
89
70
{
90
71
//Unicode groups allowed: Lu, Ll, Lt, Lm, Lo, Nl or Nd see https://msdn.microsoft.com/en-us/library/aa664670%28v=vs.71%29.aspx
91
72
return invalidPropertyNameRegex . Replace ( name , string . Empty ) ;
92
73
}
93
74
94
- internal static string NormalizePropertyNameToMatchAgainstAColumnName ( string name )
75
+ private static string NormalizePropertyNameToMatchAgainstAColumnName ( string name )
95
76
{
96
77
// we remove underscores, because they should be equivalent to spaces that were removed too from the column names
97
78
// we also ignore accents
@@ -100,8 +81,10 @@ internal static string NormalizePropertyNameToMatchAgainstAColumnName(string nam
100
81
101
82
internal static void LoadInstanceWithKeyValuePairs ( Table table , object instance , InstanceCreationOptions creationOptions )
102
83
{
103
- var membersThatNeedToBeSet = GetMembersThatNeedToBeSet ( table , instance . GetType ( ) ) ;
104
- var memberHandlers = membersThatNeedToBeSet . ToList ( ) ;
84
+ creationOptions ??= new InstanceCreationOptions ( ) ;
85
+ creationOptions . AssumeInstanceIsAlreadyCreated = true ;
86
+
87
+ var ( memberHandlers , _) = GetMembersThatNeedToBeSet ( table , instance . GetType ( ) , creationOptions ) ;
105
88
var memberNames = memberHandlers . Select ( h => h . MemberName ) ;
106
89
107
90
VerifyAllColumn ( table , creationOptions , memberNames ) ;
@@ -123,9 +106,13 @@ private static void VerifyAllColumn(Table table, InstanceCreationOptions creatio
123
106
}
124
107
}
125
108
126
- internal static List < MemberHandler > GetMembersThatNeedToBeSet ( Table table , Type type )
127
-
109
+ private static ( List < MemberHandler > , ConstructorInfo ) GetMembersThatNeedToBeSet ( Table table , Type type , InstanceCreationOptions creationOptions )
128
110
{
111
+ if ( creationOptions is null )
112
+ {
113
+ throw new ArgumentNullException ( nameof ( creationOptions ) ) ;
114
+ }
115
+
129
116
var properties = ( from property in type . GetProperties ( )
130
117
from row in table . Rows
131
118
where TheseTypesMatch ( type , property . PropertyType , row )
@@ -174,7 +161,86 @@ where TheseTypesMatch(type, field.FieldType, row)
174
161
}
175
162
}
176
163
177
- return memberHandlers ;
164
+ if ( creationOptions . AssumeInstanceIsAlreadyCreated )
165
+ {
166
+ return ( memberHandlers , null ) ;
167
+ }
168
+
169
+ var constructors = type . GetConstructors ( )
170
+ . Select (
171
+ c => new
172
+ {
173
+ Constructor = c ,
174
+ Parameters = c . GetParameters ( )
175
+ } )
176
+ . Where ( i => i . Parameters . Length > 0 ) ;
177
+
178
+ if ( ! creationOptions . RequireTableToProvideAllConstructorParameters )
179
+ {
180
+ // Prefer constructor with the least parameters that takes all members
181
+ var candidateConstructors = constructors . OrderBy ( c => c . Parameters . Length ) ;
182
+ foreach ( var candidate in candidateConstructors )
183
+ {
184
+ var resolvedMembers = memberHandlers . Where ( m => m . AnyParameterMatchesThisMemberHandler ( candidate . Parameters ) ) . ToList ( ) ;
185
+ if ( resolvedMembers . Count == memberHandlers . Count )
186
+ {
187
+ return ( memberHandlers , candidate . Constructor ) ;
188
+ }
189
+ }
190
+ }
191
+ else
192
+ {
193
+ // Prefer constructor with the most parameters
194
+ var candidateConstructors = constructors
195
+ . OrderByDescending ( i => i . Parameters . Length )
196
+ . ToList ( ) ;
197
+
198
+ foreach ( var candidate in candidateConstructors )
199
+ {
200
+ var unresolvedParameters = candidate . Parameters . Where ( p => p . NoMemberHandlerMatchesThisParameter ( memberHandlers ) ) . ToList ( ) ;
201
+ if ( unresolvedParameters . Count == 0 )
202
+ {
203
+ return ( memberHandlers , candidate . Constructor ) ;
204
+ }
205
+
206
+ var matchingHandlers = ( from parameter in unresolvedParameters
207
+ from row in table . Rows
208
+ where parameter . Name . MatchesThisColumnName ( row . Id ( ) ) && TheseTypesMatch ( type , parameter . ParameterType , row )
209
+ select new MemberHandler
210
+ {
211
+ Type = type ,
212
+ Row = row ,
213
+ MemberName = parameter . Name ,
214
+ PropertyType = parameter . ParameterType ,
215
+ Setter = ( _ , _ ) => throw new InvalidOperationException ( $ "This { nameof ( MemberHandler ) } is used for a constructor parameter only")
216
+ } ) . ToList ( ) ;
217
+
218
+ if ( matchingHandlers . Count == unresolvedParameters . Count )
219
+ {
220
+ // We found the correct constructor candidate
221
+ memberHandlers . AddRange ( matchingHandlers ) ;
222
+ return ( memberHandlers , candidate . Constructor ) ;
223
+ }
224
+ }
225
+ }
226
+
227
+ throw new MissingMethodException ( $ "Unable to find a suitable constructor to create instance of { type } ") ;
228
+ }
229
+
230
+ private static bool MatchesParameter ( this MemberHandler memberHandler , ParameterInfo parameter )
231
+ {
232
+ return memberHandler . MemberName . Equals ( parameter . Name , StringComparison . OrdinalIgnoreCase )
233
+ && memberHandler . PropertyType == parameter . ParameterType ;
234
+ }
235
+
236
+ private static bool AnyParameterMatchesThisMemberHandler ( this MemberHandler memberHandler , ParameterInfo [ ] parameters )
237
+ {
238
+ return parameters . Any ( memberHandler . MatchesParameter ) ;
239
+ }
240
+
241
+ private static bool NoMemberHandlerMatchesThisParameter ( this ParameterInfo parameter , List < MemberHandler > memberHandlers )
242
+ {
243
+ return ! memberHandlers . Any ( m => m . MatchesParameter ( parameter ) ) ;
178
244
}
179
245
180
246
private static bool IsMatchingAlias ( MemberInfo field , string id )
@@ -199,6 +265,11 @@ internal class MemberHandler
199
265
public object GetValue ( )
200
266
{
201
267
var valueRetriever = Service . Instance . GetValueRetrieverFor ( Row , Type , PropertyType ) ;
268
+ if ( valueRetriever is null )
269
+ {
270
+ throw new InvalidOperationException ( $ "Unable to resolve value retriever for member { MemberName } ") ;
271
+ }
272
+
202
273
return valueRetriever . Retrieve ( new KeyValuePair < string , string > ( Row [ 0 ] , Row [ 1 ] ) , Type , PropertyType ) ;
203
274
}
204
275
}
@@ -234,7 +305,7 @@ private static bool TheFirstRowValueIsTheNameOfAProperty(Table table, Type type)
234
305
. Any ( property => IsMemberMatchingToColumnName ( property , firstRowValue ) ) ;
235
306
}
236
307
237
- public static bool IsValueTupleType ( Type type , bool checkBaseTypes = false )
308
+ private static bool IsValueTupleType ( Type type , bool checkBaseTypes = false )
238
309
{
239
310
if ( type == null )
240
311
throw new ArgumentNullException ( nameof ( type ) ) ;
0 commit comments