Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DLinq / Dlinq / SqlClient / Query / QueryConverter.cs / 1305376 / QueryConverter.cs
using System; using System.Globalization; using System.Collections; using System.Collections.Generic; using System.Data; using System.Reflection; using System.Text; using System.Linq; using System.Linq.Expressions; using System.Data.Linq; using System.Data.Linq.Mapping; using System.Data.Linq.Provider; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; namespace System.Data.Linq.SqlClient { ////// These are application types used to represent types used during intermediate /// stages of the query building process. /// enum ConverterSpecialTypes { Row, Table } [Flags] internal enum ConverterStrategy { Default = 0x0, SkipWithRowNumber = 0x1, CanUseScopeIdentity = 0x2, CanUseOuterApply = 0x4, CanUseRowStatus = 0x8, CanUseJoinOn = 0x10, // Whether or not to use ON clause of JOIN. CanOutputFromInsert = 0x20 } [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification="Unknown reason.")] internal class QueryConverter { IDataServices services; Translator translator; SqlFactory sql; TypeSystemProvider typeProvider; bool outerNode; Dictionarymap; Dictionary exprMap; Dictionary dupMap; Dictionary gmap; Expression dominatingExpression; bool allowDeferred; ConverterStrategy converterStrategy = ConverterStrategy.Default; class GroupInfo { internal SqlSelect SelectWithGroup; internal SqlExpression ElementOnGroupSource; } internal ConverterStrategy ConverterStrategy { get { return converterStrategy; } set { converterStrategy = value; } } private bool UseConverterStrategy(ConverterStrategy strategy) { return (this.converterStrategy & strategy) == strategy; } internal QueryConverter(IDataServices services, TypeSystemProvider typeProvider, Translator translator, SqlFactory sql) { if (services == null) { throw Error.ArgumentNull("services"); } if (sql == null) { throw Error.ArgumentNull("sql"); } if (translator == null) { throw Error.ArgumentNull("translator"); } if (typeProvider == null) { throw Error.ArgumentNull("typeProvider"); } this.services = services; this.translator = translator; this.sql = sql; this.typeProvider = typeProvider; this.map = new Dictionary (); this.exprMap = new Dictionary (); this.dupMap = new Dictionary (); this.gmap = new Dictionary (); this.allowDeferred = true; } /// /// Convert inner expression from C# expression to basic SQL Query. /// /// The expression to convert. ///The converted SQL query. internal SqlNode ConvertOuter(Expression node) { this.dominatingExpression = node; this.outerNode = true; SqlNode retNode; if (typeof(ITable).IsAssignableFrom(node.Type)) { retNode = this.VisitSequence(node); } else { retNode = this.VisitInner(node); } if (retNode.NodeType == SqlNodeType.MethodCall) { // if a tree consists of a single method call expression only, that method // must be either a mapped stored procedure or a mapped function throw Error.InvalidMethodExecution(((SqlMethodCall)retNode).Method.Name); } // if after conversion the node is an expression, we must // wrap it in a select SqlExpression sqlExpression = retNode as SqlExpression; if (sqlExpression != null) { retNode = new SqlSelect(sqlExpression, null, this.dominatingExpression); } retNode = new SqlIncludeScope(retNode, this.dominatingExpression); return retNode; } internal SqlNode Visit(Expression node) { bool tempOuterNode = this.outerNode; this.outerNode = false; SqlNode result = this.VisitInner(node); this.outerNode = tempOuterNode; return result; } ////// Convert inner expression from C# expression to basic SQL Query. /// /// The expression to convert. /// Current dominating expression, used for producing meaningful exception text. ///The converted SQL query. internal SqlNode ConvertInner(Expression node, Expression dominantExpression) { this.dominatingExpression = dominantExpression; bool tempOuterNode = this.outerNode; this.outerNode = false; SqlNode result = this.VisitInner(node); this.outerNode = tempOuterNode; return result; } [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "[....]: Cast is dependent on node type and casts do not happen unecessarily in a single code path.")] [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")] private SqlNode VisitInner(Expression node) { if (node == null) return null; Expression save = this.dominatingExpression; this.dominatingExpression = ChooseBestDominatingExpression(this.dominatingExpression, node); try { switch (node.NodeType) { case ExpressionType.New: return this.VisitNew((NewExpression)node); case ExpressionType.MemberInit: return this.VisitMemberInit((MemberInitExpression)node); case ExpressionType.Negate: case ExpressionType.NegateChecked: case ExpressionType.Not: return this.VisitUnary((UnaryExpression)node); case ExpressionType.UnaryPlus: if (node.Type == typeof(TimeSpan)) return this.VisitUnary((UnaryExpression)node); throw Error.UnrecognizedExpressionNode(node.NodeType); case ExpressionType.Add: case ExpressionType.AddChecked: case ExpressionType.Subtract: case ExpressionType.SubtractChecked: case ExpressionType.Multiply: case ExpressionType.MultiplyChecked: case ExpressionType.Divide: case ExpressionType.Modulo: case ExpressionType.And: case ExpressionType.AndAlso: case ExpressionType.Or: case ExpressionType.OrElse: case ExpressionType.Power: case ExpressionType.LessThan: case ExpressionType.LessThanOrEqual: case ExpressionType.GreaterThan: case ExpressionType.GreaterThanOrEqual: case ExpressionType.Equal: case ExpressionType.NotEqual: case ExpressionType.Coalesce: case ExpressionType.ExclusiveOr: return this.VisitBinary((BinaryExpression)node); case ExpressionType.ArrayIndex: return this.VisitArrayIndex((BinaryExpression)node); case ExpressionType.TypeIs: return this.VisitTypeBinary((TypeBinaryExpression)node); case ExpressionType.Convert: case ExpressionType.ConvertChecked: return this.VisitCast((UnaryExpression)node); case ExpressionType.TypeAs: return this.VisitAs((UnaryExpression)node); case ExpressionType.Conditional: return this.VisitConditional((ConditionalExpression)node); case ExpressionType.Constant: return this.VisitConstant((ConstantExpression)node); case ExpressionType.Parameter: return this.VisitParameter((ParameterExpression)node); case ExpressionType.MemberAccess: return this.VisitMemberAccess((MemberExpression)node); case ExpressionType.Call: return this.VisitMethodCall((MethodCallExpression)node); case ExpressionType.ArrayLength: return this.VisitArrayLength((UnaryExpression)node); case ExpressionType.NewArrayInit: return this.VisitNewArrayInit((NewArrayExpression)node); case ExpressionType.ListInit: return this.VisitListInit((ListInitExpression)node); case ExpressionType.Quote: return this.Visit(((UnaryExpression)node).Operand); case ExpressionType.Invoke: return this.VisitInvocation((InvocationExpression)node); case ExpressionType.Lambda: return this.VisitLambda((LambdaExpression)node); case ExpressionType.RightShift: case ExpressionType.LeftShift: throw Error.UnsupportedNodeType(node.NodeType); case (ExpressionType)InternalExpressionType.Known: return ((KnownExpression)node).Node; case (ExpressionType)InternalExpressionType.LinkedTable: return this.VisitLinkedTable((LinkedTableExpression)node); default: throw Error.UnrecognizedExpressionNode(node.NodeType); } } finally { this.dominatingExpression = save; } } ////// Heuristic which chooses the best Expression root to use for displaying user messages /// and exception text. /// private static Expression ChooseBestDominatingExpression(Expression last, Expression next) { if (last == null) { return next; } else if (next == null) { return last; } else { if (next is MethodCallExpression) { return next; } if (last is MethodCallExpression) { return last; } } return next; } private SqlSelect LockSelect(SqlSelect sel) { if (sel.Selection.NodeType != SqlNodeType.AliasRef || sel.Where != null || sel.OrderBy.Count > 0 || sel.GroupBy.Count > 0 || sel.Having != null || sel.Top != null || sel.OrderingType != SqlOrderingType.Default || sel.IsDistinct) { SqlAlias alias = new SqlAlias(sel); SqlAliasRef aref = new SqlAliasRef(alias); return new SqlSelect(aref, alias, this.dominatingExpression); } return sel; } private SqlSelect VisitSequence(Expression exp) { return this.CoerceToSequence(this.Visit(exp)); } private SqlSelect CoerceToSequence(SqlNode node) { SqlSelect select = node as SqlSelect; if (select == null) { if (node.NodeType == SqlNodeType.Value) { SqlValue sv = (SqlValue)node; // Check for ITables. ITable t = sv.Value as ITable; if (t != null) { return this.CoerceToSequence(this.TranslateConstantTable(t, null)); } // Check for IQueryable. IQueryable query = sv.Value as IQueryable; if (query != null) { Expression fex = Funcletizer.Funcletize(query.Expression); // IQueryables that return self-referencing Constant expressions cause infinite recursion if (fex.NodeType != ExpressionType.Constant || ((ConstantExpression)fex).Value != query) { return this.VisitSequence(fex); } throw Error.IQueryableCannotReturnSelfReferencingConstantExpression(); } throw Error.CapturedValuesCannotBeSequences(); } else if (node.NodeType == SqlNodeType.Multiset || node.NodeType == SqlNodeType.Element) { return ((SqlSubSelect)node).Select; } else if (node.NodeType == SqlNodeType.ClientArray) { throw Error.ConstructedArraysNotSupported(); } else if (node.NodeType == SqlNodeType.ClientParameter) { throw Error.ParametersCannotBeSequences(); } // this needs to be a sequence expression! SqlExpression sqlExpr = (SqlExpression)node; SqlAlias sa = new SqlAlias(sqlExpr); SqlAliasRef aref = new SqlAliasRef(sa); return new SqlSelect(aref, sa, this.dominatingExpression); } return select; } // // Recursive call to VisitInvocation. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private SqlNode VisitInvocation(InvocationExpression invoke) { LambdaExpression lambda = (invoke.Expression.NodeType == ExpressionType.Quote) ? (LambdaExpression)((UnaryExpression)invoke.Expression).Operand : (invoke.Expression as LambdaExpression); if (lambda != null) { // just map arg values into lambda's parameters and evaluate lambda's body for (int i = 0, n = invoke.Arguments.Count; i < n; i++) { this.exprMap[lambda.Parameters[i]] = invoke.Arguments[i]; } return this.VisitInner(lambda.Body); } else { // check for compiled query invocation SqlExpression expr = this.VisitExpression(invoke.Expression); if (expr.NodeType == SqlNodeType.Value) { SqlValue value = (SqlValue)expr; Delegate d = value.Value as Delegate; if (d != null) { CompiledQuery cq = d.Target as CompiledQuery; if (cq != null) { return this.VisitInvocation(Expression.Invoke(cq.Expression, invoke.Arguments)); } else if (invoke.Arguments.Count == 0) { object invokeResult; try { invokeResult = d.DynamicInvoke(null); } catch (System.Reflection.TargetInvocationException e) { throw e.InnerException; } return this.sql.ValueFromObject(invokeResult, invoke.Type, true, this.dominatingExpression); } } } SqlExpression [] args = new SqlExpression[invoke.Arguments.Count]; for(int i = 0; i).MakeGenericType(typeof(object[]), p.Type), Expression.Convert( #pragma warning disable 618 // Disable the 'obsolete' warning Expression.ArrayIndex(pa, Expression.Constant(i)), p.Type ), #pragma warning restore 618 pa ); SqlClientParameter cp = new SqlClientParameter(p.Type, this.typeProvider.From(p.Type), accessor, this.dominatingExpression); // map references to lambda's parameter to client parameter node this.dupMap[p] = cp; } // call this so we don't erase 'outerNode' setting return this.VisitInner(lambda.Body); } private SqlExpression VisitExpression(Expression exp) { SqlNode result = this.Visit(exp); if (result == null) return null; SqlExpression x = result as SqlExpression; if (x != null) return x; SqlSelect select = result as SqlSelect; if (select != null) { SqlSubSelect ms = sql.SubSelect(SqlNodeType.Multiset, select, exp.Type); return ms; } throw Error.UnrecognizedExpressionNode(result); } private SqlSelect VisitSelect(Expression sequence, LambdaExpression selector) { SqlSelect source = this.VisitSequence(sequence); SqlAlias alias = new SqlAlias(source); SqlAliasRef aref = new SqlAliasRef(alias); this.map[selector.Parameters[0]] = aref; SqlNode project = this.Visit(selector.Body); SqlSelect pselect = project as SqlSelect; if (pselect != null) { return new SqlSelect(sql.SubSelect(SqlNodeType.Multiset, pselect, selector.Body.Type), alias, this.dominatingExpression); } else if ((project.NodeType == SqlNodeType.Element || project.NodeType == SqlNodeType.ScalarSubSelect) && (this.converterStrategy & ConverterStrategy.CanUseOuterApply) != 0) { SqlSubSelect sub = (SqlSubSelect)project; SqlSelect inner = sub.Select; SqlAlias innerAlias = new SqlAlias(inner); SqlAliasRef innerRef = new SqlAliasRef(innerAlias); if (project.NodeType == SqlNodeType.Element) { inner.Selection = new SqlOptionalValue( new SqlColumn( "test", sql.Unary( SqlNodeType.OuterJoinedValue, sql.Value(typeof(int?), this.typeProvider.From(typeof(int)), 1, false, this.dominatingExpression) ) ), sql.Unary(SqlNodeType.OuterJoinedValue, inner.Selection) ); } else { inner.Selection = sql.Unary(SqlNodeType.OuterJoinedValue, inner.Selection); } SqlJoin join = new SqlJoin(SqlJoinType.OuterApply, alias, innerAlias, null, this.dominatingExpression); return new SqlSelect(innerRef, join, this.dominatingExpression); } else { SqlExpression expr = project as SqlExpression; if (expr != null) { return new SqlSelect(expr, alias, this.dominatingExpression); } else { throw Error.BadProjectionInSelect(); } } } private SqlSelect VisitSelectMany(Expression sequence, LambdaExpression colSelector, LambdaExpression resultSelector) { SqlSelect seqSelect = this.VisitSequence(sequence); SqlAlias seqAlias = new SqlAlias(seqSelect); SqlAliasRef seqRef = new SqlAliasRef(seqAlias); this.map[colSelector.Parameters[0]] = seqRef; SqlNode colSelectorNode = this.VisitSequence(colSelector.Body); SqlAlias selAlias = new SqlAlias(colSelectorNode); SqlAliasRef selRef = new SqlAliasRef(selAlias); SqlJoin join = new SqlJoin(SqlJoinType.CrossApply, seqAlias, selAlias, null, this.dominatingExpression); SqlExpression projection = selRef; if (resultSelector != null) { this.map[resultSelector.Parameters[0]] = seqRef; this.map[resultSelector.Parameters[1]] = selRef; projection = this.VisitExpression(resultSelector.Body); } return new SqlSelect(projection, join, this.dominatingExpression); } private SqlSelect VisitJoin(Expression outerSequence, Expression innerSequence, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) { SqlSelect outerSelect = this.VisitSequence(outerSequence); SqlSelect innerSelect = this.VisitSequence(innerSequence); SqlAlias outerAlias = new SqlAlias(outerSelect); SqlAliasRef outerRef = new SqlAliasRef(outerAlias); SqlAlias innerAlias = new SqlAlias(innerSelect); SqlAliasRef innerRef = new SqlAliasRef(innerAlias); this.map[outerKeySelector.Parameters[0]] = outerRef; SqlExpression outerKey = this.VisitExpression(outerKeySelector.Body); this.map[innerKeySelector.Parameters[0]] = innerRef; SqlExpression innerKey = this.VisitExpression(innerKeySelector.Body); this.map[resultSelector.Parameters[0]] = outerRef; this.map[resultSelector.Parameters[1]] = innerRef; SqlExpression result = this.VisitExpression(resultSelector.Body); SqlExpression condition = sql.Binary(SqlNodeType.EQ, outerKey, innerKey); SqlSelect select = null; if ((this.converterStrategy & ConverterStrategy.CanUseJoinOn) != 0) { SqlJoin join = new SqlJoin(SqlJoinType.Inner, outerAlias, innerAlias, condition, this.dominatingExpression); select = new SqlSelect(result, join, this.dominatingExpression); } else { SqlJoin join = new SqlJoin(SqlJoinType.Cross, outerAlias, innerAlias, null, this.dominatingExpression); select = new SqlSelect(result, join, this.dominatingExpression); select.Where = condition; } return select; } private SqlSelect VisitGroupJoin(Expression outerSequence, Expression innerSequence, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) { SqlSelect outerSelect = this.VisitSequence(outerSequence); SqlSelect innerSelect = this.VisitSequence(innerSequence); SqlAlias outerAlias = new SqlAlias(outerSelect); SqlAliasRef outerRef = new SqlAliasRef(outerAlias); SqlAlias innerAlias = new SqlAlias(innerSelect); SqlAliasRef innerRef = new SqlAliasRef(innerAlias); this.map[outerKeySelector.Parameters[0]] = outerRef; SqlExpression outerKey = this.VisitExpression(outerKeySelector.Body); this.map[innerKeySelector.Parameters[0]] = innerRef; SqlExpression innerKey = this.VisitExpression(innerKeySelector.Body); // make multiset SqlExpression pred = sql.Binary(SqlNodeType.EQ, outerKey, innerKey); SqlSelect select = new SqlSelect(innerRef, innerAlias, this.dominatingExpression); select.Where = pred; SqlSubSelect subquery = sql.SubSelect(SqlNodeType.Multiset, select); // make outer ref & multiset for result-selector params this.map[resultSelector.Parameters[0]] = outerRef; this.dupMap[resultSelector.Parameters[1]] = subquery; SqlExpression result = this.VisitExpression(resultSelector.Body); return new SqlSelect(result, outerAlias, this.dominatingExpression); } private SqlSelect VisitDefaultIfEmpty(Expression sequence) { SqlSelect select = this.VisitSequence(sequence); SqlAlias alias = new SqlAlias(select); SqlAliasRef aliasRef = new SqlAliasRef(alias); SqlExpression opt = new SqlOptionalValue( new SqlColumn( "test", sql.Unary(SqlNodeType.OuterJoinedValue, sql.Value(typeof(int?), this.typeProvider.From(typeof(int)), 1, false, this.dominatingExpression) ) ), sql.Unary(SqlNodeType.OuterJoinedValue, aliasRef) ); SqlSelect optSelect = new SqlSelect(opt, alias, this.dominatingExpression); alias = new SqlAlias(optSelect); aliasRef = new SqlAliasRef(alias); SqlExpression litNull = sql.TypedLiteralNull(typeof(string), this.dominatingExpression); SqlSelect selNull = new SqlSelect(litNull, null, this.dominatingExpression); SqlAlias aliasNull = new SqlAlias(selNull); SqlJoin join = new SqlJoin(SqlJoinType.OuterApply, aliasNull, alias, null, this.dominatingExpression); return new SqlSelect(aliasRef, join, this.dominatingExpression); } /// /// Rewrite seq.OfType private SqlSelect VisitOfType(Expression sequence, Type ofType) { SqlSelect select = this.LockSelect(this.VisitSequence(sequence)); SqlAliasRef aref = (SqlAliasRef)select.Selection; select.Selection = new SqlUnary(SqlNodeType.Treat, ofType, typeProvider.From(ofType), aref, this.dominatingExpression); select = this.LockSelect(select); aref = (SqlAliasRef)select.Selection; // Append the 'is' operator into the WHERE clause. select.Where = sql.AndAccumulate(select.Where, sql.Unary(SqlNodeType.IsNotNull, aref, this.dominatingExpression) ); return select; } ///as seq.Select(s=>s as T).Where(p=>p!=null). /// /// Rewrite seq.Cast private SqlNode VisitSequenceCast(Expression sequence, Type type) { Type sourceType = TypeSystem.GetElementType(sequence.Type); ParameterExpression p = Expression.Parameter(sourceType, "pc"); return this.Visit(Expression.Call( typeof(Enumerable), "Select", new Type[] { sourceType, // TSource element type. type, // TResult element type. }, sequence, Expression.Lambda( Expression.Convert(p, type), new ParameterExpression[] { p } )) ); } ///as seq.Select(s=>(T)s). /// /// This is the 'is' operator. /// private SqlNode VisitTypeBinary(TypeBinaryExpression b) { SqlExpression expr = this.VisitExpression(b.Expression); SqlExpression result = null; switch (b.NodeType) { case ExpressionType.TypeIs: Type ofType = b.TypeOperand; result = sql.Unary(SqlNodeType.IsNotNull, new SqlUnary(SqlNodeType.Treat, ofType, typeProvider.From(ofType), expr, this.dominatingExpression), this.dominatingExpression); break; default: throw Error.TypeBinaryOperatorNotRecognized(); } return result; } private SqlSelect VisitWhere(Expression sequence, LambdaExpression predicate) { SqlSelect select = this.LockSelect(this.VisitSequence(sequence)); this.map[predicate.Parameters[0]] = (SqlAliasRef)select.Selection; select.Where = this.VisitExpression(predicate.Body); return select; } private SqlNode VisitAs(UnaryExpression a) { SqlNode node = this.Visit(a.Operand); SqlExpression expr = node as SqlExpression; if (expr != null) { return new SqlUnary(SqlNodeType.Treat, a.Type, typeProvider.From(a.Type), expr, a); } SqlSelect select = node as SqlSelect; if (select != null) { SqlSubSelect ms = sql.SubSelect(SqlNodeType.Multiset, select); return new SqlUnary(SqlNodeType.Treat, a.Type, typeProvider.From(a.Type), ms, a); } throw Error.DidNotExpectAs(a); } private SqlNode VisitArrayLength(UnaryExpression c) { SqlExpression exp = this.VisitExpression(c.Operand); if (exp.SqlType.IsString || exp.SqlType.IsChar) { return sql.CLRLENGTH(exp); } else { return sql.DATALENGTH(exp); } } private SqlNode VisitArrayIndex(BinaryExpression b) { SqlExpression array = this.VisitExpression(b.Left); SqlExpression index = this.VisitExpression(b.Right); if (array.NodeType == SqlNodeType.ClientParameter && index.NodeType == SqlNodeType.Value) { SqlClientParameter cpArray = (SqlClientParameter)array; SqlValue vIndex = (SqlValue)index; return new SqlClientParameter( b.Type, sql.TypeProvider.From(b.Type), Expression.Lambda( #pragma warning disable 618 // Disable the 'obsolete' warning Expression.ArrayIndex(cpArray.Accessor.Body, Expression.Constant(vIndex.Value, vIndex.ClrType)), #pragma warning restore 618 cpArray.Accessor.Parameters.ToArray() ), this.dominatingExpression ); } throw Error.UnrecognizedExpressionNode(b.NodeType); } private SqlNode VisitCast(UnaryExpression c) { if (c.Method != null) { SqlExpression exp = this.VisitExpression(c.Operand); return sql.MethodCall(c.Type, c.Method, null, new SqlExpression[] { exp }, dominatingExpression); } return this.VisitChangeType(c.Operand, c.Type); } private SqlNode VisitChangeType(Expression expression, Type type) { SqlExpression expr = this.VisitExpression(expression); return this.ChangeType(expr, type); } private SqlNode ConvertDateToDateTime2(SqlExpression expr) { SqlExpression datetime2 = new SqlVariable(expr.ClrType, expr.SqlType, "DATETIME2", expr.SourceExpression); return sql.FunctionCall(typeof(DateTime), "CONVERT", new SqlExpression[2] { datetime2, expr }, expr.SourceExpression); } private SqlNode ChangeType(SqlExpression expr, Type type) { if (type == typeof(object)) { return expr; // Boxing conversion? } else if (expr.NodeType == SqlNodeType.Value && ((SqlValue)expr).Value == null) { return sql.TypedLiteralNull(type, expr.SourceExpression); } else if (expr.NodeType == SqlNodeType.ClientParameter) { SqlClientParameter cp = (SqlClientParameter)expr; return new SqlClientParameter( type, sql.TypeProvider.From(type), Expression.Lambda(Expression.Convert(cp.Accessor.Body, type), cp.Accessor.Parameters.ToArray()), cp.SourceExpression ); } ConversionMethod cm = ChooseConversionMethod(expr.ClrType, type); switch (cm) { case ConversionMethod.Convert: return sql.UnaryConvert(type, typeProvider.From(type), expr, expr.SourceExpression); case ConversionMethod.Lift: if (SqlFactory.IsSqlDateType(expr)) { expr = (SqlExpression) ConvertDateToDateTime2(expr); } return new SqlLift(type, expr, this.dominatingExpression); case ConversionMethod.Ignore: if (SqlFactory.IsSqlDateType(expr)) { return ConvertDateToDateTime2(expr); } return expr; case ConversionMethod.Treat: return new SqlUnary(SqlNodeType.Treat, type, typeProvider.From(type), expr, expr.SourceExpression); default: throw Error.UnhandledExpressionType(cm); } } enum ConversionMethod { Treat, Ignore, Convert, Lift } private ConversionMethod ChooseConversionMethod(Type fromType, Type toType) { Type nnFromType = TypeSystem.GetNonNullableType(fromType); Type nnToType = TypeSystem.GetNonNullableType(toType); if (fromType != toType && nnFromType == nnToType) { return ConversionMethod.Lift; } else if (TypeSystem.IsSequenceType(nnFromType) || TypeSystem.IsSequenceType(nnToType)) { return ConversionMethod.Ignore; } ProviderType sfromType = typeProvider.From(nnFromType); ProviderType stoType = typeProvider.From(nnToType); bool isRuntimeOnly1 = sfromType.IsRuntimeOnlyType; bool isRuntimeOnly2 = stoType.IsRuntimeOnlyType; if (isRuntimeOnly1 || isRuntimeOnly2) { return ConversionMethod.Treat; } if (nnFromType == nnToType // same non-nullable .NET types || (sfromType.IsString && sfromType.Equals(stoType)) // same SQL string types || (nnFromType.IsEnum || nnToType.IsEnum) // any .NET enum type ) { return ConversionMethod.Ignore; } else { return ConversionMethod.Convert; } } ////// Convert ITable into SqlNodes. If the hierarchy involves inheritance then /// a type case is built. Abstractly, a type case is a CASE where each WHEN is a possible /// a typebinding that may be instantianted. /// private SqlNode TranslateConstantTable(ITable table, SqlLink link) { if (table.Context != this.services.Context) { throw Error.WrongDataContext(); } MetaTable metaTable = this.services.Model.GetTable(table.ElementType); return this.translator.BuildDefaultQuery(metaTable.RowType, this.allowDeferred, link, this.dominatingExpression); } private SqlNode VisitLinkedTable(LinkedTableExpression linkedTable) { return TranslateConstantTable(linkedTable.Table, linkedTable.Link); } private SqlNode VisitConstant(ConstantExpression cons) { // A value constant or null. Type type = cons.Type; if (cons.Value == null) { return sql.TypedLiteralNull(type, this.dominatingExpression); } if (type == typeof(object)) { type = cons.Value.GetType(); } return sql.ValueFromObject(cons.Value, type, true, this.dominatingExpression); } private SqlExpression VisitConditional(ConditionalExpression cond) { Listwhens = new List (1); whens.Add(new SqlWhen(this.VisitExpression(cond.Test), this.VisitExpression(cond.IfTrue))); SqlExpression @else = this.VisitExpression(cond.IfFalse); // combine search cases found in the else clause into a single seach case while (@else.NodeType == SqlNodeType.SearchedCase) { SqlSearchedCase sc = (SqlSearchedCase)@else; whens.AddRange(sc.Whens); @else = sc.Else; } return sql.SearchedCase(whens.ToArray(), @else, this.dominatingExpression); } private SqlExpression VisitNew(NewExpression qn) { if (TypeSystem.IsNullableType(qn.Type) && qn.Arguments.Count == 1 && TypeSystem.GetNonNullableType(qn.Type) == qn.Arguments[0].Type) { return this.VisitCast(Expression.Convert(qn.Arguments[0], qn.Type)) as SqlExpression; } else if (qn.Type == typeof(decimal) && qn.Arguments.Count == 1) { return this.VisitCast(Expression.Convert(qn.Arguments[0], typeof(decimal))) as SqlExpression; } MetaType mt = this.services.Model.GetMetaType(qn.Type); if (mt.IsEntity) { throw Error.CannotMaterializeEntityType(qn.Type); } SqlExpression[] args = null; if (qn.Arguments.Count > 0) { args = new SqlExpression[qn.Arguments.Count]; for (int i = 0, n = qn.Arguments.Count; i < n; i++) { args[i] = this.VisitExpression(qn.Arguments[i]); } } SqlNew tb = sql.New(mt, qn.Constructor, args, PropertyOrFieldOf(qn.Members), null, this.dominatingExpression); return tb; } private SqlExpression VisitMemberInit(MemberInitExpression init) { MetaType mt = this.services.Model.GetMetaType(init.Type); if (mt.IsEntity) { throw Error.CannotMaterializeEntityType(init.Type); } SqlExpression[] args = null; NewExpression qn = init.NewExpression; if (qn.Type == typeof(decimal) && qn.Arguments.Count == 1) { return this.VisitCast(Expression.Convert(qn.Arguments[0], typeof(decimal))) as SqlExpression; } if (qn.Arguments.Count > 0) { args = new SqlExpression[qn.Arguments.Count]; for (int i = 0, n = args.Length; i < n; i++) { args[i] = this.VisitExpression(qn.Arguments[i]); } } int cBindings = init.Bindings.Count; SqlMemberAssign[] members = new SqlMemberAssign[cBindings]; int[] ordinal = new int[members.Length]; for (int i = 0; i < cBindings; i++) { MemberAssignment mb = init.Bindings[i] as MemberAssignment; if (mb != null) { SqlExpression expr = this.VisitExpression(mb.Expression); SqlMemberAssign sma = new SqlMemberAssign(mb.Member, expr); members[i] = sma; ordinal[i] = mt.GetDataMember(mb.Member).Ordinal; } else { throw Error.UnhandledBindingType(init.Bindings[i].BindingType); } } // put members in type's declaration order Array.Sort(ordinal, members, 0, members.Length); SqlNew tb = sql.New(mt, qn.Constructor, args, PropertyOrFieldOf(qn.Members), members, this.dominatingExpression); return tb; } private static IEnumerable PropertyOrFieldOf(IEnumerable members) { if (members == null) { return null; } List result = new List (); foreach (MemberInfo mi in members) { switch (mi.MemberType) { case MemberTypes.Method: { foreach (PropertyInfo pi in mi.DeclaringType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { MethodInfo method = mi as MethodInfo; if (pi.CanRead && pi.GetGetMethod() == method) { result.Add(pi); break; } } break; } case MemberTypes.Field: case MemberTypes.Property: { result.Add(mi); break; } default: { throw Error.CouldNotConvertToPropertyOrField(mi); } } } return result; } private SqlSelect VisitDistinct(Expression sequence) { SqlSelect select = this.LockSelect(this.VisitSequence(sequence)); select.IsDistinct = true; select.OrderingType = SqlOrderingType.Blocked; return select; } private SqlSelect VisitTake(Expression sequence, Expression count) { // verify that count >= 0 SqlExpression takeExp = this.VisitExpression(count); if (takeExp.NodeType == SqlNodeType.Value) { SqlValue constTakeCount = (SqlValue)takeExp; if (typeof(int).IsAssignableFrom(constTakeCount.Value.GetType()) && ((int)constTakeCount.Value) < 0) { throw Error.ArgumentOutOfRange("takeCount"); } } MethodCallExpression mce = sequence as MethodCallExpression; if (mce != null && IsSequenceOperatorCall(mce) && mce.Method.Name == "Skip" && mce.Arguments.Count == 2) { SqlExpression skipExp = this.VisitExpression(mce.Arguments[1]); // verify that count >= 0 if (skipExp.NodeType == SqlNodeType.Value) { SqlValue constSkipCount = (SqlValue)skipExp; if (typeof(int).IsAssignableFrom(constSkipCount.Value.GetType()) && ((int)constSkipCount.Value) < 0) { throw Error.ArgumentOutOfRange("skipCount"); } } SqlSelect select = this.VisitSequence(mce.Arguments[0]); return this.GenerateSkipTake(select, skipExp, takeExp); } else { SqlSelect select = this.VisitSequence(sequence); return this.GenerateSkipTake(select, null, takeExp); } } /// /// In order for elements of a sequence to be skipped, they must have identity /// that can be compared. This excludes elements that are sequences and elements /// that contain sequences. /// private bool CanSkipOnSelection(SqlExpression selection) { // we can skip over groupings (since we can compare them by key) if (IsGrouping(selection.ClrType)) { return true; } // we can skip over entities (since we can compare them by primary key) MetaTable table = this.services.Model.GetTable(selection.ClrType); if (table != null) { return true; } // sequences that are not primitives are not skippable if (TypeSystem.IsSequenceType(selection.ClrType) && !selection.SqlType.CanBeColumn) { return false; } switch (selection.NodeType) { case SqlNodeType.AliasRef: { SqlNode node = ((SqlAliasRef)selection).Alias.Node; SqlSelect select = node as SqlSelect; if (select != null) { return CanSkipOnSelection(select.Selection); } SqlUnion union = node as SqlUnion; if (union != null) { bool left = default(bool); bool right = default(bool); SqlSelect selectLeft = union.Left as SqlSelect; if (selectLeft != null) { left = CanSkipOnSelection(selectLeft.Selection); } SqlSelect selectRight = union.Right as SqlSelect; if (selectRight != null) { right = CanSkipOnSelection(selectRight.Selection); } return left && right; } SqlExpression expr = (SqlExpression)node; return CanSkipOnSelection(expr); } case SqlNodeType.New: SqlNew sn = (SqlNew)selection; // check each member of the projection for sequences foreach (SqlMemberAssign ma in sn.Members) { if (!CanSkipOnSelection(ma.Expression)) return false; } if (sn.ArgMembers != null) { for (int i = 0, n = sn.ArgMembers.Count; i < n; ++i) { if (!CanSkipOnSelection(sn.Args[i])) { return false; } } } break; } return true; } ////// SQL2000: /// SELECT * /// FROM sequence /// WHERE NOT EXISTS ( /// SELECT TOP count * /// FROM sequence) /// /// SQL2005: SELECT * /// FROM (SELECT sequence.*, /// ROW_NUMBER() OVER (ORDER BY order) AS ROW_NUMBER /// FROM sequence) /// WHERE ROW_NUMBER > count /// /// Sequence containing elements to skip /// Number of elements to skip ///SELECT node private SqlSelect VisitSkip(Expression sequence, Expression skipCount) { SqlExpression skipExp = this.VisitExpression(skipCount); // verify that count >= 0 if (skipExp.NodeType == SqlNodeType.Value) { SqlValue constSkipCount = (SqlValue)skipExp; if (typeof(int).IsAssignableFrom(constSkipCount.Value.GetType()) && ((int)constSkipCount.Value) < 0) { throw Error.ArgumentOutOfRange("skipCount"); } } SqlSelect select = this.VisitSequence(sequence); return this.GenerateSkipTake(select, skipExp, null); } private SqlSelect GenerateSkipTake(SqlSelect sequence, SqlExpression skipExp, SqlExpression takeExp) { SqlSelect select = this.LockSelect(sequence); // no skip? if (skipExp == null) { if (takeExp != null) { select.Top = takeExp; } return select; } SqlAlias alias = new SqlAlias(select); SqlAliasRef aref = new SqlAliasRef(alias); if (this.UseConverterStrategy(ConverterStrategy.SkipWithRowNumber)) { // use ROW_NUMBER() (preferred) SqlColumn rowNumber = new SqlColumn("ROW_NUMBER", sql.RowNumber(new List(), this.dominatingExpression)); SqlColumnRef rowNumberRef = new SqlColumnRef(rowNumber); select.Row.Columns.Add(rowNumber); SqlSelect final = new SqlSelect(aref, alias, this.dominatingExpression); if (takeExp != null) { // use BETWEEN for skip+take combo (much faster) final.Where = sql.Between( rowNumberRef, sql.Add(skipExp, 1), sql.Binary(SqlNodeType.Add, (SqlExpression)SqlDuplicator.Copy(skipExp), takeExp), this.dominatingExpression ); } else { final.Where = sql.Binary(SqlNodeType.GT, rowNumberRef, skipExp); } return final; } else { // Ensure that the sequence contains elements that can be skipped if (!CanSkipOnSelection(select.Selection)) { throw Error.SkipNotSupportedForSequenceTypes(); } // use NOT EXISTS // Supported cases: // - Entities // - Projections that contain all PK columns // // .. where there sequence can be traced back to a: // - Single-table query // - Distinct // - Except // - Intersect // - Union, where union.All == false // Not supported: joins // Sequence should also be ordered, but we can't test for it at this // point in processing, and we won't know that we need to test it, later. SingleTableQueryVisitor stqv = new SingleTableQueryVisitor(); stqv.Visit(select); if (!stqv.IsValid) { throw Error.SkipRequiresSingleTableQueryWithPKs(); } SqlSelect dupsel = (SqlSelect)SqlDuplicator.Copy(select); dupsel.Top = skipExp; SqlAlias dupAlias = new SqlAlias(dupsel); SqlAliasRef dupRef = new SqlAliasRef(dupAlias); SqlSelect eqsel = new SqlSelect(dupRef, dupAlias, this.dominatingExpression); eqsel.Where = sql.Binary(SqlNodeType.EQ2V, aref, dupRef); SqlSubSelect ss = sql.SubSelect(SqlNodeType.Exists, eqsel); SqlSelect final = new SqlSelect(aref, alias, this.dominatingExpression); final.Where = sql.Unary(SqlNodeType.Not, ss, this.dominatingExpression); final.Top = takeExp; return final; } } private SqlNode VisitParameter(ParameterExpression p) { SqlExpression sqlExpr; if (this.map.TryGetValue(p, out sqlExpr)) return sqlExpr; Expression expr; if (this.exprMap.TryGetValue(p, out expr)) return this.Visit(expr); SqlNode nodeToDup; if (this.dupMap.TryGetValue(p, out nodeToDup)) { SqlDuplicator duplicator = new SqlDuplicator(true); return duplicator.Duplicate(nodeToDup); } throw Error.ParameterNotInScope(p.Name); } /// /// Translate a call to a table valued function expression into a sql select. /// private SqlNode TranslateTableValuedFunction(MethodCallExpression mce, MetaFunction function) { // translate method call into sql function call ListsqlParams = GetFunctionParameters(mce, function); SqlTableValuedFunctionCall functionCall = sql.TableValuedFunctionCall(function.ResultRowTypes[0].InheritanceRoot, mce.Method.ReturnType, function.MappedName, sqlParams, mce); SqlAlias alias = new SqlAlias(functionCall); SqlAliasRef aref = new SqlAliasRef(alias); // Build default projection SqlExpression projection = this.translator.BuildProjection(aref, function.ResultRowTypes[0].InheritanceRoot, this.allowDeferred, null, mce); SqlSelect select = new SqlSelect(projection, alias, mce); return select; } /// /// Translate a call to a stored procedure /// private SqlNode TranslateStoredProcedureCall(MethodCallExpression mce, MetaFunction function) { if (!this.outerNode) { throw Error.SprocsCannotBeComposed(); } // translate method call into sql function call ListsqlParams = GetFunctionParameters(mce, function); SqlStoredProcedureCall spc = new SqlStoredProcedureCall(function, null, sqlParams, mce); Type returnType = mce.Method.ReturnType; if (returnType.IsGenericType && (returnType.GetGenericTypeDefinition() == typeof(IEnumerable<>) || returnType.GetGenericTypeDefinition() == typeof(ISingleResult<>))) { // Since this is a single rowset returning sproc, we use the one // and only root metatype. MetaType rowType = function.ResultRowTypes[0].InheritanceRoot; SqlUserRow rowExp = new SqlUserRow(rowType, this.typeProvider.GetApplicationType((int)ConverterSpecialTypes.Row), spc, mce); spc.Projection = this.translator.BuildProjection(rowExp, rowType, this.allowDeferred, null, mce); } else if (!( typeof(IMultipleResults).IsAssignableFrom(returnType) || returnType == typeof(int) || returnType == typeof(int?) )) { throw Error.InvalidReturnFromSproc(returnType); } return spc; } /// /// Create a list of sql parameters for the specified method call expression, /// taking into account any explicit typing applied to the parameters via the /// Parameter attribute. /// private ListGetFunctionParameters(MethodCallExpression mce, MetaFunction function) { List sqlParams = new List (mce.Arguments.Count); // create sql parameters for each method parameter for (int i = 0, n = mce.Arguments.Count; i < n; i++) { SqlExpression newParamExpression = this.VisitExpression(mce.Arguments[i]); // If the parameter explicitly specifies a type in metadata, // use it as the provider type. MetaParameter currMetaParam = function.Parameters[i]; if (!string.IsNullOrEmpty(currMetaParam.DbType)) { SqlSimpleTypeExpression typeExpression = newParamExpression as SqlSimpleTypeExpression; if (typeExpression != null) { // determine provider type, and update the parameter expression ProviderType providerType = typeProvider.Parse(currMetaParam.DbType); typeExpression.SetSqlType(providerType); } } sqlParams.Add(newParamExpression); } return sqlParams; } private SqlUserQuery VisitUserQuery(string query, Expression[] arguments, Type resultType) { SqlExpression[] args = new SqlExpression[arguments.Length]; for (int i = 0, n = args.Length; i < n; i++) { args[i] = this.VisitExpression(arguments[i]); } SqlUserQuery suq = new SqlUserQuery(query, null, args, this.dominatingExpression); if (resultType != typeof(void)) { Type elementType = TypeSystem.GetElementType(resultType); MetaType mType = this.services.Model.GetMetaType(elementType); // if the element type is a simple type (int, bool, etc.) we create // a single column binding if (TypeSystem.IsSimpleType(elementType)) { SqlUserColumn col = new SqlUserColumn(elementType, typeProvider.From(elementType), suq, "", false, this.dominatingExpression); suq.Columns.Add(col); suq.Projection = col; } else { // ... otherwise we generate a default projection SqlUserRow rowExp = new SqlUserRow(mType.InheritanceRoot, this.typeProvider.GetApplicationType((int)ConverterSpecialTypes.Row), suq, this.dominatingExpression); suq.Projection = this.translator.BuildProjection(rowExp, mType, this.allowDeferred, null, this.dominatingExpression); } } return suq; } private SqlNode VisitUnary(UnaryExpression u) { SqlExpression exp = this.VisitExpression(u.Operand); if (u.Method != null) { return sql.MethodCall(u.Type, u.Method, null, new SqlExpression[] { exp }, dominatingExpression); } SqlExpression result = null; switch (u.NodeType) { case ExpressionType.Negate: case ExpressionType.NegateChecked: result = sql.Unary(SqlNodeType.Negate, exp, this.dominatingExpression); break; case ExpressionType.Not: if (u.Operand.Type == typeof(bool) || u.Operand.Type == typeof(bool?)) { result = sql.Unary(SqlNodeType.Not, exp, this.dominatingExpression); } else { result = sql.Unary(SqlNodeType.BitNot, exp, this.dominatingExpression); } break; case ExpressionType.TypeAs: result = sql.Unary(SqlNodeType.Treat, exp, this.dominatingExpression); break; } return result; } [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")] private SqlNode VisitBinary(BinaryExpression b) { SqlExpression left = this.VisitExpression(b.Left); SqlExpression right = this.VisitExpression(b.Right); if (b.Method != null) { return sql.MethodCall(b.Type, b.Method, null, new SqlExpression[] { left, right }, dominatingExpression); } SqlExpression result = null; switch (b.NodeType) { case ExpressionType.Add: case ExpressionType.AddChecked: result = sql.Binary(SqlNodeType.Add, left, right, b.Type); break; case ExpressionType.Subtract: case ExpressionType.SubtractChecked: result = sql.Binary(SqlNodeType.Sub, left, right, b.Type); break; case ExpressionType.Multiply: case ExpressionType.MultiplyChecked: result = sql.Binary(SqlNodeType.Mul, left, right, b.Type); break; case ExpressionType.Divide: result = sql.Binary(SqlNodeType.Div, left, right, b.Type); break; case ExpressionType.Modulo: result = sql.Binary(SqlNodeType.Mod, left, right, b.Type); break; case ExpressionType.And: if (b.Left.Type == typeof(bool) || b.Left.Type == typeof(bool?)) { result = sql.Binary(SqlNodeType.And, left, right, b.Type); } else { result = sql.Binary(SqlNodeType.BitAnd, left, right, b.Type); } break; case ExpressionType.AndAlso: result = sql.Binary(SqlNodeType.And, left, right, b.Type); break; case ExpressionType.Or: if (b.Left.Type == typeof(bool) || b.Left.Type == typeof(bool?)) { result = sql.Binary(SqlNodeType.Or, left, right, b.Type); } else { result = sql.Binary(SqlNodeType.BitOr, left, right, b.Type); } break; case ExpressionType.OrElse: result = sql.Binary(SqlNodeType.Or, left, right, b.Type); break; case ExpressionType.LessThan: result = sql.Binary(SqlNodeType.LT, left, right, b.Type); break; case ExpressionType.LessThanOrEqual: result = sql.Binary(SqlNodeType.LE, left, right, b.Type); break; case ExpressionType.GreaterThan: result = sql.Binary(SqlNodeType.GT, left, right, b.Type); break; case ExpressionType.GreaterThanOrEqual: result = sql.Binary(SqlNodeType.GE, left, right, b.Type); break; case ExpressionType.Equal: result = sql.Binary(SqlNodeType.EQ, left, right, b.Type); break; case ExpressionType.NotEqual: result = sql.Binary(SqlNodeType.NE, left, right, b.Type); break; case ExpressionType.ExclusiveOr: result = sql.Binary(SqlNodeType.BitXor, left, right, b.Type); break; case ExpressionType.Coalesce: result = this.MakeCoalesce(left, right, b.Type); break; default: throw Error.BinaryOperatorNotRecognized(b.NodeType); } return result; } private SqlExpression MakeCoalesce(SqlExpression left, SqlExpression right, Type resultType) { CompensateForLowerPrecedenceOfDateType(ref left, ref right); // DevDiv 176874 if (TypeSystem.IsSimpleType(resultType)) { return sql.Binary(SqlNodeType.Coalesce, left, right, resultType); } else { List whens = new List (1); whens.Add(new SqlWhen(sql.Unary(SqlNodeType.IsNull, left, left.SourceExpression), right)); SqlDuplicator dup = new SqlDuplicator(true); return sql.SearchedCase(whens.ToArray(), (SqlExpression)dup.Duplicate(left), this.dominatingExpression); } } // The result *type* of a COALESCE function call is that of the operand with the highest precedence. // However, the SQL DATE type has a lower precedence than DATETIME or SMALLDATETIME, despite having // a hihger range. The following logic compensates for that discrepancy. // private void CompensateForLowerPrecedenceOfDateType(ref SqlExpression left, ref SqlExpression right) { if (SqlFactory.IsSqlDateType(left) && SqlFactory.IsSqlDateTimeType(right)) { right = (SqlExpression)ConvertDateToDateTime2(right); } else if (SqlFactory.IsSqlDateType(right) && SqlFactory.IsSqlDateTimeType(left)) { left = (SqlExpression)ConvertDateToDateTime2(left); } } private SqlNode VisitConcat(Expression source1, Expression source2) { SqlSelect left = this.VisitSequence(source1); SqlSelect right = this.VisitSequence(source2); SqlUnion union = new SqlUnion(left, right, true); SqlAlias alias = new SqlAlias(union); SqlAliasRef aref = new SqlAliasRef(alias); SqlSelect result = new SqlSelect(aref, alias, this.dominatingExpression); result.OrderingType = SqlOrderingType.Blocked; return result; } private SqlNode VisitUnion(Expression source1, Expression source2) { SqlSelect left = this.VisitSequence(source1); SqlSelect right = this.VisitSequence(source2); SqlUnion union = new SqlUnion(left, right, false); SqlAlias alias = new SqlAlias(union); SqlAliasRef aref = new SqlAliasRef(alias); SqlSelect result = new SqlSelect(aref, alias, this.dominatingExpression); result.OrderingType = SqlOrderingType.Blocked; return result; } private SqlNode VisitIntersect(Expression source1, Expression source2) { Type type = TypeSystem.GetElementType(source1.Type); if (IsGrouping(type)) { throw Error.IntersectNotSupportedForHierarchicalTypes(); } SqlSelect select1 = this.LockSelect(this.VisitSequence(source1)); SqlSelect select2 = this.VisitSequence(source2); SqlAlias alias1 = new SqlAlias(select1); SqlAliasRef aref1 = new SqlAliasRef(alias1); SqlAlias alias2 = new SqlAlias(select2); SqlAliasRef aref2 = new SqlAliasRef(alias2); SqlExpression any = this.GenerateQuantifier(alias2, sql.Binary(SqlNodeType.EQ2V, aref1, aref2), true); SqlSelect result = new SqlSelect(aref1, alias1, select1.SourceExpression); result.Where = any; result.IsDistinct = true; result.OrderingType = SqlOrderingType.Blocked; return result; } private SqlNode VisitExcept(Expression source1, Expression source2) { Type type = TypeSystem.GetElementType(source1.Type); if (IsGrouping(type)) { throw Error.ExceptNotSupportedForHierarchicalTypes(); } SqlSelect select1 = this.LockSelect(this.VisitSequence(source1)); SqlSelect select2 = this.VisitSequence(source2); SqlAlias alias1 = new SqlAlias(select1); SqlAliasRef aref1 = new SqlAliasRef(alias1); SqlAlias alias2 = new SqlAlias(select2); SqlAliasRef aref2 = new SqlAliasRef(alias2); SqlExpression any = this.GenerateQuantifier(alias2, sql.Binary(SqlNodeType.EQ2V, aref1, aref2), true); SqlSelect result = new SqlSelect(aref1, alias1, select1.SourceExpression); result.Where = sql.Unary(SqlNodeType.Not, any); result.IsDistinct = true; result.OrderingType = SqlOrderingType.Blocked; return result; } /// /// Returns true if the type is an IGrouping. /// [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")] private bool IsGrouping(Type t) { if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IGrouping<,>)) return true; return false; } private SqlSelect VisitOrderBy(Expression sequence, LambdaExpression expression, SqlOrderType orderType) { if (IsGrouping(expression.Body.Type)) { throw Error.GroupingNotSupportedAsOrderCriterion(); } if (!this.typeProvider.From(expression.Body.Type).IsOrderable) { throw Error.TypeCannotBeOrdered(expression.Body.Type); } SqlSelect select = this.LockSelect(this.VisitSequence(sequence)); if (select.Selection.NodeType != SqlNodeType.AliasRef || select.OrderBy.Count > 0) { SqlAlias alias = new SqlAlias(select); SqlAliasRef aref = new SqlAliasRef(alias); select = new SqlSelect(aref, alias, this.dominatingExpression); } this.map[expression.Parameters[0]] = (SqlAliasRef)select.Selection; SqlExpression expr = this.VisitExpression(expression.Body); select.OrderBy.Add(new SqlOrderExpression(orderType, expr)); return select; } private SqlSelect VisitThenBy(Expression sequence, LambdaExpression expression, SqlOrderType orderType) { if (IsGrouping(expression.Body.Type)) { throw Error.GroupingNotSupportedAsOrderCriterion(); } if (!this.typeProvider.From(expression.Body.Type).IsOrderable) { throw Error.TypeCannotBeOrdered(expression.Body.Type); } SqlSelect select = this.VisitSequence(sequence); System.Diagnostics.Debug.Assert(select.Selection.NodeType == SqlNodeType.AliasRef); this.map[expression.Parameters[0]] = (SqlAliasRef)select.Selection; SqlExpression expr = this.VisitExpression(expression.Body); select.OrderBy.Add(new SqlOrderExpression(orderType, expr)); return select; } private SqlNode VisitGroupBy(Expression sequence, LambdaExpression keyLambda, LambdaExpression elemLambda, LambdaExpression resultSelector) { // Convert seq.Group(elem, key) into // // SELECT s.key, MULTISET(select s2.elem from seq AS s2 where s.key == s2.key) // FROM seq AS s // // where key and elem can be either simple scalars or object constructions // SqlSelect seq = this.VisitSequence(sequence); seq = this.LockSelect(seq); SqlAlias seqAlias = new SqlAlias(seq); SqlAliasRef seqAliasRef = new SqlAliasRef(seqAlias); // evaluate the key expression relative to original sequence this.map[keyLambda.Parameters[0]] = seqAliasRef; SqlExpression keyExpr = this.VisitExpression(keyLambda.Body); // make a duplicate of the original sequence to use as a foundation of our group multiset SqlDuplicator sd = new SqlDuplicator(); SqlSelect selDup = (SqlSelect)sd.Duplicate(seq); // rebind key in relative to the duplicate sequence SqlAlias selDupAlias = new SqlAlias(selDup); SqlAliasRef selDupRef = new SqlAliasRef(selDupAlias); this.map[keyLambda.Parameters[0]] = selDupRef; SqlExpression keyDup = this.VisitExpression(keyLambda.Body); SqlExpression elemExpr = null; SqlExpression elemOnGroupSource = null; if (elemLambda != null) { // evaluate element expression relative to the duplicate sequence this.map[elemLambda.Parameters[0]] = selDupRef; elemExpr = this.VisitExpression(elemLambda.Body); // evaluate element expression relative to original sequence this.map[elemLambda.Parameters[0]] = seqAliasRef; elemOnGroupSource = this.VisitExpression(elemLambda.Body); } else { // no elem expression supplied, so just use an alias ref to the duplicate sequence. // this will resolve to whatever was being produced by the sequence elemExpr = selDupRef; elemOnGroupSource = seqAliasRef; } // Make a sub expression out of the key. This will allow a single definition of the // expression to be shared at multiple points in the tree (via SqlSharedExpressionRef's) SqlSharedExpression keySubExpr = new SqlSharedExpression(keyExpr); keyExpr = new SqlSharedExpressionRef(keySubExpr); // construct the select clause that picks out the elements (this may be redundant...) SqlSelect selElem = new SqlSelect(elemExpr, selDupAlias, this.dominatingExpression); selElem.Where = sql.Binary(SqlNodeType.EQ2V, keyExpr, keyDup); // Finally, make the MULTISET node. this will be used as part of the final select SqlSubSelect ss = sql.SubSelect(SqlNodeType.Multiset, selElem); // add a layer to the original sequence before applying the actual group-by clause SqlSelect gsel = new SqlSelect(new SqlSharedExpressionRef(keySubExpr), seqAlias, this.dominatingExpression); gsel.GroupBy.Add(keySubExpr); SqlAlias gselAlias = new SqlAlias(gsel); SqlSelect result = null; if (resultSelector != null) { // Create final select to include construction of group multiset // select new Grouping { Key = key, Group = Multiset(select elem from seq where match) } from ... Type elementType = typeof(IGrouping<,>).MakeGenericType(keyExpr.ClrType, elemExpr.ClrType); SqlExpression keyGroup = new SqlGrouping(elementType, this.typeProvider.From(elementType), keyExpr, ss, this.dominatingExpression); SqlSelect keyGroupSel = new SqlSelect(keyGroup, gselAlias, this.dominatingExpression); SqlAlias kgAlias = new SqlAlias(keyGroupSel); SqlAliasRef kgAliasRef = new SqlAliasRef(kgAlias); this.map[resultSelector.Parameters[0]] = sql.Member(kgAliasRef, elementType.GetProperty("Key")); this.map[resultSelector.Parameters[1]] = kgAliasRef; // remember the select that has the actual group (for optimizing aggregates later) this.gmap[kgAliasRef] = new GroupInfo { SelectWithGroup = gsel, ElementOnGroupSource = elemOnGroupSource }; SqlExpression resultExpr = this.VisitExpression(resultSelector.Body); result = new SqlSelect(resultExpr, kgAlias, this.dominatingExpression); // remember the select that has the actual group (for optimizing aggregates later) this.gmap[resultExpr] = new GroupInfo { SelectWithGroup = gsel, ElementOnGroupSource = elemOnGroupSource }; } else { // Create final select to include construction of group multiset // select new Grouping { Key = key, Group = Multiset(select elem from seq where match) } from ... Type elementType = typeof(IGrouping<,>).MakeGenericType(keyExpr.ClrType, elemExpr.ClrType); SqlExpression resultExpr = new SqlGrouping(elementType, this.typeProvider.From(elementType), keyExpr, ss, this.dominatingExpression); result = new SqlSelect(resultExpr, gselAlias, this.dominatingExpression); // remember the select that has the actual group (for optimizing aggregates later) this.gmap[resultExpr] = new GroupInfo { SelectWithGroup = gsel, ElementOnGroupSource = elemOnGroupSource }; } return result; } [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")] private SqlNode VisitAggregate(Expression sequence, LambdaExpression lambda, SqlNodeType aggType, Type returnType) { // Convert seq.Agg(exp) into // // 1) SELECT Agg(exp) FROM seq // 2) SELECT Agg1 FROM (SELECT Agg(exp) as Agg1 FROM group-seq GROUP BY ...) // 3) SCALAR(SELECT Agg(exp) FROM seq) // bool isCount = aggType == SqlNodeType.Count || aggType == SqlNodeType.LongCount; SqlNode source = this.Visit(sequence); SqlSelect select = this.CoerceToSequence(source); SqlAlias alias = new SqlAlias(select); SqlAliasRef aref = new SqlAliasRef(alias); // If the sequence is of the form x.Select(expr).Agg() and the lambda for the aggregate is null, // or is a no-op parameter expression (like u=>u), clone the group by selection lambda // expression, and use for the aggregate. // Final form should be x.Agg(expr) MethodCallExpression mce = sequence as MethodCallExpression; if (!outerNode && !isCount && (lambda == null || (lambda.Parameters.Count == 1 && lambda.Parameters[0] == lambda.Body)) && (mce != null) && IsSequenceOperatorCall(mce, "Select") && select.From is SqlAlias) { LambdaExpression selectionLambda = GetLambda(mce.Arguments[1]); lambda = Expression.Lambda(selectionLambda.Type, selectionLambda.Body, selectionLambda.Parameters); alias = (SqlAlias)select.From; aref = new SqlAliasRef(alias); } if (lambda != null && !TypeSystem.IsSimpleType(lambda.Body.Type)) { throw Error.CannotAggregateType(lambda.Body.Type); } //Empty parameter aggregates are not allowed on anonymous types //i.e. db.Customers.Select(c=>new{c.Age}).Max() instead it should be // db.Customers.Select(c=>new{c.Age}).Max(c=>c.Age) if (select.Selection.SqlType.IsRuntimeOnlyType && !IsGrouping(sequence.Type) && !isCount && lambda == null) { throw Error.NonCountAggregateFunctionsAreNotValidOnProjections(aggType); } if (lambda != null) this.map[lambda.Parameters[0]] = aref; if (this.outerNode) { // If this aggregate is basically the last/outer-most operator of the query // // produce SELECT Agg(exp) FROM seq // SqlExpression exp = (lambda != null) ? this.VisitExpression(lambda.Body) : null; SqlExpression where = null; if (isCount && exp != null) { where = exp; exp = null; } else if (exp == null && !isCount) { exp = aref; } if (exp != null) { // in case this contains another aggregate exp = new SqlSimpleExpression(exp); } SqlSelect sel = new SqlSelect( this.GetAggregate(aggType, returnType, exp), alias, this.dominatingExpression ); sel.Where = where; sel.OrderingType = SqlOrderingType.Never; return sel; } else if (!isCount || lambda == null) { // Look to optimize aggregate by pushing its evaluation down to the select node that has the // actual group-by operator. // // Produce: SELECT Agg1 FROM (SELECT Agg(exp) as Agg1 FROM seq GROUP BY ...) // GroupInfo info = this.FindGroupInfo(source); if (info != null) { SqlExpression exp = null; if (lambda != null) { // evaluate expression relative to the group-by select node this.map[lambda.Parameters[0]] = (SqlExpression)SqlDuplicator.Copy(info.ElementOnGroupSource); exp = this.VisitExpression(lambda.Body); } else if (!isCount) { // support aggregates w/o an explicit selector specified exp = info.ElementOnGroupSource; } if (exp != null) { // in case this contains another aggregate exp = new SqlSimpleExpression(exp); } SqlExpression agg = this.GetAggregate(aggType, returnType, exp); SqlColumn c = new SqlColumn(agg.ClrType, agg.SqlType, null, null, agg, this.dominatingExpression); info.SelectWithGroup.Row.Columns.Add(c); return new SqlColumnRef(c); } } // Otherwise, if we cannot optimize then fall back to generating a nested aggregate in a correlated sub query // // SCALAR(SELECT Agg(exp) FROM seq) { SqlExpression exp = (lambda != null) ? this.VisitExpression(lambda.Body) : null; if (exp != null) { // in case this contains another aggregate exp = new SqlSimpleExpression(exp); } SqlSelect sel = new SqlSelect( this.GetAggregate(aggType, returnType, isCount ? null : (lambda == null) ? aref : exp), alias, this.dominatingExpression ); sel.Where = isCount ? exp : null; return sql.SubSelect(SqlNodeType.ScalarSubSelect, sel); } } private GroupInfo FindGroupInfo(SqlNode source) { GroupInfo info = null; this.gmap.TryGetValue(source, out info); if (info != null) { return info; } SqlAlias alias = source as SqlAlias; if (alias != null) { SqlSelect select = alias.Node as SqlSelect; if (select != null) { return this.FindGroupInfo(select.Selection); } // it might be an expression (not yet fully resolved) source = alias.Node; } SqlExpression expr = source as SqlExpression; if (expr != null) { switch (expr.NodeType) { case SqlNodeType.AliasRef: return this.FindGroupInfo(((SqlAliasRef)expr).Alias); case SqlNodeType.Member: return this.FindGroupInfo(((SqlMember)expr).Expression); default: this.gmap.TryGetValue(expr, out info); return info; } } return null; } private SqlExpression GetAggregate(SqlNodeType aggType, Type clrType, SqlExpression exp) { ProviderType sqlType = this.typeProvider.From(clrType); return new SqlUnary(aggType, clrType, sqlType, exp, this.dominatingExpression); } private SqlNode VisitContains(Expression sequence, Expression value) { Type elemType = TypeSystem.GetElementType(sequence.Type); SqlNode seqNode = this.Visit(sequence); if (seqNode.NodeType == SqlNodeType.ClientArray) { SqlClientArray array = (SqlClientArray)seqNode; return this.GenerateInExpression(this.VisitExpression(value), array.Expressions); } else if (seqNode.NodeType == SqlNodeType.Value) { IEnumerable values = ((SqlValue)seqNode).Value as IEnumerable; IQueryable query = values as IQueryable; if (query == null) { SqlExpression expr = this.VisitExpression(value); Listlist = values.OfType
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- BindMarkupExtensionSerializer.cs
- CollectionBuilder.cs
- ReservationCollection.cs
- ClientScriptManager.cs
- WebBaseEventKeyComparer.cs
- Queue.cs
- QilNode.cs
- SubqueryTrackingVisitor.cs
- MouseActionValueSerializer.cs
- UnknownWrapper.cs
- LayoutEditorPart.cs
- ILGenerator.cs
- FrameworkElementAutomationPeer.cs
- InternalsVisibleToAttribute.cs
- odbcmetadatafactory.cs
- DataViewManager.cs
- CustomWebEventKey.cs
- TrackingServices.cs
- KeyGestureConverter.cs
- NetworkInterface.cs
- isolationinterop.cs
- DocumentApplicationJournalEntry.cs
- TemplatePropertyEntry.cs
- TeredoHelper.cs
- login.cs
- GridViewEditEventArgs.cs
- SerializationFieldInfo.cs
- Size.cs
- SynchronizationHandlesCodeDomSerializer.cs
- Ops.cs
- CertificateElement.cs
- HandleCollector.cs
- Rotation3DAnimationUsingKeyFrames.cs
- KeyManager.cs
- JulianCalendar.cs
- DataGridViewRowConverter.cs
- AllMembershipCondition.cs
- ObjectManager.cs
- _DigestClient.cs
- RuleSetDialog.cs
- DependencyObjectCodeDomSerializer.cs
- XmlSchemaRedefine.cs
- SetStoryboardSpeedRatio.cs
- StringExpressionSet.cs
- NetworkInformationException.cs
- NativeMethods.cs
- MgmtConfigurationRecord.cs
- PagedDataSource.cs
- ImageAttributes.cs
- ChannelManager.cs
- RSAPKCS1KeyExchangeFormatter.cs
- DataTableNameHandler.cs
- X509Utils.cs
- AstTree.cs
- PeerObject.cs
- ColumnCollection.cs
- ProxyWebPartConnectionCollection.cs
- HttpListenerResponse.cs
- MultipartIdentifier.cs
- GeneralTransform2DTo3DTo2D.cs
- SimpleWorkerRequest.cs
- WsdlParser.cs
- Interfaces.cs
- SqlTypeSystemProvider.cs
- SafeMarshalContext.cs
- Psha1DerivedKeyGeneratorHelper.cs
- ServiceInstallComponent.cs
- Pool.cs
- Missing.cs
- NativeMethods.cs
- DebugHandleTracker.cs
- ChannelServices.cs
- DomNameTable.cs
- HttpMethodAttribute.cs
- CompositionAdorner.cs
- StringValidator.cs
- TraceSource.cs
- GPStream.cs
- XmlObjectSerializerReadContextComplexJson.cs
- ScriptingJsonSerializationSection.cs
- DelegateHelpers.cs
- UIServiceHelper.cs
- DifferencingCollection.cs
- CellNormalizer.cs
- DataGridItemEventArgs.cs
- XsdDateTime.cs
- ResourceIDHelper.cs
- BitmapEffectInputData.cs
- SimpleMailWebEventProvider.cs
- IncrementalReadDecoders.cs
- ListViewItemMouseHoverEvent.cs
- SqlCacheDependencyDatabase.cs
- BlurBitmapEffect.cs
- PropertyGroupDescription.cs
- RegionData.cs
- CuspData.cs
- ChannelSinkStacks.cs
- EntityDataSourceWrapperCollection.cs
- UIElement3DAutomationPeer.cs
- DBConcurrencyException.cs