New tests.
[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 #if MONO_STRICT\r
40 using DataContext = System.Data.Linq.DataContext;\r
41 #else\r
42 using DataContext = DbLinq.Data.Linq.DataContext;\r
43 #endif\r
44 \r
45 namespace DbLinq.Data.Linq.Sugar.Implementation\r
46 {\r
47     internal partial class ExpressionDispatcher\r
48     {\r
49 \r
50         /// <summary>\r
51         /// Returns a registered column, or null if not found\r
52         /// This method requires the table to be already registered\r
53         /// </summary>\r
54         /// <param name="table"></param>\r
55         /// <param name="name"></param>\r
56         /// <param name="builderContext"></param>\r
57         /// <returns></returns>\r
58         protected virtual ColumnExpression GetRegisteredColumn(TableExpression table, string name,\r
59                                                                BuilderContext builderContext)\r
60         {\r
61             return\r
62                 (from queryColumn in builderContext.EnumerateScopeColumns()\r
63                  where queryColumn.Table.IsEqualTo(table) && queryColumn.Name == name\r
64                  select queryColumn).SingleOrDefault();\r
65         }\r
66 \r
67         /// <summary>\r
68         /// Returns an existing table or registers the current one\r
69         /// </summary>\r
70         /// <param name="tableExpression"></param>\r
71         /// <param name="builderContext"></param>\r
72         /// <returns>A registered table or the current newly registered one</returns>\r
73         public virtual TableExpression RegisterTable(TableExpression tableExpression, BuilderContext builderContext)\r
74         {\r
75             // 1. Find the table in current scope\r
76             var foundTableExpression = (from t in builderContext.EnumerateScopeTables()\r
77                                         where t.IsEqualTo(tableExpression)\r
78                                         select t).SingleOrDefault();\r
79             if (foundTableExpression != null)\r
80                 return foundTableExpression;\r
81             // 2. Find it in all scopes, and promote it to current scope.\r
82             foundTableExpression = PromoteTable(tableExpression, builderContext);\r
83             if (foundTableExpression != null)\r
84                 return foundTableExpression;\r
85             // 3. Add it\r
86             builderContext.CurrentSelect.Tables.Add(tableExpression);\r
87             return tableExpression;\r
88         }\r
89 \r
90         /// <summary>\r
91         /// Promotes a table to a common parent between its current scope and our current scope\r
92         /// </summary>\r
93         /// <param name="tableExpression"></param>\r
94         /// <param name="builderContext"></param>\r
95         /// <returns></returns>\r
96         protected virtual TableExpression PromoteTable(TableExpression tableExpression, BuilderContext builderContext)\r
97         {\r
98             int currentIndex = 0;\r
99             SelectExpression oldSelect = null;\r
100             SelectExpression commonScope = null;\r
101             TableExpression foundTable = null;\r
102             do\r
103             {\r
104                 // take a select\r
105                 oldSelect = builderContext.SelectExpressions[currentIndex];\r
106 \r
107                 // look for a common scope\r
108                 if (oldSelect != builderContext.CurrentSelect)\r
109                 {\r
110                     commonScope = FindCommonScope(oldSelect, builderContext.CurrentSelect);\r
111                     if (commonScope != null)\r
112                         // if a common scope exists, look for an equivalent table in that select\r
113                         for (int tableIndex = 0; tableIndex < oldSelect.Tables.Count && foundTable == null; tableIndex++)\r
114                         {\r
115                             if (oldSelect.Tables[tableIndex].IsEqualTo(tableExpression))\r
116                             {\r
117                                 // found a matching table!\r
118                                 foundTable = oldSelect.Tables[tableIndex];\r
119                             }\r
120                         }\r
121                 }\r
122                 ++currentIndex;\r
123             }\r
124             while (currentIndex < builderContext.SelectExpressions.Count && foundTable == null);\r
125 \r
126             if (foundTable != null)\r
127             {\r
128                 oldSelect.Tables.Remove(foundTable);\r
129                 commonScope.Tables.Add(foundTable);\r
130             }\r
131             return foundTable;\r
132         }\r
133 \r
134         /// <summary>\r
135         /// Find the common ancestor between two ScopeExpressions\r
136         /// </summary>\r
137         /// <param name="a"></param>\r
138         /// <param name="b"></param>\r
139         /// <returns></returns>\r
140         protected virtual SelectExpression FindCommonScope(SelectExpression a, SelectExpression b)\r
141         {\r
142             for (var aScope = a; aScope != null; aScope = aScope.Parent)\r
143             {\r
144                 for (var bScope = b; bScope != null; bScope = bScope.Parent)\r
145                 {\r
146                     if (aScope == bScope)\r
147                         return aScope;\r
148                 }\r
149             }\r
150             return null;\r
151         }\r
152 \r
153         /// <summary>\r
154         /// Registers a column\r
155         /// This method requires the table to be already registered\r
156         /// </summary>\r
157         /// <param name="table"></param>\r
158         /// <param name="memberInfo"></param>\r
159         /// <param name="name"></param>\r
160         /// <param name="builderContext"></param>\r
161         /// <returns></returns>\r
162         public ColumnExpression RegisterColumn(TableExpression table,\r
163                                                MemberInfo memberInfo, string name,\r
164                                                BuilderContext builderContext)\r
165         {\r
166             if (memberInfo == null)\r
167                 return null;\r
168             var queryColumn = GetRegisteredColumn(table, name, builderContext);\r
169             if (queryColumn == null)\r
170             {\r
171                 table = RegisterTable(table, builderContext);\r
172                 queryColumn = CreateColumn(table, memberInfo, builderContext);\r
173                 builderContext.CurrentSelect.Columns.Add(queryColumn);\r
174             }\r
175             return queryColumn;\r
176         }\r
177 \r
178         /// <summary>\r
179         /// Registers a column with only a table and a MemberInfo (this is the preferred method overload)\r
180         /// </summary>\r
181         /// <param name="tableExpression"></param>\r
182         /// <param name="memberInfo"></param>\r
183         /// <param name="builderContext"></param>\r
184         /// <returns></returns>\r
185         public ColumnExpression RegisterColumn(TableExpression tableExpression, MemberInfo memberInfo,\r
186                                                BuilderContext builderContext)\r
187         {\r
188             var dataMember = builderContext.QueryContext.DataContext.Mapping.GetTable(tableExpression.Type).RowType\r
189                 .GetDataMember(memberInfo);\r
190             if (dataMember == null)\r
191                 return null;\r
192             return RegisterColumn(tableExpression, memberInfo, dataMember.MappedName, builderContext);\r
193         }\r
194 \r
195         public ColumnExpression CreateColumn(TableExpression table, MemberInfo memberInfo, BuilderContext builderContext)\r
196         {\r
197             var dataMember = builderContext.QueryContext.DataContext.Mapping.GetTable(table.Type).RowType\r
198                 .GetDataMember(memberInfo);\r
199             if (dataMember == null)\r
200                 return null;\r
201             return new ColumnExpression(table, dataMember);\r
202         }\r
203 \r
204         /// <summary>\r
205         /// Creates a default TableExpression\r
206         /// </summary>\r
207         /// <param name="tableType"></param>\r
208         /// <param name="builderContext"></param>\r
209         /// <returns></returns>\r
210         public virtual TableExpression CreateTable(Type tableType, BuilderContext builderContext)\r
211         {\r
212             return new TableExpression(tableType, DataMapper.GetTableName(tableType, builderContext.QueryContext.DataContext));\r
213         }\r
214 \r
215         /// <summary>\r
216         /// Registers an association\r
217         /// </summary>\r
218         /// <param name="tableExpression">The table holding the member, to become the joinedTable</param>\r
219         /// <param name="tableMemberInfo"></param>\r
220         /// <param name="otherType"></param>\r
221         /// <param name="builderContext"></param>\r
222         /// <returns></returns>\r
223         public virtual TableExpression RegisterAssociation(TableExpression tableExpression, MemberInfo tableMemberInfo,\r
224                                                            Type otherType, BuilderContext builderContext)\r
225         {\r
226             IList<MemberInfo> otherKeys;\r
227             TableJoinType joinType;\r
228             string joinID;\r
229             var theseKeys = DataMapper.GetAssociation(tableExpression, tableMemberInfo, otherType, out otherKeys,\r
230                                                       out joinType, out joinID, builderContext.QueryContext.DataContext);\r
231             // if the memberInfo has no corresponding association, we get a null, that we propagate\r
232             if (theseKeys == null)\r
233                 return null;\r
234 \r
235             // the current table has the foreign key, the other table the referenced (usually primary) key\r
236             if (theseKeys.Count != otherKeys.Count)\r
237                 throw Error.BadArgument("S0128: Association arguments (FK and ref'd PK) don't match");\r
238 \r
239             // we first create the table, with the JoinID, and we MUST complete the table later, with the Join() method\r
240             var otherTableExpression = new TableExpression(otherType, DataMapper.GetTableName(otherType, builderContext.QueryContext.DataContext), joinID);\r
241 \r
242             Expression joinExpression = null;\r
243 \r
244             var createdColumns = new List<ColumnExpression>();\r
245             for (int keyIndex = 0; keyIndex < theseKeys.Count; keyIndex++)\r
246             {\r
247                 // joinedKey is registered, even if unused by final select (required columns will be filtered anyway)\r
248                 Expression otherKey = RegisterColumn(otherTableExpression, otherKeys[keyIndex], builderContext);\r
249                 // foreign is created, we will store it later if this assocation is registered too\r
250                 Expression thisKey = CreateColumn(tableExpression, theseKeys[keyIndex], builderContext);\r
251                 createdColumns.Add((ColumnExpression)thisKey);\r
252 \r
253                 // if the key is nullable, then convert it\r
254                 // TODO: this will probably need to be changed\r
255                 if (otherKey.Type.IsNullable())\r
256                     otherKey = Expression.Convert(otherKey, otherKey.Type.GetNullableType());\r
257                 if (thisKey.Type.IsNullable())\r
258                     thisKey = Expression.Convert(thisKey, thisKey.Type.GetNullableType());\r
259                 // the other key is set as left operand, this must be this way\r
260                 // since some vendors (SQL Server) don't support the opposite\r
261                 var referenceExpression = Expression.Equal(otherKey, thisKey);\r
262 \r
263                 // if we already have a join expression, then we have a double condition here, so "AND" it\r
264                 if (joinExpression != null)\r
265                     joinExpression = Expression.And(joinExpression, referenceExpression);\r
266                 else\r
267                     joinExpression = referenceExpression;\r
268             }\r
269             // we complete the table here, now that we have all join information\r
270             otherTableExpression.Join(joinType, tableExpression, joinExpression);\r
271 \r
272             // our table is created, with the expressions\r
273             // now check if we didn't register exactly the same\r
274             var existingTable = (from t in builderContext.EnumerateScopeTables() where t.IsEqualTo(otherTableExpression) select t).SingleOrDefault();\r
275             if (existingTable != null)\r
276                 return existingTable;\r
277  \r
278             builderContext.CurrentSelect.Tables.Add(otherTableExpression);\r
279             foreach (var createdColumn in createdColumns)\r
280                 builderContext.CurrentSelect.Columns.Add(createdColumn);\r
281             return otherTableExpression;\r
282         }\r
283 \r
284         /// <summary>\r
285         /// Registers an external parameter\r
286         /// Since these can be complex expressions, we don't try to identify them\r
287         /// and push them every time\r
288         /// The only loss may be a small memory loss (if anyone can prove me that the same Expression can be used twice)\r
289         /// </summary>\r
290         /// <param name="expression"></param>\r
291         /// <param name="alias"></param>\r
292         /// <param name="builderContext"></param>\r
293         /// <returns></returns>\r
294         public virtual InputParameterExpression RegisterParameter(Expression expression, string alias, BuilderContext builderContext)\r
295         {\r
296             var queryParameterExpression = new InputParameterExpression(expression, alias);\r
297             builderContext.ExpressionQuery.Parameters.Add(queryParameterExpression);\r
298             return queryParameterExpression;\r
299         }\r
300 \r
301         public virtual void UnregisterParameter(InputParameterExpression expression, BuilderContext builderContext)\r
302         {\r
303             builderContext.ExpressionQuery.Parameters.Remove(expression);\r
304         }\r
305 \r
306         /// <summary>\r
307         /// Registers a MetaTable\r
308         /// </summary>\r
309         /// <param name="metaTableType"></param>\r
310         /// <param name="aliases"></param>\r
311         /// <param name="builderContext"></param>\r
312         /// <returns></returns>\r
313         public virtual MetaTableExpression RegisterMetaTable(Type metaTableType, IDictionary<MemberInfo, MutableExpression> aliases,\r
314                                                              BuilderContext builderContext)\r
315         {\r
316             MetaTableExpression metaTableExpression;\r
317             if (!builderContext.MetaTables.TryGetValue(metaTableType, out metaTableExpression))\r
318             {\r
319                 metaTableExpression = new MetaTableExpression(aliases, metaTableType);\r
320                 builderContext.MetaTables[metaTableType] = metaTableExpression;\r
321             }\r
322             return metaTableExpression;\r
323         }\r
324 \r
325         /// <summary>\r
326         /// Registers a where clause in the current context scope\r
327         /// </summary>\r
328         /// <param name="whereExpression"></param>\r
329         /// <param name="builderContext"></param>\r
330         public virtual void RegisterWhere(Expression whereExpression, BuilderContext builderContext)\r
331         {\r
332             builderContext.CurrentSelect.Where.Add(whereExpression);\r
333         }\r
334 \r
335         /// <summary>\r
336         /// Registers all columns of a table.\r
337         /// </summary>\r
338         /// <param name="tableExpression"></param>\r
339         /// <param name="builderContext"></param>\r
340         /// <returns></returns>\r
341         public virtual IEnumerable<ColumnExpression> RegisterAllColumns(TableExpression tableExpression, BuilderContext builderContext)\r
342         {\r
343             foreach (var metaMember in builderContext.QueryContext.DataContext.Mapping.GetTable(tableExpression.Type).RowType.PersistentDataMembers)\r
344             {\r
345                 yield return RegisterColumn(tableExpression, metaMember.Member, builderContext);\r
346             }\r
347         }\r
348 \r
349         /// <summary>\r
350         /// Registers an expression to be returned by main request.\r
351         /// The strategy is to try to find it in the already registered parameters, and if not found, add it\r
352         /// </summary>\r
353         /// <param name="expression">The expression to be registered</param>\r
354         /// <param name="builderContext"></param>\r
355         /// <returns>Expression index</returns>\r
356         public virtual int RegisterOutputParameter(Expression expression, BuilderContext builderContext)\r
357         {\r
358             var scope = builderContext.CurrentSelect;\r
359             var operands = scope.Operands.ToList();\r
360             for (int index = 0; index < operands.Count; index++)\r
361             {\r
362                 if (ExpressionEquals(operands[index], expression))\r
363                     return index;\r
364             }\r
365             operands.Add(expression);\r
366             builderContext.CurrentSelect = (SelectExpression)scope.Mutate(operands);\r
367             return operands.Count - 1;\r
368         }\r
369 \r
370         protected virtual bool ExpressionEquals(Expression a, Expression b)\r
371         {\r
372             // TODO: something smarter, to compare contents and not only references (works fine only for columns)\r
373             return a == b;\r
374         }\r
375 \r
376         /// <summary>\r
377         /// Registers the table as returned by the SQL request.\r
378         /// Actually, the table is split into its columns.\r
379         /// </summary>\r
380         /// <param name="tableExpression"></param>\r
381         /// <param name="dataRecordParameter"></param>\r
382         /// <param name="mappingContextParameter"></param>\r
383         /// <param name="builderContext"></param>\r
384         /// <returns></returns>\r
385         protected virtual Expression GetOutputTableReader(TableExpression tableExpression,\r
386                                                           ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter,\r
387                                                           BuilderContext builderContext)\r
388         {\r
389             var bindings = new List<MemberBinding>();\r
390             \r
391             foreach (ColumnExpression columnExpression in RegisterAllColumns(tableExpression, builderContext))\r
392             {\r
393                 MemberInfo memberInfo = columnExpression.StorageInfo ?? columnExpression.MemberInfo;\r
394                 PropertyInfo propertyInfo = memberInfo as PropertyInfo;\r
395                 if (propertyInfo == null || propertyInfo.CanWrite)\r
396                 {\r
397                     var parameterColumn = GetOutputValueReader(columnExpression,\r
398                                                                dataRecordParameter, mappingContextParameter, builderContext);\r
399                     var binding = Expression.Bind(memberInfo, parameterColumn);\r
400                     bindings.Add(binding);\r
401                 }\r
402             }\r
403             var newExpression = Expression.New(tableExpression.Type);\r
404             var initExpression = Expression.MemberInit(newExpression, bindings);\r
405             return initExpression;\r
406         }\r
407 \r
408         /// <summary>\r
409         /// Builds a Row builder, based on a given list of parameters\r
410         /// </summary>\r
411         /// <param name="tableType"></param>\r
412         /// <param name="parameters"></param>\r
413         /// <param name="builderContext"></param>\r
414         /// <returns></returns>\r
415         public virtual LambdaExpression BuildTableReader(Type tableType, IList<string> parameters, BuilderContext builderContext)\r
416         {\r
417             var dataRecordParameter = Expression.Parameter(typeof(IDataRecord), "dataRecord");\r
418             var mappingContextParameter = Expression.Parameter(typeof(MappingContext), "mappingContext");\r
419             //var table = builderContext.QueryContext.DataContext.Mapping.GetTable(tableType);\r
420             var bindings = new List<MemberBinding>();\r
421             for (int parameterIndex = 0; parameterIndex < parameters.Count; parameterIndex++)\r
422             {\r
423                                 var parameter = parameters[parameterIndex];\r
424                                 var memberInfo = tableType.GetTableColumnMember(parameter);\r
425                 if (memberInfo == null)\r
426                 {\r
427                     memberInfo = tableType.GetSingleMember(parameter, BindingFlags.Public | BindingFlags.NonPublic\r
428                                                                       | BindingFlags.Instance | BindingFlags.IgnoreCase);\r
429                 }\r
430                 // TODO real error\r
431                 if (memberInfo == null)\r
432                     throw new ArgumentException(string.Format("Invalid column '{0}'", parameter));\r
433                 //var column = DataMapper.GetColumnName(tableType, memberInfo, builderContext.QueryContext.DataContext);\r
434                 //var columnName = DataMapper.GetColumnName(tableType, memberInfo, builderContext.QueryContext.DataContext);\r
435                 var invoke = GetOutputValueReader(memberInfo.GetMemberType(), parameterIndex, //GetTableIndex(parameters, columnName),\r
436                                                   dataRecordParameter, mappingContextParameter);\r
437                 var parameterColumn = GetOutputValueReader(invoke, dataRecordParameter, mappingContextParameter,\r
438                                                            builderContext);\r
439                 var binding = Expression.Bind(memberInfo, parameterColumn);\r
440                 bindings.Add(binding);\r
441             }\r
442             var newExpression = Expression.New(tableType);\r
443             var initExpression = Expression.MemberInit(newExpression, bindings);\r
444             return Expression.Lambda(initExpression, dataRecordParameter, mappingContextParameter);\r
445         }\r
446 \r
447         protected virtual int GetTableIndex(IList<string> parameters, string columnName)\r
448         {\r
449             int index = parameters.IndexOf(columnName);\r
450             if (index >= 0)\r
451                 return index;\r
452             for (index = 0; index < parameters.Count; index++)\r
453             {\r
454                 if (string.Compare(parameters[index], columnName, true) == 0)\r
455                 {\r
456                     return index;\r
457                 }\r
458             }\r
459             return -1;\r
460         }\r
461 \r
462         /// <summary>\r
463         /// Creates an entity set creator, to be used at run-time\r
464         /// </summary>\r
465         /// <param name="expression"></param>\r
466         /// <param name="dataRecordParameter"></param>\r
467         /// <param name="mappingContextParameter"></param>\r
468         /// <param name="builderContext"></param>\r
469         /// <returns></returns>\r
470         protected virtual Expression GetEntitySetBuilder(EntitySetExpression expression,\r
471                                                           ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter,\r
472                                                           BuilderContext builderContext)\r
473         {\r
474             var entityType = expression.EntitySetType.GetGenericArguments()[0];\r
475             List<ElementInit> members = new List<ElementInit>();\r
476             var add = expression.EntitySetType.GetMethod("Add", \r
477                     BindingFlags.NonPublic | BindingFlags.Instance,\r
478                     null,\r
479                     new Type[] { typeof(KeyValuePair<object, MemberInfo>) },\r
480                     null);\r
481 \r
482             foreach (var info in expression.Columns)\r
483             {\r
484                 var column = info.Key;\r
485                 var tk = info.Value;\r
486                 MemberInfo memberInfo = column.StorageInfo ?? column.MemberInfo;\r
487                 PropertyInfo propertyInfo = memberInfo as PropertyInfo;\r
488                 if (propertyInfo == null || propertyInfo.CanWrite)\r
489                 {\r
490                     var parameterColumn = GetOutputValueReader(column,\r
491                             dataRecordParameter, mappingContextParameter, builderContext);\r
492                     members.Add(Expression.ElementInit(add, \r
493                             new Expression[]{\r
494                                 Expression.New(typeof(KeyValuePair<object, MemberInfo>).GetConstructor(new Type[]{typeof(object), typeof(MemberInfo)}),\r
495                                     Expression.Convert(parameterColumn, typeof(object)), \r
496                                     Expression.Constant(tk.Member, typeof(MemberInfo)))}));\r
497                 }\r
498             }\r
499 \r
500             return Expression.ListInit(\r
501                     Expression.New(\r
502                         expression.EntitySetType.GetConstructor(\r
503                             BindingFlags.NonPublic | BindingFlags.Instance,\r
504                             null,\r
505                             new[] { typeof(DataContext) },\r
506                             null),\r
507                         Expression.Constant(builderContext.QueryContext.DataContext)),\r
508                     members);\r
509         }\r
510 \r
511         /// <summary>\r
512         /// Registers the expression as returned by the SQL request.\r
513         /// </summary>\r
514         /// <param name="expression"></param>\r
515         /// <param name="dataRecordParameter"></param>\r
516         /// <param name="mappingContextParameter"></param>\r
517         /// <param name="builderContext"></param>\r
518         /// <returns></returns>\r
519         protected virtual Expression GetOutputValueReader(Expression expression,\r
520                                                           ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter,\r
521                                                           BuilderContext builderContext)\r
522         {\r
523             int valueIndex = RegisterOutputParameter(expression, builderContext);\r
524             return GetOutputValueReader(expression.Type, valueIndex, dataRecordParameter, mappingContextParameter);\r
525         }\r
526 \r
527         /// <summary>\r
528         /// Registers the ColumnExpression as returned by the SQL request.\r
529         /// </summary>\r
530         /// <param name="expression"></param>\r
531         /// <param name="dataRecordParameter"></param>\r
532         /// <param name="mappingContextParameter"></param>\r
533         /// <param name="builderContext"></param>\r
534         /// <returns></returns>\r
535         protected virtual Expression GetOutputValueReader(ColumnExpression expression,\r
536                                                           ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter,\r
537                                                           BuilderContext builderContext)\r
538         {\r
539             int valueIndex = RegisterOutputParameter(expression, builderContext);\r
540             Type storageType = expression.StorageInfo != null ? expression.StorageInfo.GetMemberType() : null;\r
541             return GetOutputValueReader(storageType ?? expression.Type, valueIndex, dataRecordParameter, mappingContextParameter);\r
542         }\r
543 \r
544 \r
545         /// <summary>\r
546         /// Registers the expression as returned column\r
547         /// </summary>\r
548         /// <param name="columnType"></param>\r
549         /// <param name="valueIndex"></param>\r
550         /// <param name="dataRecordParameter"></param>\r
551         /// <param name="mappingContextParameter"></param>\r
552         /// <returns></returns>\r
553         protected virtual Expression GetOutputValueReader(Type columnType, int valueIndex, ParameterExpression dataRecordParameter,\r
554                                                           ParameterExpression mappingContextParameter)\r
555         {\r
556             var propertyReaderLambda = DataRecordReader.GetPropertyReader(columnType);\r
557             Expression invoke = new ParameterBinder().BindParams(propertyReaderLambda,\r
558                 dataRecordParameter, mappingContextParameter, Expression.Constant(valueIndex));\r
559             if (!columnType.IsNullable())\r
560                 invoke = Expression.Convert(invoke, columnType);\r
561             return invoke;\r
562         }\r
563     }\r
564 \r
565     class ParameterBinder\r
566     {\r
567         Dictionary<Expression, Expression> map;\r
568 \r
569         public Expression BindParams(LambdaExpression expr, params Expression[] args)\r
570         {\r
571             map = new Dictionary<Expression, Expression>();\r
572 \r
573             if (expr.Parameters.Count != args.Length)\r
574                 throw new NotImplementedException();\r
575             for (int i = 0; i < expr.Parameters.Count; ++i)\r
576                 map[expr.Parameters[i]] = args[i];\r
577             return Visit(expr.Body);\r
578         }\r
579 \r
580         Expression Visit(Expression expr)\r
581         {\r
582             switch (expr.NodeType)\r
583             {\r
584                 case ExpressionType.Call:\r
585                     MethodCallExpression call = expr as MethodCallExpression;\r
586                     Expression[] new_args = new Expression[call.Arguments.Count];\r
587                     for (int i = 0; i < new_args.Length; ++i)\r
588                         new_args[i] = Visit(call.Arguments[i]);\r
589                     return Expression.Call(call.Object, call.Method, new_args);\r
590                 case ExpressionType.Parameter:\r
591                     Expression new_expr;\r
592                     if (map.TryGetValue(expr, out new_expr))\r
593                         return new_expr;\r
594                     break;\r
595                 default:\r
596                     throw new Exception("Can't handle " + expr.NodeType);\r
597             }\r
598 \r
599             return expr;\r
600         }\r
601     }\r
602 }