File: SqlClient\Query\QueryConverter.cs
Project: ndp\fx\src\DLinq\Dlinq\System.Data.Linq.csproj (System.Data.Linq)
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 {
 
    /// <summary>
    /// These are application types used to represent types used during intermediate
    /// stages of the query building process.
    /// </summary>
    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;
        Dictionary<ParameterExpression, SqlExpression> map;
        Dictionary<ParameterExpression, Expression> exprMap;
        Dictionary<ParameterExpression, SqlNode> dupMap;
        Dictionary<SqlNode, GroupInfo> 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<ParameterExpression, SqlExpression>();
            this.exprMap = new Dictionary<ParameterExpression, Expression>();
            this.dupMap = new Dictionary<ParameterExpression, SqlNode>();
            this.gmap = new Dictionary<SqlNode, GroupInfo>();
            this.allowDeferred = true;
        }
 
        /// <summary>
        /// Convert inner expression from C# expression to basic SQL Query.
        /// </summary>
        /// <param name="node">The expression to convert.</param>
        /// <returns>The converted SQL query.</returns>
        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;
        }
 
        /// <summary>
        /// Convert inner expression from C# expression to basic SQL Query.
        /// </summary>
        /// <param name="node">The expression to convert.</param>
        /// <param name="dominantExpression">Current dominating expression, used for producing meaningful exception text.</param>
        /// <returns>The converted SQL query.</returns>
        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 = "Microsoft: 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;
            }
        }
 
        /// <summary>
        /// Heuristic which chooses the best Expression root to use for displaying user messages
        /// and exception text.
        /// </summary>
        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<args.Length; ++i) {
                    args[i] = (SqlExpression)this.Visit(invoke.Arguments[i]);
                }
                var sca = new SqlClientArray(typeof(object[]), this.typeProvider.From(typeof(object[])), args, this.dominatingExpression); 
                return sql.MethodCall(invoke.Type, typeof(Delegate).GetMethod("DynamicInvoke"), expr, new SqlExpression[] {sca}, this.dominatingExpression);
            }
        }
 
        // inline lambda expressions w/o invocation are parameterized queries
        private SqlNode VisitLambda(LambdaExpression lambda) {
 
            // turn lambda parameters into client parameters
            for (int i = 0, n = lambda.Parameters.Count; i < n; i++) {
                ParameterExpression p = lambda.Parameters[i];
 
                if (p.Type == typeof(Type)) {
                    throw Error.BadParameterType(p.Type);
                }
 
                // construct accessor for parameter
                ParameterExpression pa = Expression.Parameter(typeof(object[]), "args");
                LambdaExpression accessor =
                    Expression.Lambda(
                        typeof(Func<,>).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);
        }
 
        /// <summary>
        /// Rewrite seq.OfType<T> as seq.Select(s=>s as T).Where(p=>p!=null).
        /// </summary>
        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;
        }
 
        /// <summary>
        /// Rewrite seq.Cast<T> as seq.Select(s=>(T)s).
        /// </summary>
        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 }
                ))
           );
        }
 
        /// <summary>
        /// This is the 'is' operator.
        /// </summary>
        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;
            }
        }
 
        /// <summary>
        /// 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. 
        /// </summary>
        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) {
            List<SqlWhen> whens = new List<SqlWhen>(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<MemberInfo> PropertyOrFieldOf(IEnumerable<MemberInfo> members) {
            if (members == null) {
                return null;
            }
            List<MemberInfo> result = new List<MemberInfo>();
            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);
            }
        }
 
        /// <summary>
        /// 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.
        /// </summary>
        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;
        }
 
        /// <summary>
        /// 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
        /// </summary>
        /// <param name="sequence">Sequence containing elements to skip</param>
        /// <param name="count">Number of elements to skip</param>
        /// <returns>SELECT node</returns>
        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<SqlOrderExpression>(), 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);
        }
 
        /// <summary>
        /// Translate a call to a table valued function expression into a sql select.
        /// </summary>             
        private SqlNode TranslateTableValuedFunction(MethodCallExpression mce, MetaFunction function) {
            // translate method call into sql function call
            List<SqlExpression> sqlParams = 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;
        }
 
        /// <summary>
        /// Translate a call to a stored procedure
        /// </summary>             
        private SqlNode TranslateStoredProcedureCall(MethodCallExpression mce, MetaFunction function) {
            if (!this.outerNode) {
                throw Error.SprocsCannotBeComposed();
            }
 
            // translate method call into sql function call
            List<SqlExpression> sqlParams = 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;
        }
 
        /// <summary>
        /// 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.
        /// </summary>        
        private List<SqlExpression> GetFunctionParameters(MethodCallExpression mce, MetaFunction function) {
            List<SqlExpression> sqlParams = new List<SqlExpression>(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<SqlWhen> whens = new List<SqlWhen>(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;
        }
 
        /// <summary>
        /// Returns true if the type is an IGrouping.
        /// </summary>        
        [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);
                    List<SqlExpression> list = values.OfType<object>().Select(v => sql.ValueFromObject(v, elemType, true, this.dominatingExpression)).ToList();
                    return this.GenerateInExpression(expr, list);
                }
                seqNode = this.Visit(query.Expression);
            }
            ParameterExpression p = Expression.Parameter(value.Type, "p");
            LambdaExpression lambda = Expression.Lambda(Expression.Equal(p, value), p);
            return this.VisitQuantifier(this.CoerceToSequence(seqNode), lambda, true);
        }
 
        private SqlExpression GenerateInExpression(SqlExpression expr, List<SqlExpression> list) {
            if (list.Count == 0) {
                return sql.ValueFromObject(false, this.dominatingExpression);
            }
            else if (list[0].SqlType.CanBeColumn) {
                return sql.In(expr, list, this.dominatingExpression);
            }
            else {
                SqlExpression pred = sql.Binary(SqlNodeType.EQ, expr, list[0]);
                for (int i = 1, n = list.Count; i < n; i++) {
                    pred = sql.Binary(SqlNodeType.Or, pred, sql.Binary(SqlNodeType.EQ, (SqlExpression)SqlDuplicator.Copy(expr), list[i]));
                }
                return pred;
            }
        }
 
        private SqlNode VisitQuantifier(Expression sequence, LambdaExpression lambda, bool isAny) {
            return this.VisitQuantifier(this.VisitSequence(sequence), lambda, isAny);
        }
 
        private SqlNode VisitQuantifier(SqlSelect select, LambdaExpression lambda, bool isAny) {
            SqlAlias alias = new SqlAlias(select);
            SqlAliasRef aref = new SqlAliasRef(alias);
            if (lambda != null) {
                this.map[lambda.Parameters[0]] = aref;
            }
            SqlExpression cond = lambda != null ? this.VisitExpression(lambda.Body) : null;
            return this.GenerateQuantifier(alias, cond, isAny);
        }
 
        private SqlExpression GenerateQuantifier(SqlAlias alias, SqlExpression cond, bool isAny) {
            SqlAliasRef aref = new SqlAliasRef(alias);
            if (isAny) {
                SqlSelect sel = new SqlSelect(aref, alias, this.dominatingExpression);
                sel.Where = cond;
                sel.OrderingType = SqlOrderingType.Never;
                SqlSubSelect exists = sql.SubSelect(SqlNodeType.Exists, sel);
                return exists;
            }
            else {
                SqlSelect sel = new SqlSelect(aref, alias, this.dominatingExpression);
                SqlSubSelect ss = sql.SubSelect(SqlNodeType.Exists, sel);
                sel.Where = sql.Unary(SqlNodeType.Not2V, cond, this.dominatingExpression);
                return sql.Unary(SqlNodeType.Not, ss, this.dominatingExpression);
            }
        }
 
        private void CheckContext(SqlExpression expr) {
            // try to catch use of incorrect context if we can
            SqlValue value = expr as SqlValue;
            if (value != null) {
                DataContext dc = value.Value as DataContext;
                if (dc != null) {
                    if (dc != this.services.Context) {
                        throw Error.WrongDataContext();
                    }
                }
            }
        }
 
        private SqlNode VisitMemberAccess(MemberExpression ma) {
            Type memberType = TypeSystem.GetMemberType(ma.Member);
            if (memberType.IsGenericType && memberType.GetGenericTypeDefinition() == typeof(Table<>)) {
                Type rowType = memberType.GetGenericArguments()[0];
                CheckContext(this.VisitExpression(ma.Expression));
                ITable table = this.services.Context.GetTable(rowType);
                if (table != null)
                    return this.Visit(Expression.Constant(table));
            }
            if (ma.Member.Name == "Count" && TypeSystem.IsSequenceType(ma.Expression.Type)) {
                return this.VisitAggregate(ma.Expression, null, SqlNodeType.Count, typeof(int));
            }
 
            return sql.Member(VisitExpression(ma.Expression), ma.Member);
        }
 
        [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 VisitMethodCall(MethodCallExpression mc) {
            Type declType = mc.Method.DeclaringType;
            if (mc.Method.IsStatic) {
                if (this.IsSequenceOperatorCall(mc)) {
                    return this.VisitSequenceOperatorCall(mc);
                }
                else if (IsDataManipulationCall(mc)) {
                    return this.VisitDataManipulationCall(mc);
                }
                // why is this handled here and not in SqlMethodCallConverter?
                else if (declType == typeof(DBConvert) || declType == typeof(Convert)) {
                    if (mc.Method.Name == "ChangeType") {
                        SqlNode sn = null;
                        if (mc.Arguments.Count == 2) {
                            object value = GetValue(mc.Arguments[1], "ChangeType");
                            if (value != null && typeof(Type).IsAssignableFrom(value.GetType())) {
                                sn = this.VisitChangeType(mc.Arguments[0], (Type)value);
                            }
                        } 
                        if(sn == null) {
                            throw Error.MethodFormHasNoSupportConversionToSql(mc.Method.Name, mc.Method);
                        }
                        return sn;
                    }
                }
            }
            else if (typeof(DataContext).IsAssignableFrom(mc.Method.DeclaringType)) {
                switch (mc.Method.Name) {
                    case "GetTable": {
                            // calls to GetTable<T> can be translated directly as table references
                            if (mc.Method.IsGenericMethod) {
                                Type[] typeArgs = mc.Method.GetGenericArguments();
                                if (typeArgs.Length == 1 && mc.Method.GetParameters().Length == 0) {
                                    CheckContext(this.VisitExpression(mc.Object));
                                    ITable table = this.services.Context.GetTable(typeArgs[0]);
                                    if (table != null) {
                                        return this.Visit(Expression.Constant(table));
                                    }
                                }
                            }
                            break;
                        }
                    case "ExecuteCommand":
                    case "ExecuteQuery":
                        return this.VisitUserQuery((string)GetValue(mc.Arguments[0], mc.Method.Name), GetArray(mc.Arguments[1]), mc.Type);
                }
 
                if (this.IsMappedFunctionCall(mc)) {
                    return this.VisitMappedFunctionCall(mc);
                }
            }
            else if (
                mc.Method.DeclaringType != typeof(string)
                && mc.Method.Name == "Contains"
                && !mc.Method.IsStatic
                && typeof(IList).IsAssignableFrom(mc.Method.DeclaringType)
                && mc.Type == typeof(bool)
                && mc.Arguments.Count == 1
                && TypeSystem.GetElementType(mc.Method.DeclaringType).IsAssignableFrom(mc.Arguments[0].Type)
                ) {
                return this.VisitContains(mc.Object, mc.Arguments[0]);
            }
 
            // default: create sql method call node instead
            SqlExpression obj = VisitExpression(mc.Object);
            SqlExpression[] args = new SqlExpression[mc.Arguments.Count];
            for (int i = 0, n = args.Length; i < n; i++) {
                args[i] = VisitExpression(mc.Arguments[i]);
            }
            return sql.MethodCall(mc.Method, obj, args, dominatingExpression);
        }
 
        private object GetValue(Expression expression, string operation) {
            SqlExpression exp = this.VisitExpression(expression);
            if (exp.NodeType == SqlNodeType.Value) {
                return ((SqlValue)exp).Value;
            }
            throw Error.NonConstantExpressionsNotSupportedFor(operation);
        }
 
        private static Expression[] GetArray(Expression array) {
            NewArrayExpression n = array as NewArrayExpression;
            if (n != null) {
                return n.Expressions.ToArray();
            }
            ConstantExpression c = array as ConstantExpression;
            if (c != null) {
                object[] obs = c.Value as object[];
                if (obs != null) {
                    Type elemType = TypeSystem.GetElementType(c.Type);
                    return obs.Select(o => Expression.Constant(o, elemType)).ToArray();
                }
            }
            return new Expression[] { };
        }
 
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
        private Expression RemoveQuotes(Expression expression) {
            while (expression.NodeType == ExpressionType.Quote) {
                expression = ((UnaryExpression)expression).Operand;
            }
            return expression;
        }
 
        private bool IsLambda(Expression expression) {
            return this.RemoveQuotes(expression).NodeType == ExpressionType.Lambda;
        }
 
        private LambdaExpression GetLambda(Expression expression) {
            return this.RemoveQuotes(expression) as LambdaExpression;
        }
 
        private bool IsMappedFunctionCall(MethodCallExpression mc) {
            MetaFunction function = services.Model.GetFunction(mc.Method);
            return function != null;
        }
 
        private SqlNode VisitMappedFunctionCall(MethodCallExpression mc) {
            // See if the method maps to a user defined function
            MetaFunction function = services.Model.GetFunction(mc.Method);
            System.Diagnostics.Debug.Assert(function != null);
            CheckContext(this.VisitExpression(mc.Object));
            if (!function.IsComposable) {
                return this.TranslateStoredProcedureCall(mc, function);
            }
            else if (function.ResultRowTypes.Count > 0) {
                return this.TranslateTableValuedFunction(mc, function);
            }
            else {
                ProviderType sqlType = function.ReturnParameter != null && !string.IsNullOrEmpty(function.ReturnParameter.DbType)
                    ? this.typeProvider.Parse(function.ReturnParameter.DbType)
                    : this.typeProvider.From(mc.Method.ReturnType);
                List<SqlExpression> sqlParams = this.GetFunctionParameters(mc, function);
                return sql.FunctionCall(mc.Method.ReturnType, sqlType, function.MappedName, sqlParams, mc);
            }
        }
 
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
        private bool IsSequenceOperatorCall(MethodCallExpression mc) {
            Type declType = mc.Method.DeclaringType;
            if (declType == typeof(System.Linq.Enumerable) ||
                declType == typeof(System.Linq.Queryable)) {
                return true;
            }
            return false;
        }
 
        private bool IsSequenceOperatorCall(MethodCallExpression mc, string methodName) {
            if (IsSequenceOperatorCall(mc) && mc.Method.Name == methodName) {
                return true;
            }
            return false;
        }
 
        [SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", 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.")]
        [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 VisitSequenceOperatorCall(MethodCallExpression mc) {
            Type declType = mc.Method.DeclaringType;
            bool isSupportedSequenceOperator = false;
            if (IsSequenceOperatorCall(mc)) {
                switch (mc.Method.Name) {
                    case "Select":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitSelect(mc.Arguments[0], this.GetLambda(mc.Arguments[1]));
                        }
                        break;
                    case "SelectMany":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitSelectMany(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), null);
                        }
                        else if (mc.Arguments.Count == 3 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1 &&
                            this.IsLambda(mc.Arguments[2]) && this.GetLambda(mc.Arguments[2]).Parameters.Count == 2) {
                            return this.VisitSelectMany(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), this.GetLambda(mc.Arguments[2]));
                        }
                        break;
                    case "Join":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 5 &&
                            this.IsLambda(mc.Arguments[2]) && this.GetLambda(mc.Arguments[2]).Parameters.Count == 1 &&
                            this.IsLambda(mc.Arguments[3]) && this.GetLambda(mc.Arguments[3]).Parameters.Count == 1 &&
                            this.IsLambda(mc.Arguments[4]) && this.GetLambda(mc.Arguments[4]).Parameters.Count == 2) {
                            return this.VisitJoin(mc.Arguments[0], mc.Arguments[1], this.GetLambda(mc.Arguments[2]), this.GetLambda(mc.Arguments[3]), this.GetLambda(mc.Arguments[4]));
                        }
                        break;
                    case "GroupJoin":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 5 &&
                            this.IsLambda(mc.Arguments[2]) && this.GetLambda(mc.Arguments[2]).Parameters.Count == 1 &&
                            this.IsLambda(mc.Arguments[3]) && this.GetLambda(mc.Arguments[3]).Parameters.Count == 1 &&
                            this.IsLambda(mc.Arguments[4]) && this.GetLambda(mc.Arguments[4]).Parameters.Count == 2) {
                            return this.VisitGroupJoin(mc.Arguments[0], mc.Arguments[1], this.GetLambda(mc.Arguments[2]), this.GetLambda(mc.Arguments[3]), this.GetLambda(mc.Arguments[4]));
                        }
                        break;
                    case "DefaultIfEmpty":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.VisitDefaultIfEmpty(mc.Arguments[0]);
                        }
                        break;
                    case "OfType":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            Type ofType = mc.Method.GetGenericArguments()[0];
                            return this.VisitOfType(mc.Arguments[0], ofType);
                        }
                        break;
                    case "Cast":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            Type type = mc.Method.GetGenericArguments()[0];
                            return this.VisitSequenceCast(mc.Arguments[0], type);
                        }
                        break;
                    case "Where":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitWhere(mc.Arguments[0], this.GetLambda(mc.Arguments[1]));
                        }
                        break;
                    case "First":
                    case "FirstOrDefault":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.VisitFirst(mc.Arguments[0], null, true);
                        }
                        else if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitFirst(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), true);
                        }
                        break;
                    case "Single":
                    case "SingleOrDefault":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.VisitFirst(mc.Arguments[0], null, false);
                        }
                        else if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitFirst(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), false);
                        }
                        break;
                    case "Distinct":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.VisitDistinct(mc.Arguments[0]);
                        }
                        break;
                    case "Concat":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2) {
                            return this.VisitConcat(mc.Arguments[0], mc.Arguments[1]);
                        }
                        break;
                    case "Union":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2) {
                            return this.VisitUnion(mc.Arguments[0], mc.Arguments[1]);
                        }
                        break;
                    case "Intersect":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2) {
                            return this.VisitIntersect(mc.Arguments[0], mc.Arguments[1]);
                        }
                        break;
                    case "Except":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2) {
                            return this.VisitExcept(mc.Arguments[0], mc.Arguments[1]);
                        }
                        break;
                    case "Any":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.VisitQuantifier(mc.Arguments[0], null, true);
                        }
                        else if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitQuantifier(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), true);
                        }
                        break;
                    case "All":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitQuantifier(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), false);
                        }
                        break;
                    case "Count":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], null, SqlNodeType.Count, mc.Type);
                        }
                        else if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), SqlNodeType.Count, mc.Type);
                        }
                        break;
                    case "LongCount":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], null, SqlNodeType.LongCount, mc.Type);
                        }
                        else if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), SqlNodeType.LongCount, mc.Type);
                        }
                        break;
                    case "Sum":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], null, SqlNodeType.Sum, mc.Type);
                        }
                        else if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), SqlNodeType.Sum, mc.Type);
                        }
                        break;
                    case "Min":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], null, SqlNodeType.Min, mc.Type);
                        }
                        else if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), SqlNodeType.Min, mc.Type);
                        }
                        break;
                    case "Max":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], null, SqlNodeType.Max, mc.Type);
                        }
                        else if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), SqlNodeType.Max, mc.Type);
                        }
                        break;
                    case "Average":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], null, SqlNodeType.Avg, mc.Type);
                        }
                        else if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitAggregate(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), SqlNodeType.Avg, mc.Type);
                        }
                        break;
                    case "GroupBy":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitGroupBy(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), null, null);
                        }
                        else if (mc.Arguments.Count == 3 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1 &&
                            this.IsLambda(mc.Arguments[2]) && this.GetLambda(mc.Arguments[2]).Parameters.Count == 1) {
                            return this.VisitGroupBy(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), this.GetLambda(mc.Arguments[2]), null);
                        }
                        else if (mc.Arguments.Count == 3 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1 &&
                            this.IsLambda(mc.Arguments[2]) && this.GetLambda(mc.Arguments[2]).Parameters.Count == 2) {
                            return this.VisitGroupBy(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), null, this.GetLambda(mc.Arguments[2]));
                        }
                        else if (mc.Arguments.Count == 4 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1 &&
                            this.IsLambda(mc.Arguments[2]) && this.GetLambda(mc.Arguments[2]).Parameters.Count == 1 &&
                            this.IsLambda(mc.Arguments[3]) && this.GetLambda(mc.Arguments[3]).Parameters.Count == 2) {
                            return this.VisitGroupBy(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), this.GetLambda(mc.Arguments[2]), this.GetLambda(mc.Arguments[3]));
                        }
                        break;
                    case "OrderBy":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitOrderBy(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), SqlOrderType.Ascending);
                        }
                        break;
                    case "OrderByDescending":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitOrderBy(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), SqlOrderType.Descending);
                        }
                        break;
                    case "ThenBy":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitThenBy(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), SqlOrderType.Ascending);
                        }
                        break;
                    case "ThenByDescending":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2 &&
                            this.IsLambda(mc.Arguments[1]) && this.GetLambda(mc.Arguments[1]).Parameters.Count == 1) {
                            return this.VisitThenBy(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), SqlOrderType.Descending);
                        }
                        break;
                    case "Take":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2) {
                            return this.VisitTake(mc.Arguments[0], mc.Arguments[1]);
                        }
                        break;
                    case "Skip":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2) {
                            return this.VisitSkip(mc.Arguments[0], mc.Arguments[1]);
                        }
                        break;
                    case "Contains":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 2) {
                            return this.VisitContains(mc.Arguments[0], mc.Arguments[1]);
                        }
                        break;
                    case "ToList":
                    case "AsEnumerable":
                    case "ToArray":
                        isSupportedSequenceOperator = true;
                        if (mc.Arguments.Count == 1) {
                            return this.Visit(mc.Arguments[0]);
                        }
                        break;
                }
                // If the operator is supported, but the particular overload is not,
                // give an appropriate error message
                if (isSupportedSequenceOperator) {
                    throw Error.QueryOperatorOverloadNotSupported(mc.Method.Name);
                }
                throw Error.QueryOperatorNotSupported(mc.Method.Name);
            }
            else {
                throw Error.InvalidSequenceOperatorCall(declType);
            }
        }
 
        private static bool IsDataManipulationCall(MethodCallExpression mc) {
            return mc.Method.IsStatic && mc.Method.DeclaringType == typeof(DataManipulation);
        }
 
        private SqlNode VisitDataManipulationCall(MethodCallExpression mc) {
            if (IsDataManipulationCall(mc)) {
                bool isSupportedDML = false;
                switch (mc.Method.Name) {
                    case "Insert":
                        isSupportedDML = true;
                        if (mc.Arguments.Count == 2) {
                            return this.VisitInsert(mc.Arguments[0], this.GetLambda(mc.Arguments[1]));
                        }
                        else if (mc.Arguments.Count == 1) {
                            return this.VisitInsert(mc.Arguments[0], null);
                        }
                        break;
                    case "Update":
                        isSupportedDML = true;
                        if (mc.Arguments.Count == 3) {
                            return this.VisitUpdate(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), this.GetLambda(mc.Arguments[2]));
                        }
                        else if (mc.Arguments.Count == 2) {
                            if (mc.Method.GetGenericArguments().Length == 1) {
                                return this.VisitUpdate(mc.Arguments[0], this.GetLambda(mc.Arguments[1]), null);
                            }
                            else {
                                return this.VisitUpdate(mc.Arguments[0], null, this.GetLambda(mc.Arguments[1]));
                            }
                        }
                        else if (mc.Arguments.Count == 1) {
                            return this.VisitUpdate(mc.Arguments[0], null, null);
                        }
                        break;
                    case "Delete":
                        isSupportedDML = true;
                        if (mc.Arguments.Count == 2) {
                            return this.VisitDelete(mc.Arguments[0], this.GetLambda(mc.Arguments[1]));
                        }
                        else if (mc.Arguments.Count == 1) {
                            return this.VisitDelete(mc.Arguments[0], null);
                        }
                        break;
                }
                if (isSupportedDML) {
                    throw Error.QueryOperatorOverloadNotSupported(mc.Method.Name);
                }
                throw Error.QueryOperatorNotSupported(mc.Method.Name);
            }
            throw Error.InvalidSequenceOperatorCall(mc.Method.Name);
        }
 
        private SqlNode VisitFirst(Expression sequence, LambdaExpression lambda, bool isFirst) {
            SqlSelect select = this.LockSelect(this.VisitSequence(sequence));
            if (lambda != null) {
                this.map[lambda.Parameters[0]] = (SqlAliasRef)select.Selection;
                select.Where = this.VisitExpression(lambda.Body);
            }
            if (isFirst) {
                select.Top = this.sql.ValueFromObject(1, false, this.dominatingExpression);
            }
            if (this.outerNode) {
                return select;
            }
            SqlNodeType subType = (this.typeProvider.From(select.Selection.ClrType).CanBeColumn) ? SqlNodeType.ScalarSubSelect : SqlNodeType.Element;
            SqlSubSelect elem = sql.SubSelect(subType, select, sequence.Type);
            return elem;
        }
 
        [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", 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 SqlStatement VisitInsert(Expression item, LambdaExpression resultSelector) {
            if (item == null) {
                throw Error.ArgumentNull("item");
            }
            this.dominatingExpression = item;
 
            MetaTable metaTable = this.services.Model.GetTable(item.Type);
            Expression source = this.services.Context.GetTable(metaTable.RowType.Type).Expression;
 
            MetaType itemMetaType = null;
            SqlNew sqlItem = null;
 
            // construct insert assignments from 'item' info
            ConstantExpression conItem = item as ConstantExpression;
            if (conItem == null) {
                throw Error.InsertItemMustBeConstant();
            }
            if (conItem.Value == null) {
                throw Error.ArgumentNull("item");
            }
            // construct insert based on constant value
            List<SqlMemberAssign> bindings = new List<SqlMemberAssign>();
            itemMetaType = metaTable.RowType.GetInheritanceType(conItem.Value.GetType());
            SqlExpression sqlExprItem = sql.ValueFromObject(conItem.Value, true, source);
            foreach (MetaDataMember mm in itemMetaType.PersistentDataMembers) {
                if (!mm.IsAssociation && !mm.IsDbGenerated && !mm.IsVersion) {
                    bindings.Add(new SqlMemberAssign(mm.Member, sql.Member(sqlExprItem, mm.Member)));
                }
            }
            ConstructorInfo cons = itemMetaType.Type.GetConstructor(Type.EmptyTypes);
            System.Diagnostics.Debug.Assert(cons != null);
            sqlItem = sql.New(itemMetaType, cons, null, null, bindings, item);
 
            SqlTable tab = sql.Table(metaTable, metaTable.RowType, this.dominatingExpression);
            SqlInsert sin = new SqlInsert(tab, sqlItem, item);
 
            if (resultSelector == null) {
                return sin;
            }
            else {
                MetaDataMember id = itemMetaType.DBGeneratedIdentityMember;
                bool isDbGenOnly = false;  
                if (id != null) {
                    isDbGenOnly = this.IsDbGeneratedKeyProjectionOnly(resultSelector.Body, id);
                    if (id.Type == typeof(Guid) && (this.converterStrategy & ConverterStrategy.CanOutputFromInsert) != 0) {
                        sin.OutputKey = new SqlColumn(id.Type, sql.Default(id), id.Name, id, null, this.dominatingExpression);
                        if (!isDbGenOnly) {
                            sin.OutputToLocal = true;
                        }
                    }
                }
 
                SqlSelect result = null;
                SqlSelect preResult = null;
                SqlAlias tableAlias = new SqlAlias(tab);
                SqlAliasRef tableAliasRef = new SqlAliasRef(tableAlias);
                System.Diagnostics.Debug.Assert(resultSelector.Parameters.Count == 1);
                this.map.Add(resultSelector.Parameters[0], tableAliasRef);
                SqlExpression projection = this.VisitExpression(resultSelector.Body);
 
                // build select to return result
                SqlExpression pred = null;
                if (id != null) {
                    pred = sql.Binary(
                            SqlNodeType.EQ,
                            sql.Member(tableAliasRef, id.Member),
                            this.GetIdentityExpression(id, sin.OutputKey != null)
                            );
                }
                else {
                    SqlExpression itemExpression = this.VisitExpression(item);
                    pred = sql.Binary(SqlNodeType.EQ2V, tableAliasRef, itemExpression);
                }
                result = new SqlSelect(projection, tableAlias, resultSelector);
                result.Where = pred;
 
                // Since we're only projecting back a single generated key, we can
                // optimize the query to a simple selection (e.g. SELECT @@IDENTITY)
                // rather than selecting back from the table.
                if (id != null && isDbGenOnly) {
                    if (sin.OutputKey == null) {
                        SqlExpression exp = this.GetIdentityExpression(id, false);
                        if (exp.ClrType != id.Type) {
                            ProviderType sqlType = sql.Default(id);
                            exp = sql.ConvertTo(id.Type, sqlType, exp);
                        }
                        // The result selector passed in was bound to the table -
                        // we need to rebind to the single result as an array projection
                        ParameterExpression p = Expression.Parameter(id.Type, "p");
                        Expression[] init = new Expression[1] { Expression.Convert(p, typeof(object)) };
                        NewArrayExpression arrExp = Expression.NewArrayInit(typeof(object), init);
                        LambdaExpression rs = Expression.Lambda(arrExp, p);
                        this.map.Add(p, exp);
                        SqlExpression proj = this.VisitExpression(rs.Body);
                        preResult = new SqlSelect(proj, null, rs);
                    }
                    else {
                        // case handled in formatter automatically
                    }
                    result.DoNotOutput = true;
                }
 
                // combine insert & result into block
                SqlBlock block = new SqlBlock(this.dominatingExpression);
                block.Statements.Add(sin);
                if (preResult != null) {
                    block.Statements.Add(preResult);
                }
                block.Statements.Add(result);
                return block;
            }
        }
 
        private bool IsDbGeneratedKeyProjectionOnly(Expression projection, MetaDataMember keyMember) {
            NewArrayExpression array = projection as NewArrayExpression;
            if (array != null && array.Expressions.Count == 1) {
                Expression exp = array.Expressions[0];
                while (exp.NodeType == ExpressionType.Convert || exp.NodeType == ExpressionType.ConvertChecked) {
                    exp = ((UnaryExpression)exp).Operand;
                }
                MemberExpression mex = exp as MemberExpression;
                if (mex != null && mex.Member == keyMember.Member) {
                    return true;
                }
            }
            return false;
        }
 
        private SqlExpression GetIdentityExpression(MetaDataMember id, bool isOutputFromInsert) {
            if (isOutputFromInsert) {
                return new SqlVariable(id.Type, sql.Default(id), "@id", this.dominatingExpression);
            }
            else {
                ProviderType sqlType = sql.Default(id);
                if (!IsLegalIdentityType(sqlType.GetClosestRuntimeType())) {                    
                    throw Error.InvalidDbGeneratedType(sqlType.ToQueryString());
                }
                if ((this.converterStrategy & ConverterStrategy.CanUseScopeIdentity) != 0) {
                    return new SqlVariable(typeof(decimal), typeProvider.From(typeof(decimal)), "SCOPE_IDENTITY()", this.dominatingExpression);
                }
                else {
                    return new SqlVariable(typeof(decimal), typeProvider.From(typeof(decimal)), "@@IDENTITY", this.dominatingExpression);
                }
            }
        }
 
        private static bool IsLegalIdentityType(Type type) {
            switch (Type.GetTypeCode(type)) {
                case TypeCode.SByte:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.Decimal:
                    return true;
            }
            return false;
        }
 
        private SqlExpression GetRowCountExpression() {
            if ((this.converterStrategy & ConverterStrategy.CanUseRowStatus) != 0) {
                return new SqlVariable(typeof(decimal), typeProvider.From(typeof(decimal)), "@@ROWCOUNT", this.dominatingExpression);
            }
            else {
                return new SqlVariable(typeof(decimal), typeProvider.From(typeof(decimal)), "@ROWCOUNT", this.dominatingExpression);
            }
        }
 
        private SqlStatement VisitUpdate(Expression item, LambdaExpression check, LambdaExpression resultSelector) {
            if (item == null) {
                throw Error.ArgumentNull("item");
            }
            MetaTable metaTable = this.services.Model.GetTable(item.Type);
            Expression source = this.services.Context.GetTable(metaTable.RowType.Type).Expression;
            Type rowType = metaTable.RowType.Type;
 
            bool saveAllowDeferred = this.allowDeferred;
            this.allowDeferred = false;
 
            try {
                Expression seq = source;
                // construct identity predicate based on supplied item
                ParameterExpression p = Expression.Parameter(rowType, "p");
                LambdaExpression idPredicate = Expression.Lambda(Expression.Equal(p, item), p);
 
                // combine predicate and check expression into single find predicate
                LambdaExpression findPredicate = idPredicate;
                if (check != null) {
                    findPredicate = Expression.Lambda(Expression.And(Expression.Invoke(findPredicate, p), Expression.Invoke(check, p)), p);
                }
                seq = Expression.Call(typeof(Enumerable), "Where", new Type[] { rowType }, seq, findPredicate);
 
                // source 'query' is based on table + find predicate
                SqlSelect ss = new RetypeCheckClause().VisitSelect(this.VisitSequence(seq));
 
                // construct update assignments from 'item' info
                List<SqlAssign> assignments = new List<SqlAssign>();
                ConstantExpression conItem = item as ConstantExpression;
                if (conItem == null) {
                    throw Error.UpdateItemMustBeConstant();
                }
                if (conItem.Value == null) {
                    throw Error.ArgumentNull("item");
                }
                // get changes from data services to construct update command
                Type entityType = conItem.Value.GetType();
                MetaType metaType = this.services.Model.GetMetaType(entityType);
                ITable table = this.services.Context.GetTable(metaType.InheritanceRoot.Type);
                foreach (ModifiedMemberInfo mmi in table.GetModifiedMembers(conItem.Value)) {
                    MetaDataMember mdm = metaType.GetDataMember(mmi.Member);
                    assignments.Add(
                        new SqlAssign(
                            sql.Member(ss.Selection, mmi.Member),
                            new SqlValue(mdm.Type, this.typeProvider.From(mdm.Type), mmi.CurrentValue, true, source),
                            source
                            ));
                }
 
                SqlUpdate upd = new SqlUpdate(ss, assignments, source);
 
                if (resultSelector == null) {
                    return upd;
                }
 
                SqlSelect select = null;
 
                // build select to return result
                seq = source;
                seq = Expression.Call(typeof(Enumerable), "Where", new Type[] { rowType }, seq, idPredicate);
                seq = Expression.Call(typeof(Enumerable), "Select", new Type[] { rowType, resultSelector.Body.Type }, seq, resultSelector);
                select = this.VisitSequence(seq);
                select.Where = sql.AndAccumulate(
                    sql.Binary(SqlNodeType.GT, this.GetRowCountExpression(), sql.ValueFromObject(0, false, this.dominatingExpression)),
                    select.Where    
                    );
 
                // combine update & select into statement block
                SqlBlock block = new SqlBlock(source);
                block.Statements.Add(upd);
                block.Statements.Add(select);
                return block;
            }
            finally {
                this.allowDeferred = saveAllowDeferred;
            }
        }
 
        private SqlStatement VisitDelete(Expression item, LambdaExpression check) {
            if (item == null) {
                throw Error.ArgumentNull("item");
            }
 
            bool saveAllowDeferred = this.allowDeferred;
            this.allowDeferred = false;
 
            try {
                MetaTable metaTable = this.services.Model.GetTable(item.Type);
                Expression source = this.services.Context.GetTable(metaTable.RowType.Type).Expression;
                Type rowType = metaTable.RowType.Type;
 
                // construct identity predicate based on supplied item
                ParameterExpression p = Expression.Parameter(rowType, "p");
                LambdaExpression idPredicate = Expression.Lambda(Expression.Equal(p, item), p);
 
                // combine predicate and check expression into single find predicate
                LambdaExpression findPredicate = idPredicate;
                if (check != null) {
                    findPredicate = Expression.Lambda(Expression.And(Expression.Invoke(findPredicate, p), Expression.Invoke(check, p)), p);
                }
                Expression seq = Expression.Call(typeof(Enumerable), "Where", new Type[] { rowType }, source, findPredicate);
                SqlSelect ss = new RetypeCheckClause().VisitSelect(this.VisitSequence(seq));
                this.allowDeferred = saveAllowDeferred;
 
                SqlDelete sd = new SqlDelete(ss, source);
                return sd;
            }
            finally {
                this.allowDeferred = saveAllowDeferred;
            }
        }
        private class RetypeCheckClause : SqlVisitor {
            internal override SqlExpression VisitMethodCall(SqlMethodCall mc) {
                if (mc.Arguments.Count==2 && mc.Method.Name=="op_Equality") {
                    var r = mc.Arguments[1];
                    if (r.NodeType == SqlNodeType.Value) {
                        var v = (SqlValue)r;
                        v.SetSqlType(mc.Arguments[0].SqlType);
                    }
                }
                return base.VisitMethodCall(mc);
            }
        }
 
 
        private SqlExpression VisitNewArrayInit(NewArrayExpression arr) {
            SqlExpression[] exprs = new SqlExpression[arr.Expressions.Count];
            for (int i = 0, n = exprs.Length; i < n; i++) {
                exprs[i] = this.VisitExpression(arr.Expressions[i]);
            }
            return new SqlClientArray(arr.Type, this.typeProvider.From(arr.Type), exprs, this.dominatingExpression);
        }
 
        private SqlExpression VisitListInit(ListInitExpression list) {
            if (null != list.NewExpression.Constructor && 0 != list.NewExpression.Arguments.Count) {
                // Throw existing exception for unrecognized expressions if list
                // init does not use a default constructor.
                throw Error.UnrecognizedExpressionNode(list.NodeType);
            }
            SqlExpression[] exprs = new SqlExpression[list.Initializers.Count];
            for (int i = 0, n = exprs.Length; i < n; i++) {
                if (1 != list.Initializers[i].Arguments.Count) {
                    // Throw existing exception for unrecognized expressions if element
                    // init is not adding a single element.
                    throw Error.UnrecognizedExpressionNode(list.NodeType);
                }
                exprs[i] = this.VisitExpression(list.Initializers[i].Arguments.Single());
            }
            return new SqlClientArray(list.Type, this.typeProvider.From(list.Type), exprs, this.dominatingExpression);
        }
    }
 
    class SingleTableQueryVisitor : SqlVisitor {
        public bool IsValid;
        bool IsDistinct;
        List<MemberInfo> IdentityMembers;
 
        void AddIdentityMembers(IEnumerable<MemberInfo> members) {
            System.Diagnostics.Debug.Assert(this.IdentityMembers == null, "We already have a set of keys -- why are we adding more?");
            this.IdentityMembers = new List<MemberInfo>(members);
        }
 
        internal SingleTableQueryVisitor(): base() {
            this.IsValid = true;
        }
 
        internal override SqlNode Visit(SqlNode node) {
            // recurse until we know we're invalid
            if (this.IsValid && node != null) {
                return base.Visit(node);
            }
 
            return node;
        }
 
        internal override SqlTable VisitTable(SqlTable tab) {
            // if we're distinct, we don't care about joins
            if (this.IsDistinct) {
                return tab;
            }
 
            if (this.IdentityMembers != null) {
                this.IsValid = false;
            } else {
                this.AddIdentityMembers(tab.MetaTable.RowType.IdentityMembers.Select(m => m.Member));
            }
 
            return tab;
        }
 
        internal override SqlSource VisitSource(SqlSource source) {
            return base.VisitSource(source);
        }
 
        internal override SqlSelect VisitSelect(SqlSelect select) {
            if (select.IsDistinct) {
                this.IsDistinct = true;
                // get all members from selection
                this.AddIdentityMembers(select.Selection.ClrType.GetProperties());
                return select;
            }
 
            // 
            //
            //
            //
            //
 
            // We're not distinct, but let's check our sources...
            select.From = (SqlSource)base.Visit(select.From);
 
            if (this.IdentityMembers == null || this.IdentityMembers.Count == 0) {
                throw Error.SkipRequiresSingleTableQueryWithPKs();
            }
            else {
                switch (select.Selection.NodeType) {
                    case SqlNodeType.Column:
                    case SqlNodeType.ColumnRef:
                    case SqlNodeType.Member: {
                            // we've got a bare member/column node, eg "select c.CustomerId"
                            // find out if it refers to the table's PK, of which there must be only 1
                            if (this.IdentityMembers.Count == 1) {
                                MemberInfo column = this.IdentityMembers[0];
                                this.IsValid &= IsColumnMatch(column, select.Selection);
                            }
                            else {
                                this.IsValid = false;
                            }
 
                            break;
                        }
                    case SqlNodeType.New:
                    case SqlNodeType.AliasRef: {
                            select.Selection = this.VisitExpression(select.Selection);
                            break;
                        }
                    case SqlNodeType.Treat:
                    case SqlNodeType.TypeCase: {
                            break;
                        }
                    default: {
                            this.IsValid = false;
                            break;
                        }
                }
            }
 
            return select;
        }
 
        // 
        //
        //
        //
        //
 
        internal override SqlExpression VisitNew(SqlNew sox) {
            // check the args for the PKs
            foreach (MemberInfo column in this.IdentityMembers) {
                // assume we're invalid unless we find a matching argument which is
                // a bare column/columnRef to the PK
                bool isMatch = false;
 
                // find a matching arg
                foreach (SqlExpression expr in sox.Args) {
                    isMatch = IsColumnMatch(column, expr);
 
                    if (isMatch) {
                        break;
                    }
                }
 
                if (!isMatch) {
                    foreach (SqlMemberAssign ma in sox.Members) {
                        SqlExpression expr = ma.Expression;
 
                        isMatch = IsColumnMatch(column, expr);
 
                        if (isMatch) {
                            break;
                        }
                    }
                }
 
                this.IsValid &= isMatch;
                if (!this.IsValid) {
                    break;
                }
            }
 
            return sox;
        }
 
        internal override SqlNode VisitUnion(SqlUnion su) {
            // we don't want to descend inward
 
            // just check that it's not a UNION ALL
            if (su.All) {
                this.IsValid = false;
            }
 
            // UNIONs are distinct
            this.IsDistinct = true;
 
            // get all members from selection
            this.AddIdentityMembers(su.GetClrType().GetProperties());
            return su;
        }
 
        private static bool IsColumnMatch(MemberInfo column, SqlExpression expr) {
            MemberInfo memberInfo = null;
 
            switch (expr.NodeType) {
                case SqlNodeType.Column: {
                        memberInfo = ((SqlColumn)expr).MetaMember.Member;
                        break;
                    }
                case SqlNodeType.ColumnRef: {
                        memberInfo = (((SqlColumnRef)expr).Column).MetaMember.Member;
                        break;
                    }
                case SqlNodeType.Member: {
                        memberInfo = ((SqlMember)expr).Member;
                        break;
                    }
            }
 
            return (memberInfo != null && memberInfo == column);
        }
    }
}