Return Task> from EF Core query without await?

c# ef-core-2.1 entity-framework-core

Question

I like to be as specific as is possible with my types. When offering up a method encapsulating a query it's common to see List<T> returned (or more specifically Task<List<T>>.) No wonder with EF Core's .ToListAsync() method. But that's not very accurate is it? List<T> is a projection of a larger collection in SQL Server (or whatever persistence you're using). It's not modifiable.

I want to instead return IReadOnlyCollection<T> from my methods. This seems a lot more accurate. I'm returning a projection from my source data and you can't modify the source data by modifying the projection. Nor can my result be modified erroneously without casting back to List<T> at which point you've gone far enough out of your way I'm assuming you know what you're doing.

So I attempted to just return the interface like I would synchronously.

public Task<IReadOnlyCollection<TResult>> GetResultAsync() =>
  _dbContext.Where(x => x.Property == true).ToListAsync();

I'm avoiding using the async and await operators here. I want to pass the Task unawaited to avoid the await overhead. But when wrapped in Task<T> C#'s type system doesn't recognize that the wrapped List<T> is castable to IReadOnlyCollection<T>. So Task<List<T>> cannot be cast to Task<IReadOnlyCollection<T>>. You can try an explicit cast, of course, but the compiler won't like that either and for the same reasons.

Well that's annoying. But we can peel off the Task<T> with async wait easy enough.

public async Task<IReadOnlyCollection<TResult>> GetResultAsync() =>
  await _dbContext.Where(x => x.Property = true).ToListAsync();

In order to avoid the added code I considered an extension method.

public static async Task<IReadOnlyCollection<T>> ToReadOnlyCollectionAsync<T>(this IQueryable<T> query) =>
  await query.ToListAsync();

This gets the await out of my methods but of course still adds another await. Is there a way to return Task<IReadOnlyCollection<T>> for "free" from EF Core 2.1?

1
2
7/9/2018 1:01:41 PM

Accepted Answer

No idea what ToListAsync() does but going by the name, and if I'm guessing correctly, it iterates the results and converts them to a list on a background thread and returns the task that must be awaited before the list is produced.

If that's the case, your question is

how to convert Task<List<T>> to Task<IReadonlyCollection<T>>.

If so, you could in theory do something like this:

    public Task<IReadOnlyCollection<string>> ToReadOnlyCollection()
    {
        return ToListAsync().ContinueWith(x => new ReadOnlyCollection<string>(x.Result) as IReadOnlyCollection<string>);
    }

    public Task<IReadOnlyList<string>> ToReadOnlyList()
    {
        // if you don't mind an IReadOnlyList, you can use this
        // one which doesn't involve creating a new collection
        return ToListAsync().ContinueWith(x => x.Result as IReadOnlyList<string>);
    }

    private Task<List<string>> ToListAsync()
    {
        return Task.Run(() =>
        {
            Task.Delay(1000);
            return new List<string>
            {
                "1",
                "2",
                "3"
            };
        });
    }

I don't believe Task<T> allows contra-variance so a Task<TChild> (where Child : Parent) cannot be automatically converted to Task<TParent> so you'll need to do it yourself.

3
7/9/2018 1:58:46 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow