5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
\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
14 // The above copyright notice and this permission notice shall be included in
\r
15 // all copies or substantial portions of the Software.
\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
28 using System.Collections.Generic;
\r
30 using System.Linq.Expressions;
\r
31 using System.Reflection;
\r
34 using System.Data.Linq.Implementation;
\r
35 using System.Data.Linq.Sugar;
\r
36 using System.Data.Linq.Sugar.ExpressionMutator;
\r
37 using System.Data.Linq.Sugar.Expressions;
\r
38 using System.Data.Linq.Sugar.Implementation;
\r
40 using DbLinq.Data.Linq.Implementation;
\r
41 using DbLinq.Data.Linq.Sugar;
\r
42 using DbLinq.Data.Linq.Sugar.ExpressionMutator;
\r
43 using DbLinq.Data.Linq.Sugar.Expressions;
\r
44 using DbLinq.Data.Linq.Sugar.Implementation;
\r
47 using DbLinq.Factory;
\r
51 namespace System.Data.Linq.Sugar.Implementation
\r
53 namespace DbLinq.Data.Linq.Sugar.Implementation
\r
56 partial class ExpressionDispatcher
\r
59 /// Entry point for Analyzis
\r
61 /// <param name="expression"></param>
\r
62 /// <param name="parameter"></param>
\r
63 /// <param name="builderContext"></param>
\r
64 /// <returns></returns>
\r
65 public virtual Expression Analyze(Expression expression, Expression parameter, BuilderContext builderContext)
\r
67 return Analyze(expression, new[] { parameter }, builderContext);
\r
70 protected virtual Expression Analyze(Expression expression, BuilderContext builderContext)
\r
72 return Analyze(expression, new Expression[0], builderContext);
\r
75 protected virtual Expression Analyze(Expression expression, IList<Expression> parameters, BuilderContext builderContext)
\r
77 switch (expression.NodeType)
\r
79 case ExpressionType.Call:
\r
80 return AnalyzeCall((MethodCallExpression)expression, parameters, builderContext);
\r
81 case ExpressionType.Lambda:
\r
82 return AnalyzeLambda(expression, parameters, builderContext);
\r
83 case ExpressionType.Parameter:
\r
84 return AnalyzeParameter(expression, builderContext);
\r
85 case ExpressionType.Quote:
\r
86 return AnalyzeQuote(expression, parameters, builderContext);
\r
87 case ExpressionType.MemberAccess:
\r
88 return AnalyzeMember(expression, builderContext);
\r
89 #region case ExpressionType.<Common operators>:
\r
90 case ExpressionType.Add:
\r
91 case ExpressionType.AddChecked:
\r
92 case ExpressionType.Divide:
\r
93 case ExpressionType.Modulo:
\r
94 case ExpressionType.Multiply:
\r
95 case ExpressionType.MultiplyChecked:
\r
96 case ExpressionType.Power:
\r
97 case ExpressionType.Subtract:
\r
98 case ExpressionType.SubtractChecked:
\r
99 case ExpressionType.And:
\r
100 case ExpressionType.Or:
\r
101 case ExpressionType.ExclusiveOr:
\r
102 case ExpressionType.LeftShift:
\r
103 case ExpressionType.RightShift:
\r
104 case ExpressionType.AndAlso:
\r
105 case ExpressionType.OrElse:
\r
106 case ExpressionType.Equal:
\r
107 case ExpressionType.NotEqual:
\r
108 case ExpressionType.GreaterThanOrEqual:
\r
109 case ExpressionType.GreaterThan:
\r
110 case ExpressionType.LessThan:
\r
111 case ExpressionType.LessThanOrEqual:
\r
112 case ExpressionType.Coalesce:
\r
113 //case ExpressionType.ArrayIndex
\r
114 //case ExpressionType.ArrayLength
\r
115 case ExpressionType.Convert:
\r
116 case ExpressionType.ConvertChecked:
\r
117 case ExpressionType.Negate:
\r
118 case ExpressionType.NegateChecked:
\r
119 case ExpressionType.Not:
\r
120 //case ExpressionType.TypeAs
\r
121 case ExpressionType.UnaryPlus:
\r
122 case ExpressionType.MemberInit:
\r
124 return AnalyzeOperator(expression, builderContext);
\r
125 case ExpressionType.New:
\r
126 return AnalyzeNewOperator(expression, builderContext);
\r
127 case ExpressionType.Constant:
\r
128 return AnalyzeConstant(expression, builderContext);
\r
129 case ExpressionType.Invoke:
\r
130 return AnalyzeInvoke(expression, parameters, builderContext);
\r
136 /// Analyzes method call, uses specified parameters
\r
138 /// <param name="expression"></param>
\r
139 /// <param name="parameters"></param>
\r
140 /// <param name="builderContext"></param>
\r
141 /// <returns></returns>
\r
142 protected virtual Expression AnalyzeCall(MethodCallExpression expression, IList<Expression> parameters, BuilderContext builderContext)
\r
144 var operands = expression.GetOperands().ToList();
\r
145 var operarandsToSkip = expression.Method.IsStatic ? 1 : 0;
\r
146 var originalParameters = operands.Skip(parameters.Count + operarandsToSkip);
\r
147 var newParameters = parameters.Union(originalParameters);
\r
149 return AnalyzeCall(expression.Method, newParameters.ToList(), builderContext);
\r
166 /// Analyzes method call
\r
168 /// <param name="method"></param>
\r
169 /// <param name="parameters"></param>
\r
170 /// <param name="builderContext"></param>
\r
171 /// <returns></returns>
\r
172 protected virtual Expression AnalyzeCall(MethodInfo method, IList<Expression> parameters, BuilderContext builderContext)
\r
174 // all methods to handle are listed here:
\r
175 // ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/fxref_system.core/html/2a54ce9d-76f2-81e2-95bb-59740c85386b.htm
\r
176 string methodName = method.Name;
\r
177 if (method.DeclaringType == typeof(System.Data.Linq.SqlClient.SqlMethods))
\r
179 switch (methodName)
\r
182 if (parameters.Count == 3)
\r
183 throw new NotImplementedException();
\r
184 return AnalyzeLike(parameters[0], null, parameters[1], null, builderContext);
\r
185 case "DateDiffDay":
\r
186 return AnalyzeSqlDateDiff(DateTimePart.Day, parameters, builderContext);
\r
187 case "DateDiffHour":
\r
188 return AnalyzeSqlDateDiff(DateTimePart.Hour, parameters, builderContext);
\r
189 case "DateDiffMicrosecond":
\r
190 return AnalyzeSqlDateDiff(DateTimePart.Microsecond, parameters, builderContext);
\r
191 case "DateDiffMillisecond":
\r
192 return AnalyzeSqlDateDiff(DateTimePart.Millisecond, parameters, builderContext);
\r
193 case "DateDiffMinute":
\r
194 return AnalyzeSqlDateDiff(DateTimePart.Minute, parameters, builderContext);
\r
195 case "DateDiffMonth":
\r
196 return AnalyzeSqlDateDiff(DateTimePart.Month, parameters, builderContext);
\r
197 case "DateDiffNanosecond":
\r
198 return AnalyzeSqlDateDiff(DateTimePart.Nanosecond, parameters, builderContext);
\r
199 case "DateDiffSecond":
\r
200 return AnalyzeSqlDateDiff(DateTimePart.Second, parameters, builderContext);
\r
201 case "DateDiffYeah":
\r
202 return AnalyzeSqlDateDiff(DateTimePart.Yeah, parameters, builderContext);
\r
204 throw Error.BadArgument("S0134: Implement QueryMethod '{0}'", methodName);
\r
207 switch (methodName)
\r
210 return AnalyzeSelect(parameters, builderContext);
\r
212 return AnalyzeWhere(parameters, builderContext);
\r
214 return AnalyzeSelectMany(parameters, builderContext);
\r
216 return AnalyzeJoin(parameters, builderContext);
\r
218 return AnalyzeGroupJoin(parameters, builderContext);
\r
219 case "DefaultIfEmpty":
\r
220 return AnalyzeOuterJoin(parameters, builderContext);
\r
222 return AnalyzeDistinct(parameters, builderContext);
\r
224 return AnalyzeGroupBy(parameters, builderContext);
\r
226 return AnalyzeAll(parameters, builderContext);
\r
228 return AnalyzeAny(parameters, builderContext);
\r
230 return AnalyzeProjectionQuery(SpecialExpressionType.Average, parameters, builderContext);
\r
232 return AnalyzeProjectionQuery(SpecialExpressionType.Count, parameters, builderContext);
\r
234 return AnalyzeProjectionQuery(SpecialExpressionType.Max, parameters, builderContext);
\r
236 return AnalyzeProjectionQuery(SpecialExpressionType.Min, parameters, builderContext);
\r
238 return AnalyzeProjectionQuery(SpecialExpressionType.Sum, parameters, builderContext);
\r
240 return AnalyzeLikeStart(parameters, builderContext);
\r
242 return AnalyzeLikeEnd(parameters, builderContext);
\r
244 if (typeof(string).IsAssignableFrom(parameters[0].Type))
\r
245 return AnalyzeLike(parameters, builderContext);
\r
246 return AnalyzeContains(parameters, builderContext);
\r
248 return AnalyzeSubString(parameters, builderContext);
\r
250 case "FirstOrDefault":
\r
251 return AnalyzeScalar(methodName, 1, parameters, builderContext);
\r
253 case "SingleOrDefault":
\r
254 return AnalyzeScalar(methodName, 2, parameters, builderContext);
\r
256 return AnalyzeScalar(methodName, null, parameters, builderContext);
\r
258 return AnalyzeTake(parameters, builderContext);
\r
260 return AnalyzeSkip(parameters, builderContext);
\r
262 return AnalyzeToUpper(parameters, builderContext);
\r
264 return AnalyzeToLower(parameters, builderContext);
\r
267 return AnalyzeOrderBy(parameters, false, builderContext);
\r
268 case "OrderByDescending":
\r
269 case "ThenByDescending":
\r
270 return AnalyzeOrderBy(parameters, true, builderContext);
\r
272 return AnalyzeSelectOperation(SelectOperatorType.Union, parameters, builderContext);
\r
274 return AnalyzeSelectOperation(SelectOperatorType.UnionAll, parameters, builderContext);
\r
276 return AnalyzeSelectOperation(SelectOperatorType.Intersection, parameters, builderContext);
\r
278 return AnalyzeSelectOperation(SelectOperatorType.Exception, parameters, builderContext);
\r
280 return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Trim, parameters, builderContext);
\r
282 return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.LTrim, parameters, builderContext);
\r
284 return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.RTrim, parameters, builderContext);
\r
286 return AnalyzeStringInsert(parameters, builderContext);
\r
288 return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Replace, parameters, builderContext);
\r
290 return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Remove, parameters, builderContext);
\r
292 return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.IndexOf, parameters, builderContext);
\r
294 return AnalyzeToString(method, parameters, builderContext);
\r
296 return AnalyzeParse(method, parameters, builderContext);
\r
304 return AnalyzeGenericSpecialExpressionType((SpecialExpressionType)Enum.Parse(typeof(SpecialExpressionType), methodName), parameters, builderContext);
\r
306 return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.Log, parameters, builderContext);
\r
308 return AnalyzeLog(parameters, builderContext);
\r
310 throw Error.BadArgument("S0133: Implement QueryMethod '{0}'", methodName);
\r
314 static readonly MethodInfo dateTimeHasValue = typeof(DateTime?).GetProperty("HasValue").GetGetMethod();
\r
315 static readonly MethodInfo dateTimeOffsetHasValue = typeof(DateTimeOffset?).GetProperty("HasValue").GetGetMethod();
\r
316 static readonly MethodInfo dateTimeValue = typeof(DateTime?).GetProperty("Value").GetGetMethod();
\r
317 static readonly MethodInfo dateTimeOffsetValue = typeof(DateTimeOffset?).GetProperty("Value").GetGetMethod();
\r
318 static readonly ConstructorInfo nintctr = typeof(int?).GetConstructors()[0];
\r
320 static MethodInfo GetPropertyGetterForDatePart(Type t, DateTimePart p)
\r
324 case DateTimePart.Day: return t.GetProperty("Day").GetGetMethod();
\r
325 case DateTimePart.Hour: return t.GetProperty("Hour").GetGetMethod();
\r
326 case DateTimePart.Microsecond: return t.GetProperty("Microsecond").GetGetMethod();
\r
327 case DateTimePart.Millisecond: return t.GetProperty("Millisecond").GetGetMethod();
\r
328 case DateTimePart.Minute: return t.GetProperty("Minute").GetGetMethod();
\r
329 case DateTimePart.Month: return t.GetProperty("Month").GetGetMethod();
\r
330 case DateTimePart.Nanosecond: return t.GetProperty("Nanosecond").GetGetMethod();
\r
331 case DateTimePart.Second: return t.GetProperty("Second").GetGetMethod();
\r
332 case DateTimePart.Yeah: return t.GetProperty("Yeah").GetGetMethod();
\r
334 throw new NotImplementedException();
\r
337 private Expression AnalyzeSqlDateDiff(DateTimePart part, IList<Expression> parameters, BuilderContext builderContext)
\r
339 Expression l = parameters[0], r = parameters [1];
\r
340 Type valueType = l.Type.IsNullable() ? (l.Type == typeof(DateTime?) ? typeof(DateTime) : typeof(DateTimeOffset)) : l.Type;
\r
341 MethodInfo p = GetPropertyGetterForDatePart(valueType, part);
\r
342 if (l.Type.IsNullable())
\r
344 MethodInfo hasValue = l.Type == typeof(DateTime?) ? dateTimeHasValue : dateTimeOffsetHasValue;
\r
345 MethodInfo value = l.Type == typeof(DateTime?) ? dateTimeValue : dateTimeOffsetValue;
\r
346 // Console.WriteLine("!!!! {0}, {1}", Expression.Subtract(Expression.Property(Expression.Property(l, value), p), Expression.Property(Expression.Property(r, value), p)).Type, Expression.New(int?).Type);
\r
347 return Expression.Condition(
\r
348 Expression.AndAlso(Expression.Property(l, hasValue), Expression.Property(r, hasValue)),
\r
349 Expression.New (nintctr,
\r
350 Expression.Subtract(
\r
351 Expression.Property(Expression.Property (l, value), p),
\r
352 Expression.Property(Expression.Property (r, value), p))),
\r
353 Expression.Convert (Expression.Constant (null), typeof (int?)));
\r
356 return Expression.Subtract(Expression.Property(l, p), Expression.Property(r, p));
\r
359 private Expression AnalyzeStringInsert(IList<Expression> parameters, BuilderContext builderContext)
\r
361 var startIndexExpression = new StartIndexOffsetExpression(builderContext.QueryContext.DataContext.Vendor.SqlProvider.StringIndexStartsAtOne, parameters.ElementAt(1));
\r
362 var stringToInsertExpression = parameters.ElementAt(2);
\r
363 return AnalyzeGenericSpecialExpressionType(SpecialExpressionType.StringInsert, new Expression[] { parameters.First(), startIndexExpression, stringToInsertExpression }, builderContext);
\r
366 protected virtual Expression AnalyzeLog(IList<Expression> parameters, BuilderContext builderContext)
\r
368 if (parameters.Count == 1)
\r
369 return new SpecialExpression(SpecialExpressionType.Ln, parameters.Select(p => Analyze(p, builderContext)).ToList());
\r
370 else if (parameters.Count == 2)
\r
371 return new SpecialExpression(SpecialExpressionType.Log, parameters.Select(p => Analyze(p, builderContext)).ToList());
\r
373 throw new NotSupportedException();
\r
376 protected virtual Expression AnalyzeGenericSpecialExpressionType(SpecialExpressionType specialType, IList<Expression> parameters, BuilderContext builderContext)
\r
378 return new SpecialExpression(specialType, parameters.Select(p => Analyze(p, builderContext)).ToList());
\r
381 protected virtual Expression AnalyzeParse(MethodInfo method, IList<Expression> parameters, BuilderContext builderContext)
\r
383 if (method.IsStatic && parameters.Count == 1)
\r
385 var expression = Expression.Convert(Analyze(parameters.First(), builderContext), method.ReturnType, method);
\r
386 ExpressionTier tier = ExpressionQualifier.GetTier(expression);
\r
389 //pibgeus: I would like to call to the expression optimizer since the exception must be thrown if the expression cannot be executed
\r
390 //in Clr tier, if it can be executed in Clr tier it should continue
\r
391 // ie: from e in db.Employees where DateTime.Parse("1/1/1999").Year==1999 select e <--- this should work
\r
392 // 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)
\r
394 //if (method.ReturnType == typeof(DateTime))
\r
396 // expression = ExpressionOptimizer.Analyze(expression);
\r
397 // //same behaviour that Linq2Sql
\r
398 // throw new NotSupportedException("Method 'System.DateTime Parse(System.String)' has no supported translation to SQL");
\r
404 throw new ArgumentException();
\r
408 protected virtual Expression AnalyzeToString(MethodInfo method, IList<Expression> parameters, BuilderContext builderContext)
\r
410 if (parameters.Count != 1)
\r
411 throw new ArgumentException();
\r
413 Expression parameter;
\r
414 if (parameters.First().Type.IsNullable())
\r
415 parameter = Expression.Convert(parameters.First(), parameters.First().Type.GetNullableType());
\r
417 parameter = parameters.First();
\r
419 if (!parameter.Type.IsPrimitive && parameter.Type != typeof(string))
\r
421 //TODO: ExpressionDispacher.Analyze.AnalyzeToString is not complete
\r
422 //This is the standar behaviour in linq2sql, nonetheless the behaviour isn't complete since when the expression
\r
423 //can be executed in the clr, ie: (where new StrangeObject().ToString()) should work. The problem is that
\r
424 //we don't have a reference to the optimizer here.
\r
425 //Working samples in: /Tests/Test_Nunit/ReadTests_Conversions.cs
\r
426 throw new NotSupportedException("Method ToString can only be translated to SQL for primitive types.");
\r
429 return Expression.Convert(Analyze(parameter, builderContext), typeof(string), typeof(Convert).GetMethod("ToString", new[] { parameter.Type }));
\r
433 /// Limits selection count
\r
435 /// <param name="parameters"></param>
\r
436 /// <param name="builderContext"></param>
\r
437 /// <returns></returns>
\r
438 protected virtual Expression AnalyzeTake(IList<Expression> parameters, BuilderContext builderContext)
\r
440 AddLimit(Analyze(parameters[1], builderContext), builderContext);
\r
441 return Analyze(parameters[0], builderContext);
\r
444 protected virtual void AddLimit(Expression limit, BuilderContext builderContext)
\r
446 var previousLimit = builderContext.CurrentSelect.Limit;
\r
447 if (previousLimit != null)
\r
448 builderContext.CurrentSelect.Limit = Expression.Condition(Expression.LessThan(previousLimit, limit),
\r
449 previousLimit, limit);
\r
451 builderContext.CurrentSelect.Limit = limit;
\r
455 /// Skip selection items
\r
457 /// <param name="parameters"></param>
\r
458 /// <param name="builderContext"></param>
\r
459 /// <returns></returns>
\r
460 protected virtual Expression AnalyzeSkip(IList<Expression> parameters, BuilderContext builderContext)
\r
462 AddOffset(Analyze(parameters[1], builderContext), builderContext);
\r
463 return Analyze(parameters[0], builderContext);
\r
466 protected virtual void AddOffset(Expression offset, BuilderContext builderContext)
\r
468 var previousOffset = builderContext.CurrentSelect.Offset;
\r
469 if (previousOffset != null)
\r
470 builderContext.CurrentSelect.Offset = Expression.Add(offset, previousOffset);
\r
472 builderContext.CurrentSelect.Offset = offset;
\r
476 /// Registers a scalar method call for result
\r
478 /// <param name="methodName"></param>
\r
479 /// <param name="limit"></param>
\r
480 /// <param name="parameters"></param>
\r
481 /// <param name="builderContext"></param>
\r
482 /// <returns></returns>
\r
483 protected virtual Expression AnalyzeScalar(string methodName, int? limit, IList<Expression> parameters, BuilderContext builderContext)
\r
485 builderContext.CurrentSelect.ExecuteMethodName = methodName;
\r
486 if (limit.HasValue)
\r
487 AddLimit(Expression.Constant(limit.Value), builderContext);
\r
488 var table = Analyze(parameters[0], builderContext);
\r
489 CheckWhere(table, parameters, 1, builderContext);
\r
494 /// Some methods, like Single(), Count(), etc. can get an extra parameter, specifying a restriction.
\r
495 /// This method checks if the parameter is specified, and adds it to the WHERE clauses
\r
497 /// <param name="table"></param>
\r
498 /// <param name="parameters"></param>
\r
499 /// <param name="extraParameterIndex"></param>
\r
500 /// <param name="builderContext"></param>
\r
501 private void CheckWhere(Expression table, IList<Expression> parameters, int extraParameterIndex, BuilderContext builderContext)
\r
503 if (parameters.Count > extraParameterIndex) // a lambda can be specified here, this is a restriction
\r
504 RegisterWhere(Analyze(parameters[extraParameterIndex], table, builderContext), builderContext);
\r
508 /// Returns a projection method call
\r
510 /// <param name="specialExpressionType"></param>
\r
511 /// <param name="parameters"></param>
\r
512 /// <param name="builderContext"></param>
\r
513 /// <returns></returns>
\r
514 protected virtual Expression AnalyzeProjectionQuery(SpecialExpressionType specialExpressionType, IList<Expression> parameters,
\r
515 BuilderContext builderContext)
\r
518 if (builderContext.IsExternalInExpressionChain)
\r
520 var operand0 = Analyze(parameters[0], builderContext);
\r
521 Expression projectionOperand;
\r
523 // basically, we have three options for projection methods:
\r
524 // - projection on grouped table (1 operand, a GroupExpression)
\r
525 // - projection on grouped column (2 operands, GroupExpression and ColumnExpression)
\r
526 // - projection on table/column, with optional restriction
\r
527 var groupOperand0 = operand0 as GroupExpression;
\r
528 if (groupOperand0 != null)
\r
530 if (parameters.Count > 1)
\r
532 projectionOperand = Analyze(parameters[1], groupOperand0.GroupedExpression,
\r
536 projectionOperand = Analyze(groupOperand0.GroupedExpression, builderContext);
\r
540 projectionOperand = operand0;
\r
541 CheckWhere(projectionOperand, parameters, 1, builderContext);
\r
544 if (projectionOperand is TableExpression)
\r
545 projectionOperand = RegisterTable((TableExpression)projectionOperand, builderContext);
\r
547 if (groupOperand0 != null)
\r
548 projectionOperand = new GroupExpression(projectionOperand, groupOperand0.KeyExpression);
\r
550 return new SpecialExpression(specialExpressionType, projectionOperand);
\r
554 var projectionQueryBuilderContext = builderContext.NewSelect();
\r
555 var tableExpression = Analyze(parameters[0], projectionQueryBuilderContext);
\r
557 if (!(tableExpression is TableExpression))
\r
558 tableExpression = Analyze(tableExpression, projectionQueryBuilderContext);
\r
560 // from here we build a custom clause:
\r
561 // <anyClause> ==> "(select count(*) from <table> where <anyClause>)>0"
\r
562 // TODO (later...): see if some vendors support native Any operator and avoid this substitution
\r
563 if (parameters.Count > 1)
\r
565 var anyClause = Analyze(parameters[1], tableExpression, projectionQueryBuilderContext);
\r
566 RegisterWhere(anyClause, projectionQueryBuilderContext);
\r
569 projectionQueryBuilderContext.CurrentSelect = projectionQueryBuilderContext.CurrentSelect.ChangeOperands(new SpecialExpression(specialExpressionType, tableExpression));
\r
571 // we now switch back to current context, and compare the result with 0
\r
572 return projectionQueryBuilderContext.CurrentSelect;
\r
578 /// Entry point for a Select()
\r
579 /// static Select(this Expression table, λ(table))
\r
581 /// <param name="parameters"></param>
\r
582 /// <param name="builderContext"></param>
\r
583 /// <returns></returns>
\r
584 protected virtual Expression AnalyzeSelect(IList<Expression> parameters, BuilderContext builderContext)
\r
586 // just call back the underlying lambda (or quote, whatever)
\r
587 return Analyze(parameters[1], parameters[0], builderContext);
\r
591 /// Entry point for a Where()
\r
592 /// static Where(this Expression table, λ(table))
\r
594 /// <param name="parameters"></param>
\r
595 /// <param name="builderContext"></param>
\r
596 /// <returns></returns>
\r
597 protected virtual Expression AnalyzeWhere(IList<Expression> parameters, BuilderContext builderContext)
\r
599 var tablePiece = parameters[0];
\r
600 RegisterWhere(Analyze(parameters[1], tablePiece, builderContext), builderContext);
\r
605 /// Handling a lambda consists in:
\r
606 /// - filling its input parameters with what's on the stack
\r
607 /// - using the body (parameters are registered in the context)
\r
609 /// <param name="expression"></param>
\r
610 /// <param name="parameters"></param>
\r
611 /// <param name="builderContext"></param>
\r
612 /// <returns></returns>
\r
613 protected virtual Expression AnalyzeLambda(Expression expression, IList<Expression> parameters, BuilderContext builderContext)
\r
615 var lambdaExpression = expression as LambdaExpression;
\r
616 if (lambdaExpression == null)
\r
617 throw Error.BadArgument("S0227: Unknown type for AnalyzeLambda() ({0})", expression.GetType());
\r
618 // for a lambda, first parameter is body, others are input parameters
\r
619 // so we create a parameters stack
\r
620 for (int parameterIndex = 0; parameterIndex < lambdaExpression.Parameters.Count; parameterIndex++)
\r
622 var parameterExpression = lambdaExpression.Parameters[parameterIndex];
\r
623 builderContext.Parameters[parameterExpression.Name] = Analyze(parameters[parameterIndex], builderContext);
\r
625 // we keep only the body, the header is now useless
\r
626 // and once the parameters have been substituted, we don't pass one anymore
\r
627 return Analyze(lambdaExpression.Body, builderContext);
\r
631 /// When a parameter is used, we replace it with its original value
\r
633 /// <param name="expression"></param>
\r
634 /// <param name="builderContext"></param>
\r
635 /// <returns></returns>
\r
636 protected virtual Expression AnalyzeParameter(Expression expression, BuilderContext builderContext)
\r
638 Expression unaliasedExpression;
\r
639 var parameterName = GetParameterName(expression);
\r
640 builderContext.Parameters.TryGetValue(parameterName, out unaliasedExpression);
\r
641 if (unaliasedExpression == null)
\r
642 throw Error.BadArgument("S0257: can not find parameter '{0}'", parameterName);
\r
644 #region set alias helper
\r
647 var unaliasedTableExpression = unaliasedExpression as TableExpression;
\r
648 if (unaliasedTableExpression != null && unaliasedTableExpression.Alias == null)
\r
649 unaliasedTableExpression.Alias = parameterName;
\r
651 var unaliasedColumnExpression = unaliasedExpression as ColumnExpression;
\r
652 if (unaliasedColumnExpression != null && unaliasedColumnExpression.Alias == null)
\r
653 unaliasedColumnExpression.Alias = parameterName;
\r
657 //var groupByExpression = unaliasedExpression as GroupByExpression;
\r
658 //if (groupByExpression != null)
\r
659 // unaliasedExpression = groupByExpression.ColumnExpression.Table;
\r
661 return unaliasedExpression;
\r
665 /// Returns if the given member can be considered as an EntitySet<>
\r
667 /// <param name="memberType"></param>
\r
668 /// <param name="entityType"></param>
\r
669 /// <returns></returns>
\r
670 protected virtual bool IsEntitySet(Type memberType, out Type entityType)
\r
672 entityType = memberType;
\r
673 // one check, a generic EntityRef<> or inherited
\r
674 if (memberType.IsGenericType && typeof(EntitySet<>).IsAssignableFrom(memberType.GetGenericTypeDefinition()))
\r
676 entityType = memberType.GetGenericArguments()[0];
\r
680 // this is for compatibility with previously generated .cs files
\r
681 // TODO: remove in 2009
\r
682 if (memberType.IsGenericType && typeof(System.Data.Linq.EntitySet<>).IsAssignableFrom(memberType.GetGenericTypeDefinition()))
\r
684 entityType = memberType.GetGenericArguments()[0];
\r
692 /// Analyzes a member access.
\r
693 /// This analyzis is down to top: the highest identifier is at bottom
\r
695 /// <param name="expression"></param>
\r
696 /// <param name="builderContext"></param>
\r
697 /// <returns></returns>
\r
698 protected virtual Expression AnalyzeMember(Expression expression, BuilderContext builderContext)
\r
700 var memberExpression = (MemberExpression)expression;
\r
702 Expression objectExpression = null;
\r
703 //maybe is a static member access like DateTime.Now
\r
704 bool isStaticMemberAccess = memberExpression.Member.GetIsStaticMember();
\r
706 if (!isStaticMemberAccess)
\r
707 // first parameter is object, second is member
\r
708 objectExpression = Analyze(memberExpression.Expression, builderContext);
\r
710 var memberInfo = memberExpression.Member;
\r
711 // then see what we can do, depending on object type
\r
712 // - MetaTable --> then the result is a table
\r
713 // - Table --> the result may be a column or a join
\r
714 // - Object --> external parameter or table (can this happen here? probably not... to be checked)
\r
716 if (objectExpression is MetaTableExpression)
\r
718 var metaTableExpression = (MetaTableExpression)objectExpression;
\r
719 var tableExpression = metaTableExpression.GetTableExpression(memberInfo);
\r
720 if (tableExpression == null)
\r
721 throw Error.BadArgument("S0270: MemberInfo '{0}' not found in MetaTable", memberInfo.Name);
\r
722 return tableExpression;
\r
725 if (objectExpression is GroupExpression)
\r
727 if (memberInfo.Name == "Key")
\r
728 return ((GroupExpression)objectExpression).KeyExpression;
\r
731 // if object is a table, then we need a column, or an association
\r
732 if (objectExpression is TableExpression)
\r
734 var tableExpression = (TableExpression)objectExpression;
\r
736 // before finding an association, we check for an EntitySet<>
\r
737 // this will be used in RegisterAssociation
\r
739 bool isEntitySet = IsEntitySet(memberInfo.GetMemberType(), out entityType);
\r
740 // first of all, then, try to find the association
\r
741 var queryAssociationExpression = RegisterAssociation(tableExpression, memberInfo, entityType,
\r
743 if (queryAssociationExpression != null)
\r
745 // no entitySet? we have right association
\r
746 //if (!isEntitySet)
\r
747 return queryAssociationExpression;
\r
749 // from here, we may require to cast the table to an entitySet
\r
750 return new EntitySetExpression(queryAssociationExpression, memberInfo.GetMemberType());
\r
752 // then, try the column
\r
753 var queryColumnExpression = RegisterColumn(tableExpression, memberInfo, builderContext);
\r
754 if (queryColumnExpression != null)
\r
755 return queryColumnExpression;
\r
757 if (memberInfo.Name == "Count")
\r
758 return AnalyzeProjectionQuery(SpecialExpressionType.Count, new[] { memberExpression.Expression }, builderContext);
\r
760 throw Error.BadArgument("S0293: Column must be mapped. Non-mapped columns are not handled by now.");
\r
763 // if object is still an object (== a constant), then we have an external parameter
\r
764 if (objectExpression is ConstantExpression)
\r
766 // the memberInfo.Name is provided here only to ease the SQL reading
\r
767 var parameterExpression = RegisterParameter(expression, memberInfo.Name, builderContext);
\r
768 if (parameterExpression != null)
\r
769 return parameterExpression;
\r
770 throw Error.BadArgument("S0302: Can not created parameter from expression '{0}'", expression);
\r
773 // we have here a special cases for nullables
\r
774 if (!isStaticMemberAccess && objectExpression.Type != null && objectExpression.Type.IsNullable())
\r
776 // Value means we convert the nullable to a value --> use Convert instead (works both on CLR and SQL, too)
\r
777 if (memberInfo.Name == "Value")
\r
778 return Expression.Convert(objectExpression, memberInfo.GetMemberType());
\r
779 // HasValue means not null (works both on CLR and SQL, too)
\r
780 if (memberInfo.Name == "HasValue")
\r
781 return new SpecialExpression(SpecialExpressionType.IsNotNull, objectExpression);
\r
785 if (memberInfo.DeclaringType == typeof(DateTime))
\r
786 return AnalyzeDateTimeMemberAccess(objectExpression, memberInfo, isStaticMemberAccess);
\r
788 // TODO: make this expresion safe (objectExpression can be null here)
\r
789 if (objectExpression.Type == typeof(TimeSpan))
\r
790 return AnalyzeTimeSpanMemberAccess(objectExpression, memberInfo);
\r
793 if (objectExpression is InputParameterExpression)
\r
795 return AnalyzeExternalParameterMember((InputParameterExpression)objectExpression, memberInfo, builderContext);
\r
798 if (objectExpression is MemberInitExpression)
\r
800 var foundExpression = AnalyzeMemberInit((MemberInitExpression)objectExpression, memberInfo, builderContext);
\r
801 if (foundExpression != null)
\r
802 return foundExpression;
\r
805 return AnalyzeCommonMember(objectExpression, memberInfo, builderContext);
\r
808 private Expression AnalyzeTimeSpanMemberAccess(Expression objectExpression, MemberInfo memberInfo)
\r
810 throw new NotImplementedException();
\r
813 protected Expression AnalyzeTimeSpamMemberAccess(Expression objectExpression, MemberInfo memberInfo)
\r
815 //A timespan expression can be only generated in a c# query as a DateTime difference, as a function call return or as a paramter
\r
816 //this case is for the DateTime difference operation
\r
818 if (!(objectExpression is BinaryExpression))
\r
819 throw new NotSupportedException();
\r
821 var operands = objectExpression.GetOperands();
\r
823 bool absoluteSpam = memberInfo.Name.StartsWith("Total");
\r
824 string operationKey = absoluteSpam ? memberInfo.Name.Substring(5) : memberInfo.Name;
\r
826 Expression currentExpression;
\r
827 switch (operationKey)
\r
829 case "Milliseconds":
\r
830 currentExpression = Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double));
\r
833 currentExpression = Expression.Divide(
\r
834 Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),
\r
835 Expression.Constant(1000.0));
\r
838 currentExpression = Expression.Divide(
\r
839 Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),
\r
840 Expression.Constant(60000.0));
\r
843 currentExpression = Expression.Divide(
\r
844 Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),
\r
845 Expression.Constant(3600000.0));
\r
848 currentExpression = Expression.Divide(
\r
849 Expression.Convert(new SpecialExpression(SpecialExpressionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)),
\r
850 Expression.Constant(86400000.0));
\r
853 throw new NotSupportedException(string.Format("The operation {0} over the TimeSpan isn't currently supported", memberInfo.Name));
\r
858 switch (memberInfo.Name)
\r
860 case "Milliseconds":
\r
861 currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)), Expression.Constant(1000L)), typeof(int));
\r
864 currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)),
\r
865 Expression.Constant(60L)), typeof(int));
\r
868 currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)),
\r
869 Expression.Constant(60L)), typeof(int));
\r
872 currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(
\r
873 currentExpression, typeof(long)),
\r
874 Expression.Constant(24L)), typeof(int));
\r
877 currentExpression = Expression.Convert(currentExpression, typeof(int));
\r
882 return currentExpression;
\r
885 protected Expression AnalyzeDateTimeMemberAccess(Expression objectExpression, MemberInfo memberInfo, bool isStaticMemberAccess)
\r
887 if (isStaticMemberAccess)
\r
889 if (memberInfo.Name == "Now")
\r
890 return new SpecialExpression(SpecialExpressionType.Now);
\r
892 throw new NotSupportedException(string.Format("DateTime Member access {0} not supported", memberInfo.Name));
\r
896 switch (memberInfo.Name)
\r
899 return new SpecialExpression(SpecialExpressionType.Year, objectExpression);
\r
901 return new SpecialExpression(SpecialExpressionType.Month, objectExpression);
\r
903 return new SpecialExpression(SpecialExpressionType.Day, objectExpression);
\r
905 return new SpecialExpression(SpecialExpressionType.Hour, objectExpression);
\r
907 return new SpecialExpression(SpecialExpressionType.Minute, objectExpression);
\r
909 return new SpecialExpression(SpecialExpressionType.Second, objectExpression);
\r
910 case "Millisecond":
\r
911 return new SpecialExpression(SpecialExpressionType.Millisecond, objectExpression);
\r
913 throw new NotSupportedException(string.Format("DateTime Member access {0} not supported", memberInfo.Name));
\r
919 /// This method analyzes the case of a new followed by a member access
\r
920 /// for example new "A(M = value).M", where the Expression can be reduced to "value"
\r
921 /// Caution: it may return null if no result is found
\r
923 /// <param name="expression"></param>
\r
924 /// <param name="memberInfo"></param>
\r
925 /// <param name="builderContext"></param>
\r
926 /// <returns>A member initializer or null</returns>
\r
927 protected virtual Expression AnalyzeMemberInit(MemberInitExpression expression, MemberInfo memberInfo,
\r
928 BuilderContext builderContext)
\r
930 // TODO: a method for NewExpression that we will use directly from AnalyzeMember and indirectly from here
\r
931 foreach (var binding in expression.Bindings)
\r
933 var memberAssignment = binding as MemberAssignment;
\r
934 if (memberAssignment != null)
\r
936 if (memberAssignment.Member == memberInfo)
\r
937 return memberAssignment.Expression;
\r
943 protected virtual Expression AnalyzeExternalParameterMember(InputParameterExpression expression, MemberInfo memberInfo, BuilderContext builderContext)
\r
945 UnregisterParameter(expression, builderContext);
\r
946 return RegisterParameter(Expression.MakeMemberAccess(expression.Expression, memberInfo), memberInfo.Name, builderContext);
\r
949 protected virtual Expression AnalyzeCommonMember(Expression objectExpression, MemberInfo memberInfo, BuilderContext builderContext)
\r
951 if (typeof(string).IsAssignableFrom(objectExpression.Type))
\r
953 switch (memberInfo.Name)
\r
956 return new SpecialExpression(SpecialExpressionType.StringLength, objectExpression);
\r
959 //throw Error.BadArgument("S0324: Don't know how to handle Piece");
\r
960 return Expression.MakeMemberAccess(objectExpression, memberInfo);
\r
964 /// A Quote creates a new local context, outside which created parameters disappear
\r
965 /// This is why we clone the BuilderContext
\r
967 /// <param name="piece"></param>
\r
968 /// <param name="parameters"></param>
\r
969 /// <param name="builderContext"></param>
\r
970 /// <returns></returns>
\r
971 protected virtual Expression AnalyzeQuote(Expression piece, IList<Expression> parameters, BuilderContext builderContext)
\r
973 var builderContextClone = builderContext.NewQuote();
\r
974 var firstExpression = piece.GetOperands().First();
\r
975 return Analyze(firstExpression, parameters, builderContextClone);
\r
980 /// <param name="expression"></param>
\r
981 /// <param name="builderContext"></param>
\r
982 /// <returns></returns>
\r
983 protected virtual Expression AnalyzeOperatorSubstract(Expression expression, BuilderContext builderContext)
\r
985 return AnalyzeOperator(expression, builderContext);
\r
989 /// Operator analysis consists in anlyzing all operands
\r
991 /// <param name="expression"></param>
\r
992 /// <param name="builderContext"></param>
\r
993 /// <returns></returns>
\r
994 protected virtual Expression AnalyzeOperator(Expression expression, BuilderContext builderContext)
\r
996 var operands = expression.GetOperands().ToList();
\r
997 for (int operandIndex = 0; operandIndex < operands.Count; operandIndex++)
\r
999 var operand = operands[operandIndex];
\r
1000 operands[operandIndex] = Analyze(operand, builderContext);
\r
1002 return expression.ChangeOperands(operands);
\r
1005 protected virtual Expression AnalyzeNewOperator(Expression expression, BuilderContext builderContext)
\r
1007 if (builderContext.ExpectMetaTableDefinition)
\r
1009 // first, check if we have a MetaTable definition
\r
1011 var typeInitializers = GetTypeInitializers<Expression>((NewExpression)expression, true, out metaType);
\r
1012 var aliases = new Dictionary<MemberInfo, TableExpression>();
\r
1013 foreach (var memberInfo in typeInitializers.Keys)
\r
1015 var tableExpression = Analyze(typeInitializers[memberInfo], builderContext) as TableExpression;
\r
1016 aliases[memberInfo] = tableExpression;
\r
1018 if (IsMetaTableDefinition(aliases))
\r
1019 return RegisterMetaTable(metaType, aliases, builderContext);
\r
1021 return AnalyzeOperator(expression, builderContext);
\r
1024 protected virtual bool IsMetaTableDefinition(IDictionary<MemberInfo, TableExpression> aliases)
\r
1026 if (aliases.Count != 2)
\r
1028 foreach (var tableExpression in aliases.Values)
\r
1030 if (tableExpression == null)
\r
1037 /// SelectMany() joins tables
\r
1039 /// <param name="parameters"></param>
\r
1040 /// <param name="builderContext"></param>
\r
1041 /// <returns></returns>
\r
1042 protected virtual Expression AnalyzeSelectMany(IList<Expression> parameters, BuilderContext builderContext)
\r
1044 if (parameters.Count == 3)
\r
1046 // ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/fxref_system.core/html/3371348f-7811-b0bc-8c0a-2a595e08e086.htm
\r
1047 var tableExpression = parameters[0];
\r
1048 var projectionExpression = Analyze(parameters[1], new[] { tableExpression }, builderContext);
\r
1049 //var manyPiece = Analyze(parameters[2], new[] { tableExpression, projectionExpression }, builderContext);
\r
1050 // from here, our manyPiece is a MetaTable definition
\r
1051 //var newExpression = manyPiece as NewExpression;
\r
1052 //if (newExpression == null)
\r
1053 // throw Error.BadArgument("S0377: Expected a NewExpression as SelectMany() return value");
\r
1054 //Type metaTableType;
\r
1055 //var associations = GetTypeInitializers<TableExpression>(newExpression, true, out metaTableType);
\r
1056 //return RegisterMetaTable(metaTableType, associations, builderContext);
\r
1057 var metaTableDefinitionBuilderContext = builderContext.Clone();
\r
1058 metaTableDefinitionBuilderContext.ExpectMetaTableDefinition = true;
\r
1059 var expression = Analyze(parameters[2], new[] { tableExpression, projectionExpression },
\r
1060 metaTableDefinitionBuilderContext);
\r
1061 return expression;
\r
1063 throw Error.BadArgument("S0358: Don't know how to handle this SelectMany() overload ({0} parameters)", parameters.Count);
\r
1066 protected virtual IDictionary<MemberInfo, E> GetTypeInitializers<E>(NewExpression newExpression,
\r
1067 bool checkCast, out Type metaType)
\r
1068 where E : Expression
\r
1070 var associations = new Dictionary<MemberInfo, E>();
\r
1072 for (int ctorParameterIndex = 0; ctorParameterIndex < newExpression.Arguments.Count; ctorParameterIndex++)
\r
1074 var aliasedExpression = newExpression.Arguments[ctorParameterIndex] as E;
\r
1075 if (aliasedExpression == null && checkCast)
\r
1076 throw Error.BadArgument("S0541: Expected an specific Expression type for GetTypeInitializers()");
\r
1077 var memberInfo = newExpression.Members[ctorParameterIndex];
\r
1078 metaType = memberInfo.ReflectedType;
\r
1079 // the property info is the reflecting property for the memberInfo, if memberInfo is a get_*
\r
1080 // otherwise we keep the memberInfo as is, since it is a field
\r
1081 var propertyInfo = memberInfo.GetExposingProperty() ?? memberInfo;
\r
1082 associations[propertyInfo] = aliasedExpression;
\r
1084 if (metaType == null && checkCast)
\r
1085 throw Error.BadArgument("S0550: Empty NewExpression found"); // this should never happen, otherwise we may simply ignore it or take the type from elsewhere
\r
1086 return associations;
\r
1089 //protected virtual IDictionary<MemberInfo, E> GetTypeInitializers<E>(NewExpression newExpression)
\r
1090 // where E : Expression
\r
1093 // return GetTypeInitializers<E>(newExpression, out metaType);
\r
1097 /// Analyzes a Join statement (explicit join)
\r
1099 /// <param name="parameters"></param>
\r
1100 /// <param name="builderContext"></param>
\r
1101 /// <returns></returns>
\r
1102 protected virtual Expression AnalyzeJoin(IList<Expression> parameters, BuilderContext builderContext)
\r
1104 return AnalyzeJoin(parameters, TableJoinType.Inner, builderContext);
\r
1108 /// Analyzes a Join statement (explicit join)
\r
1110 /// <param name="parameters"></param>
\r
1111 /// <param name="builderContext"></param>
\r
1112 /// <returns></returns>
\r
1113 protected virtual Expression AnalyzeGroupJoin(IList<Expression> parameters, BuilderContext builderContext)
\r
1115 return AnalyzeJoin(parameters, TableJoinType.Inner, builderContext);
\r
1118 protected virtual Expression AnalyzeOuterJoin(IList<Expression> parameters, BuilderContext builderContext)
\r
1120 var expression = Analyze(parameters[0], builderContext);
\r
1121 var tableExpression = expression as TableExpression;
\r
1122 if (tableExpression != null)
\r
1124 tableExpression.SetOuterJoin();
\r
1126 return expression;
\r
1129 private Expression AnalyzeJoin(IList<Expression> parameters, TableJoinType joinType, BuilderContext builderContext)
\r
1131 if (parameters.Count == 5)
\r
1133 var leftExpression = Analyze(parameters[0], builderContext);
\r
1134 var rightTable = Analyze(parameters[1], builderContext) as TableExpression;
\r
1135 if (rightTable == null)
\r
1136 throw Error.BadArgument("S0536: Expected a TableExpression for Join");
\r
1137 var leftJoin = Analyze(parameters[2], leftExpression, builderContext);
\r
1138 var rightJoin = Analyze(parameters[3], rightTable, builderContext);
\r
1139 // from here, we have two options to join:
\r
1140 // 1. left and right are tables, we can use generic expressions (most common)
\r
1141 // 2. left is something else (a meta table)
\r
1142 var leftTable = leftExpression as TableExpression;
\r
1143 if (leftTable == null)
\r
1145 var leftColumn = leftJoin as ColumnExpression;
\r
1146 if (leftColumn == null)
\r
1147 throw Error.BadArgument("S0701: No way to find left table for Join");
\r
1148 leftTable = leftColumn.Table;
\r
1150 rightTable.Join(joinType, leftTable, Expression.Equal(leftJoin, rightJoin),
\r
1151 string.Format("join{0}", builderContext.EnumerateAllTables().Count()));
\r
1152 // last part is lambda, with two tables as parameters
\r
1153 var metaTableDefinitionBuilderContext = builderContext.Clone();
\r
1154 metaTableDefinitionBuilderContext.ExpectMetaTableDefinition = true;
\r
1155 var expression = Analyze(parameters[4], new[] { leftExpression, rightTable }, metaTableDefinitionBuilderContext);
\r
1156 return expression;
\r
1158 throw Error.BadArgument("S0530: Don't know how to handle GroupJoin() with {0} parameters", parameters.Count);
\r
1162 /// "Distinct" means select X group by X
\r
1164 /// <param name="parameters"></param>
\r
1165 /// <param name="builderContext"></param>
\r
1166 /// <returns></returns>
\r
1167 protected virtual Expression AnalyzeDistinct(IList<Expression> parameters, BuilderContext builderContext)
\r
1169 var expression = Analyze(parameters[0], builderContext);
\r
1170 // we select and group by the same criterion
\r
1171 var group = new GroupExpression(expression, expression);
\r
1172 builderContext.CurrentSelect.Group.Add(group);
\r
1173 // "Distinct" method is equivalent to a GroupBy
\r
1174 // but for some obscure reasons, Linq expects a IQueryable instead of an IGrouping
\r
1175 // so we return the column, not the group
\r
1176 return expression;
\r
1180 /// Creates a group by clause
\r
1182 /// <param name="parameters"></param>
\r
1183 /// <param name="builderContext"></param>
\r
1184 /// <returns></returns>
\r
1185 protected virtual Expression AnalyzeGroupBy(IList<Expression> parameters, BuilderContext builderContext)
\r
1187 var table = Analyze(parameters[0], builderContext);
\r
1188 var keyExpression = Analyze(parameters[1], table, builderContext);
\r
1190 Expression result;
\r
1191 if (parameters.Count == 2)
\r
1192 result = table; // we return the whole table
\r
1193 else if (parameters.Count == 3)
\r
1194 result = Analyze(parameters[2], table, builderContext); // 3 parameters for a projection expression
\r
1196 throw Error.BadArgument("S0629: Don't know how to handle Expression to group by with {0} parameters", parameters.Count);
\r
1198 var group = new GroupExpression(result, keyExpression);
\r
1199 builderContext.CurrentSelect.Group.Add(group);
\r
1204 /// All() returns true if the given condition satisfies all provided elements
\r
1206 /// <param name="parameters"></param>
\r
1207 /// <param name="builderContext"></param>
\r
1208 /// <returns></returns>
\r
1209 protected virtual Expression AnalyzeAll(IList<Expression> parameters, BuilderContext builderContext)
\r
1211 var allBuilderContext = builderContext.NewSelect();
\r
1212 var tableExpression = Analyze(parameters[0], allBuilderContext);
\r
1213 var allClause = Analyze(parameters[1], tableExpression, allBuilderContext);
\r
1214 // from here we build a custom clause:
\r
1215 // <allClause> ==> "(select count(*) from <table> where not <allClause>)==0"
\r
1216 // TODO (later...): see if some vendors support native All operator and avoid this substitution
\r
1217 var whereExpression = Expression.Not(allClause);
\r
1218 RegisterWhere(whereExpression, allBuilderContext);
\r
1219 allBuilderContext.CurrentSelect = allBuilderContext.CurrentSelect.ChangeOperands(new SpecialExpression(SpecialExpressionType.Count, tableExpression));
\r
1220 // TODO: see if we need to register the tablePiece here (we probably don't)
\r
1222 // we now switch back to current context, and compare the result with 0
\r
1223 var allExpression = Expression.Equal(allBuilderContext.CurrentSelect, Expression.Constant(0));
\r
1224 return allExpression;
\r
1228 /// Any() returns true if the given condition satisfies at least one of provided elements
\r
1230 /// <param name="parameters"></param>
\r
1231 /// <param name="builderContext"></param>
\r
1232 /// <returns></returns>
\r
1233 protected virtual Expression AnalyzeAny(IList<Expression> parameters, BuilderContext builderContext)
\r
1235 if (builderContext.IsExternalInExpressionChain)
\r
1237 var tableExpression = Analyze(parameters[0], builderContext);
\r
1238 Expression projectionOperand;
\r
1240 // basically, we have three options for projection methods:
\r
1241 // - projection on grouped table (1 operand, a GroupExpression)
\r
1242 // - projection on grouped column (2 operands, GroupExpression and ColumnExpression)
\r
1243 // - projection on table/column, with optional restriction
\r
1244 var groupOperand0 = tableExpression as GroupExpression;
\r
1245 if (groupOperand0 != null)
\r
1247 if (parameters.Count > 1)
\r
1249 projectionOperand = Analyze(parameters[1], groupOperand0.GroupedExpression,
\r
1253 projectionOperand = Analyze(groupOperand0.GroupedExpression, builderContext);
\r
1257 projectionOperand = tableExpression;
\r
1258 CheckWhere(projectionOperand, parameters, 1, builderContext);
\r
1261 if (projectionOperand is TableExpression)
\r
1262 projectionOperand = RegisterTable((TableExpression)projectionOperand, builderContext);
\r
1264 if (groupOperand0 != null)
\r
1265 projectionOperand = new GroupExpression(projectionOperand, groupOperand0.KeyExpression);
\r
1267 return Expression.GreaterThan(new SpecialExpression(SpecialExpressionType.Count, projectionOperand), Expression.Constant(0));
\r
1271 var anyBuilderContext = builderContext.NewSelect();
\r
1272 var tableExpression = Analyze(parameters[0], anyBuilderContext);
\r
1274 if (!(tableExpression is TableExpression))
\r
1275 tableExpression = Analyze(tableExpression, anyBuilderContext);
\r
1277 // from here we build a custom clause:
\r
1278 // <anyClause> ==> "(select count(*) from <table> where <anyClause>)>0"
\r
1279 // TODO (later...): see if some vendors support native Any operator and avoid this substitution
\r
1280 if (parameters.Count > 1)
\r
1282 var anyClause = Analyze(parameters[1], tableExpression, anyBuilderContext);
\r
1283 RegisterWhere(anyClause, anyBuilderContext);
\r
1285 anyBuilderContext.CurrentSelect = anyBuilderContext.CurrentSelect.ChangeOperands(new SpecialExpression(SpecialExpressionType.Count, tableExpression));
\r
1286 // TODO: see if we need to register the tablePiece here (we probably don't)
\r
1288 // we now switch back to current context, and compare the result with 0
\r
1289 var anyExpression = Expression.GreaterThan(anyBuilderContext.CurrentSelect, Expression.Constant(0));
\r
1290 return anyExpression;
\r
1294 protected virtual Expression AnalyzeLikeStart(IList<Expression> parameters, BuilderContext builderContext)
\r
1296 return AnalyzeLike(parameters[0], null, parameters[1], "%", builderContext);
\r
1299 protected virtual Expression AnalyzeLikeEnd(IList<Expression> parameters, BuilderContext builderContext)
\r
1301 return AnalyzeLike(parameters[0], "%", parameters[1], null, builderContext);
\r
1304 protected virtual Expression AnalyzeLike(IList<Expression> parameters, BuilderContext builderContext)
\r
1306 return AnalyzeLike(parameters[0], "%", parameters[1], "%", builderContext);
\r
1309 protected virtual Expression AnalyzeLike(Expression value, string before, Expression operand, string after, BuilderContext builderContext)
\r
1311 operand = Analyze(operand, builderContext);
\r
1312 if (before != null)
\r
1313 operand = new SpecialExpression(SpecialExpressionType.Concat, Expression.Constant(before), operand);
\r
1314 if (after != null)
\r
1315 operand = new SpecialExpression(SpecialExpressionType.Concat, operand, Expression.Constant(after));
\r
1316 return new SpecialExpression(SpecialExpressionType.Like, Analyze(value, builderContext), operand);
\r
1319 protected virtual Expression AnalyzeSubString(IList<Expression> parameters, BuilderContext builderContext)
\r
1321 var stringExpression = Analyze(parameters[0], builderContext);
\r
1322 var startExpression = new StartIndexOffsetExpression(builderContext.QueryContext.DataContext.Vendor.SqlProvider.StringIndexStartsAtOne,
\r
1323 Analyze(parameters[1], builderContext));
\r
1324 if (parameters.Count > 2)
\r
1326 var lengthExpression = parameters[2];
\r
1327 return new SpecialExpression(SpecialExpressionType.Substring, stringExpression, startExpression, lengthExpression);
\r
1329 return new SpecialExpression(SpecialExpressionType.Substring, stringExpression, startExpression);
\r
1332 protected virtual Expression AnalyzeContains(IList<Expression> parameters, BuilderContext builderContext)
\r
1334 if (parameters[0].Type.IsArray)
\r
1336 var array = Analyze(parameters[0], builderContext);
\r
1337 var expression = Analyze(parameters[1], builderContext);
\r
1338 return new SpecialExpression(SpecialExpressionType.In, expression, array);
\r
1340 throw Error.BadArgument("S0548: Can't analyze Contains() method");
\r
1343 protected virtual Expression AnalyzeToUpper(IList<Expression> parameters, BuilderContext builderContext)
\r
1345 return new SpecialExpression(SpecialExpressionType.ToUpper, Analyze(parameters[0], builderContext));
\r
1348 protected virtual Expression AnalyzeToLower(IList<Expression> parameters, BuilderContext builderContext)
\r
1350 return new SpecialExpression(SpecialExpressionType.ToLower, Analyze(parameters[0], builderContext));
\r
1354 /// Registers ordering request
\r
1356 /// <param name="parameters"></param>
\r
1357 /// <param name="descending"></param>
\r
1358 /// <param name="builderContext"></param>
\r
1359 /// <returns></returns>
\r
1360 protected virtual Expression AnalyzeOrderBy(IList<Expression> parameters, bool descending, BuilderContext builderContext)
\r
1362 var table = Analyze(parameters[0], builderContext);
\r
1363 // the column is related to table
\r
1364 var column = Analyze(parameters[1], table, builderContext);
\r
1365 builderContext.CurrentSelect.OrderBy.Add(new OrderByExpression(descending, column));
\r
1370 /// Analyzes constant expression value, and eventually extracts a table
\r
1372 /// <param name="expression"></param>
\r
1373 /// <param name="builderContext"></param>
\r
1374 /// <returns></returns>
\r
1375 protected virtual Expression AnalyzeConstant(Expression expression, BuilderContext builderContext)
\r
1377 var constantExpression = expression as ConstantExpression;
\r
1378 if (constantExpression != null)
\r
1380 var queriedType = GetQueriedType(expression);
\r
1381 if (queriedType != null)
\r
1385 if (constantExpression.Value is ITable)
\r
1387 var tableType = constantExpression.Type.GetGenericArguments()[0];
\r
1388 return new TableExpression(tableType, DataMapper.GetTableName(tableType, builderContext.QueryContext.DataContext));
\r
1391 return expression;
\r
1394 protected virtual Expression AnalyzeSelectOperation(SelectOperatorType operatorType, IList<Expression> parameters, BuilderContext builderContext)
\r
1396 // a special case: if we have several SELECT expressions linked together,
\r
1397 // we maximize the load to the database, since the result must use the same parameters
\r
1398 // types and count.
\r
1399 builderContext.QueryContext.MaximumDatabaseLoad = true; // all select expression goes to SQL tier
\r
1401 var currentSelect = builderContext.CurrentSelect;
\r
1402 var nextSelect = new SelectExpression(currentSelect.Parent);
\r
1403 builderContext.CurrentSelect = nextSelect;
\r
1405 // TODO: this is very dirty code, unreliable, unstable, and unlovable
\r
1406 var constantExpression = parameters[1] as ConstantExpression;
\r
1407 var queryProvider = (QueryProvider)constantExpression.Value;
\r
1408 var expressionChain = queryProvider.ExpressionChain;
\r
1409 var table = queryProvider.TableType;
\r
1410 var queryBuilder = ObjectFactory.Get<QueryBuilder>();
\r
1411 var tableExpression = new TableExpression(table,
\r
1412 DataMapper.GetTableName(table,
\r
1413 builderContext.QueryContext.DataContext));
\r
1415 var selectOperandExpression = queryBuilder.BuildSelectExpression(expressionChain, tableExpression, builderContext);
\r
1416 builderContext.SelectExpressions.Add(selectOperandExpression);
\r
1418 nextSelect = builderContext.CurrentSelect;
\r
1419 builderContext.CurrentSelect = currentSelect;
\r
1420 currentSelect.NextSelectExpression = nextSelect;
\r
1421 currentSelect.NextSelectExpressionOperator = operatorType;
\r
1423 return Analyze(parameters[0], builderContext);
\r
1427 /// Analyses InvokeExpression
\r
1429 /// <param name="expression"></param>
\r
1430 /// <param name="parameters"></param>
\r
1431 /// <param name="builderContext"></param>
\r
1432 /// <returns></returns>
\r
1433 protected virtual Expression AnalyzeInvoke(Expression expression, IList<Expression> parameters,
\r
1434 BuilderContext builderContext)
\r
1436 var invocationExpression = (InvocationExpression)expression;
\r
1437 var lambda = invocationExpression.Expression as LambdaExpression;
\r
1438 if (lambda != null)
\r
1440 var localBuilderContext = builderContext.NewQuote();
\r
1441 //for (int parameterIndex = 0; parameterIndex < lambda.Parameters.Count; parameterIndex++)
\r
1443 // var parameter = lambda.Parameters[parameterIndex];
\r
1444 // localBuilderContext.Parameters[parameter.Name] = Analyze(invocationExpression.Arguments[parameterIndex], builderContext);
\r
1446 //return Analyze(lambda, localBuilderContext);
\r
1447 return Analyze(lambda, invocationExpression.Arguments, localBuilderContext);
\r
1449 // TODO: see what we must do here
\r
1450 return expression;
\r