For background, read the introduction.
This is essentially the opposite of option #1. With this approach you are optimized for processing one item at a time. Or even process all items in parallel! So this is a very good option if you do not need all your data at once and you also have a large number of rows to work on. This option is also very flexible if you have a few scenarios where you actually need all the data since you can always do a Task.WhenAll to wait for all data to be available.
The only big drawback with this approach is filtering using LINQ. Since it is an enumeration of tasks there is no way for you to asynchronously wait for an item in order to for example filter on its value using LINQ. Hence this is not a good option if you need to do a lot of filtering on your items. If you have just a few simple filters it might be worth implementing your own filters like WhereAsync and SelectAsync. All that together miht be worth it given your data and scenarios.
LinqToAwait provides a standard library for SelectAsync/WhereAsync and others:
@Max Battcher; actually LinqToAwait does not provide a "standard library" since it is just a wrapper hiding the reactive extensions I'll discuss in part 3 of this series. Also it provides very few wrappers.