Running Multiple Linq Queries Async - c#

I am trying to figure out how to use async/await correctly in the Data Layer of my application to do multiple database calls in parallel.
I am using Entity Framework and Linq to do my data calls.
The Data Layer can be called from any application type (MVC Web App, Web API Project, Win From, Windows Service etc.) - therefore the result of the method should be a single object.
public static async void GetLedger(long id, DateTime StartDate, DateTime EndDate)
{
var task_1 = DoComplexDBCallAsync1();
var task_2 = DoComplexDBCallAsync2();
var task_3 = DoComplexDBCallAsync3();
var task_4 = DoComplexDBCallAsync4();
var task_5 = DoComplexDBCallAsync5();
await Task.WhenAll(task_1, task_2, task_3, task_4, task_5);
var result = ProcessAndMergeResultsFromAllDBCalls(task_1.Result, task_2.Result, task_3.Result, task_4.Result, task_5.Result);
return result;
}
private static async Task<List<SomeComplexType>> DoComplexDBCallAsync1(long id)
{
using (var metadata = DataAccess.getDesktopMetadata())
{
var accounts = await (from row in metadata.db_GLAccount
where row.busID == id
select row).ToListAsync();
return accounts;
}
}
How would I change the above to return a single object instead of a Task<>?
Would I need to wrap it in another method which calls the GetLedger async method via a Task run or RunSynchronously() and returns the results?
If so how would the code look and is there anything I need to be aware of?
What is best practice in this scenario?

You would use await as follows
public static async Task<SomeObject> GetLedger(long id, DateTime StartDate, DateTime EndDate)
{
var task_1 = DoComplexDBCallAsync1();
var task_2 = DoComplexDBCallAsync2();
var task_3 = DoComplexDBCallAsync3();
var task_4 = DoComplexDBCallAsync4();
var task_5 = DoComplexDBCallAsync5();
var result = ProcessAndMergeResultsFromAllDBCalls(await task_1, await task_2, await task_3, await task_4, await task_5);
return result;
}
The 'await' keyword will automatically get the result of the Task and apply it to the variables when the DB calls return. MSDN
You would then call
var result = await GetLedger(id, StartDate, EndDate);
which would proceed after everything is complete.

Related

LINQ expression to replace foreach with multiple awaits

I am trying to determine if there is a linq expression equivalent of the following foreach statement below?
Two of the several solutions I've tried are commented with their results below the loop. I thought the first example or a variation would work but the type is always IEnumerable<Task> unless I call it synchronously which just feels apprehensive.
public async Task<IEnumerable<CompanySettings>> GetClientSettingsAsync()
{
foreach(var company in await GetParticipatingCompaniesAsync())
{
settings.Add(new CompanySettings(company, await GetSyncDataAsync(company)));
}
// a's Type is IEnumerable<Task<CompanySetting>> and not IEnumerable<CompanySetting>
// var a = (await GetParticipatingCompaniesAsync())
// .Select(async x => new CompanySettings(x, await GetSyncDataAsync(x)));
// return a;
// b's Type is correct it's also synchronous. Does that matter?
// var b = (GetParticipatingCompaniesAsync()).Result
// .Select(x => new CompanySettings(x, GetSyncDataAsync(x).Result));
//return b;
return settings;
}
Signatures of the methods:
private async Task<IEnumerable<UpdoxCompany>> GetParticipatingCompaniesAsync();
private async Task<UpdoxSyncData> GetUpdoxSyncDataAsync(UpdoxCompany company);
You are close with your first attempt, you just need to await the tasks that are returned
async Task<IEnumerable<CompanySettings>> GetClientSettingsAsync() {
var companies = await GetParticipatingCompaniesAsync();
var companySettings = companies.Select(async company =>
new CompanySettings(company, await GetSyncDataAsync(company))
);
return await Task.WhenAll(companySettings);
}
Yes, you can use Select to achieve the same result
Here is one way to do it:
public async Task<IEnumerable<CompanySettings>> GetClientSettingsAsync()
{
var companies = await GetParticipatingCompaniesAsync();
var companySettingsTasks = companies.Select(async company =>
new CompanySettings(company, await GetSyncDataAsync(company)));
var settings = await Task.WhenAll(companySettingsTasks);
return settings;
}

