I've got a function that determines a user's permissions to interact with certain objects based on a bunch of rules that require querying multiple parts of the database in different ways. One of these queries is particularly expensive (it's querying a massive view made up of unions and CTEs) and this query returns maybe 50 results or so for the current user. This particular query currently runs toward the start of this function and populates a list named userRoles
. This is then followed by a series of 10 if...else if...else if... blocks, meaning that once one of these blocks matches the condition, it jumps to the end and returns the result without processing the rest of them. The userRoles
list is used in 4 of these blocks, but there are 3 blocks that come before the first block that uses it. So if the conditions in any of those first 3 blocks match, then there's really no reason to query the expensive view in the first place.
In pseudo code, here's what it currently looks like:
var userRoles = await DAL.GetUserRolesAsync(userId); // Expensive query using EF
if (unrelatedConditions == 1) { doSomething1(); }
else if (unrelatedConditions == 2) { doSomething2(); }
else if (unrelatedConditions == 3) { doSomething3(); }
else if (userRoles.Any(x => x.role == "red")) { logicForRedRole(); }
else if (userRoles.Any(x => x.role == "blue")) { logicForBlueRole(); }
// more else if blocks...
What I want to have happen is that the query that populates the list would only get executed if the first three blocks have failed to match and it is now needing the list for the 4th block to test. I need to have the query defined at the top because it's actually more performant to just get all the results and then filter them down to the cases that are needed in each of the blocks than it is to run the query for each block. I've tried constructing a Task<T>
object, passing in the asynchronous query (var userRolesTask = new Task<List<UserRole>>(async () => await DAL.GetUserRolesAsync(userId));
), but that doesn't work because it actually creates a Task<Task<List<UserRole>>>
object and has to be unwrapped to get the actual result, and I'm not sure that await userRolesTask
would actually Start the query execution. I could change it to var userRolesTask = new Task<List<UserRole>>(() => DAL.GetUserRolesAsync(userId).Result);
, but then it's just blocking the calling thread and I'm losing the benefits of async.
So what I need is something that functions as a Task, but that really just wraps an async method and doesn't execute until awaited. I can definitely make something clunky that would take a Func<Task<T>>
and have a T Result
property and a async Task<T> GetResult()
method that could be awaited on that would only execute the query on the first call. I was just hoping to find something that's either built in or is used seamlessly without having to use some extra construct like await userRolesTask.GetResult()
.
I've seen some results in my searching that suggest using Rx Observables for doing something like this, but that is not an option on this project.