Datei: NDODLL/Linq/ExpressionTreeTransformer.cs

Last Commit (28c8c45)
1 //
2 // Copyright (c) 2002-2016 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Collections.Generic;
25 using System.Collections;
26 using System.Text;
27 using System.Linq.Expressions;
28 using System.Linq;
29
30 namespace NDO.Linq
31 {
32 ····/// <summary>
33 ····/// This class transforms Linq queries to NDOql
34 ····/// </summary>
35 ····public class ExpressionTreeTransformer
36 ····{
37 ········class OperatorEntry
38 ········{
39 ············public OperatorEntry( ExpressionType exprType, string replacement, int prec )
40 ············{
41 ················this.ExpressionType = exprType;
42 ················this.Precedence = prec;
43 ················this.Replacement = replacement;
44 ············}
45 ············public ExpressionType ExpressionType;
46 ············public string Replacement;
47 ············public int Precedence;
48
49 ············public static bool IsComparison( ExpressionType expType )
50 ············{
51 ················return expType == ExpressionType.GreaterThanOrEqual || expType == ExpressionType.GreaterThan || expType == ExpressionType.LessThan || expType == ExpressionType.LessThanOrEqual || expType == ExpressionType.Equal || expType == ExpressionType.NotEqual;
52 ············}
53 ········}
54
55 ········static OperatorEntry[] operators =
56 ········{
57 ············new OperatorEntry(ExpressionType.Modulo, "%", 0),
58 ············new OperatorEntry(ExpressionType.Multiply, "*", 0),
59 ············new OperatorEntry(ExpressionType.Divide, "/", 0),
60 ············new OperatorEntry(ExpressionType.Add, "+", 1),
61 ············new OperatorEntry(ExpressionType.Subtract, "-", 1),
62 ············new OperatorEntry(ExpressionType.GreaterThanOrEqual, ">=", 2),
63 ············new OperatorEntry(ExpressionType.GreaterThan, ">", 2),
64 ············new OperatorEntry(ExpressionType.LessThanOrEqual, "<=", 2),
65 ············new OperatorEntry(ExpressionType.LessThan, "<", 2),
66 ············new OperatorEntry(ExpressionType.NotEqual, "<>", 2),
67 ············new OperatorEntry(ExpressionType.Equal, "=", 3),
68 ············new OperatorEntry(ExpressionType.And, "AND", 4),
69 ············new OperatorEntry(ExpressionType.AndAlso, "AND", 4),
70 ············new OperatorEntry(ExpressionType.Or, "OR", 5),
71 ············new OperatorEntry(ExpressionType.OrElse, "OR", 5),
72 ········};
73
74 ········LambdaExpression baseExpression;
75 ········Expression baseLeftSide;
76 ········StringBuilder sb;
77 ········List<object> parameters;
78 ········string baseParameterName;
79 ········int baseParameterLength;
80 ········Stack<Expression> expressionStack;
81
82 ········/// <summary>
83 ········/// Returns the generated parameters
84 ········/// </summary>
85 ········public IEnumerable<object> Parameters
86 ········{
87 ············get { return parameters; }
88 ········}
89
90 ········/// <summary>
91 ········/// Constructs an ExpressionTreeTransformer objct
92 ········/// </summary>
93 ········/// <param name="ex"></param>
94 ········public ExpressionTreeTransformer( LambdaExpression ex )
95 ········{
96 ············this.baseExpression = ex;
97 ········}
98
99 ········/// <summary>
100 ········/// Transforms the lambda expression passed to the constructor into an NDOql string.
101 ········/// </summary>
102 ········/// <returns></returns>
103 ········public string Transform()
104 ········{
105 ············baseParameterName = baseExpression.Parameters[0].Name;
106 ············baseLeftSide = baseExpression.Parameters[0];
107 ············baseParameterLength = baseParameterName.Length + 1;
108 ············this.expressionStack = new Stack<Expression>();
109 ············sb = new StringBuilder();
110 ············parameters = new List<object>();
111 ············Transform( baseExpression.Body );
112 ············return sb.ToString();
113 ········}
114
115 ········bool IsNull( Expression ex )
116 ········{
117 ············if (ex is ConstantExpression ce)
118 ············{
119 ················if (ce.Value == null)
120 ····················return true;
121 ············}
122
123 ············if (ex.NodeType == ExpressionType.MemberAccess)
124 ············{
125 ················var me = ex as MemberExpression;
126 ················if (me != null)
127 ················{
128 ····················if (me.Type == typeof( DateTime ))
129 ····················{
130 ························try
131 ························{
132 ····························var d = Expression.Lambda( ex ).Compile();
133 ····························var dt = (DateTime)d.DynamicInvoke();
134 ····························if (dt == DateTime.MinValue)
135 ································return true;
136 ························}
137 ························catch { }
138 ····················}
139 ····················else if (me.Type == typeof( Guid ))
140 ····················{
141 ························try
142 ························{
143 ····························var d = Expression.Lambda( ex ).Compile();
144 ····························var g = (Guid)d.DynamicInvoke();
145 ····························if (g == Guid.Empty)
146 ································return true;
147 ························}
148 ························catch { }
149 ····················}
150 ················}
151 ············}
152
153 ············return false;
154 ········}
155
156 ········void TransformBinaryOperator( ExpressionType exprType, ref Expression right )
157 ········{
158 ············sb.Append( ' ' );
159 ············if (IsNull( right ))
160 ············{
161 ················if (exprType == ExpressionType.NotEqual)
162 ················{
163 ····················sb.Append( "IS NOT" );
164 ····················right = Expression.Constant(null);
165 ················}
166 ················else if (exprType == ExpressionType.Equal)
167 ················{
168 ····················sb.Append( "IS" );
169 ····················right = Expression.Constant( null );
170 ················}
171 ················else
172 ················{
173 ····················OperatorEntry oe = FindOperator( exprType );
174 ····················sb.Append( oe.Replacement );
175 ················}
176 ············}
177 ············else
178 ············{
179 ················OperatorEntry oe = FindOperator( exprType );
180 ················sb.Append( oe.Replacement );
181 ············}
182 ············sb.Append( ' ' );
183 ········}
184
185 ········OperatorEntry FindOperator( ExpressionType exprType )
186 ········{
187 ············foreach (OperatorEntry oe in operators)
188 ················if (oe.ExpressionType == exprType)
189 ····················return oe;
190 ············throw new Exception( "ExpressionTreeTransformer: Unsupported operator: " + exprType );
191 ········}
192
193 ········int GetPrecedence( ExpressionType exprType )
194 ········{
195 ············OperatorEntry oe = FindOperator(exprType);
196 ············return oe.Precedence;
197 ········}
198
199 ········void AddParameter( object value )
200 ········{
201 ············var ix = parameters.FindIndex(p => p.Equals(value));
202 ············if (ix > -1)
203 ············{
204 ················sb.Append( '{' );
205 ················sb.Append( ix.ToString() );
206 ················sb.Append( '}' );
207 ············}
208 ············else
209 ············{
210 ················sb.Append( '{' );
211 ················sb.Append( parameters.Count.ToString() );
212 ················sb.Append( '}' );
213 ················parameters.Add( value );
214 ············}
215 ········}
216
217 ········BinaryExpression FlipExpression( BinaryExpression binex )
218 ········{
219 ············if (binex.NodeType == ExpressionType.GreaterThan)
220 ················return BinaryExpression.LessThan( binex.Right, binex.Left );
221 ············if (binex.NodeType == ExpressionType.GreaterThanOrEqual)
222 ················return BinaryExpression.LessThanOrEqual( binex.Right, binex.Left );
223 ············if (binex.NodeType == ExpressionType.LessThan)
224 ················return BinaryExpression.GreaterThan( binex.Right, binex.Left );
225 ············if (binex.NodeType == ExpressionType.LessThanOrEqual)
226 ················return BinaryExpression.GreaterThanOrEqual( binex.Right, binex.Left );
227 ············return Expression.MakeBinary( binex.NodeType, binex.Right, binex.Left );
228 ········}
229
230 ········void FlipArguments( List<Expression> arguments )
231 ········{
232 ············var temp = arguments[0];
233 ············arguments[0] = arguments[1];
234 ············arguments[1] = temp;
235 ········}
236
237 ········bool IsReversedExpression( List<Expression> arguments )
238 ········{
239 ············return arguments.Count == 2 && arguments[0] is ConstantExpression && !( arguments[1] is ConstantExpression );
240 ········}
241
242 ········void TransformEquality(Expression ex1, Expression ex2)
243 ········{
244 ············Transform( ex1 );
245 ············TransformBinaryOperator( ExpressionType.Equal, ref ex2 );
246 ············Transform( ex2 );
247 ········}
248
249 ········bool IsPropertyOfBaseExpression( Expression ex )
250 ········{
251 ············if (ex is MemberExpression pe)
252 ············{
253 ················return pe.Expression == baseLeftSide;
254 ············}
255
256 ············return false;
257 ········}
258
259 ········void Transform( Expression ex )
260 ········{
261 ············expressionStack.Push( ex );··// This helps determining parent expressions
262 ············try
263 ············{
264 ················if (ex == this.baseLeftSide)
265 ····················return;
266 ··················//-------
267
268 ················string exStr = ex.ToString();
269 ················if (exStr == "null")
270 ················{
271 ····················sb.Append( "NULL" );
272 ····················return;
273 ··················//-------
274 ················}
275
276 ················if (ex.NodeType == ExpressionType.MemberAccess )
277 ················{
278 ····················// We try to compile the expression.
279 ····················// If it can be compiled, we have a value,
280 ····················// which should be added as a parameter.
281 ····················// We are safe to examine MemberAccess nodes only,
282 ····················// because local variables will be wrapped in
283 ····················// display classes and accessed as members of
284 ····················// these classes. All other cases are anyway
285 ····················// members of arbitrary types (like String.Empty).
286 ····················try
287 ····················{
288 ························var d = Expression.Lambda( ex ).Compile();
289 ························AddParameter( d.DynamicInvoke() );
290 ························return;
291 ······················//-------
292 ····················}
293 ····················catch
294 ····················{··// Can't be compiled, so pass on.
295 ····················}
296 ················}
297
298 ················BinaryExpression binex = ex as BinaryExpression;
299 ················if (binex != null)
300 ················{
301 ····················Expression left = binex.Left;
302 ····················Expression right = binex.Right;
303 ····················if (left.NodeType == ExpressionType.Convert)
304 ························left = ( (UnaryExpression) left ).Operand;
305 ····················if (right.NodeType == ExpressionType.Convert)
306 ························right = ( (UnaryExpression) right ).Operand;
307
308 ····················if (!IsPropertyOfBaseExpression( binex.Left ) && IsPropertyOfBaseExpression( binex.Right ))
309 ····················{
310 ························Transform( FlipExpression( binex ) );
311 ························return;
312 ························//-------
313 ····················}
314
315 ····················int ownPrecedence = GetPrecedence(binex.NodeType);
316 ····················int childPrecedence = 0;
317 ····················BinaryExpression childBinex = binex.Left as BinaryExpression;
318 ····················bool leftbracket = false;
319 ····················if (childBinex != null)
320 ····················{
321 ························childPrecedence = Math.Max( childPrecedence, GetPrecedence( childBinex.NodeType ) );
322 ························leftbracket = childPrecedence > ownPrecedence;
323 ····················}
324 ····················childBinex = binex.Right as BinaryExpression;
325 ····················bool rightbracket = false;
326 ····················if (childBinex != null)
327 ····················{
328 ························childPrecedence = Math.Max( childPrecedence, GetPrecedence( childBinex.NodeType ) );
329 ························rightbracket = childPrecedence > ownPrecedence;
330 ····················}
331 ····················if (leftbracket)
332 ························sb.Append( '(' );
333 ····················Transform( left );
334 ····················if (leftbracket)
335 ························sb.Append( ')' );
336 ····················TransformBinaryOperator( ex.NodeType, ref right );
337 ····················if (rightbracket)
338 ························sb.Append( '(' );
339 ····················Transform( right );
340 ····················if (rightbracket)
341 ························sb.Append( ')' );
342 ····················return;
343 ····················//-------
344 ················}
345
346 ················MethodCallExpression mcex = ex as MethodCallExpression;
347 ················if (mcex != null)
348 ················{
349 ····················var arguments = new List<Expression>(mcex.Arguments);
350 ····················string mname = mcex.Method.Name;
351 ····················if (mname == "op_Equality")
352 ····················{
353 ························if (IsReversedExpression( arguments ))
354 ························{
355 ····························FlipArguments( arguments );
356 ························}
357 ························TransformEquality( arguments[0], arguments[1] );
358 ····················}
359 ····················else if (mname == "Equals")
360 ····················{
361 ························TransformEquality( mcex.Object, arguments[0] );
362 ····················}
363 ····················else if (mname == "Like")
364 ····················{
365 ························if (IsReversedExpression( arguments ))
366 ························{
367 ····························FlipArguments( arguments );
368 ························}
369 ························Transform( arguments[0] );
370 ························sb.Append( " LIKE " );
371 ························Transform( arguments[1] );
372 ····················}
373 ····················else if (mname == "GreaterEqual")
374 ····················{
375 ························string op = " >= ";
376 ························if (IsReversedExpression( arguments ))
377 ························{
378 ····························FlipArguments( arguments );
379 ····························op = " <= ";
380 ························}
381 ························Transform( arguments[0] );
382 ························sb.Append( op );
383 ························Transform( arguments[1] );
384 ····················}
385 ····················else if (mname == "LowerEqual")
386 ····················{
387 ························string op = " <= ";
388 ························if (IsReversedExpression( arguments ))
389 ························{
390 ····························FlipArguments( arguments );
391 ····························op = " >= ";
392 ························}
393 ························Transform( arguments[0] );
394 ························sb.Append( op );
395 ························Transform( arguments[1] );
396 ····················}
397 ····················else if (mname == "GreaterThan")
398 ····················{
399 ························string op = " > ";
400 ························if (IsReversedExpression( arguments ))
401 ························{
402 ····························FlipArguments( arguments );
403 ····························op = " < ";
404 ························}
405 ························Transform( arguments[0] );
406 ························sb.Append( op );
407 ························Transform( arguments[1] );
408 ····················}
409 ····················else if (mname == "LowerThan")
410 ····················{
411 ························string op = " < ";
412 ························if (IsReversedExpression( arguments ))
413 ························{
414 ····························FlipArguments( arguments );
415 ····························op = " > ";
416 ························}
417 ························Transform( arguments[0] );
418 ························sb.Append( op );
419 ························Transform( arguments[1] );
420 ····················}
421 ····················else if (mname == "Between")
422 ····················{
423 ························Transform( mcex.Arguments[0] );
424 ························sb.Append( " BETWEEN " );
425 ························Transform( mcex.Arguments[1] );
426 ························sb.Append( " AND " );
427 ························Transform( mcex.Arguments[2] );
428 ····················}
429 ····················else if (mname == "get_Item")
430 ····················{
431 ························string argStr = mcex.Arguments[0].ToString();
432 ························if (argStr == "Any.Index")
433 ························{
434 ····························Transform( mcex.Object );
435 ····························return;
436 ····························//-------
437 ························}
438 ························Transform( mcex.Object );
439 ························if (mcex.Object.Type.FullName == "NDO.ObjectId")
440 ························{
441 ····························TransformOidIndex( mcex.Arguments[0] );
442 ····························return;
443 ····························//-------
444 ························}
445 ························sb.Append( '(' );
446 ························Transform( mcex.Arguments[0] );
447 ························sb.Append( ')' );
448 ····················}
449 ····················else if (mname == "ElementAt")
450 ····················{
451 ························string argStr = mcex.Arguments[1].ToString();
452 ························if (argStr == "Any.Index" || "Index.Any" == argStr)
453 ························{
454 ····························Transform( mcex.Arguments[0] );
455 ····························return;
456 ····························//-------
457 ························}
458 ························Transform( mcex.Arguments[0] );
459 ························sb.Append( '(' );
460 ························Transform( mcex.Arguments[1] );
461 ························sb.Append( ')' );
462 ····················}
463 ····················else if (mname == "Any")
464 ····················{
465 ························Transform( mcex.Arguments[0] );
466 ························var exprx = mcex.Arguments[1];
467 ························if (sb[sb.Length - 1] != '.')
468 ····························sb.Append( '.' );
469 ························Transform( ( (LambdaExpression) mcex.Arguments[1] ).Body );
470 ····················}
471 ····················else if (mname == "In")
472 ····················{
473 ························var arg = mcex.Arguments[1];
474 ························IEnumerable list = (IEnumerable)(Expression.Lambda(arg).Compile().DynamicInvoke());
475 ························Transform( mcex.Arguments[0] );
476 ························sb.Append( " IN (" );
477 ························var en = list.GetEnumerator();
478 ························en.MoveNext();
479 ························bool doQuote = en.Current is string || en.Current is Guid || en.Current is DateTime;
480 ························foreach (object obj in list)
481 ························{
482 ····························if (doQuote)
483 ································sb.Append( '\'' );
484 ····························sb.Append( obj );
485 ····························if (doQuote)
486 ································sb.Append( '\'' );
487 ····························sb.Append( ',' );
488 ························}
489 ························sb.Length -= 1;
490 ························sb.Append( ')' );
491 ····················}
492 ····················else if (mname == "Oid")
493 ····················{
494 ························var sbLength = sb.Length;
495 ························Transform( mcex.Arguments[0] );
496 ························if (sb.Length > sbLength)
497 ····························sb.Append( ".oid" );
498 ························else
499 ····························sb.Append( "oid" );
500 ························if (mcex.Arguments.Count > 1)
501 ························{
502 ····························TransformOidIndex( mcex.Arguments[1] );
503 ························}
504 ····················}
505 else
506 throw new Exception( "Method call not supported: " + mname ) ;
 
 
 
 
 
 
 
 
 
 
 
 
 
507 ····················return;
508 ····················//-------
509 ················}
510
511 ················if (ex.NodeType == ExpressionType.MemberAccess)
512 ················{
513 ····················MemberExpression memberex = (MemberExpression) ex;
514 ····················if (memberex.Member.Name == "NDOObjectId")
515 ····················{
516 ························if (memberex.Expression.ToString() != this.baseParameterName)
517 ························{
518 ····························Transform( memberex.Expression );
519 ····························if (sb[sb.Length - 1] != '.')
520 ································sb.Append( '.' );
521 ························}
522 ························sb.Append( "oid" );
523 ························return;
524 ····················}
525 ····················if (ex.Type == typeof( bool ))
526 ····················{
527 ························var top = expressionStack.Pop();
528 ························bool transformIt = expressionStack.Count == 0;··// The boolean expression is the top
529 ························if (expressionStack.Count > 0)
530 ························{
531 ····························var parent = expressionStack.Peek();
532 ····························if (parent.NodeType != ExpressionType.Equal && parent.NodeType != ExpressionType.NotEqual)
533 ····························{
534 ································// A Boolean Expression, which is not part of an Equals or NotEquals expression,
535 ································// must be unary. Since Sql Server doesn't support unary boolean expressions, we add an Equals Expression which compares with 1.
536 ································// Fortunately this works with other databases, too.
537 ································transformIt = true;
538 ····························}
539 ························}
540 ························if (transformIt)
541 ························{
542 ····························sb.Append( memberex.Member.Name );
543 ····························sb.Append( " = 1" );
544 ························}
545 ························expressionStack.Push( top );
546 ························if (transformIt)
547 ····························return;
548 ························//-------
549 ····················}
550 ····················if (memberex.Expression != baseLeftSide)
551 ····················{
552 ························// This happens while navigating through relations.
553 ························Transform( memberex.Expression );
554 ························if (sb[sb.Length - 1] != '.')
555 ····························sb.Append( '.' );
556 ····················}
557 ····················sb.Append( memberex.Member.Name );
558 ····················return;
559 ····················//-------
560 ················}
561
562 ················else if (ex.NodeType == ExpressionType.Constant)
563 ················{
564 ····················ConstantExpression constEx = (ConstantExpression) ex;
565 ····················AddParameter( constEx.Value );
566 ····················return;
567 ····················//-------
568 ················}
569
570 ················else if (ex.NodeType == ExpressionType.Convert)
571 ················{
572 ····················Transform( ( (UnaryExpression) ex ).Operand );
573 ····················return;
574 ····················//-------
575 ················}
576
577 ················else if (ex.NodeType == ExpressionType.Not)
578 ················{
579 ····················sb.Append( " NOT " );
580 ····················Expression inner = ((UnaryExpression)ex).Operand;
581 ····················var binary = (inner is BinaryExpression);
582 ····················if (binary)
583 ························sb.Append( '(' );
584 ····················Transform( inner );
585 ····················if (binary)
586 ························sb.Append( ')' );
587 ····················return;
588 ····················//-------
589 ················}
590 ············}
591 ············finally
592 ············{················
593 ················expressionStack.Pop();
594 ············}
595 ········}
596
597 ········private void TransformEquality( List<Expression> arguments )
598 ········{
599 ············Transform( arguments[0] );
600 ············sb.Append( " = " );
601 ············Transform( arguments[1] );
602 ········}
603
604 ········private void TransformOidIndex( Expression ex )
605 ········{
606 ············var ce = ex as ConstantExpression;
607 ············if (ce == null)
608 ················throw new Exception( "Oid index expression must be a ConstantExpression" );
609 ············if (!( ce.Value is System.Int32 ))
610 ················throw new Exception( "Oid index expression must be an Int32" );
611
612 ············if ((int) ce.Value != 0)
613 ············{
614 ················sb.Append( '(' );
615 ················sb.Append( ce.Value );
616 ················sb.Append( ')' );
617 ············}
618 ········}
619 ····}
620 }
New Commit (b47b95e)
1 //
2 // Copyright (c) 2002-2016 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Collections.Generic;
25 using System.Collections;
26 using System.Text;
27 using System.Linq.Expressions;
28 using System.Linq;
29
30 namespace NDO.Linq
31 {
32 ····/// <summary>
33 ····/// This class transforms Linq queries to NDOql
34 ····/// </summary>
35 ····public class ExpressionTreeTransformer
36 ····{
37 ········class OperatorEntry
38 ········{
39 ············public OperatorEntry( ExpressionType exprType, string replacement, int prec )
40 ············{
41 ················this.ExpressionType = exprType;
42 ················this.Precedence = prec;
43 ················this.Replacement = replacement;
44 ············}
45 ············public ExpressionType ExpressionType;
46 ············public string Replacement;
47 ············public int Precedence;
48
49 ············public static bool IsComparison( ExpressionType expType )
50 ············{
51 ················return expType == ExpressionType.GreaterThanOrEqual || expType == ExpressionType.GreaterThan || expType == ExpressionType.LessThan || expType == ExpressionType.LessThanOrEqual || expType == ExpressionType.Equal || expType == ExpressionType.NotEqual;
52 ············}
53 ········}
54
55 ········static OperatorEntry[] operators =
56 ········{
57 ············new OperatorEntry(ExpressionType.Modulo, "%", 0),
58 ············new OperatorEntry(ExpressionType.Multiply, "*", 0),
59 ············new OperatorEntry(ExpressionType.Divide, "/", 0),
60 ············new OperatorEntry(ExpressionType.Add, "+", 1),
61 ············new OperatorEntry(ExpressionType.Subtract, "-", 1),
62 ············new OperatorEntry(ExpressionType.GreaterThanOrEqual, ">=", 2),
63 ············new OperatorEntry(ExpressionType.GreaterThan, ">", 2),
64 ············new OperatorEntry(ExpressionType.LessThanOrEqual, "<=", 2),
65 ············new OperatorEntry(ExpressionType.LessThan, "<", 2),
66 ············new OperatorEntry(ExpressionType.NotEqual, "<>", 2),
67 ············new OperatorEntry(ExpressionType.Equal, "=", 3),
68 ············new OperatorEntry(ExpressionType.And, "AND", 4),
69 ············new OperatorEntry(ExpressionType.AndAlso, "AND", 4),
70 ············new OperatorEntry(ExpressionType.Or, "OR", 5),
71 ············new OperatorEntry(ExpressionType.OrElse, "OR", 5),
72 ········};
73
74 ········LambdaExpression baseExpression;
75 ········Expression baseLeftSide;
76 ········StringBuilder sb;
77 ········List<object> parameters;
78 ········string baseParameterName;
79 ········int baseParameterLength;
80 ········Stack<Expression> expressionStack;
81
82 ········/// <summary>
83 ········/// Returns the generated parameters
84 ········/// </summary>
85 ········public IEnumerable<object> Parameters
86 ········{
87 ············get { return parameters; }
88 ········}
89
90 ········/// <summary>
91 ········/// Constructs an ExpressionTreeTransformer objct
92 ········/// </summary>
93 ········/// <param name="ex"></param>
94 ········public ExpressionTreeTransformer( LambdaExpression ex )
95 ········{
96 ············this.baseExpression = ex;
97 ········}
98
99 ········/// <summary>
100 ········/// Transforms the lambda expression passed to the constructor into an NDOql string.
101 ········/// </summary>
102 ········/// <returns></returns>
103 ········public string Transform()
104 ········{
105 ············baseParameterName = baseExpression.Parameters[0].Name;
106 ············baseLeftSide = baseExpression.Parameters[0];
107 ············baseParameterLength = baseParameterName.Length + 1;
108 ············this.expressionStack = new Stack<Expression>();
109 ············sb = new StringBuilder();
110 ············parameters = new List<object>();
111 ············Transform( baseExpression.Body );
112 ············return sb.ToString();
113 ········}
114
115 ········bool IsNull( Expression ex )
116 ········{
117 ············if (ex is ConstantExpression ce)
118 ············{
119 ················if (ce.Value == null)
120 ····················return true;
121 ············}
122
123 ············if (ex.NodeType == ExpressionType.MemberAccess)
124 ············{
125 ················var me = ex as MemberExpression;
126 ················if (me != null)
127 ················{
128 ····················if (me.Type == typeof( DateTime ))
129 ····················{
130 ························try
131 ························{
132 ····························var d = Expression.Lambda( ex ).Compile();
133 ····························var dt = (DateTime)d.DynamicInvoke();
134 ····························if (dt == DateTime.MinValue)
135 ································return true;
136 ························}
137 ························catch { }
138 ····················}
139 ····················else if (me.Type == typeof( Guid ))
140 ····················{
141 ························try
142 ························{
143 ····························var d = Expression.Lambda( ex ).Compile();
144 ····························var g = (Guid)d.DynamicInvoke();
145 ····························if (g == Guid.Empty)
146 ································return true;
147 ························}
148 ························catch { }
149 ····················}
150 ················}
151 ············}
152
153 ············return false;
154 ········}
155
156 ········void TransformBinaryOperator( ExpressionType exprType, ref Expression right )
157 ········{
158 ············sb.Append( ' ' );
159 ············if (IsNull( right ))
160 ············{
161 ················if (exprType == ExpressionType.NotEqual)
162 ················{
163 ····················sb.Append( "IS NOT" );
164 ····················right = Expression.Constant(null);
165 ················}
166 ················else if (exprType == ExpressionType.Equal)
167 ················{
168 ····················sb.Append( "IS" );
169 ····················right = Expression.Constant( null );
170 ················}
171 ················else
172 ················{
173 ····················OperatorEntry oe = FindOperator( exprType );
174 ····················sb.Append( oe.Replacement );
175 ················}
176 ············}
177 ············else
178 ············{
179 ················OperatorEntry oe = FindOperator( exprType );
180 ················sb.Append( oe.Replacement );
181 ············}
182 ············sb.Append( ' ' );
183 ········}
184
185 ········OperatorEntry FindOperator( ExpressionType exprType )
186 ········{
187 ············foreach (OperatorEntry oe in operators)
188 ················if (oe.ExpressionType == exprType)
189 ····················return oe;
190 ············throw new Exception( "ExpressionTreeTransformer: Unsupported operator: " + exprType );
191 ········}
192
193 ········int GetPrecedence( ExpressionType exprType )
194 ········{
195 ············OperatorEntry oe = FindOperator(exprType);
196 ············return oe.Precedence;
197 ········}
198
199 ········void AddParameter( object value )
200 ········{
201 ············var ix = parameters.FindIndex(p => p.Equals(value));
202 ············if (ix > -1)
203 ············{
204 ················sb.Append( '{' );
205 ················sb.Append( ix.ToString() );
206 ················sb.Append( '}' );
207 ············}
208 ············else
209 ············{
210 ················sb.Append( '{' );
211 ················sb.Append( parameters.Count.ToString() );
212 ················sb.Append( '}' );
213 ················parameters.Add( value );
214 ············}
215 ········}
216
217 ········BinaryExpression FlipExpression( BinaryExpression binex )
218 ········{
219 ············if (binex.NodeType == ExpressionType.GreaterThan)
220 ················return BinaryExpression.LessThan( binex.Right, binex.Left );
221 ············if (binex.NodeType == ExpressionType.GreaterThanOrEqual)
222 ················return BinaryExpression.LessThanOrEqual( binex.Right, binex.Left );
223 ············if (binex.NodeType == ExpressionType.LessThan)
224 ················return BinaryExpression.GreaterThan( binex.Right, binex.Left );
225 ············if (binex.NodeType == ExpressionType.LessThanOrEqual)
226 ················return BinaryExpression.GreaterThanOrEqual( binex.Right, binex.Left );
227 ············return Expression.MakeBinary( binex.NodeType, binex.Right, binex.Left );
228 ········}
229
230 ········void FlipArguments( List<Expression> arguments )
231 ········{
232 ············var temp = arguments[0];
233 ············arguments[0] = arguments[1];
234 ············arguments[1] = temp;
235 ········}
236
237 ········bool IsReversedExpression( List<Expression> arguments )
238 ········{
239 ············return arguments.Count == 2 && arguments[0] is ConstantExpression && !( arguments[1] is ConstantExpression );
240 ········}
241
242 ········void TransformEquality(Expression ex1, Expression ex2)
243 ········{
244 ············Transform( ex1 );
245 ············TransformBinaryOperator( ExpressionType.Equal, ref ex2 );
246 ············Transform( ex2 );
247 ········}
248
249 ········bool IsPropertyOfBaseExpression( Expression ex )
250 ········{
251 ············if (ex is MemberExpression pe)
252 ············{
253 ················return pe.Expression == baseLeftSide;
254 ············}
255
256 ············return false;
257 ········}
258
259 ········void Transform( Expression ex )
260 ········{
261 ············expressionStack.Push( ex );··// This helps determining parent expressions
262 ············try
263 ············{
264 ················if (ex == this.baseLeftSide)
265 ····················return;
266 ··················//-------
267
268 ················string exStr = ex.ToString();
269 ················if (exStr == "null")
270 ················{
271 ····················sb.Append( "NULL" );
272 ····················return;
273 ··················//-------
274 ················}
275
276 ················if (ex.NodeType == ExpressionType.MemberAccess )
277 ················{
278 ····················// We try to compile the expression.
279 ····················// If it can be compiled, we have a value,
280 ····················// which should be added as a parameter.
281 ····················// We are safe to examine MemberAccess nodes only,
282 ····················// because local variables will be wrapped in
283 ····················// display classes and accessed as members of
284 ····················// these classes. All other cases are anyway
285 ····················// members of arbitrary types (like String.Empty).
286 ····················try
287 ····················{
288 ························var d = Expression.Lambda( ex ).Compile();
289 ························AddParameter( d.DynamicInvoke() );
290 ························return;
291 ······················//-------
292 ····················}
293 ····················catch
294 ····················{··// Can't be compiled, so pass on.
295 ····················}
296 ················}
297
298 ················BinaryExpression binex = ex as BinaryExpression;
299 ················if (binex != null)
300 ················{
301 ····················Expression left = binex.Left;
302 ····················Expression right = binex.Right;
303 ····················if (left.NodeType == ExpressionType.Convert)
304 ························left = ( (UnaryExpression) left ).Operand;
305 ····················if (right.NodeType == ExpressionType.Convert)
306 ························right = ( (UnaryExpression) right ).Operand;
307
308 ····················if (!IsPropertyOfBaseExpression( binex.Left ) && IsPropertyOfBaseExpression( binex.Right ))
309 ····················{
310 ························Transform( FlipExpression( binex ) );
311 ························return;
312 ························//-------
313 ····················}
314
315 ····················int ownPrecedence = GetPrecedence(binex.NodeType);
316 ····················int childPrecedence = 0;
317 ····················BinaryExpression childBinex = binex.Left as BinaryExpression;
318 ····················bool leftbracket = false;
319 ····················if (childBinex != null)
320 ····················{
321 ························childPrecedence = Math.Max( childPrecedence, GetPrecedence( childBinex.NodeType ) );
322 ························leftbracket = childPrecedence > ownPrecedence;
323 ····················}
324 ····················childBinex = binex.Right as BinaryExpression;
325 ····················bool rightbracket = false;
326 ····················if (childBinex != null)
327 ····················{
328 ························childPrecedence = Math.Max( childPrecedence, GetPrecedence( childBinex.NodeType ) );
329 ························rightbracket = childPrecedence > ownPrecedence;
330 ····················}
331 ····················if (leftbracket)
332 ························sb.Append( '(' );
333 ····················Transform( left );
334 ····················if (leftbracket)
335 ························sb.Append( ')' );
336 ····················TransformBinaryOperator( ex.NodeType, ref right );
337 ····················if (rightbracket)
338 ························sb.Append( '(' );
339 ····················Transform( right );
340 ····················if (rightbracket)
341 ························sb.Append( ')' );
342 ····················return;
343 ····················//-------
344 ················}
345
346 ················MethodCallExpression mcex = ex as MethodCallExpression;
347 ················if (mcex != null)
348 ················{
349 ····················var arguments = new List<Expression>(mcex.Arguments);
350 ····················string mname = mcex.Method.Name;
351 ····················if (mname == "op_Equality")
352 ····················{
353 ························if (IsReversedExpression( arguments ))
354 ························{
355 ····························FlipArguments( arguments );
356 ························}
357 ························TransformEquality( arguments[0], arguments[1] );
358 ····················}
359 ····················else if (mname == "Equals")
360 ····················{
361 ························TransformEquality( mcex.Object, arguments[0] );
362 ····················}
363 ····················else if (mname == "Like")
364 ····················{
365 ························if (IsReversedExpression( arguments ))
366 ························{
367 ····························FlipArguments( arguments );
368 ························}
369 ························Transform( arguments[0] );
370 ························sb.Append( " LIKE " );
371 ························Transform( arguments[1] );
372 ····················}
373 ····················else if (mname == "GreaterEqual")
374 ····················{
375 ························string op = " >= ";
376 ························if (IsReversedExpression( arguments ))
377 ························{
378 ····························FlipArguments( arguments );
379 ····························op = " <= ";
380 ························}
381 ························Transform( arguments[0] );
382 ························sb.Append( op );
383 ························Transform( arguments[1] );
384 ····················}
385 ····················else if (mname == "LowerEqual")
386 ····················{
387 ························string op = " <= ";
388 ························if (IsReversedExpression( arguments ))
389 ························{
390 ····························FlipArguments( arguments );
391 ····························op = " >= ";
392 ························}
393 ························Transform( arguments[0] );
394 ························sb.Append( op );
395 ························Transform( arguments[1] );
396 ····················}
397 ····················else if (mname == "GreaterThan")
398 ····················{
399 ························string op = " > ";
400 ························if (IsReversedExpression( arguments ))
401 ························{
402 ····························FlipArguments( arguments );
403 ····························op = " < ";
404 ························}
405 ························Transform( arguments[0] );
406 ························sb.Append( op );
407 ························Transform( arguments[1] );
408 ····················}
409 ····················else if (mname == "LowerThan")
410 ····················{
411 ························string op = " < ";
412 ························if (IsReversedExpression( arguments ))
413 ························{
414 ····························FlipArguments( arguments );
415 ····························op = " > ";
416 ························}
417 ························Transform( arguments[0] );
418 ························sb.Append( op );
419 ························Transform( arguments[1] );
420 ····················}
421 ····················else if (mname == "Between")
422 ····················{
423 ························Transform( mcex.Arguments[0] );
424 ························sb.Append( " BETWEEN " );
425 ························Transform( mcex.Arguments[1] );
426 ························sb.Append( " AND " );
427 ························Transform( mcex.Arguments[2] );
428 ····················}
429 ····················else if (mname == "get_Item")
430 ····················{
431 ························string argStr = mcex.Arguments[0].ToString();
432 ························if (argStr == "Any.Index")
433 ························{
434 ····························Transform( mcex.Object );
435 ····························return;
436 ····························//-------
437 ························}
438 ························Transform( mcex.Object );
439 ························if (mcex.Object.Type.FullName == "NDO.ObjectId")
440 ························{
441 ····························TransformOidIndex( mcex.Arguments[0] );
442 ····························return;
443 ····························//-------
444 ························}
445 ························sb.Append( '(' );
446 ························Transform( mcex.Arguments[0] );
447 ························sb.Append( ')' );
448 ····················}
449 ····················else if (mname == "ElementAt")
450 ····················{
451 ························string argStr = mcex.Arguments[1].ToString();
452 ························if (argStr == "Any.Index" || "Index.Any" == argStr)
453 ························{
454 ····························Transform( mcex.Arguments[0] );
455 ····························return;
456 ····························//-------
457 ························}
458 ························Transform( mcex.Arguments[0] );
459 ························sb.Append( '(' );
460 ························Transform( mcex.Arguments[1] );
461 ························sb.Append( ')' );
462 ····················}
463 ····················else if (mname == "Any")
464 ····················{
465 ························Transform( mcex.Arguments[0] );
466 ························var exprx = mcex.Arguments[1];
467 ························if (sb[sb.Length - 1] != '.')
468 ····························sb.Append( '.' );
469 ························Transform( ( (LambdaExpression) mcex.Arguments[1] ).Body );
470 ····················}
471 ····················else if (mname == "In")
472 ····················{
473 ························var arg = mcex.Arguments[1];
474 ························IEnumerable list = (IEnumerable)(Expression.Lambda(arg).Compile().DynamicInvoke());
475 ························Transform( mcex.Arguments[0] );
476 ························sb.Append( " IN (" );
477 ························var en = list.GetEnumerator();
478 ························en.MoveNext();
479 ························bool doQuote = en.Current is string || en.Current is Guid || en.Current is DateTime;
480 ························foreach (object obj in list)
481 ························{
482 ····························if (doQuote)
483 ································sb.Append( '\'' );
484 ····························sb.Append( obj );
485 ····························if (doQuote)
486 ································sb.Append( '\'' );
487 ····························sb.Append( ',' );
488 ························}
489 ························sb.Length -= 1;
490 ························sb.Append( ')' );
491 ····················}
492 ····················else if (mname == "Oid")
493 ····················{
494 ························var sbLength = sb.Length;
495 ························Transform( mcex.Arguments[0] );
496 ························if (sb.Length > sbLength)
497 ····························sb.Append( ".oid" );
498 ························else
499 ····························sb.Append( "oid" );
500 ························if (mcex.Arguments.Count > 1)
501 ························{
502 ····························TransformOidIndex( mcex.Arguments[1] );
503 ························}
504 ····················}
505 else // Assume, we have a server function here
506 {
507 ························sb.Append( mname );
508 ························sb.Append( '(' );
509 ························var end = arguments.Count - 1;
510 ························var i = 0;
511 ························foreach (var arg in arguments)
512 ························{
513 ····························Transform( arg );
514 ····························if (i < end)
515 ································sb.Append( ", " );
516 ····························i++;
517 ························}
518 ························sb.Append( ')' );
519 ····················}
520 ····················return;
521 ····················//-------
522 ················}
523
524 ················if (ex.NodeType == ExpressionType.MemberAccess)
525 ················{
526 ····················MemberExpression memberex = (MemberExpression) ex;
527 ····················if (memberex.Member.Name == "NDOObjectId")
528 ····················{
529 ························if (memberex.Expression.ToString() != this.baseParameterName)
530 ························{
531 ····························Transform( memberex.Expression );
532 ····························if (sb[sb.Length - 1] != '.')
533 ································sb.Append( '.' );
534 ························}
535 ························sb.Append( "oid" );
536 ························return;
537 ····················}
538 ····················if (ex.Type == typeof( bool ))
539 ····················{
540 ························var top = expressionStack.Pop();
541 ························bool transformIt = expressionStack.Count == 0;··// The boolean expression is the top
542 ························if (expressionStack.Count > 0)
543 ························{
544 ····························var parent = expressionStack.Peek();
545 ····························if (parent.NodeType != ExpressionType.Equal && parent.NodeType != ExpressionType.NotEqual)
546 ····························{
547 ································// A Boolean Expression, which is not part of an Equals or NotEquals expression,
548 ································// must be unary. Since Sql Server doesn't support unary boolean expressions, we add an Equals Expression which compares with 1.
549 ································// Fortunately this works with other databases, too.
550 ································transformIt = true;
551 ····························}
552 ························}
553 ························if (transformIt)
554 ························{
555 ····························sb.Append( memberex.Member.Name );
556 ····························sb.Append( " = 1" );
557 ························}
558 ························expressionStack.Push( top );
559 ························if (transformIt)
560 ····························return;
561 ························//-------
562 ····················}
563 ····················if (memberex.Expression != baseLeftSide)
564 ····················{
565 ························// This happens while navigating through relations.
566 ························Transform( memberex.Expression );
567 ························if (sb[sb.Length - 1] != '.')
568 ····························sb.Append( '.' );
569 ····················}
570 ····················sb.Append( memberex.Member.Name );
571 ····················return;
572 ····················//-------
573 ················}
574
575 ················else if (ex.NodeType == ExpressionType.Constant)
576 ················{
577 ····················ConstantExpression constEx = (ConstantExpression) ex;
578 ····················AddParameter( constEx.Value );
579 ····················return;
580 ····················//-------
581 ················}
582
583 ················else if (ex.NodeType == ExpressionType.Convert)
584 ················{
585 ····················Transform( ( (UnaryExpression) ex ).Operand );
586 ····················return;
587 ····················//-------
588 ················}
589
590 ················else if (ex.NodeType == ExpressionType.Not)
591 ················{
592 ····················sb.Append( " NOT " );
593 ····················Expression inner = ((UnaryExpression)ex).Operand;
594 ····················var binary = (inner is BinaryExpression);
595 ····················if (binary)
596 ························sb.Append( '(' );
597 ····················Transform( inner );
598 ····················if (binary)
599 ························sb.Append( ')' );
600 ····················return;
601 ····················//-------
602 ················}
603 ············}
604 ············finally
605 ············{················
606 ················expressionStack.Pop();
607 ············}
608 ········}
609
610 ········private void TransformEquality( List<Expression> arguments )
611 ········{
612 ············Transform( arguments[0] );
613 ············sb.Append( " = " );
614 ············Transform( arguments[1] );
615 ········}
616
617 ········private void TransformOidIndex( Expression ex )
618 ········{
619 ············var ce = ex as ConstantExpression;
620 ············if (ce == null)
621 ················throw new Exception( "Oid index expression must be a ConstantExpression" );
622 ············if (!( ce.Value is System.Int32 ))
623 ················throw new Exception( "Oid index expression must be an Int32" );
624
625 ············if ((int) ce.Value != 0)
626 ············{
627 ················sb.Append( '(' );
628 ················sb.Append( ce.Value );
629 ················sb.Append( ')' );
630 ············}
631 ········}
632 ····}
633 }