I need to get results from two different DbContext (which target two different schemas of an Oracle database) to populate a page. I to want to execute both database queries (read only, no write operations) simultaneously and return the results when they're both done. Trouble is, I'm a serial dude in a parallel world, and I don't know jack about Async/Await/TPL etc…
I have a controller Action that looks basically like this:
public Task<IActionResult> Foo(MyViewModel vm)
{
if (!ModelState.IsValid) return Task.Run(()=> (IActionResult)View(vm));
var filter = new FilterObject(vm);
var firstTask = _firstContext.FilterItems(filter); // returns Task<IQueryable<Items>>
var secondTask = _secondContext.FilterItems(filter); // returns Task<IQueryable<Items>>
vm.Result.Clear();
vm.Results.AddRange(firstTask.Result);
vm.Results.AddRange(secondTask.Result);
return Task.Run(()=> (IActionResult)View("Index", vm));
}
My DbContext calls the filter to do its job:
public class FirstContext : DbContext
{
public DbQuery<Items> Items{get;set;}
public Task<IQueryable<Items>> FilterItems(FilterObject filter)
{
filter.ApplyTo(Items.AsQueryable());
}
}
... and my FilterObject modifies the IQueryable:
public class FilterObject
{
public Task<IQueryable<Items>> ApplyTo(IQueryable<Items> items)
{
return Task.Run(()=> items
.Where(item => item.Property1.Contains(this.Property1))
.Where(item => item.Other == this.OtherString)
// more Where clauses ad nauseum; you get the idea
}
}
Am I doing this anywhere close to right? As I understand it, the call to the database won't actually execute until the AddRange method, because the Task.Result is an IQueryable - so that seems wrong to me: the AddRange() methods will execute synchronously, not asynchronously, right? All I've done is build two queries, not execute them. I'm guessing that I need to return a List<Item> from the DbContext, which will cause each query to actually execute... but is that the only change I need to make so that these calls happen at the same time, without blocking each other?
As always, any guidance is immensely appreciated.
Drop all your Task work from the IQueryables you have. That's one layer too much. Keep it simple. You have a query. That will return an IQueryable<>. Then you use ToListAsync to get the results asynchronously:
public async Task<IActionResult> Foo(MyViewModel vm)
{
if (!ModelState.IsValid) return View(vm);
var filter = new FilterObject(vm);
var firstTask = _firstContext.YourQueryable.ToListAsync();
var secondTask = _secondContext.YourQueryable.ToListAsync();
var firstResult = await firstTask;
var secondResult = await secondTask;
vm.Result.Clear();
vm.Results.AddRange(firstResult);
vm.Results.AddRange(secondResult);
return View("Index", vm);
}
Task.Run will move the task into another thread, which isn't really necessary. Use async and await, which will run everything on the same thread, but will allow other work to be done on the same thread while waiting for the results. For example, after firing the first query, it can start the second query while waiting for the first to finish.
It seems like FilterItems and ApplyTo are just modifying the query, and not actually executing the result, so I don't see why they need to return a Task at all. You can use ToListAsync() to actually execute the query, after the query has been built.
For example:
public async Task<IActionResult> Foo(MyViewModel vm)
{
if (!ModelState.IsValid) return Task.Run(()=> (IActionResult)View(vm));
var filter = new FilterObject(vm);
//This will execute the queries
var firstTask = _firstContext.FilterItems(filter).ToListAsync();
var secondTask = _secondContext.FilterItems(filter).ToListAsync();
vm.Result.Clear();
vm.Results.AddRange(await firstTask); //add the first set when they're available
vm.Results.AddRange(await secondTask); //add the second set when they're available
return View("Index", vm);
}
You didn't have a return in FilterItems, but I assumed it should be there:
public class FirstContext : DbContext
{
public DbQuery<Items> Items{get;set;}
public IQueryable<Items> FilterItems(FilterObject filter)
{
return filter.ApplyTo(Items.AsQueryable());
}
}
public class FilterObject
{
public IQueryable<Items> ApplyTo(IQueryable<Items> items)
{
return items
.Where(item => item.Property1.Contains(this.Property1))
.Where(item => item.Other == this.OtherString)
// more Where clauses ad nauseum; you get the idea
}
}
Related
I have a GUI that allows users to make changes to various objects (which embody some internal rules), then click Save to write the changes to a database using EF6. When they make a change to some objects, I want to first deactivate all the currently active objects in the database (the order is not important), and then when that is completed, I would like to insert new, active objects (again, in any order).
I thought I would be able to achieve this by making two lists of tasks - one for deactivating objects (deactivate_Tasks) and the other for adding new objects (add_Tasks). I thought that I would then be able to await Task.WhenAll(deactivate_Tasks) to make sure that the first set of tasks completed in parallel, but in series with the second set, executed on the next line (await Task.WhenAll(add_Tasks)). Thus the additions would only occur once the deactivations have completed.
However, when I run the code, I get seemingly erratic results, with all the adding and deactivating tasks occurring in an unpredictable order. I don't know why this is and I would like to avoid it - any suggestions would be really welcome.
I have mocked up a sample of my code below to help - hopefully the method names are self-explanatory enough, but please ask if not. In the real case, there are more types of rule to be added and deactivated. I am using C# 4.8 and writing a windows WPF application using the MVVM framework as much as I can.
public class MyViewModel
{
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
return _saveCommand ?? (_saveCommand = new RelayCommand(execute => Save(), canExecute => IsDirty));
}
set
{ _saveCommand = value; }
}
private RuleDataService _ruleDataService { get; set; }
public async void Save()
{
var add_Tasks = new List<Task<StatusCode>>();
var deactivate_Tasks = new List<Task<StatusCode>>();
if (CustomerRule_IsDirty)
{
add_Tasks.Add(_rulesDataService.CREATE_Rule(newCustomerRule));
if (existingCustomerRule != null)
{
deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingCustomerRule));
}
}
if (WarehouseRule_IsDirty)
{
add_Tasks.Add(_rulesDataService.CREATE_Rule(newWarehouseRule));
if (existingWarehouseRule != null)
{
deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingWarehouseRule))
}
}
//MY COMMENTS REFLECT WHAT I HOPED TO ACHIEVE, NOT WHAT ACTUALLY HAPPENS
//wait for all the deactivations to be done
await Task.WhenAll(deactivate_Tasks).ConfigureAwait(false);
//once everything is deactivated, add the replacements
await Task.WhenAll(add_Tasks).ConfigureAwait(false);
}
}
/// <summary>
/// Sample only - hopefully the methods names used above are self explanatory, but they all return a Task<StatusCode>
/// </summary>
public class RuleDataService
{
public async Task<StatusCode> DEACTIVATE_Rule(Customer_Rule customer_Rule)
{
using(var context = new DBContext())
{
context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
return await Task.Run(() =>
{
foreach (var existingRule in context.Customer_Rules.Where(r => r.CustomerName == customer_Rule.CustomerName && r.IsActive && r.RuleSetId != customer_Rule.RuleSetId))
{
existingRule.IsActive = false;
}
context.SaveChanges();
return StatusCode.TaskComplete;
}).ConfigureAwait(false);
}
}
public async Task<StatusCode> DEACTIVATE_Rule(Warehouse_Rule warehouse_Rule)
{
//basically the same as the other DEACTIVATE methods, just a different table
}
public async Task<StatusCode> CREATE_Rule(Customer_Rule customer_Rule)
{
//basically the same as the other DB methods, but performs an Add instead of an UPDATE
}
public async Task<StatusCode> CREATE_Rule(Warehouse_Rule warehouse_Rule)
{
//basically the same as the other DB methods, but performs an Add instead of an UPDATE
}
}
I have done a fair amount of googling for answers, but the answers only seem to give advice on how to run a series of tasks in parallel, which I have achieved, not how to bundle up in-series sets of parallel actions.
All async tasks are created "hot". In other words, when your code calls the method that returns a task, that method invocation is what starts the task. The task is in progress by the time it is returned. It's still in progress when it's added to the list.
So, if you want to delay the creation of the second batch, don't call those methods yet:
public async Task Save()
{
var deactivate_Tasks = new List<Task<StatusCode>>();
if (CustomerRule_IsDirty)
{
if (existingCustomerRule != null)
{
deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingCustomerRule));
}
}
if (WarehouseRule_IsDirty)
{
if (existingWarehouseRule != null)
{
deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingWarehouseRule))
}
}
await Task.WhenAll(deactivate_Tasks).ConfigureAwait(false);
var add_Tasks = new List<Task<StatusCode>>();
if (CustomerRule_IsDirty)
{
add_Tasks.Add(_rulesDataService.CREATE_Rule(newCustomerRule));
}
if (WarehouseRule_IsDirty)
{
add_Tasks.Add(_rulesDataService.CREATE_Rule(newWarehouseRule));
}
await Task.WhenAll(add_Tasks).ConfigureAwait(false);
}
await is akin to "continue execution when this task is done". Notably, it does not say anything at all about when the task is started. So your example code both add and deactivate tasks will run in parallel, resulting in the behavior you describe. The solution is to start the add tasks after the deactivate tasks. I.e.
// Create all deactivate tasks
if (CustomerRule_IsDirty && existingCustomerRule != null)
{
deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingCustomerRule));
}
if (WarehouseRule_IsDirty && existingWarehouseRule != null)
{
deactivate_Tasks.Add(_rulesDataService.DEACTIVATE_Rule(existingWarehouseRule))
}
await Task.WhenAll(deactivate_Tasks).ConfigureAwait(false);
// Create all add tasks
if (CustomerRule_IsDirty)
{
add_Tasks.Add(_rulesDataService.CREATE_Rule(newCustomerRule));
}
if (WarehouseRule_IsDirty)
{
add_Tasks.Add(_rulesDataService.CREATE_Rule(newWarehouseRule));
}
await Task.WhenAll(add_Tasks).ConfigureAwait(false);
also, you are creating the DbContext object in another thread-context than where it is used. I would suggest moving the creation and disposal into the Task.Run(...) just to avoid any potential issues.
Given the following classes:
class Account
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id {get; set;}
...
}
class AccountModel
{
public long Id {get; set;}
...
}
If I called this method:
async Task AddAccountAsync(AccountModel model)
{
Account entity = CreateFromModel(model);
DbContext.Add(entity);
await DbContext.SaveChangesAsync();
model.Id = entity.Id;
}
model.Id after running (with await) is -9223372036854775808.
But if I use this instead:
async Task<long> AddAccountAsync(AccountModel model)
{
Account entity = CreateFromModel(model);
DbContext.Add(entity);
await DbContext.SaveChangesAsync();
return entity.Id;
}
it returns the right value. Why so?
This code returns a Task
async Task AddAccountAsync(AccountModel model)
So, you would need to Wait for the Task to finish. Typically like this:
var result = AddAccountAsync(model);
result.Wait()
Mixing Wait and await is bit dangerous ! You could typically end up with deadlocks.
The better option would be to use await on the Task
await result
After the call finishes, I would expect your model object to have the correct value.
The above technique is useful in scenarios where you are calling multiple async methods and finally have to wait for all of them to finish.
eg.
var result1Task = Method1Async();
var result2Task = Method2Async();
var result3Task = Method3Async();
...
--finally
await Task.WhenAll(result1Task, result2Task, result3Task);
Your second method returns an actual value itself
async Task<long> AddAccountAsync(AccountModel model)
you would typically await the above method call and get back the result
var id = await AddAccountAsync(model)
You would have seen that the comments in your question is asking about: how exactly you are calling these methods ?
The way you call these methods will give a clue to why the values are not as per your expectation.
You can explore Stephen Cleary's blog about async/await:
http://blog.stephencleary.com/2012/02/async-and-await.html
The Async Composition section would be relevant to your query.
I am getting the above error, when I run this method in my view model for my WPF app:
public async Task LoadTypesAsync()
{
EquipmentTypes = await _equipmentRepository.GetEquipmentTypes();
}
This calls a method in my repository that returns a list of Equipment Types as shown here:
public async Task<IEnumerable<EquipmentType>> GetEquipmentTypes()
{
return await _context.EquipmentTypes.Select(t => new EquipmentType()
{
EquipmentTypeID = t.EquipmentTypeID,
EquipmentTypeDescription = t.EquipmentTypeDescription,
EquipmentTypeName = t.EquipmentTypeName
}).ToListAsync();
}
When my main view model loads, I call the LoadTypesAsync method to save the data in my EquipmentTypes property, which is of type IEnumerable.
I really don't understand this error. What is preventing me from grabbing the data and inserting it into my list? My repository maintains a constant connection to the DB and can do other functions like inserting data just fine.
The error is shown when I call the LoadAsync method here:
public async Task LoadAsync() //method must be async when loading in async data and return a task
{
await EquipmentListViewModel.LoadAsync(); //load up the list to the left
await EquipmentCreateViewModel.LoadTypesAsync();
}
I'm unsure if I can have several awaits in an async method.
I am writing MVC web application and I've got stucked at one point.
The application throws ObjectDisposedException eventhough I used ToList() method while dbContext was still open. Here is my code:
Data Access Layer:
public List<CompanyCode> SearchCompanyCodes(int? projectId=null)
{
IQueryable<sl_CompanyCodes> filtered=dbContext.sl_CompanyCodes;
if(projectId!=null)
{
filtered = filtered.Where(cc => cc.Project_ID == projectId);
}
return filtered.ToList();
}
Bussiness logic layer:
public List<CompanyCode> CompanyCodes(int? cc_projectId=null)
{
List<CompanyCode> result;
using(var repository=new E6Repository())
{
result=repository.SearchCompanyCodes(projectId:cc_projectId);
}
// E6Repository.Dispose calls dbContext.Dispose()
return result;
}
Controller:
public JsonResult CompanyCodes()
{
var logic = new GetDataLogic();
List<CompanyCode> l = logic.CompanyCodes();
return Json(l, JsonRequestBehavior.AllowGet);
}
I have no idea what to do... When I stop debugger just before return action in controller, I can see well filled list when I type l in immediate window (notice at this point dbContext is already disposed).
Interesting thing: if I change ActionResult into view with IEnumerable<CompanyCode> as model, response is fine and there is no errors.
Can anyone explain me what's going on?
I think you have not created an instance of your EF context. Try this:
public List<CompanyCode> SearchCompanyCodes(int? projectId=null)
{
using (var context = new dbContext())
{
IQueryable<sl_CompanyCodes> filtered = from compCodes in dbContext.sl_CompanyCodes
select compCodes;
}
if(projectId!=null)
{
filtered = filtered.Where(cc => cc.Project_ID == projectId);
}
return filtered.ToList();
}
I've created a method to return a task from a query on an Azure mobile service.
But when I set the return type for the method, ie Task I get these errors pointing to the return part of the method:
I tried to remedy this by using a generic ILIst but it seems this can't be used for an async method.
Does anyone know how to correct the return type error in this method?
http://hastebin.com/ziwovovabe.tex
public async static Task<Item> QueryTable()
{
var table = App.MobileService.GetTable<Item>();
IMobileServiceTableQuery<Item> query = table.
OrderBy(item => item.Id);
Task<Item> items = new Task<Item>();
items = await query.ToListAsync();
return items;
}
The method returns a Task<Item>, but the asynchronous nature of the method handles the Task<> part for you. Your code should just be returning an Item. Maybe something like this?:
public async static Task<Item> QueryTable()
{
var table = App.MobileService.GetTable<Item>();
IMobileServiceTableQuery<Item> query = table.
OrderBy(item => item.Id);
return await query.ToListAsync();
}
However, ToListAsync() implies that this is returning a collection of items, not a single item. Do you want to return a specific items from that collection? Something like this?:
return await query.ToListAsync().Single(i => i.SomeProperty == someValue);
Or maybe you actually want to return the collection? In which case maybe the return type should be a collection type. Something like this:
public async static Task<IList<Item>> QueryTable()
{
// ...
}
The point is that the await keyword creates the Task<> for you, all you need to do is return the type within the Task<>.
This:
await query.ToListAsync();
return items;
Attempts to return a Task<List<Item>>, while your return type is Task<Item>. Currently, your query is returning a collection, not a single item. The way it's currently implemented, the return type should be Task<List<Item>>:
public async static Task<List<Item>> QueryTable()
{
var table = App.MobileService.GetTable<Item>();
IMobileServiceTableQuery<Item> query = table.
OrderBy(item => item.Id);
return await query.ToListAsync();
}