///<summary> /// Move order-bys to the outermost select ///</summary> internalclassOrderByRewriter : DbExpressionVisitor { IEnumerable<OrderExpression> gatheredOrderings; bool isOuterMostSelect;
publicOrderByRewriter() { }
public Expression Rewrite(Expression expression) { this.isOuterMostSelect = true; returnthis.Visit(expression); }
protectedoverride Expression VisitSelect(SelectExpression select) { bool saveIsOuterMostSelect = this.isOuterMostSelect; try { this.isOuterMostSelect = false; select = (SelectExpression)base.VisitSelect(select); bool hasOrderBy = select.OrderBy != null && select.OrderBy.Count > 0; if (hasOrderBy) { this.PrependOrderings(select.OrderBy); } bool canHaveOrderBy = saveIsOuterMostSelect; bool canPassOnOrderings = !saveIsOuterMostSelect; IEnumerable<OrderExpression> orderings = (canHaveOrderBy) ? this.gatheredOrderings : null; ReadOnlyCollection<ColumnDeclaration> columns = select.Columns; if (this.gatheredOrderings != null) { if (canPassOnOrderings) { HashSet<string> producedAliases = new AliasesProduced().Gather(select.From); // reproject order expressions using this select's alias so the outer select will have properly formed expressions BindResult project = this.RebindOrderings(this.gatheredOrderings, select.Alias, producedAliases, select.Columns); this.gatheredOrderings = project.Orderings; columns = project.Columns; } else { this.gatheredOrderings = null; } } if (orderings != select.OrderBy || columns != select.Columns) { select = new SelectExpression(select.Type, select.Alias, columns, select.From, select.Where, orderings); } returnselect; } finally { this.isOuterMostSelect = saveIsOuterMostSelect; } }
protectedoverride Expression VisitJoin(JoinExpression join) { // make sure order by expressions lifted up from the left side are not lost // when visiting the right side Expression left = this.VisitSource(join.Left); IEnumerable<OrderExpression> leftOrders = this.gatheredOrderings; this.gatheredOrderings = null; // start on the right with a clean slate Expression right = this.VisitSource(join.Right); this.PrependOrderings(leftOrders); Expression condition = this.Visit(join.Condition); if (left != join.Left || right != join.Right || condition != join.Condition) { returnnew JoinExpression(join.Type, join.Join, left, right, condition); } returnjoin; }
///<summary> /// Add a sequence of order expressions to an accumulated list, prepending so as /// to give precedence to the new expressions over any previous expressions ///</summary> ///<param name="newOrderings"></param> protectedvoidPrependOrderings(IEnumerable<OrderExpression> newOrderings) { if (newOrderings != null) { if (this.gatheredOrderings == null) { this.gatheredOrderings = newOrderings; } else { List<OrderExpression> list = this.gatheredOrderings as List<OrderExpression>; if (list == null) { this.gatheredOrderings = list = new List<OrderExpression>(this.gatheredOrderings); } list.InsertRange(0, newOrderings); } } }
protectedclassBindResult { ReadOnlyCollection<ColumnDeclaration> columns; ReadOnlyCollection<OrderExpression> orderings; publicBindResult(IEnumerable<ColumnDeclaration> columns, IEnumerable<OrderExpression> orderings) { this.columns = columns as ReadOnlyCollection<ColumnDeclaration>; if (this.columns == null) { this.columns = new List<ColumnDeclaration>(columns).AsReadOnly(); } this.orderings = orderings as ReadOnlyCollection<OrderExpression>; if (this.orderings == null) { this.orderings = new List<OrderExpression>(orderings).AsReadOnly(); } } public ReadOnlyCollection<ColumnDeclaration> Columns { get { returnthis.columns; } } public ReadOnlyCollection<OrderExpression> Orderings { get { returnthis.orderings; } } }
///<summary> /// Rebind order expressions to reference a new alias and add to column declarations if necessary ///</summary> protectedvirtual BindResult RebindOrderings(IEnumerable<OrderExpression> orderings, stringalias, HashSet<string> existingAliases, IEnumerable<ColumnDeclaration> existingColumns) { List<ColumnDeclaration> newColumns = null; List<OrderExpression> newOrderings = new List<OrderExpression>(); foreach (OrderExpression ordering in orderings) { Expression expr = ordering.Expression; ColumnExpression column = expr as ColumnExpression; if (column == null || (existingAliases != null && existingAliases.Contains(column.Alias))) { // check to see if a declared column already contains a similar expression int iOrdinal = 0; foreach (ColumnDeclaration decl in existingColumns) { ColumnExpression declColumn = decl.Expression as ColumnExpression; if (decl.Expression == ordering.Expression || (column != null && declColumn != null && column.Alias == declColumn.Alias && column.Name == declColumn.Name)) { // found it, so make a reference to this column expr = new ColumnExpression(column.Type, alias, decl.Name, iOrdinal); break; } iOrdinal++; } // if not already projected, add a new column declaration for it if (expr == ordering.Expression) { if (newColumns == null) { newColumns = new List<ColumnDeclaration>(existingColumns); existingColumns = newColumns; } string colName = column != null ? column.Name : "c" + iOrdinal; newColumns.Add(new ColumnDeclaration(colName, ordering.Expression)); expr = new ColumnExpression(expr.Type, alias, colName, iOrdinal); } newOrderings.Add(new OrderExpression(ordering.OrderType, expr)); } } returnnew BindResult(existingColumns, newOrderings); } }
现在回到之前说的那个问题,如果一个select节点使用了DISTINCT关键字,那么往投影中添加order-by表达式中引用到的列就会出错了。这些新添加的列会影响到distinct操作的结果。现在倒是不用担心这个问题,因为我们根本就不支持distinct,但是我们以后会支持,所以最好要提前考虑到这点。这就是LINQ to SQL在distinct或union操作中不支持排序的真正原因。
var query = from c in db.Customers orderby c.City where c.Country == "UK" selectnew { c.City, c.ContactName };
翻译后得到如下SQL:
1 2 3 4 5 6 7 8 9 10 11 12 13
SELECT t3.City, t3.ContactName FROM ( SELECT t2.City, t2.Country, t2.ContactName, t2.CustomerID, t2.Phone FROM ( SELECT t1.City, t1.Country, t1.ContactName, t1.CustomerID, t1.Phone FROM ( SELECT t0.City, t0.Country, t0.ContactName, t0.CustomerID, t0.Phone FROM Customers AS t0 ) AS t1 ) AS t2 WHERE (t2.Country = 'UK') ) AS t3 ORDERBY t3.City
这可比之前生成的SQL好多了。
执行完成后,得到如下输出:
1 2 3 4 5 6 7
{ City = Cowes, ContactName = Helen Bennett } { City = London, ContactName = Simon Crowther } { City = London, ContactName = Hari Kumar } { City = London, ContactName = Thomas Hardy } { City = London, ContactName = Victoria Ashworth } { City = London, ContactName = Elizabeth Brown } { City = London, ContactName = Ann Devon }