Awaiting data from Xamarin/MAUI IOS Healthkit query

I'm not familiar with threads, awaiting and delegates etc. I have the following problem. This method:
public HKSample[] Get(DateTime from, DateTime? to)
{
HKSample[] result = null;
var restingHeartRateType = HKQuantityType.Create(HKQuantityTypeIdentifier.RestingHeartRate);
var predicate = HKQuery.GetPredicateForSamples(from.ToNSDate(), to.HasValue ? to.Value.ToNSDate() : null, HKQueryOptions.None);
var q = new HKSampleQuery(
restingHeartRateType,
predicate,
500,
new NSSortDescriptor[] { },
new HKSampleQueryResultsHandler(
(HKSampleQuery query2, HKSample[] results, NSError error2) =>
{
result = results;
}));
healthKitStore.ExecuteQuery(q);
return result;
}
returns before the resultshandler sets the method return variable. How can I wait for the result variable to be set before finishing the method?
I'm doing a lot of healthkit investigation, I'm shocked at how little is sampled / documented for xamarin/maui c#.
In general, if a task or function takes a long time, we generally recommend executing code asynchronously. Otherwise, the program is prone to crash or blocked.
If you want to wait for a function to finish before performing subsequent operations, you can use Async and await to do so.
Please refer to the following code:
async void callMethod() {
Task<HKSample[]> task = Get(from, to);
HKSample[] hKs = await task;
}
public async Task<HKSample[]> Get(DateTime from, DateTime? to)
{
HKSample[] result = null;
var restingHeartRateType = HKQuantityType.Create(HKQuantityTypeIdentifier.RestingHeartRate);
var predicate = HKQuery.GetPredicateForSamples(from.ToNSDate(), to.HasValue ? to.Value.ToNSDate() : null, HKQueryOptions.None);
var q = new HKSampleQuery(
restingHeartRateType,
predicate,
500,
new NSSortDescriptor[] { },
new HKSampleQueryResultsHandler(
(HKSampleQuery query2, HKSample[] results, NSError error2) =>
{
result = results;
}));
healthKitStore.ExecuteQuery(q);
return result;
}
For more about async and await, you can check: Asynchronous programming with Async and Await (Visual Basic) and Async And Await In C#.

Why doesn't my function await the end of ForEachAsync?

