Buginator
Buginator

Reputation: 846

Creating a C# LINQ optional Join extension

I'm trying to make an extension for Linq that I can use with optional joins. I think I'm close, but I'm missing something. Can you spot it?

public static IQueryable<T> OptionalJoin<T>(this IQueryable<T> source, bool condition, 
        Expression<IEnumerable<T>> TInner, 
        Expression<Func<T, object>> outerKeySelector, 
        Expression<Func<T, object>> innerKeySelector,
        Expression<Func<IQueryable<T>, IEnumerable<T>, object>> resultSelector)
    {
        return condition ? source.Join(TInner, outerKeySelector, innerKeySelector, resultSelector) : source;
    }

Upvotes: 1

Views: 737

Answers (2)

Enigmativity
Enigmativity

Reputation: 117027

I think you'll need something like this:

public static IQueryable<TResult> OptionalJoin<TOuter, TInner, TKey, TResult>(
    this IQueryable<TOuter> outer,
    bool condition,
    IEnumerable<TInner> inner,
    Expression<Func<TOuter, TKey>> outerKeySelector,
    Expression<Func<TInner, TKey>> innerKeySelector,
    Expression<Func<TOuter, TInner, TResult>> joinResultSelector,
    Expression<Func<TOuter, TResult>> outerResultSelector)
{
    return condition
        ? outer.Join(inner,
            outerKeySelector,
            innerKeySelector,
            joinResultSelector)
        : outer.Select(outerResultSelector);
}

You build operators like this you need to start with the signatures of the built-in operators and keep the parameter signatures where possible. You are effectively merging Join with Select, so start with them:

IQueryable<TResult> Join<TOuter, TInner, TKey, TResult>(
    this IQueryable<TOuter> outer,
    IEnumerable<TInner> inner,
    Expression<Func<TOuter, TKey>> outerKeySelector,
    Expression<Func<TInner, TKey>> innerKeySelector,
    Expression<Func<TOuter, TInner, TResult>> resultSelector)

public static IQueryable<TResult> Select<TSource, TResult>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TResult>> selector)

Your code had some of the signatures wrong.

Also, you could reduce down my answer to work with just T quite easily:

public static IQueryable<T> OptionalJoin<T, K>(
    this IQueryable<T> outer,
    bool condition,
    IEnumerable<T> inner,
    Expression<Func<T, K>> outerKeySelector,
    Expression<Func<T, K>> innerKeySelector,
    Expression<Func<T, T, T>> joinResultSelector)
{
    return condition
        ? outer.Join(inner,
            outerKeySelector,
            innerKeySelector,
            joinResultSelector)
        : outer;
}

I hope this helps.

Upvotes: 2

svick
svick

Reputation: 244767

Optional join doesn't really make much sense. Your return type is IQueryable<T>, which is correct if you want to return the original collection. But if you want to join something into it, the return type would have to change. And you can't have a method that returns different compile-time types based on a run-time condition.

Because of that, I think what you're trying to do is impossible.

The only way I can imagine something like this could work would be if if you had two result selectors: one when condition is true and the other when it's false. And both of them would return the same type.

Also, there are several mistakes in your code, which I tried to fix.

public static IQueryable<TResult> OptionalJoin<TSource, TInner, TKey, TResult>(
        this IQueryable<TSource> source,
        bool condition, 
        IQueryable<TInner> innerCollection,
        Expression<Func<T, TKey>> outerKeySelector, 
        Expression<Func<T, TKey>> innerKeySelector,
        Expression<Func<TSource, TInner, TResult>> trueResultSelector,
        Expression<Func<TSource, TResult>> falseResultSelector)
{
    return condition
        ? source.Join(innerCollection, outerKeySelector, innerKeySelector, trueResultSelector)
        : source.Select(falseResultSelector);
}

Upvotes: 3

Related Questions