2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[mono.git] / mcs / class / System.Data.Linq / src / DbLinq / Data / Linq / Sugar / Implementation / ExpressionDispatcher.Registrar.cs
1 #region MIT license\r
2 // \r
3 // MIT license\r
4 //\r
5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne\r
6 // \r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy\r
8 // of this software and associated documentation files (the "Software"), to deal\r
9 // in the Software without restriction, including without limitation the rights\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
11 // copies of the Software, and to permit persons to whom the Software is\r
12 // furnished to do so, subject to the following conditions:\r
13 // \r
14 // The above copyright notice and this permission notice shall be included in\r
15 // all copies or substantial portions of the Software.\r
16 // \r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
23 // THE SOFTWARE.\r
24 // \r
25 #endregion\r
26 \r
27 using System;\r
28 using System.Collections.Generic;\r
29 using System.Data;\r
30 using System.Linq;\r
31 using System.Linq.Expressions;\r
32 using System.Reflection;\r
33 using DbLinq.Util;\r
34 \r
35 using DbLinq.Data.Linq.Mapping;\r
36 using DbLinq.Data.Linq.Sugar;\r
37 using DbLinq.Data.Linq.Sugar.Expressions;\r
38 \r
39 \r
40 namespace DbLinq.Data.Linq.Sugar.Implementation\r
41 {\r
42     internal partial class ExpressionDispatcher\r
43     {\r
44 \r
45         /// <summary>\r
46         /// Returns a registered column, or null if not found\r
47         /// This method requires the table to be already registered\r
48         /// </summary>\r
49         /// <param name="table"></param>\r
50         /// <param name="name"></param>\r
51         /// <param name="builderContext"></param>\r
52         /// <returns></returns>\r
53         protected virtual ColumnExpression GetRegisteredColumn(TableExpression table, string name,\r
54                                                                BuilderContext builderContext)\r
55         {\r
56             return\r
57                 (from queryColumn in builderContext.EnumerateScopeColumns()\r
58                  where queryColumn.Table.IsEqualTo(table) && queryColumn.Name == name\r
59                  select queryColumn).SingleOrDefault();\r
60         }\r
61 \r
62         /// <summary>\r
63         /// Returns an existing table or registers the current one\r
64         /// </summary>\r
65         /// <param name="tableExpression"></param>\r
66         /// <param name="builderContext"></param>\r
67         /// <returns>A registered table or the current newly registered one</returns>\r
68         public virtual TableExpression RegisterTable(TableExpression tableExpression, BuilderContext builderContext)\r
69         {\r
70             // 1. Find the table in current scope\r
71             var foundTableExpression = (from t in builderContext.EnumerateScopeTables()\r
72                                         where t.IsEqualTo(tableExpression)\r
73                                         select t).SingleOrDefault();\r
74             if (foundTableExpression != null)\r
75                 return foundTableExpression;\r
76             // 2. Find it in all scopes, and promote it to current scope.\r
77             foundTableExpression = PromoteTable(tableExpression, builderContext);\r
78             if (foundTableExpression != null)\r
79                 return foundTableExpression;\r
80             // 3. Add it\r
81             builderContext.CurrentSelect.Tables.Add(tableExpression);\r
82             return tableExpression;\r
83         }\r
84 \r
85         /// <summary>\r
86         /// Promotes a table to a common parent between its current scope and our current scope\r
87         /// </summary>\r
88         /// <param name="tableExpression"></param>\r
89         /// <param name="builderContext"></param>\r
90         /// <returns></returns>\r
91         protected virtual TableExpression PromoteTable(TableExpression tableExpression, BuilderContext builderContext)\r
92         {\r
93             int currentIndex = 0;\r
94             SelectExpression oldSelect = null;\r
95             SelectExpression commonScope = null;\r
96             TableExpression foundTable = null;\r
97             do\r
98             {\r
99                 // take a select\r
100                 oldSelect = builderContext.SelectExpressions[currentIndex];\r
101 \r
102                 // look for a common scope\r
103                 if (oldSelect != builderContext.CurrentSelect)\r
104                 {\r
105                     commonScope = FindCommonScope(oldSelect, builderContext.CurrentSelect);\r
106                     if (commonScope != null)\r
107                         // if a common scope exists, look for an equivalent table in that select\r
108                         for (int tableIndex = 0; tableIndex < oldSelect.Tables.Count && foundTable == null; tableIndex++)\r
109                         {\r
110                             if (oldSelect.Tables[tableIndex].IsEqualTo(tableExpression))\r
111                             {\r
112                                 // found a matching table!\r
113                                 foundTable = oldSelect.Tables[tableIndex];\r
114                             }\r
115                         }\r
116                 }\r
117                 ++currentIndex;\r
118             }\r
119             while (currentIndex < builderContext.SelectExpressions.Count && foundTable == null);\r
120 \r
121             if (foundTable != null)\r
122             {\r
123                 oldSelect.Tables.Remove(foundTable);\r
124                 commonScope.Tables.Add(foundTable);\r
125             }\r
126             return foundTable;\r
127         }\r
128 \r
129         /// <summary>\r
130         /// Find the common ancestor between two ScopeExpressions\r
131         /// </summary>\r
132         /// <param name="a"></param>\r
133         /// <param name="b"></param>\r
134         /// <returns></returns>\r
135         protected virtual SelectExpression FindCommonScope(SelectExpression a, SelectExpression b)\r
136         {\r
137             for (var aScope = a; aScope != null; aScope = aScope.Parent)\r
138             {\r
139                 for (var bScope = b; bScope != null; bScope = bScope.Parent)\r
140                 {\r
141                     if (aScope == bScope)\r
142                         return aScope;\r
143                 }\r
144             }\r
145             return null;\r
146         }\r
147 \r
148         /// <summary>\r
149         /// Registers a column\r
150         /// This method requires the table to be already registered\r
151         /// </summary>\r
152         /// <param name="table"></param>\r
153         /// <param name="memberInfo"></param>\r
154         /// <param name="name"></param>\r
155         /// <param name="builderContext"></param>\r
156         /// <returns></returns>\r
157         public ColumnExpression RegisterColumn(TableExpression table,\r
158                                                MemberInfo memberInfo, string name,\r
159                                                BuilderContext builderContext)\r
160         {\r
161             if (memberInfo == null)\r
162                 return null;\r
163             var queryColumn = GetRegisteredColumn(table, name, builderContext);\r
164             if (queryColumn == null)\r
165             {\r
166                 table = RegisterTable(table, builderContext);\r
167                 queryColumn = CreateColumn(table, memberInfo, builderContext);\r
168                 builderContext.CurrentSelect.Columns.Add(queryColumn);\r
169             }\r
170             return queryColumn;\r
171         }\r
172 \r
173         /// <summary>\r
174         /// Registers a column with only a table and a MemberInfo (this is the preferred method overload)\r
175         /// </summary>\r
176         /// <param name="tableExpression"></param>\r
177         /// <param name="memberInfo"></param>\r
178         /// <param name="builderContext"></param>\r
179         /// <returns></returns>\r
180         public ColumnExpression RegisterColumn(TableExpression tableExpression, MemberInfo memberInfo,\r
181                                                BuilderContext builderContext)\r
182         {\r
183             var dataMember = builderContext.QueryContext.DataContext.Mapping.GetTable(tableExpression.Type).RowType\r
184                 .GetDataMember(memberInfo);\r
185             if (dataMember == null)\r
186                 return null;\r
187             return RegisterColumn(tableExpression, memberInfo, dataMember.MappedName, builderContext);\r
188         }\r
189 \r
190         public ColumnExpression CreateColumn(TableExpression table, MemberInfo memberInfo, BuilderContext builderContext)\r
191         {\r
192             var dataMember = builderContext.QueryContext.DataContext.Mapping.GetTable(table.Type).RowType\r
193                 .GetDataMember(memberInfo);\r
194             if (dataMember == null)\r
195                 return null;\r
196             return new ColumnExpression(table, dataMember);\r
197         }\r
198 \r
199         /// <summary>\r
200         /// Creates a default TableExpression\r
201         /// </summary>\r
202         /// <param name="tableType"></param>\r
203         /// <param name="builderContext"></param>\r
204         /// <returns></returns>\r
205         public virtual TableExpression CreateTable(Type tableType, BuilderContext builderContext)\r
206         {\r
207             return new TableExpression(tableType, DataMapper.GetTableName(tableType, builderContext.QueryContext.DataContext));\r
208         }\r
209 \r
210         /// <summary>\r
211         /// Registers an association\r
212         /// </summary>\r
213         /// <param name="tableExpression">The table holding the member, to become the joinedTable</param>\r
214         /// <param name="tableMemberInfo"></param>\r
215         /// <param name="otherType"></param>\r
216         /// <param name="builderContext"></param>\r
217         /// <returns></returns>\r
218         public virtual TableExpression RegisterAssociation(TableExpression tableExpression, MemberInfo tableMemberInfo,\r
219                                                            Type otherType, BuilderContext builderContext)\r
220         {\r
221             IList<MemberInfo> otherKeys;\r
222             TableJoinType joinType;\r
223             string joinID;\r
224             var theseKeys = DataMapper.GetAssociation(tableExpression, tableMemberInfo, otherType, out otherKeys,\r
225                                                       out joinType, out joinID, builderContext.QueryContext.DataContext);\r
226             // if the memberInfo has no corresponding association, we get a null, that we propagate\r
227             if (theseKeys == null)\r
228                 return null;\r
229 \r
230             // the current table has the foreign key, the other table the referenced (usually primary) key\r
231             if (theseKeys.Count != otherKeys.Count)\r
232                 throw Error.BadArgument("S0128: Association arguments (FK and ref'd PK) don't match");\r
233 \r
234             // we first create the table, with the JoinID, and we MUST complete the table later, with the Join() method\r
235             var otherTableExpression = new TableExpression(otherType, DataMapper.GetTableName(otherType, builderContext.QueryContext.DataContext), joinID);\r
236 \r
237             Expression joinExpression = null;\r
238 \r
239             var createdColumns = new List<ColumnExpression>();\r
240             for (int keyIndex = 0; keyIndex < theseKeys.Count; keyIndex++)\r
241             {\r
242                 // joinedKey is registered, even if unused by final select (required columns will be filtered anyway)\r
243                 Expression otherKey = RegisterColumn(otherTableExpression, otherKeys[keyIndex], builderContext);\r
244                 // foreign is created, we will store it later if this assocation is registered too\r
245                 Expression thisKey = CreateColumn(tableExpression, theseKeys[keyIndex], builderContext);\r
246                 createdColumns.Add((ColumnExpression)thisKey);\r
247 \r
248                 // if the key is nullable, then convert it\r
249                 // TODO: this will probably need to be changed\r
250                 if (otherKey.Type.IsNullable())\r
251                     otherKey = Expression.Convert(otherKey, otherKey.Type.GetNullableType());\r
252                 if (thisKey.Type.IsNullable())\r
253                     thisKey = Expression.Convert(thisKey, thisKey.Type.GetNullableType());\r
254                 // the other key is set as left operand, this must be this way\r
255                 // since some vendors (SQL Server) don't support the opposite\r
256                 var referenceExpression = Expression.Equal(otherKey, thisKey);\r
257 \r
258                 // if we already have a join expression, then we have a double condition here, so "AND" it\r
259                 if (joinExpression != null)\r
260                     joinExpression = Expression.And(joinExpression, referenceExpression);\r
261                 else\r
262                     joinExpression = referenceExpression;\r
263             }\r
264             // we complete the table here, now that we have all join information\r
265             otherTableExpression.Join(joinType, tableExpression, joinExpression);\r
266 \r
267             // our table is created, with the expressions\r
268             // now check if we didn't register exactly the same\r
269             if ((from t in builderContext.EnumerateScopeTables() where t.IsEqualTo(otherTableExpression) select t).SingleOrDefault() == null)\r
270             {\r
271                 builderContext.CurrentSelect.Tables.Add(otherTableExpression);\r
272                 foreach (var createdColumn in createdColumns)\r
273                     builderContext.CurrentSelect.Columns.Add(createdColumn);\r
274             }\r
275 \r
276             return otherTableExpression;\r
277         }\r
278 \r
279         /// <summary>\r
280         /// Registers an external parameter\r
281         /// Since these can be complex expressions, we don't try to identify them\r
282         /// and push them every time\r
283         /// The only loss may be a small memory loss (if anyone can prove me that the same Expression can be used twice)\r
284         /// </summary>\r
285         /// <param name="expression"></param>\r
286         /// <param name="alias"></param>\r
287         /// <param name="builderContext"></param>\r
288         /// <returns></returns>\r
289         public virtual InputParameterExpression RegisterParameter(Expression expression, string alias, BuilderContext builderContext)\r
290         {\r
291             var queryParameterExpression = new InputParameterExpression(expression, alias);\r
292             builderContext.ExpressionQuery.Parameters.Add(queryParameterExpression);\r
293             return queryParameterExpression;\r
294         }\r
295 \r
296         public virtual void UnregisterParameter(InputParameterExpression expression, BuilderContext builderContext)\r
297         {\r
298             builderContext.ExpressionQuery.Parameters.Remove(expression);\r
299         }\r
300 \r
301         /// <summary>\r
302         /// Registers a MetaTable\r
303         /// </summary>\r
304         /// <param name="metaTableType"></param>\r
305         /// <param name="aliases"></param>\r
306         /// <param name="builderContext"></param>\r
307         /// <returns></returns>\r
308         public virtual MetaTableExpression RegisterMetaTable(Type metaTableType, IDictionary<MemberInfo, MutableExpression> aliases,\r
309                                                              BuilderContext builderContext)\r
310         {\r
311             MetaTableExpression metaTableExpression;\r
312             if (!builderContext.MetaTables.TryGetValue(metaTableType, out metaTableExpression))\r
313             {\r
314                 metaTableExpression = new MetaTableExpression(aliases, metaTableType);\r
315                 builderContext.MetaTables[metaTableType] = metaTableExpression;\r
316             }\r
317             return metaTableExpression;\r
318         }\r
319 \r
320         /// <summary>\r
321         /// Registers a where clause in the current context scope\r
322         /// </summary>\r
323         /// <param name="whereExpression"></param>\r
324         /// <param name="builderContext"></param>\r
325         public virtual void RegisterWhere(Expression whereExpression, BuilderContext builderContext)\r
326         {\r
327             builderContext.CurrentSelect.Where.Add(whereExpression);\r
328         }\r
329 \r
330         /// <summary>\r
331         /// Registers all columns of a table.\r
332         /// </summary>\r
333         /// <param name="tableExpression"></param>\r
334         /// <param name="builderContext"></param>\r
335         /// <returns></returns>\r
336         public virtual IEnumerable<ColumnExpression> RegisterAllColumns(TableExpression tableExpression, BuilderContext builderContext)\r
337         {\r
338             foreach (var metaMember in builderContext.QueryContext.DataContext.Mapping.GetTable(tableExpression.Type).RowType.PersistentDataMembers)\r
339             {\r
340                 yield return RegisterColumn(tableExpression, metaMember.Member, builderContext);\r
341             }\r
342         }\r
343 \r
344         /// <summary>\r
345         /// Registers an expression to be returned by main request.\r
346         /// The strategy is to try to find it in the already registered parameters, and if not found, add it\r
347         /// </summary>\r
348         /// <param name="expression">The expression to be registered</param>\r
349         /// <param name="builderContext"></param>\r
350         /// <returns>Expression index</returns>\r
351         public virtual int RegisterOutputParameter(Expression expression, BuilderContext builderContext)\r
352         {\r
353             var scope = builderContext.CurrentSelect;\r
354             var operands = scope.Operands.ToList();\r
355             for (int index = 0; index < operands.Count; index++)\r
356             {\r
357                 if (ExpressionEquals(operands[index], expression))\r
358                     return index;\r
359             }\r
360             operands.Add(expression);\r
361             builderContext.CurrentSelect = (SelectExpression)scope.Mutate(operands);\r
362             return operands.Count - 1;\r
363         }\r
364 \r
365         protected virtual bool ExpressionEquals(Expression a, Expression b)\r
366         {\r
367             // TODO: something smarter, to compare contents and not only references (works fine only for columns)\r
368             return a == b;\r
369         }\r
370 \r
371         /// <summary>\r
372         /// Registers the table as returned by the SQL request.\r
373         /// Actually, the table is split into its columns.\r
374         /// </summary>\r
375         /// <param name="tableExpression"></param>\r
376         /// <param name="dataRecordParameter"></param>\r
377         /// <param name="mappingContextParameter"></param>\r
378         /// <param name="builderContext"></param>\r
379         /// <returns></returns>\r
380         protected virtual Expression GetOutputTableReader(TableExpression tableExpression,\r
381                                                           ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter,\r
382                                                           BuilderContext builderContext)\r
383         {\r
384             var bindings = new List<MemberBinding>();\r
385             \r
386             foreach (ColumnExpression columnExpression in RegisterAllColumns(tableExpression, builderContext))\r
387             {\r
388                 MemberInfo memberInfo = columnExpression.StorageInfo ?? columnExpression.MemberInfo;\r
389                 PropertyInfo propertyInfo = memberInfo as PropertyInfo;\r
390                 if (propertyInfo == null || propertyInfo.CanWrite)\r
391                 {\r
392                     var parameterColumn = GetOutputValueReader(columnExpression,\r
393                                                                dataRecordParameter, mappingContextParameter, builderContext);\r
394                     var binding = Expression.Bind(memberInfo, parameterColumn);\r
395                     bindings.Add(binding);\r
396                 }\r
397             }\r
398             var newExpression = Expression.New(tableExpression.Type);\r
399             var initExpression = Expression.MemberInit(newExpression, bindings);\r
400             return initExpression;\r
401         }\r
402 \r
403         /// <summary>\r
404         /// Builds a Row builder, based on a given list of parameters\r
405         /// </summary>\r
406         /// <param name="tableType"></param>\r
407         /// <param name="parameters"></param>\r
408         /// <param name="builderContext"></param>\r
409         /// <returns></returns>\r
410         public virtual LambdaExpression BuildTableReader(Type tableType, IList<string> parameters, BuilderContext builderContext)\r
411         {\r
412             var dataRecordParameter = Expression.Parameter(typeof(IDataRecord), "dataRecord");\r
413             var mappingContextParameter = Expression.Parameter(typeof(MappingContext), "mappingContext");\r
414             //var table = builderContext.QueryContext.DataContext.Mapping.GetTable(tableType);\r
415             var bindings = new List<MemberBinding>();\r
416             for (int parameterIndex = 0; parameterIndex < parameters.Count; parameterIndex++)\r
417             {\r
418                                 var parameter = parameters[parameterIndex];\r
419                                 var memberInfo = tableType.GetTableColumnMember(parameter);\r
420                 if (memberInfo == null)\r
421                 {\r
422                     memberInfo = tableType.GetSingleMember(parameter, BindingFlags.Public | BindingFlags.NonPublic\r
423                                                                       | BindingFlags.Instance | BindingFlags.IgnoreCase);\r
424                 }\r
425                 // TODO real error\r
426                 if (memberInfo == null)\r
427                     throw new ArgumentException(string.Format("Invalid column '{0}'", parameter));\r
428                 //var column = DataMapper.GetColumnName(tableType, memberInfo, builderContext.QueryContext.DataContext);\r
429                 //var columnName = DataMapper.GetColumnName(tableType, memberInfo, builderContext.QueryContext.DataContext);\r
430                 var invoke = GetOutputValueReader(memberInfo.GetMemberType(), parameterIndex, //GetTableIndex(parameters, columnName),\r
431                                                   dataRecordParameter, mappingContextParameter);\r
432                 var parameterColumn = GetOutputValueReader(invoke, dataRecordParameter, mappingContextParameter,\r
433                                                            builderContext);\r
434                 var binding = Expression.Bind(memberInfo, parameterColumn);\r
435                 bindings.Add(binding);\r
436             }\r
437             var newExpression = Expression.New(tableType);\r
438             var initExpression = Expression.MemberInit(newExpression, bindings);\r
439             return Expression.Lambda(initExpression, dataRecordParameter, mappingContextParameter);\r
440         }\r
441 \r
442         protected virtual int GetTableIndex(IList<string> parameters, string columnName)\r
443         {\r
444             int index = parameters.IndexOf(columnName);\r
445             if (index >= 0)\r
446                 return index;\r
447             for (index = 0; index < parameters.Count; index++)\r
448             {\r
449                 if (string.Compare(parameters[index], columnName, true) == 0)\r
450                 {\r
451                     return index;\r
452                 }\r
453             }\r
454             return -1;\r
455         }\r
456 \r
457         /// <summary>\r
458         /// Creates an entity set creator, to be used at run-time\r
459         /// </summary>\r
460         /// <param name="expression"></param>\r
461         /// <param name="dataRecordParameter"></param>\r
462         /// <param name="mappingContextParameter"></param>\r
463         /// <param name="builderContext"></param>\r
464         /// <returns></returns>\r
465         protected virtual Expression GetEntitySetBuilder(EntitySetExpression expression,\r
466                                                           ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter,\r
467                                                           BuilderContext builderContext)\r
468         {\r
469             // from here, creating an EntitySet consists in just creating the instance\r
470             return Expression.New(expression.Type);\r
471         }\r
472 \r
473         /// <summary>\r
474         /// Registers the expression as returned by the SQL request.\r
475         /// </summary>\r
476         /// <param name="expression"></param>\r
477         /// <param name="dataRecordParameter"></param>\r
478         /// <param name="mappingContextParameter"></param>\r
479         /// <param name="builderContext"></param>\r
480         /// <returns></returns>\r
481         protected virtual Expression GetOutputValueReader(Expression expression,\r
482                                                           ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter,\r
483                                                           BuilderContext builderContext)\r
484         {\r
485             int valueIndex = RegisterOutputParameter(expression, builderContext);\r
486             return GetOutputValueReader(expression.Type, valueIndex, dataRecordParameter, mappingContextParameter);\r
487         }\r
488 \r
489         /// <summary>\r
490         /// Registers the ColumnExpression as returned by the SQL request.\r
491         /// </summary>\r
492         /// <param name="expression"></param>\r
493         /// <param name="dataRecordParameter"></param>\r
494         /// <param name="mappingContextParameter"></param>\r
495         /// <param name="builderContext"></param>\r
496         /// <returns></returns>\r
497         protected virtual Expression GetOutputValueReader(ColumnExpression expression,\r
498                                                           ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter,\r
499                                                           BuilderContext builderContext)\r
500         {\r
501             int valueIndex = RegisterOutputParameter(expression, builderContext);\r
502             Type storageType = expression.StorageInfo != null ? expression.StorageInfo.GetMemberType() : null;\r
503             return GetOutputValueReader(storageType ?? expression.Type, valueIndex, dataRecordParameter, mappingContextParameter);\r
504         }\r
505 \r
506 \r
507         /// <summary>\r
508         /// Registers the expression as returned column\r
509         /// </summary>\r
510         /// <param name="columnType"></param>\r
511         /// <param name="valueIndex"></param>\r
512         /// <param name="dataRecordParameter"></param>\r
513         /// <param name="mappingContextParameter"></param>\r
514         /// <returns></returns>\r
515         protected virtual Expression GetOutputValueReader(Type columnType, int valueIndex, ParameterExpression dataRecordParameter,\r
516                                                           ParameterExpression mappingContextParameter)\r
517         {\r
518             var propertyReaderLambda = DataRecordReader.GetPropertyReader(columnType);\r
519             Expression invoke = Expression.Invoke(propertyReaderLambda, dataRecordParameter,\r
520                                                   mappingContextParameter, Expression.Constant(valueIndex));\r
521             if (!columnType.IsNullable())\r
522                 invoke = Expression.Convert(invoke, columnType);\r
523             return invoke;\r
524         }\r
525     }\r
526 }