I'm trying to fill my model with data that I get from an asynchronous operation to my database. The problem is that the function returns the View (without the completed model), despite my await call.
I have tried to put a timer (I know that is not the solution), to be sure that the problem come from the asynchronous, I have also tried to put on comment some part of code inside my ForEachAsync, but it doesn't seem to help.
I get a list of project, that I fill with some additional information, finally, I assign my object to my model then return the View
public async Task<IActionResult> newProjetList(int GestionaireId, int VilleId)
{
ProjetListModel model = new ProjetListModel();
ProjetService projetService = new ProjetService(connectionString);
UserServices userServices = new UserServices(connectionString);
AvancementService avancementService = new AvancementService(connectionString);
VilleService villeService = new VilleService(connectionString);
List<Projet> projets = await projetService.NewProjetLine(GestionaireId, VilleId);
await projets.ToAsyncEnumerable().ForEachAsync(async p =>
{
int villeId = await villeService.getVilleIdByProjetId(p.ProjetId);
Ville ville = await villeService.GetById(villeId);
p.Ville = ville.VilleLabel;
p.GestionnaireProjet = await userServices.GetNameById(p.GestionnaireProjetId ?? 0);
await p.SousProjet.ToAsyncEnumerable().ForEachAsync(async sp =>
{
sp.Avancement = await avancementService.GetLabelById(sp.AvancementId);
});
});
model.projets = projets;
//System.Threading.Thread.Sleep(5000);
return View("ProjetList", model);
}
I expected an output with the missing information (here are the 'ville', 'gestionnairesProjet' and 'Avancement'
ForEachAsync only takes an Action<...>, not a Func<..., Task>, so the async lambda your code is passing to ForEachAsync is becoming an async void method. One of the primary reasons async void should be avoided is that it's not easy to determine when the method completes - and in fact, in this case, there is nothing ensuring that it will complete before sending the response.
I recommend doing what Marc suggested and just using foreach:
List<Projet> projets = await projetService.NewProjetLine(GestionaireId, VilleId);
foreach (var p in projects)
{
int villeId = await villeService.getVilleIdByProjetId(p.ProjetId);
Ville ville = await villeService.GetById(villeId);
p.Ville = ville.VilleLabel;
p.GestionnaireProjet = await userServices.GetNameById(p.GestionnaireProjetId ?? 0);
foreach (var sp in p.SousProject)
{
sp.Avancement = await avancementService.GetLabelById(sp.AvancementId);
}
}
model.projets = projets;
Or, if you want to use asynchronous concurrency, you can make use of Task.WhenAll:
List<Projet> projets = await projetService.NewProjetLine(GestionaireId, VilleId);
await Task.WhenAll(projects.Select(async p =>
{
int villeId = await villeService.getVilleIdByProjetId(p.ProjetId);
Ville ville = await villeService.GetById(villeId);
p.Ville = ville.VilleLabel;
p.GestionnaireProjet = await userServices.GetNameById(p.GestionnaireProjetId ?? 0);
await Task.WhenAll(p.SousProject.Select(async sp =>
{
sp.Avancement = await avancementService.GetLabelById(sp.AvancementId);
});
});
model.projets = projets;
Use ForEachAwaitAsync instead of ForEachAsync
Explanation: ForEachAsync can't wait since it is simply a multi-threaded execution of your loop (accepts Action). In fact you might have received a compiler warning by using async for your lambda return, because you intend to assign a Task to a void (unused)
ForEachAwaitAsync will wait because it accepts a Func<Task> and internally also awaits / asyncs accordingly.
For curious minds, you can see the source code here: https://github.com/dotnet/reactive/blob/main/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ForEach.cs mentioned as ForEachAwaitAsyncCore and ForEachAsync

Async Await generating a null exception in testing

I've done a lot of heavy modification to some async code, and I've been tinkering with it long enough that I am in need of a second set of eyes.
In a unit test, I'm calling my async code, and then validating the values it should return. The object that ultimately performs the async operation is mocked.
The Unit Test:
public void GetCharactersAsync_ById_Test()
{
var charDictionary = TestHelper.GetCharactersIdDictionary(1);
var mockReq = new Mock<IRequester>();
mockReq.Setup(x => x.CreateGetRequestAsync<Dictionary<long, Character>>
(It.IsAny<string>())).ReturnsAsync(charDictionary);
var char1 = TestHelper.GetCharacter();
var api = GameObject.GetInstance(mockReq.Object);
var character = api.GetCharacterAsync(TestHelper.GetRegion(), (int)char.Id);
Assert.AreEqual(character.Result.Name, char1.Name);
}
GetCharacterAsync:
public async Task<Character> GetCharacterAsync(Region region, int charId)
{
var res = await requester.CreateGetRequestAsync<Task<Dictionary<long, Character>>>
(bld.BuildUrlForEndpoint("GetCharacter",
region, charId.ToString()));
var obj = res.Result.Values.FirstOrDefault();
return obj;
}
requester.CreateGetRequestAsync (though this should be mocked and not matter):
public async Task<T> CreateGetRequestAsync<T>(string urlString)
{
return await this.GetResultAsyncDeserialized<T>(new HttpRequestMessage(HttpMethod.Get, urlString));
}
and finally, GetResultAsyncDeserialized (though this is mocked and shouldn't matter):
protected async Task<T> GetResultAsyncDeserialized<T>(HttpRequestMessage request)
{
var result = string.Empty;
using (var response = await httpClient.GetAsync(request.RequestUri))
{
if (!response.IsSuccessStatusCode)
{
HandleRequestFailure(response.StatusCode);
}
using (var content = response.Content)
{
result = await content.ReadAsStringAsync();
}
}
return JsonConvert.DeserializeObject<T>(result);
}
I have next to zero experience with async, and this code is very similar to the old working code in structure (I moved a few things around, like the JsonConvert to the bottom layer when it used to be up top). The major changes, in fact, were all at the level that is being mocked, and shouldn't impact the unit test. The unit test is almost identical to the old one (removed some serialization/deserialization).
Exception is a Null Reference Exception on GetCharacterAsync line:
var obj = res.Result.Values.FirstOrDefault();
Your await requester.CreateGetRequestAsync<Task<Dictionary<long, Character>>> is wrong, you should not be passing Task in to that function, the method signature should be await requester.CreateGetRequestAsync<Dictionary<long, Character>>
public async Task<Character> GetCharacterAsync(Region region, int charId)
{
var res = await requester.CreateGetRequestAsync<Dictionary<long, Character>>
(bld.BuildUrlForEndpoint("GetCharacter",
region, charId.ToString()));
var obj = res.Values.FirstOrDefault();
return obj;
}
The reason you are getting null is in GetCharacterAsync you call CreateGetRequestAsync<Task<Dictionary<long, Character>>> but your moq was set up with mockReq.Setup(x => x.CreateGetRequestAsync<Dictionary<long, Character>>
P.S. As a side note, you really should not likely be doing character.Result.Name in your test, make the test return async Task instead of void and await the result of api.GetCharacterAsync. Pretty much every unit test library (including the one built in to visual studio) supports async/await now.
public async Task GetCharactersAsync_ById_Test()
{
var charDictionary = TestHelper.GetCharactersIdDictionary(1);
var mockReq = new Mock<IRequester>();
mockReq.Setup(x => x.CreateGetRequestAsync<Dictionary<long, Character>>
(It.IsAny<string>())).ReturnsAsync(charDictionary);
var char1 = TestHelper.GetCharacter();
var api = GameObject.GetInstance(mockReq.Object);
var character = await api.GetCharacterAsync(TestHelper.GetRegion(), (int)char.Id);
Assert.AreEqual(character.Name, char1.Name);
}

Refresh DataServiceCollection

I wonder if there is a code there that refresh my DataServiceCollection that is loaded in a WPF client using BeginExecute async await Task as show below:
public static async Task<IEnumerable<TResult>> ExecuteAsync<TResult>(this DataServiceQuery<TResult> query)
{
//Thread.Sleep(10000);
var queryTask = Task.Factory.FromAsync<IEnumerable<TResult>>(query.BeginExecute(null, null), (asResult) =>
{
var result = query.EndExecute(asResult).ToList();
return result;
});
return await queryTask;
}
I call the extension method as the following:
public async void LoadData()
{
_ctx = new TheContext(the URI);
_dataSource = new DataServiceCollection<TheEntity>(_ctx);
var query = _ctx.TheEntity;
var data = await Task.Run(async () => await query.ExecuteAsync());
_dataSource.Clear(true);
_dataSource.Load(data);
}
LoadData is Called in a ViewModel
Change a field value by hand using SQL Management Studio
Call LoadData again <<<< Does not refresh!!!
Meanwhile, If I use Load method the data is refreshed without any problems as:
var query = _ctx.TheEntity;
_dataSource.Load(query);
Another issue is that I do not know how to Cancel client changes. Last question is whether the MergeOption has any effect to BeginExecute..EndExecute or it only works with Load method?
I suspect that the data context does not like being accessed from multiple threads.
I recommend that you first remove all processing from your extension method:
public static Task<IEnumerable<TResult>> ExecuteAsync<TResult>(this DataServiceQuery<TResult> query)
{
return Task.Factory.FromAsync(query.BeginExecute, query.EndExecute, null);
}
Which you should use without jumping onto a background thread:
public async Task LoadDataAsync()
{
_ctx = new TheContext(the URI);
_dataSource = new DataServiceCollection<TheEntity>(_ctx);
var query = _ctx.TheEntity;
var data = await query.ExecuteAsync();
_dataSource.Clear(true);
_dataSource.Load(data);
}

Categories

Resources