Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query: Implement ToLookupAsync #15916

Closed
smitpatel opened this issue Jun 3, 2019 · 7 comments
Closed

Query: Implement ToLookupAsync #15916

smitpatel opened this issue Jun 3, 2019 · 7 comments
Labels
closed-out-of-scope This is not something that will be fixed/implemented and the issue is closed. type-enhancement

Comments

@smitpatel
Copy link
Contributor

Since Lookup class is semi-public & IX-Async's implementation was internal.

@ajcvickers
Copy link
Contributor

We need a new Lookup implementation to do this, since existing one has private constructors.

@ajcvickers ajcvickers added this to the Backlog milestone Jun 6, 2019
smitpatel added a commit that referenced this issue Jun 6, 2019
Remove test for #15950
Remove ToLookup API for #15916
Resolves #15924
smitpatel added a commit that referenced this issue Jun 6, 2019
Remove test for #15950
Remove ToLookup API for #15916
Resolves #15924
@ajcvickers ajcvickers added closed-out-of-scope This is not something that will be fixed/implemented and the issue is closed. and removed propose-close labels Nov 16, 2019
@ajcvickers ajcvickers removed this from the Backlog milestone Nov 16, 2019
@smitpatel
Copy link
Contributor Author

We decided not to fix this since the implementation was converting queryable to enumerable and calling API on top of it (implementation was coming from IX-Async). Users should themselves convert the queryable to enumerable and call ToLookup API from appropriate library.

@Shane32
Copy link

Shane32 commented Apr 3, 2020

Can ToLookup / ToLookupAsync be translated appropriately via .Select(), processed by .ToList() / .ToListAsync() and then converted to a lookup with .ToLookup() ? See my similar comments regarding ToDictionary in #16730 . This would be a convenience but not necessary of course. It would also leave the implementation open to future performance enhancements.

@huysentruitw
Copy link
Contributor

huysentruitw commented Dec 23, 2020

For anyone in need of a ToLookupAsync, you could use these extension methods:

internal static class QueryableExtensions
{
    public static Task<ILookup<TKey, TSource>> ToLookupAsync<TSource, TKey>(
        [NotNull] this IQueryable<TSource> source,
        [NotNull] Func<TSource, TKey> keySelector,
        CancellationToken cancellationToken = default)
        where TKey : notnull
        => ToLookupAsync(source, keySelector, e => e, comparer: null, cancellationToken);

    public static Task<ILookup<TKey, TSource>> ToLookupAsync<TSource, TKey>(
        [NotNull] this IQueryable<TSource> source,
        [NotNull] Func<TSource, TKey> keySelector,
        [NotNull] IEqualityComparer<TKey> comparer,
        CancellationToken cancellationToken = default)
        => ToLookupAsync(source, keySelector, e => e, comparer, cancellationToken);

    public static Task<ILookup<TKey, TElement>> ToLookupAsync<TSource, TKey, TElement>(
        [NotNull] this IQueryable<TSource> source,
        [NotNull] Func<TSource, TKey> keySelector,
        [NotNull] Func<TSource, TElement> elementSelector,
        CancellationToken cancellationToken = default)
        where TKey : notnull
        => ToLookupAsync(source, keySelector, elementSelector, comparer: null, cancellationToken);

    public static async Task<ILookup<TKey, TElement>> ToLookupAsync<TSource, TKey, TElement>(
        [NotNull] this IQueryable<TSource> source,
        [NotNull] Func<TSource, TKey> keySelector,
        [NotNull] Func<TSource, TElement> elementSelector,
        IEqualityComparer<TKey> comparer,
        CancellationToken cancellationToken = default)
        where TKey : notnull
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
        if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector));

        return (await source.ToListAsync(cancellationToken)).ToLookup(keySelector, elementSelector, comparer);
    }
}

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
@Synthwaver
Copy link

I see this thread is abandoned, but I'll leave this here in case anyone stumbles upon it in the future.

From @huysentruitw's example, I changed the implementation of the method that accepts an elementSelector.
So that we avoid getting the whole entity from the database, but only its projection. And so that we can get related data through navigation properties.

public static async Task<ILookup<TKey, TElement>> ToLookupAsync<TSource, TKey, TElement>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TKey>> keySelector,
    Expression<Func<TSource, TElement>> elementSelector,
    IEqualityComparer<TKey> comparer,
    CancellationToken cancellationToken = default)
{
    ArgumentNullException.ThrowIfNull(source);
    ArgumentNullException.ThrowIfNull(keySelector);
    ArgumentNullException.ThrowIfNull(elementSelector);

    var groupedElements = await source
        .GroupBy(keySelector, elementSelector)
        .ToArrayAsync(cancellationToken);

    return groupedElements
        .SelectMany(g => g
            .Select(x => new
            {
                g.Key,
                Element = x,
            }))
        .ToLookup(x => x.Key, x => x.Element, comparer);
}

This is the simplest implementation I could come up with without a custom implementation of ILookup.

@huysentruitw
Copy link
Contributor

@Synthwaver the extension methods I've posted above can be applied after doing your own projection in a .Select().

@Synthwaver
Copy link

Synthwaver commented Aug 29, 2024

@huysentruitw yes, I understand. I'd just like to be able to use it something like this:

var bookTitlesByReaderIds = await query.ToLookupAsync(x => x.ReaderId, x => x.Book.Title);

Similar to how I often use .ToDictionaryAsync().

UPD:
OMG, I just noticed, shockingly for me, that .ToDictionaryAsync() is evaluated on client and expressions not translated to the server. This changes a lot...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-out-of-scope This is not something that will be fixed/implemented and the issue is closed. type-enhancement
Projects
None yet
Development

No branches or pull requests

5 participants