PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://bitbucket.org/foobar22/mono
C# | 1648 lines | 1160 code | 152 blank | 336 comment | 245 complexity | 70c18b9b0029d1d7d5df93da8b885d06 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0, Unlicense, Apache-2.0, LGPL-2.0

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

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