Note that whatever you do, you can never complete the dictionary until after the source has completed. A simple approach would therefore be to asynchronously obtain the list like this:
await GetSource(...).ToList();
and then proceed with the resulting IList<T>
as you were with the GroupBy
and ToDictionary
- noting that you are now using the IEnumerable<T>
implementations of these operators against a completed list.
This approach assumes that amount and timing of the events in the GetSource()
observable, and the distribution between the groups is such that the expense of computing the groups and dictionaries on the completed stream is not prohibitive. Since ultimately you are returning a completed dictionary which will be held in memory anyway we are likely only considering the cost of sorting into groups and creating dictionary entries. This will be extremely fast on in memory data compared to the serialized delivery of a lengthy event stream, so I tend to think that there are few cases where this approach isn't perfectly fine.
If it is prohibitive, it may be worth building up each group and list concurrently and the events arrive, in which case you could do this instead:
await GetSource(...).GroupBy(x => x.SiteId)
.SelectMany(x => x.ToList())
.ToDictionary(x => x[0].SiteId);
Note that the first element of the list in the ToDictionary
keySelector will always exist (otherwise no group would have been materialized), so this is safe. It does look a little odd, but it's the easiest way I can think of to get the key out.
Or as a general purpose function:
async Task<IDictionary<TKey, IList<T>>> ToDictionaryOfLists<T, TKey>(
IObservable<T> source,
Func<T, TKey> keySelector)
{
return await source.GroupBy(keySelector)
.SelectMany(x => x.ToList())
.ToDictionary(x => keySelector(x[0]));
}
Which assuming a class:
public class Site
{
public int SiteId { get; set; }
}
You could use like:
var result = ToDictionaryOfLists(GetSource(...), x=> x.SiteId);