/// <summary> /// Correlates the elements of two queries based on matching keys. /// </summary> /// <typeparam name="TOuter">The type of ParseObjects of the first query.</typeparam> /// <typeparam name="TInner">The type of ParseObjects of the second query.</typeparam> /// <typeparam name="TKey">The type of the keys returned by the key selector /// functions.</typeparam> /// <typeparam name="TResult">The type of the result. This must match either /// TOuter or TInner</typeparam> /// <param name="outer">The first query to join.</param> /// <param name="inner">The query to join to the first query.</param> /// <param name="outerKeySelector">A function to extract a join key from the results of /// the first query.</param> /// <param name="innerKeySelector">A function to extract a join key from the results of /// the second query.</param> /// <param name="resultSelector">A function to select either the outer or inner query /// result to determine which query is the base query.</param> /// <returns>A new ParseQuery with a WhereMatchesQuery or WhereMatchesKeyInQuery /// clause based upon the query indicated in the <paramref name="resultSelector"/>.</returns> public static ParseQuery <TResult> Join <TOuter, TInner, TKey, TResult>( this ParseQuery <TOuter> outer, ParseQuery <TInner> inner, Expression <Func <TOuter, TKey> > outerKeySelector, Expression <Func <TInner, TKey> > innerKeySelector, Expression <Func <TOuter, TInner, TResult> > resultSelector) where TOuter : ParseObject where TInner : ParseObject where TResult : ParseObject { // resultSelector must select either the inner object or the outer object. If it's the inner // object, reverse the query. if (resultSelector.Body == resultSelector.Parameters[1]) { // The inner object was selected. return(inner.Join <TInner, TOuter, TKey, TInner>( outer, innerKeySelector, outerKeySelector, (i, o) => i) as ParseQuery <TResult>); } if (resultSelector.Body != resultSelector.Parameters[0]) { throw new InvalidOperationException("Joins must select either the outer or inner object."); } // Normalize both selectors Expression outerNormalized = new ObjectNormalizer().Visit(outerKeySelector.Body); Expression innerNormalized = new ObjectNormalizer().Visit(innerKeySelector.Body); MethodCallExpression outerAsGet = outerNormalized as MethodCallExpression; MethodCallExpression innerAsGet = innerNormalized as MethodCallExpression; if (IsParseObjectGet(outerAsGet) && outerAsGet.Object == outerKeySelector.Parameters[0]) { var outerKey = GetValue(outerAsGet.Arguments[0]) as string; if (IsParseObjectGet(innerAsGet) && innerAsGet.Object == innerKeySelector.Parameters[0]) { // Both are key accesses, so treat this as a WhereMatchesKeyInQuery var innerKey = GetValue(innerAsGet.Arguments[0]) as string; return(outer.WhereMatchesKeyInQuery(outerKey, innerKey, inner) as ParseQuery <TResult>); } if (innerKeySelector.Body == innerKeySelector.Parameters[0]) { // The inner selector is on the result of the query itself, so treat this as a // WhereMatchesQuery return(outer.WhereMatchesQuery(outerKey, inner) as ParseQuery <TResult>); } throw new InvalidOperationException( "The key for the joined object must be a ParseObject or a field access " + "on the ParseObject."); } // TODO (hallucinogen): If we ever support "and" queries fully and/or support a "where this object // matches some key in some other query" (as opposed to requiring a key on this query), we // can add support for even more types of joins. throw new InvalidOperationException( "The key for the selected object must be a field access on the ParseObject."); }