private void TranslateGroupJoin(GroupJoinExpression node)
        {
            Translate(node.Source);

            var joined = node.Joined as CollectionExpression;

            if (joined == null)
            {
                throw new NotSupportedException("Only a collection is allowed to be joined.");
            }

            var localFieldValue = AggregateLanguageTranslator.Translate(node.SourceKeySelector);

            if (localFieldValue.BsonType != BsonType.String)
            {
                throw new NotSupportedException("Could not translate the local field.");
            }

            var localField = localFieldValue.ToString().Substring(1); // remove '$'

            var foreignFieldValue = AggregateLanguageTranslator.Translate(node.SourceKeySelector);

            if (foreignFieldValue.BsonType != BsonType.String)
            {
                throw new NotSupportedException("Could not translate the foreign field.");
            }

            var foreignField = foreignFieldValue.ToString().Substring(1); // remove '$'

            _stages.Add(new BsonDocument("$lookup", new BsonDocument
            {
                { "from", ((CollectionExpression)node.Joined).CollectionNamespace.CollectionName },
                { "localField", localField },
                { "foreignField", foreignField },
                { "as", node.JoinedItemName }
            }));
        }
        public Expression Bind(PipelineExpression pipeline, PipelineBindingContext bindingContext, MethodCallExpression node, IEnumerable <Expression> arguments)
        {
            var args   = arguments.ToList();
            var joined = bindingContext.Bind(args[0]) as CollectionExpression;

            if (joined == null)
            {
                throw new NotSupportedException("The joined collection cannot have any qualifiers.");
            }

            var sourceKeySelectorLambda = ExpressionHelper.GetLambda(args[1]);

            bindingContext.AddExpressionMapping(sourceKeySelectorLambda.Parameters[0], pipeline.Projector);
            var sourceKeySelector = bindingContext.Bind(sourceKeySelectorLambda.Body) as IFieldExpression;

            if (sourceKeySelector == null)
            {
                var message = string.Format("Unable to determine the serialization information for the outer key selector in the tree: {0}", node.ToString());
                throw new NotSupportedException(message);
            }

            var joinedArraySerializer = joined.Serializer as IBsonArraySerializer;
            BsonSerializationInfo joinedItemSerializationInfo;

            if (joinedArraySerializer == null || !joinedArraySerializer.TryGetItemSerializationInfo(out joinedItemSerializationInfo))
            {
                var message = string.Format("Unable to determine the serialization information for the inner collection: {0}", node.ToString());
                throw new NotSupportedException(message);
            }

            var joinedKeySelectorLambda = ExpressionHelper.GetLambda(args[2]);
            var joinedDocument          = new DocumentExpression(joinedItemSerializationInfo.Serializer);

            bindingContext.AddExpressionMapping(joinedKeySelectorLambda.Parameters[0], joinedDocument);
            var joinedKeySelector = bindingContext.Bind(joinedKeySelectorLambda.Body) as IFieldExpression;

            if (joinedKeySelector == null)
            {
                var message = string.Format("Unable to determine the serialization information for the inner key selector in the tree: {0}", node.ToString());
                throw new NotSupportedException(message);
            }

            var resultSelectorLambda = ExpressionHelper.GetLambda(args[3]);

            var sourceSerializer = pipeline.Projector.Serializer;
            var joinedSerializer = node.Method.Name == "GroupJoin" ?
                                   joined.Serializer :
                                   joinedItemSerializationInfo.Serializer;
            var sourceDocument = new DocumentExpression(sourceSerializer);
            var joinedField    = new FieldExpression(
                resultSelectorLambda.Parameters[1].Name,
                joinedSerializer);

            bindingContext.AddExpressionMapping(
                resultSelectorLambda.Parameters[0],
                sourceDocument);
            bindingContext.AddExpressionMapping(
                resultSelectorLambda.Parameters[1],
                joinedField);
            var resultSelector = bindingContext.Bind(resultSelectorLambda.Body);

            Expression source;

            if (node.Method.Name == "GroupJoin")
            {
                source = new GroupJoinExpression(
                    node.Type,
                    pipeline.Source,
                    joined,
                    (Expression)sourceKeySelector,
                    (Expression)joinedKeySelector,
                    resultSelectorLambda.Parameters[1].Name);
            }
            else
            {
                source = new JoinExpression(
                    node.Type,
                    pipeline.Source,
                    joined,
                    (Expression)sourceKeySelector,
                    (Expression)joinedKeySelector,
                    resultSelectorLambda.Parameters[1].Name);
            }

            SerializationExpression projector;
            var newResultSelector = resultSelector as NewExpression;

            if (newResultSelector != null &&
                newResultSelector.Arguments[0] == sourceDocument &&
                newResultSelector.Arguments[1] == joinedField)
            {
                Func <object, object, object> creator = (s, j) => Activator.CreateInstance(resultSelector.Type, s, j);
                var serializer = (IBsonSerializer)Activator.CreateInstance(
                    typeof(JoinSerializer <>).MakeGenericType(resultSelector.Type),
                    sourceSerializer,
                    newResultSelector.Members[0].Name,
                    joinedSerializer,
                    newResultSelector.Members[1].Name,
                    resultSelectorLambda.Parameters[1].Name,
                    creator);

                projector = new DocumentExpression(serializer);
            }
            else
            {
                projector = bindingContext.BindProjector(ref resultSelector);
                source    = new SelectExpression(
                    source,
                    "__i", // since this is a top-level pipeline, this doesn't matter
                    resultSelector);
            }

            return(new PipelineExpression(
                       source,
                       projector));
        }