protected override Expression VisitClientJoin(ClientJoinExpression join) { // convert client join into a up-front lookup table builder and replace client-join in tree with lookup accessor // 1) lookup = query.Select(e => new KVP(key: inner, value: e)).ToLookup(kvp => kvp.Key, kvp => kvp.Value) Expression innerKey = MakeJoinKey(join.InnerKey); Expression outerKey = MakeJoinKey(join.OuterKey); ConstructorInfo kvpConstructor = typeof(KeyValuePair <,>).MakeGenericType(innerKey.Type, join.Projection.Projector.Type).GetConstructor( new[] { innerKey.Type, join.Projection.Projector.Type }); Expression constructKVPair = Expression.New(kvpConstructor, innerKey, join.Projection.Projector); ProjectionExpression newProjection = new ProjectionExpression(join.Projection.Source, constructKVPair); int iLookup = ++nLookup; Expression execution = ExecuteProjection(newProjection, false); ParameterExpression kvp = Expression.Parameter(constructKVPair.Type, "kvp"); // filter out nulls if (join.Projection.Projector.NodeType == (ExpressionType)DbExpressionType.OuterJoined) { LambdaExpression pred = Expression.Lambda( Expression.NotEqual( Expression.PropertyOrField(kvp, "Value"), Expression.Constant(null, join.Projection.Projector.Type) ), kvp ); execution = Expression.Call(typeof(Enumerable), "Where", new[] { kvp.Type }, execution, pred); } // make lookup LambdaExpression keySelector = Expression.Lambda(Expression.PropertyOrField(kvp, "Key"), kvp); LambdaExpression elementSelector = Expression.Lambda(Expression.PropertyOrField(kvp, "Value"), kvp); Expression toLookup = Expression.Call(typeof(Enumerable), "ToLookup", new[] { kvp.Type, outerKey.Type, join.Projection.Projector.Type }, execution, keySelector, elementSelector); // 2) agg(lookup[outer]) ParameterExpression lookup = Expression.Parameter(toLookup.Type, "lookup" + iLookup); PropertyInfo property = lookup.Type.GetProperty("Item"); Expression access = Expression.Call(lookup, property.GetGetMethod(), Visit(outerKey)); if (join.Projection.Aggregator != null) { // apply aggregator access = DbExpressionReplacer.Replace(join.Projection.Aggregator.Body, join.Projection.Aggregator.Parameters[0], access); } variables.Add(lookup); initializers.Add(toLookup); return(access); }
// end mike private Expression ExecuteProjection(ProjectionExpression projection, bool okayToDefer) { okayToDefer &= (receivingMember != null && policy.IsDeferLoaded(receivingMember)); // parameterize query projection = (ProjectionExpression)Parameterizer.Parameterize(projection); if (scope != null) { // also convert references to outer alias to named values! these become SQL parameters too projection = (ProjectionExpression)OuterParameterizer.Parameterize(scope.Alias, projection); } var saveScope = scope; ParameterExpression reader = Expression.Parameter(typeof(DbDataReader), "r" + nReaders++); scope = new Scope(scope, reader, projection.Source.Alias, projection.Source.Columns); LambdaExpression projector = Expression.Lambda(Visit(projection.Projector), reader); scope = saveScope; List <string> columnNames = ColumnNamedGatherer.Gather(projector.Body); //mike string commandText = policy.Mapping.Language.Format(projection.Source); ReadOnlyCollection <NamedValueExpression> namedValues = NamedValueGatherer.Gather(projection.Source); string[] names = namedValues.Select(v => v.Name).ToArray(); Expression[] values = namedValues.Select(v => Expression.Convert(Visit(v.Value), typeof(object))).ToArray(); string methExecute = okayToDefer ? "ExecuteDeferred" : "Execute"; if (okayToDefer) { } // call low-level execute directly on supplied DbQueryProvider Expression result = Expression.Call(provider, methExecute, new[] { projector.Body.Type }, Expression.New( typeof(QueryCommand <>).MakeGenericType(projector.Body.Type). GetConstructors()[0], Expression.Constant(commandText), Expression.Constant(names), projector, Expression.Constant(columnNames) ), Expression.NewArrayInit(typeof(object), values) ); if (projection.Aggregator != null) { // apply aggregator result = DbExpressionReplacer.Replace(projection.Aggregator.Body, projection.Aggregator.Parameters[0], result); } return(result); }