PageRenderTime 61ms CodeModel.GetById 18ms app.highlight 31ms RepoModel.GetById 2ms app.codeStats 0ms

/mcs/class/System.Data.Linq/src/DbLinq/Data/Linq/Sugar/Implementation/ExpressionDispatcher.Analyzer.cs

https://bitbucket.org/puffnfresh/mono-dependency-analysis
C# | 1648 lines | 1160 code | 152 blank | 336 comment | 245 complexity | 70c18b9b0029d1d7d5df93da8b885d06 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1#region MIT license
  2// 
  3// MIT license
  4//
  5// Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
  6// 
  7// Permission is hereby granted, free of charge, to any person obtaining a copy
  8// of this software and associated documentation files (the "Software"), to deal
  9// in the Software without restriction, including without limitation the rights
 10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11// copies of the Software, and to permit persons to whom the Software is
 12// furnished to do so, subject to the following conditions:
 13// 
 14// The above copyright notice and this permission notice shall be included in
 15// all copies or substantial portions of the Software.
 16// 
 17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23// THE SOFTWARE.
 24// 
 25#endregion
 26
 27using System;
 28using System.Collections;
 29using System.Collections.Generic;
 30using System.Linq;
 31using System.Linq.Expressions;
 32using System.Reflection;
 33
 34#if MONO_STRICT
 35using System.Data.Linq;
 36#else
 37using DbLinq.Data.Linq;
 38#endif
 39using DbLinq.Data.Linq.Implementation;
 40using DbLinq.Data.Linq.Sugar;
 41using DbLinq.Data.Linq.Sugar.ExpressionMutator;
 42using DbLinq.Data.Linq.Sugar.Expressions;
 43using DbLinq.Data.Linq.Sugar.Implementation;
 44using DbLinq.Factory;
 45using DbLinq.Util;
 46
 47namespace DbLinq.Data.Linq.Sugar.Implementation
 48{
 49    partial class ExpressionDispatcher
 50    {
 51        public Expression Analyze(ExpressionChain expressions, Expression parameter, BuilderContext builderContext)
 52        {
 53            Expression tableExpression = parameter;
 54
 55            Expression last = expressions.Last();
 56            IExpressionLanguageParser languageParser = ObjectFactory.Get<IExpressionLanguageParser>();
 57            foreach (Expression e in expressions)
 58            {
 59                if (e == last)
 60                    builderContext.IsExternalInExpressionChain = true;
 61
 62                // write full debug
 63#if DEBUG && !MONO_STRICT
 64                var log = builderContext.QueryContext.DataContext.Log;
 65                if (log != null)
 66                    log.WriteExpression(e);
 67#endif
 68
 69                // Convert linq Expressions to QueryOperationExpressions and QueryConstantExpressions 
 70                // Query expressions language identification
 71                var currentExpression = languageParser.Parse(e, builderContext);
 72                // Query expressions query identification 
 73                currentExpression = this.Analyze(currentExpression, tableExpression, builderContext);
 74
 75                if (!builderContext.IsExternalInExpressionChain)
 76                {
 77                    EntitySetExpression setExpression = currentExpression as EntitySetExpression;
 78                    if (setExpression != null)
 79                        currentExpression = setExpression.TableExpression;
 80                }
 81                tableExpression = currentExpression;
 82            }
 83
 84            return tableExpression;
 85        }
 86
 87        /// <summary>
 88        /// Entry point for Analyzis
 89        /// </summary>
 90        /// <param name="expression"></param>
 91        /// <param name="parameter"></param>
 92        /// <param name="builderContext"></param>
 93        /// <returns></returns>
 94        protected virtual Expression Analyze(Expression expression, Expression parameter, BuilderContext builderContext)
 95        {
 96            return Analyze(expression, new[] { parameter }, builderContext);
 97        }
 98
 99        protected virtual Expression Analyze(Expression expression, BuilderContext builderContext)
100        {
101            return Analyze(expression, new Expression[0], builderContext);
102        }
103
104        protected virtual Expression Analyze(Expression expression, IList<Expression> parameters, BuilderContext builderContext)
105        {
106            switch (expression.NodeType)
107            {
108                case ExpressionType.Call:
109                    return AnalyzeCall((MethodCallExpression)expression, parameters, builderContext);
110                case ExpressionType.Lambda:
111                    return AnalyzeLambda(expression, parameters, builderContext);
112                case ExpressionType.Parameter:
113                    return AnalyzeParameter(expression, builderContext);
114                case ExpressionType.Quote:
115                    return AnalyzeQuote(expression, parameters, builderContext);
116                case ExpressionType.MemberAccess:
117                    return AnalyzeMember(expression, builderContext);
118                #region case ExpressionType.<Common operators>:
119                case ExpressionType.Add:
120                case ExpressionType.AddChecked:
121                case ExpressionType.Divide:
122                case ExpressionType.Modulo:
123                case ExpressionType.Multiply:
124                case ExpressionType.MultiplyChecked:
125                case ExpressionType.Power:
126                case ExpressionType.Subtract:
127                case ExpressionType.SubtractChecked:
128                case ExpressionType.And:
129                case ExpressionType.Or:
130                case ExpressionType.ExclusiveOr:
131                case ExpressionType.LeftShift:
132                case ExpressionType.RightShift:
133                case ExpressionType.AndAlso:
134                case ExpressionType.OrElse:
135                case ExpressionType.Equal:
136                case ExpressionType.NotEqual:
137                case ExpressionType.GreaterThanOrEqual:
138                case ExpressionType.GreaterThan:
139                case ExpressionType.LessThan:
140                case ExpressionType.LessThanOrEqual:
141                case ExpressionType.Coalesce:
142                //case ExpressionType.ArrayIndex
143                //case ExpressionType.ArrayLength
144                case ExpressionType.Convert:
145                case ExpressionType.ConvertChecked:
146                case ExpressionType.Negate:
147                case ExpressionType.NegateChecked:
148                case ExpressionType.Not:
149                //case ExpressionType.TypeAs
150                case ExpressionType.UnaryPlus:
151                case ExpressionType.MemberInit:
152                #endregion
153                    return AnalyzeOperator(expression, builderContext);
154                case ExpressionType.New:
155                    return AnalyzeNewOperator(expression, builderContext);
156                case ExpressionType.Constant:
157                    return AnalyzeConstant(expression, builderContext);
158                case ExpressionType.Invoke:
159                    return AnalyzeInvoke(expression, parameters, builderContext);
160            }
161            return expression;
162        }
163
164        /// <summary>
165        /// Analyzes method call, uses specified parameters
166        /// </summary>
167        /// <param name="expression"></param>
168        /// <param name="parameters"></param>
169        /// <param name="builderContext"></param>
170        /// <returns></returns>
171        protected virtual Expression AnalyzeCall(MethodCallExpression expression, IList<Expression> parameters, BuilderContext builderContext)
172        {
173            var operands = expression.GetOperands().ToList();
174            var operarandsToSkip = expression.Method.IsStatic ? 1 : 0;
175            var originalParameters = operands.Skip(parameters.Count + operarandsToSkip);
176            var newParameters = parameters.Union(originalParameters).ToList();
177
178            return AnalyzeQueryableCall(expression.Method, newParameters, builderContext) ??
179                AnalyzeStringCall(expression.Method, newParameters, builderContext) ??
180                AnalyzeMathCall(expression.Method, newParameters, builderContext) ??
181                AnalyzeUnknownCall(expression, newParameters, builderContext);
182        }
183
184        private Expression AnalyzeQueryableCall(MethodInfo method, IList<Expression> parameters, BuilderContext builderContext)
185        {
186            if (!(method.DeclaringType == typeof(Queryable) || method.DeclaringType == typeof(Enumerable)))
187                return null;
188            var popCallStack = PushCallStack(method, builderContext);
189            // all methods to handle are listed here:
190            // ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/fxref_system.core/html/2a54ce9d-76f2-81e2-95bb-59740c85386b.htm
191            string methodName = method.Name;
192            switch (methodName)
193            {
194                case "All":
195                    return popCallStack(AnalyzeAll(parameters, builderContext));
196                case "Any":
197                    return popCallStack(AnalyzeAny(parameters, builderContext));
198                case "Average":
199                    return popCallStack(AnalyzeProjectionQuery(SpecialExpressionType.Average, parameters, builderContext));
200                case "Concat":
201                    return popCallStack(AnalyzeSelectOperation(SelectOperatorType.UnionAll, parameters, builderContext));
202                case "Contains":
203                    return popCallStack(AnalyzeContains(parameters, builderContext));
204                case "Count":
205                    return popCallStack(AnalyzeProjectionQuery(SpecialExpressionType.Count, parameters, builderContext));
206                case "DefaultIfEmpty":
207                    return popCallStack(AnalyzeOuterJoin(parameters, builderContext));
208                case "Distinct":
209                    return popCallStack(AnalyzeDistinct(parameters, builderContext));
210                case "Except":
211                    return popCallStack(AnalyzeSelectOperation(SelectOperatorType.Exception, parameters, builderContext));
212                case "First":
213                case "FirstOrDefault":
214                    return popCallStack(AnalyzeScalar(methodName, 1, parameters, builderContext));
215                case "GroupBy":
216                    return popCallStack(AnalyzeGroupBy(parameters, builderContext));
217                case "GroupJoin":
218                    return popCallStack(AnalyzeGroupJoin(parameters, builderContext));
219                case "Intersect":
220                    return popCallStack(AnalyzeSelectOperation(SelectOperatorType.Intersection, parameters, builderContext));
221                case "Join":
222                    return popCallStack(AnalyzeJoin(parameters, builderContext));
223                case "Last":
224                    return popCallStack(AnalyzeScalar(methodName, null, parameters, builderContext));
225                case "Max":
226                    return popCallStack(AnalyzeProjectionQuery(SpecialExpressionType.Max, parameters, builderContext));
227                case "Min":
228                    return popCallStack(AnalyzeProjectionQuery(SpecialExpressionType.Min, parameters, builderContext));
229                case "OrderBy":
230                case "ThenBy":
231                    return popCallStack(AnalyzeOrderBy(parameters, false, builderContext));
232                case "OrderByDescending":
233                case "ThenByDescending":
234                    return popCallStack(AnalyzeOrderBy(parameters, true, builderContext));
235                case "Select":
236                    return popCallStack(AnalyzeSelect(parameters, builderContext));
237                case "SelectMany":
238                    return popCallStack(AnalyzeSelectMany(parameters, builderContext));
239                case "Single":
240                case "SingleOrDefault":
241                    return popCallStack(AnalyzeScalar(methodName, 2, parameters, builderContext));
242                case "Skip":
243                    return popCallStack(AnalyzeSkip(parameters, builderContext));
244                case "Sum":
245                    return popCallStack(AnalyzeProjectionQuery(SpecialExpressionType.Sum, parameters, builderContext));
246                case "Take":
247                    return popCallStack(AnalyzeTake(parameters, builderContext));
248                case "Union":
249                    return popCallStack(AnalyzeSelectOperation(SelectOperatorType.Union, parameters, builderContext));
250                case "Where":
251                    return popCallStack(AnalyzeWhere(parameters, builderContext));
252                default:
253                    if (method.DeclaringType == typeof(Queryable))
254                        throw Error.BadArgument("S0133: Implement QueryMethod Queryable.{0}.", methodName);
255                    return popCallStack(null);
256            }
257        }
258
259        Func<Expression, Expression> PushCallStack(MethodInfo method, BuilderContext builderContext)
260        {
261
262            builderContext.CallStack.Push(method);
263            Func<Expression, Expression> popCallStack = r =>
264            {
265                builderContext.CallStack.Pop();
266                return r;
267            };
268            return popCallStack;
269        }
270
271        private Expression AnalyzeStringCall(MethodInfo method, IList<Expression> parameters, BuilderContext builderContext)
272        {
273            if (method.DeclaringType != typeof(string))
274                return null;
275            var popCallStack = PushCallStack(method, builderContext);
276            switch (method.Name)
277            {
278                case "Contains":
279                    return popCallStack(AnalyzeLike(parameters, builderContext));
280                case "EndsWith":
281                    return popCallStack(AnalyzeLikeEnd(parameters, builderContext));
282                case "IndexOf":
283                    return popCallStack(AnalyzeGenericSpecialExpressionType(SpecialExpressionType.IndexOf, parameters, builderContext));
284                case "Insert":
285                    return popCallStack(AnalyzeStringInsert(parameters, builderContext));
286                case "Remove":
287                    return popCallStack(AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Remove, parameters, builderContext));
288                case "Replace":
289                    return popCallStack(AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Replace, parameters, builderContext));
290                case "StartsWith":
291                    return popCallStack(AnalyzeLikeStart(parameters, builderContext));
292                case "Substring":
293                    return popCallStack(AnalyzeSubString(parameters, builderContext));
294                case "ToLower":
295                    return popCallStack(AnalyzeToLower(parameters, builderContext));
296                case "ToString":
297                    return popCallStack(AnalyzeToString(method, parameters, builderContext));
298                case "ToUpper":
299                    return popCallStack(AnalyzeToUpper(parameters, builderContext));
300                case "Trim":
301                    return popCallStack(AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Trim, parameters, builderContext));
302                case "TrimEnd":
303                    return popCallStack(AnalyzeGenericSpecialExpressionType(SpecialExpressionType.RTrim, parameters, builderContext));
304                case "TrimStart":
305                    return popCallStack(AnalyzeGenericSpecialExpressionType(SpecialExpressionType.LTrim, parameters, builderContext));
306                default:
307                    throw Error.BadArgument("S0133: Implement QueryMethod String.{0}.", method.Name);
308            }
309        }
310
311        private Expression AnalyzeMathCall(MethodInfo method, IList<Expression> parameters, BuilderContext builderContext)
312        {
313            if (method.DeclaringType != typeof(System.Math))
314                return null;
315            var popCallStack = PushCallStack(method, builderContext);
316            switch (method.Name)
317            {
318                case "Abs":
319                case "Exp":
320                case "Floor":
321                case "Pow":
322                case "Round":
323                case "Sign":
324                case "Sqrt":
325                    return popCallStack(AnalyzeGenericSpecialExpressionType((SpecialExpressionType)Enum.Parse(typeof(SpecialExpressionType), method.Name), parameters, builderContext));
326                case "Log":
327                    return popCallStack(AnalyzeLog(parameters, builderContext));
328                case "Log10":
329                    return popCallStack(AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Log, parameters, builderContext));
330                default:
331                    throw Error.BadArgument("S0133: Implement QueryMethod Math.{0}.", method.Name);
332            }
333        }
334
335        private Expression AnalyzeUnknownCall(MethodCallExpression expression, IList<Expression> parameters, BuilderContext builderContext)
336        {
337            var method = expression.Method;
338            switch (method.Name)
339            {
340                case "Parse":
341                    if (method.IsStatic && parameters.Count == 1)
342                        return AnalyzeParse(method, parameters, builderContext);
343                    break;
344                case "ToString": // Can we sanity check this type?
345                    return AnalyzeToString(method, parameters, builderContext);
346            }
347
348            var args = new List<Expression>();
349            foreach (var arg in expression.Arguments)
350            {
351                Expression newArg = arg;
352                var pe = arg as ParameterExpression;
353                if (pe != null)
354                {
355                    if (!builderContext.Parameters.TryGetValue(pe.Name, out newArg))
356                        throw new NotSupportedException("Do not currently support expression: " + expression);
357                }
358                else
359                    newArg = Analyze(arg, builderContext);
360                args.Add(newArg);
361            }
362            return Expression.Call(expression.Object, expression.Method, args);
363        }
364
365        private Expression AnalyzeStringInsert(IList<Expression> parameters, BuilderContext builderContext)
366        {
367            var startIndexExpression = new StartIndexOffsetExpression(builderContext.QueryContext.DataContext.Vendor.SqlProvider.StringIndexStartsAtOne, parameters.ElementAt(1));
368            var stringToInsertExpression = parameters.ElementAt(2);
369            return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.StringInsert, new Expression[] { parameters.First(), startIndexExpression, stringToInsertExpression }, builderContext);
370        }
371
372        protected virtual Expression AnalyzeLog(IList<Expression> parameters, BuilderContext builderContext)
373        {
374            if (parameters.Count == 1)
375                return new SpecialExpression(SpecialExpressionType.Ln, parameters.Select(p => Analyze(p, builderContext)).ToList());
376            else if (parameters.Count == 2)
377                return new SpecialExpression(SpecialExpressionType.Log, parameters.Select(p => Analyze(p, builderContext)).ToList());
378            else
379                throw new NotSupportedException();
380        }
381
382        protected virtual Expression AnalyzeGenericSpecialExpressionType(SpecialExpressionType specialType, IList<Expression> parameters, BuilderContext builderContext)
383        {
384            return new SpecialExpression(specialType, parameters.Select(p => Analyze(p, builderContext)).ToList());
385        }
386
387        protected virtual Expression AnalyzeParse(MethodInfo method, IList<Expression> parameters, BuilderContext builderContext)
388        {
389            if (method.IsStatic && parameters.Count == 1)
390            {
391                Expression parsed = null;
392                Expression toParse = Analyze(parameters.First(), builderContext);
393                InputParameterExpression inputParameterToParse = toParse as InputParameterExpression;
394                if (inputParameterToParse != null)
395                {
396                    ExpressionTier tier = ExpressionQualifier.GetTier(parameters[0]);
397                    if (tier == ExpressionTier.Clr)
398                    {
399                        parsed = RegisterParameter(System.Linq.Expressions.Expression.Call(method, inputParameterToParse.Expression), inputParameterToParse.Alias, builderContext);
400                        UnregisterParameter(inputParameterToParse, builderContext);
401                    }
402                }
403                if(parsed == null)
404                {
405                    parsed = Expression.Convert(toParse, method.ReturnType, method);
406                    ExpressionTier tier = ExpressionQualifier.GetTier(toParse);
407                    //pibgeus: I would like to call to the expression optimizer since the exception must be thrown if the expression cannot be executed
408                    //in Clr tier, if it can be executed in Clr tier it should continue
409                    // ie: from e in db.Employees where DateTime.Parse("1/1/1999").Year==1999 select e  <--- this should work
410                    // ie: from e in db.Employees where DateTime.Parse(e.BirthDate).Year==1999 select e  <--- a NotSupportedException must be throwed (this is the behaviour of linq2sql)
411
412                    //if (method.ReturnType == typeof(DateTime))
413                    //{
414                    //        expression = ExpressionOptimizer.Analyze(expression);
415                    //        //same behaviour that Linq2Sql
416                    //        throw new NotSupportedException("Method 'System.DateTime Parse(System.String)' has no supported translation to SQL");
417                    //}
418                }
419                return parsed;
420            }
421            else
422                throw new ArgumentException();
423
424        }
425
426        protected virtual Expression AnalyzeToString(MethodInfo method, IList<Expression> parameters, BuilderContext builderContext)
427        {
428            if (parameters.Count != 1)
429                throw new ArgumentException();
430
431            Expression parameter = parameters.First();
432            Expression parameterToHandle;
433
434            if(parameter.Type.IsNullable())
435                parameter = Analyze(Expression.Convert(parameter, parameter.Type.GetNullableType()), builderContext);
436
437            parameterToHandle = Analyze(parameter, builderContext);
438
439            InputParameterExpression inputParameter = parameterToHandle as InputParameterExpression;
440            if (inputParameter != null)
441            {
442                parameterToHandle = RegisterParameter(System.Linq.Expressions.Expression.Call(inputParameter.Expression, method), inputParameter.Alias, builderContext);
443                UnregisterParameter(inputParameter, builderContext);
444
445                return parameterToHandle;
446            }
447            
448            if (!parameter.Type.IsPrimitive && parameterToHandle.Type != typeof(string))
449            {
450                //TODO: ExpressionDispacher.Analyze.AnalyzeToString is not complete
451                //This is the standar behaviour in linq2sql, nonetheless the behaviour isn't complete since when the expression
452                //can be executed in the clr, ie: (where new StrangeObject().ToString()) should work. The problem is that
453                //we don't have a reference to the optimizer here.
454                //Working samples in: /Tests/Test_Nunit/ReadTests_Conversions.cs
455                string message = "Method ToString can only be translated to SQL for primitive types.";
456                int? select = FirstIndexOf(builderContext.CallStack, "Select");
457                int? where  = FirstIndexOf(builderContext.CallStack, "Where");
458                if ((where ?? int.MaxValue) < (select ?? int.MaxValue))
459                    // Assume we're generating the .Where() clause, not .Select()
460                    throw new NotSupportedException(message);
461                // for .Select()
462                throw new InvalidOperationException(message);
463            }
464
465            return Expression.Convert(parameterToHandle, typeof(string), typeof(Convert).GetMethod("ToString", new[] { parameterToHandle.Type }));
466        }
467
468        static int? FirstIndexOf(Stack<MethodInfo> callStack, string methodName)
469        {
470            int? index = null;
471            callStack.Where((m, i) =>
472            {
473                if (m.Name == methodName)
474                {
475                    index = i;
476                    return true;
477                }
478                return false;
479            }).FirstOrDefault();
480            return index;
481        }
482
483        /// <summary>
484        /// Limits selection count
485        /// </summary>
486        /// <param name="parameters"></param>
487        /// <param name="builderContext"></param>
488        /// <returns></returns>
489        protected virtual Expression AnalyzeTake(IList<Expression> parameters, BuilderContext builderContext)
490        {
491            AddLimit(Analyze(parameters[1], builderContext), builderContext);
492            return Analyze(parameters[0], builderContext);
493        }
494
495        protected virtual void AddLimit(Expression limit, BuilderContext builderContext)
496        {
497            var previousLimit = builderContext.CurrentSelect.Limit;
498            if (previousLimit != null)
499                builderContext.CurrentSelect.Limit = Expression.Condition(Expression.LessThan(previousLimit, limit),
500                                                                          previousLimit, limit);
501            else
502                builderContext.CurrentSelect.Limit = limit;
503        }
504
505        /// <summary>
506        /// Skip selection items
507        /// </summary>
508        /// <param name="parameters"></param>
509        /// <param name="builderContext"></param>
510        /// <returns></returns>
511        protected virtual Expression AnalyzeSkip(IList<Expression> parameters, BuilderContext builderContext)
512        {
513            AddOffset(Analyze(parameters[1], builderContext), builderContext);
514            return Analyze(parameters[0], builderContext);
515        }
516
517        protected virtual void AddOffset(Expression offset, BuilderContext builderContext)
518        {
519            var previousOffset = builderContext.CurrentSelect.Offset;
520            if (previousOffset != null)
521                builderContext.CurrentSelect.Offset = Expression.Add(offset, previousOffset);
522            else
523                builderContext.CurrentSelect.Offset = offset;
524        }
525
526        /// <summary>
527        /// Registers a scalar method call for result
528        /// </summary>
529        /// <param name="methodName"></param>
530        /// <param name="limit"></param>
531        /// <param name="parameters"></param>
532        /// <param name="builderContext"></param>
533        /// <returns></returns>
534        protected virtual Expression AnalyzeScalar(string methodName, int? limit, IList<Expression> parameters, BuilderContext builderContext)
535        {
536            builderContext.CurrentSelect.ExecuteMethodName = methodName;
537            if (limit.HasValue)
538                AddLimit(Expression.Constant(limit.Value), builderContext);
539            var table = Analyze(parameters[0], builderContext);
540            var set = table as EntitySetExpression;
541            if (set != null)
542                table = set.TableExpression;
543            CheckWhere(table, parameters, 1, builderContext);
544            return table;
545        }
546
547        /// <summary>
548        /// Some methods, like Single(), Count(), etc. can get an extra parameter, specifying a restriction.
549        /// This method checks if the parameter is specified, and adds it to the WHERE clauses
550        /// </summary>
551        /// <param name="table"></param>
552        /// <param name="parameters"></param>
553        /// <param name="extraParameterIndex"></param>
554        /// <param name="builderContext"></param>
555        private void CheckWhere(Expression table, IList<Expression> parameters, int extraParameterIndex, BuilderContext builderContext)
556        {
557            if (parameters.Count > extraParameterIndex) // a lambda can be specified here, this is a restriction
558                RegisterWhere(Analyze(parameters[extraParameterIndex], table, builderContext), builderContext);
559        }
560
561        /// <summary>
562        /// Returns a projection method call
563        /// </summary>
564        /// <param name="specialExpressionType"></param>
565        /// <param name="parameters"></param>
566        /// <param name="builderContext"></param>
567        /// <returns></returns>
568        protected virtual Expression AnalyzeProjectionQuery(SpecialExpressionType specialExpressionType, IList<Expression> parameters,
569                                                            BuilderContext builderContext)
570        {
571
572            if (builderContext.IsExternalInExpressionChain)
573            {
574                var operand0 = Analyze(parameters[0], builderContext);
575                Expression projectionOperand;
576
577                if (    builderContext.CurrentSelect.NextSelectExpression != null 
578                    ||  builderContext.CurrentSelect.Operands.Count() > 0
579                    ||  builderContext.CurrentSelect.Group.Count > 0
580                   )
581                {
582                    //BuildSelect(builderContext.CurrentSelect, builderContext);
583                    operand0 = new SubSelectExpression(builderContext.CurrentSelect, operand0.Type, "source");
584                    builderContext.NewParentSelect();
585
586                    // In the new scope we should not have MaximumDatabaseLoad
587                    builderContext.QueryContext.MaximumDatabaseLoad = false;
588
589                    builderContext.CurrentSelect.Tables.Add(operand0 as TableExpression);
590                }
591
592                // basically, we have three options for projection methods:
593                // - projection on grouped table (1 operand, a GroupExpression)
594                // - projection on grouped column (2 operands, GroupExpression and ColumnExpression)
595                // - projection on table/column, with optional restriction
596                var groupOperand0 = operand0 as GroupExpression;
597                if (groupOperand0 != null)
598                {
599                    if (parameters.Count > 1)
600                    {
601                        projectionOperand = Analyze(parameters[1], groupOperand0.GroupedExpression,
602                                                    builderContext);
603                    }
604                    else
605                        projectionOperand = Analyze(groupOperand0.GroupedExpression, builderContext);
606                }
607                else
608                {
609                    projectionOperand = operand0;
610                    CheckWhere(projectionOperand, parameters, 1, builderContext);
611                }
612
613                if (projectionOperand is TableExpression)
614                    projectionOperand = RegisterTable((TableExpression)projectionOperand, builderContext);
615
616                if (groupOperand0 != null)
617                    projectionOperand = new GroupExpression(projectionOperand, groupOperand0.KeyExpression);
618
619                return new SpecialExpression(specialExpressionType, projectionOperand);
620            }
621            else
622            {
623                var projectionQueryBuilderContext = builderContext.NewSelect();
624
625                var tableExpression = Analyze(parameters[0], projectionQueryBuilderContext);
626
627                if (!(tableExpression is TableExpression) && !(tableExpression is EntitySetExpression))
628                    tableExpression = Analyze(tableExpression, projectionQueryBuilderContext);
629                EntitySetExpression setExpression = tableExpression as EntitySetExpression;
630                if (setExpression != null)
631                    tableExpression = setExpression.TableExpression;
632
633                // from here we build a custom clause:
634                // <anyClause> ==> "(select count(*) from <table> where <anyClause>)>0"
635                // TODO (later...): see if some vendors support native Any operator and avoid this substitution
636                if (parameters.Count > 1)
637                {
638                    setExpression = tableExpression as EntitySetExpression;
639                    if (setExpression != null)
640                        tableExpression = setExpression.TableExpression;
641                    var anyClause = Analyze(parameters[1], tableExpression, projectionQueryBuilderContext);
642                    RegisterWhere(anyClause, projectionQueryBuilderContext);
643                }
644
645                projectionQueryBuilderContext.CurrentSelect = projectionQueryBuilderContext.CurrentSelect.ChangeOperands(new SpecialExpression(specialExpressionType, tableExpression));
646
647                // we now switch back to current context, and compare the result with 0
648                return projectionQueryBuilderContext.CurrentSelect;
649            }
650        }
651
652        /// <summary>
653        /// Entry point for a Select()
654        /// static Select(this Expression table, λ(table))
655        /// </summary>
656        /// <param name="parameters"></param>
657        /// <param name="builderContext"></param>
658        /// <returns></returns>
659        protected virtual Expression AnalyzeSelect(IList<Expression> parameters, BuilderContext builderContext)
660        {
661            // just call back the underlying lambda (or quote, whatever)
662            Expression ex = Analyze(parameters[1], parameters[0], builderContext);
663
664            // http://social.msdn.microsoft.com/Forums/en-US/linqprojectgeneral/thread/1ce25da3-44c6-407d-8395-4c146930004b
665            if (ex.NodeType == ExpressionType.MemberInit &&
666                    builderContext.QueryContext.DataContext.Mapping.GetMetaType(ex.Type) != null)
667                throw new NotSupportedException(
668                    string.Format("Explicit construction of entity type '{0}' in query is not allowed.",
669                        ex.Type.FullName));
670            TableExpression tableExpression = parameters[0] as TableExpression;
671            if (tableExpression != null && builderContext.CurrentSelect.Tables.Count == 0)
672                RegisterTable(tableExpression, builderContext);
673            return ex;
674        }
675
676        /// <summary>
677        /// Entry point for a Where()
678        /// static Where(this Expression table, λ(table))
679        /// </summary>
680        /// <param name="parameters"></param>
681        /// <param name="builderContext"></param>
682        /// <returns></returns>
683        protected virtual Expression AnalyzeWhere(IList<Expression> parameters, BuilderContext builderContext)
684        {
685			var tablePiece = parameters[0];
686            RegisterWhere(Analyze(parameters[1], tablePiece, builderContext), builderContext);
687            return tablePiece;
688        }
689
690        /// <summary>
691        /// Handling a lambda consists in:
692        /// - filling its input parameters with what's on the stack
693        /// - using the body (parameters are registered in the context)
694        /// </summary>
695        /// <param name="expression"></param>
696        /// <param name="parameters"></param>
697        /// <param name="builderContext"></param>
698        /// <returns></returns>
699        protected virtual Expression AnalyzeLambda(Expression expression, IList<Expression> parameters, BuilderContext builderContext)
700        {
701            var lambdaExpression = expression as LambdaExpression;
702            if (lambdaExpression == null)
703                throw Error.BadArgument("S0227: Unknown type for AnalyzeLambda() ({0})", expression.GetType());
704            // for a lambda, first parameter is body, others are input parameters
705            // so we create a parameters stack
706            for (int parameterIndex = 0; parameterIndex < lambdaExpression.Parameters.Count; parameterIndex++)
707            {
708                var parameterExpression = lambdaExpression.Parameters[parameterIndex];
709                builderContext.Parameters[parameterExpression.Name] = Analyze(parameters[parameterIndex], builderContext);
710            }
711            // we keep only the body, the header is now useless
712            // and once the parameters have been substituted, we don't pass one anymore
713            return Analyze(lambdaExpression.Body, builderContext);
714        }
715
716        /// <summary>
717        /// When a parameter is used, we replace it with its original value
718        /// </summary>
719        /// <param name="expression"></param>
720        /// <param name="builderContext"></param>
721        /// <returns></returns>
722        protected virtual Expression AnalyzeParameter(Expression expression, BuilderContext builderContext)
723        {
724            Expression unaliasedExpression;
725            var parameterName = GetParameterName(expression);
726            builderContext.Parameters.TryGetValue(parameterName, out unaliasedExpression);
727            if (unaliasedExpression == null)
728                throw Error.BadArgument("S0257: can not find parameter '{0}'", parameterName);
729
730            #region set alias helper
731
732            // for table...
733            var unaliasedTableExpression = unaliasedExpression as TableExpression;
734            if (unaliasedTableExpression != null && unaliasedTableExpression.Alias == null)
735                unaliasedTableExpression.Alias = parameterName;
736            // .. or column
737            var unaliasedColumnExpression = unaliasedExpression as ColumnExpression;
738            if (unaliasedColumnExpression != null && unaliasedColumnExpression.Alias == null)
739                unaliasedColumnExpression.Alias = parameterName;
740
741            #endregion
742
743            //var groupByExpression = unaliasedExpression as GroupByExpression;
744            //if (groupByExpression != null)
745            //    unaliasedExpression = groupByExpression.ColumnExpression.Table;
746
747            return unaliasedExpression;
748        }
749
750        /// <summary>
751        /// Returns if the given member can be considered as an EntitySet<>
752        /// </summary>
753        /// <param name="memberType"></param>
754        /// <param name="entityType"></param>
755        /// <returns></returns>
756        protected virtual bool IsEntitySet(Type memberType, out Type entityType)
757        {
758            entityType = memberType;
759            // one check, a generic EntityRef<> or inherited
760            if (memberType.IsGenericType && typeof(EntitySet<>).IsAssignableFrom(memberType.GetGenericTypeDefinition()))
761            {
762                entityType = memberType.GetGenericArguments()[0];
763                return true;
764            }
765#if !MONO_STRICT
766            // this is for compatibility with previously generated .cs files
767            // TODO: remove in 2009
768            if (memberType.IsGenericType && typeof(System.Data.Linq.EntitySet<>).IsAssignableFrom(memberType.GetGenericTypeDefinition()))
769            {
770                entityType = memberType.GetGenericArguments()[0];
771                return true;
772            }
773#endif
774            return false;
775        }
776
777        /// <summary>
778        /// Analyzes a member access.
779        /// This analyzis is down to top: the highest identifier is at bottom
780        /// </summary>
781        /// <param name="expression"></param>
782        /// <param name="builderContext"></param>
783        /// <returns></returns>
784        protected virtual Expression AnalyzeMember(Expression expression, BuilderContext builderContext)
785        {
786            var memberExpression = (MemberExpression)expression;
787
788            Expression objectExpression = null;
789            //maybe is a static member access like DateTime.Now
790            bool isStaticMemberAccess = memberExpression.Member.GetIsStaticMember();
791
792            var memberInfo = memberExpression.Member;
793            if (!isStaticMemberAccess && memberInfo.Name == "Count")
794                return AnalyzeProjectionQuery(SpecialExpressionType.Count, new[] { memberExpression.Expression }, builderContext);
795
796            if (!isStaticMemberAccess)
797                // first parameter is object, second is member
798                objectExpression = Analyze(memberExpression.Expression, builderContext);
799
800            // then see what we can do, depending on object type
801            // - MetaTable --> then the result is a table
802            // - Table --> the result may be a column or a join
803            // - Object --> external parameter or table (can this happen here? probably not... to be checked)
804
805            EntitySetExpression setExpression = objectExpression as EntitySetExpression;
806            if (setExpression != null)
807            {
808                objectExpression = setExpression.TableExpression;
809            }
810
811            if (objectExpression is MetaTableExpression)
812            {
813                var metaTableExpression = (MetaTableExpression)objectExpression;
814                var tableExpression = metaTableExpression.GetTableExpression(memberInfo);
815                if (tableExpression == null)
816                    throw Error.BadArgument("S0270: MemberInfo '{0}' not found in MetaTable", memberInfo.Name);
817                return tableExpression;
818            }
819
820            if (objectExpression is GroupExpression)
821            {
822                if (memberInfo.Name == "Key")
823                    return ((GroupExpression)objectExpression).KeyExpression;
824            }
825
826            // if object is a table, then we need a column, or an association
827            if (objectExpression is TableExpression)
828            {
829                var tableExpression = (TableExpression)objectExpression;
830
831
832                // before finding an association, we check for an EntitySet<>
833                // this will be used in RegisterAssociation
834                Type entityType;
835                if (IsEntitySet(memberInfo.GetMemberType(), out entityType))
836                    return new EntitySetExpression(tableExpression, memberInfo, memberInfo.GetMemberType(), builderContext, this);
837
838                // first of all, then, try to find the association
839                var queryAssociationExpression = RegisterAssociation(tableExpression, memberInfo, entityType,
840                                                                     builderContext);
841                if (queryAssociationExpression != null)
842                {
843                    return queryAssociationExpression;
844                }
845                // then, try the column
846                ColumnExpression queryColumnExpression = RegisterColumn(tableExpression, memberInfo, builderContext);
847                if (queryColumnExpression != null)
848                {
849                    Type storageType = queryColumnExpression.StorageInfo != null ? queryColumnExpression.StorageInfo.GetMemberType() : null;
850                    if (storageType != null && queryColumnExpression.Type != storageType)
851                    {
852                        return Expression.Convert(queryColumnExpression, queryColumnExpression.Type, typeof(Convert).GetMethod("To" + queryColumnExpression.Type.Name, new Type[] { queryColumnExpression.Type }));
853                    }
854                    else
855                    {
856                        return queryColumnExpression;
857                    }
858                }
859                // then, cry
860                throw Error.BadArgument("S0293: Column must be mapped. Non-mapped columns are not handled by now.");
861            }
862
863            // if object is still an object (== a constant), then we have an external parameter
864            if (objectExpression is ConstantExpression)
865            {
866                // the memberInfo.Name is provided here only to ease the SQL reading
867                var parameterExpression = RegisterParameter(expression, memberInfo.Name, builderContext);
868                if (parameterExpression != null)
869                    return parameterExpression;
870                throw Error.BadArgument("S0302: Can not created parameter from expression '{0}'", expression);
871            }
872
873            // we have here a special cases for nullables
874            if (!isStaticMemberAccess && objectExpression.Type != null && objectExpression.Type.IsNullable())
875            {
876                // Value means we convert the nullable to a value --> use Convert instead (works both on CLR and SQL, too)
877                if (memberInfo.Name == "Value")
878                    return Expression.Convert(objectExpression, memberInfo.GetMemberType());
879                // HasValue means not null (works both on CLR and SQL, too)
880                if (memberInfo.Name == "HasValue")
881                    return new SpecialExpression(SpecialExpressionType.IsNotNull, objectExpression);
882            }
883
884
885            if (memberInfo.DeclaringType == typeof(DateTime))
886                return AnalyzeDateTimeMemberAccess(objectExpression, memberInfo, isStaticMemberAccess);
887
888            // TODO: make this expresion safe (objectExpression can be null here)
889            if (objectExpression.Type == typeof(TimeSpan))
890                return AnalyzeTimeSpanMemberAccess(objectExpression, memberInfo);
891
892
893            if (objectExpression is InputParameterExpression)
894            {
895                return AnalyzeExternalParameterMember((InputParameterExpression)objectExpression, memberInfo, builderContext);
896            }
897
898            if (objectExpression is MemberInitExpression)
899            {
900                var foundExpression = AnalyzeMemberInit((MemberInitExpression)objectExpression, memberInfo, builderContext);
901                if (foundExpression != null)
902                    return foundExpression;
903            }
904
905            return AnalyzeCommonMember(objectExpression, memberInfo, builderContext);
906        }
907
908        protected Expression AnalyzeTimeSpanMemberAccess(Expression objectExpression, MemberInfo memberInfo)
909        {
910            //A timespan expression can be only generated in a c# query as a DateTime difference, as a function call return or as a paramter
911            //this case is for the DateTime difference operation
912
913            if (!(objectExpression is BinaryExpression))
914                throw new NotSupportedException();
915
916            var operands = objectExpression.GetOperands();
917
918            bool absoluteSpam = memberInfo.Name.StartsWith("Total");
919            string operationKey = absoluteSpam ? memberInfo.Name.Substring(5) : memberInfo.Name;
920
921            Expression currentExpression;
922            switch (operationKey)
923            {
924                case "Milliseconds":
925                    currentExpression = Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double));
926                    break;
927                case "Seconds":
928                    currentExpression = Expression.Divide(
929                        Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),
930                        Expression.Constant(1000.0));
931                    break;
932                case "Minutes":
933                    currentExpression = Expression.Divide(
934                            Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),
935                            Expression.Constant(60000.0));
936                    break;
937                case "Hours":
938                    currentExpression = Expression.Divide(
939                            Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),
940                            Expression.Constant(3600000.0));
941                    break;
942                case "Days":
943                    currentExpression = Expression.Divide(
944                            Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),
945                            Expression.Constant(86400000.0));
946                    break;
947                default:
948                    throw new NotSupportedException(string.Format("The operation {0} over the TimeSpan isn't currently supported", memberInfo.Name));
949            }
950
951            if (!absoluteSpam)
952            {
953                switch (memberInfo.Name)
954                {
955                    case "Milliseconds":
956                        currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)), Expression.Constant(1000L)), typeof(int));
957                        break;
958                    case "Seconds":
959                        currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)),
960                                                              Expression.Constant(60L)), typeof(int));
961                        break;
962                    case "Minutes":
963                        currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)),
964                                                                Expression.Constant(60L)), typeof(int));
965                        break;
966                    case "Hours":
967                        currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(
968                                                                                        currentExpression, typeof(long)),
969          

Large files files are truncated, but you can click here to view the full file