12

I'am trying to run my Controller Action in async way. How do I use async Task ? Or How to run in async way

// Db context
public class DeptContext : DbContext
{
    public LagerContext(DbContextOptions<LagerContext> options)
        : base(options)
    {

        Database.Migrate();
    }

    public DbSet<Department> Departments { get; set; }
    public DbSet<Product> Products { get; set; }


}

// This is my Interface IDepRepository

Task<Department> GetDepartmentWithOrWithoutProducts(int deptId, bool includeProducts);

// And my Repository class DepRepository

public class DepRepository : IDepRepository
{
   private DeptContext db;
    public DepRepository(DeptContext context)
    {
        db = context;
    }

    // I'am geting Department name with products or Without products

    public async Task<Department> GetDepartmentWithOrWithoutProducts(int deptId, bool includeProducts)
     {
       if(includeProductss)
        {
          return await db.Departments.Include(c => c.Products).Where(s => s.deptId == deptId).SingleAsync();
        }
                return await db.Departments.Where(s => s.deptId == deptId).SingleAsync();
    }
}

So How should I do now in my Controller to do it as async way: I tried as following but I don't know if it's right to do like this following: I'm not getting any error but I don't if it's right way ...

using System.Threading.Tasks;
using System.Net;
using Microsoft.Data.Entity;
using Microsoft.EntityFrameworkCore;

[Route("api/departments")]
public class DepartmentsController : Controller
{
    private IDeptRepository _deptInfoRepository;

    public DepartmentsController(IDeptRepository deptInfoRepository)
    {
        _deptInfoRepository = deptInfoRepository;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetDepatment(int id, bool includeProducts = false)
    {
        var dept = _deptInfoRepository.GetDepartmentWithOrWithoutProducts(id, includeComputers);
        if(dept == null)
        {
            return BadRequest();
        }

        if(includeProducts)
        {
            var depResult =  new DepartmentDto() { deptId = dept.deptId, deptName = dept.deptName };
            foreach(var department in dept.Products)
            {
                depResult.Products.Add(new ProductDto() { productId = department.productId, deptId = department.deptId, ProductName =                     department.ProductName });
            } 

            return Ok(depResult);
        }

        var departmentWithoutProductResult = new DepartmentsWithoutProductsDto() { DeptId = dept.deptId, DeptName = dept.DeptName};
        return Ok(departmentWithoutProductResult);


    }

How do I do to get my controller in async way.. I don't know where to put those await and ToListAsync(). Thank you in advance!

Nkosi
  • 235,767
  • 35
  • 427
  • 472
Helen Tekie
  • 515
  • 1
  • 6
  • 23
  • `ToListAsync` exists as an `IQueryable` extension, not an `IEnumerable` extension. And List doesn't implement `IQueryable`. Do you have an Async version of `GetDepartments`? If so, you could await that call. – R. Richards Jul 01 '17 at 20:46
  • @R.Richards Thank you for your response. Do you mean it's enough if I have async and await in my GetDepartments? – Helen Tekie Jul 01 '17 at 22:54
  • Not really. What you need is an async version of GetDepartments. The answers here hint toward that. An GetDepartmentsAsync would return a Task<> of whatever the GetDepartments returns now. This is needed because you cannot use async/await effectively without a Task based function somewhere in the mix. [See this](https://stackoverflow.com/questions/14455293/how-and-when-to-use-async-and-await). – R. Richards Jul 02 '17 at 00:40
  • @R.Richards thank you again, Now I have updated with similary detailed code, would you please check my complitly codes please? – Helen Tekie Jul 02 '17 at 15:17

3 Answers3

8

The interface should be renamed to better show the intent.

public interface IDepRepository {
    Task<Department> GetDepartmentWithOrWithoutProductsAsync(int deptId, bool includeProducts);
    //...
}

Which would update the implementation accordingly. Since the method is not actually using anything after the async call then there not really any reason to tag the method as async. Just return the Task.

public Task<Department> GetDepartmentWithOrWithoutProductsAsync(int deptId, bool includeProducts) {
    if(includeProductss) {
        return db.Departments.Include(c => c.Products).Where(s => s.deptId == deptId).SingleAsync();
    }
    return db.Departments.Where(s => s.deptId == deptId).SingleAsync();
}

The controller action however needs to await the task and then continue after the task has completed so therefore that method will be tagged with async.

[HttpGet("{id}")]
public async Task<IActionResult> GetDepatment(int id, bool includeProducts = false) {
    var dept = await _deptInfoRepository.GetDepartmentWithOrWithoutProductsAsync(id, includeComputers);
    if (dept == null) {
        return BadRequest();
    }
    if (includeProducts) {
        var depResult =  new DepartmentDto() { deptId = dept.deptId, deptName = dept.deptName };
        foreach (var department in dept.Products) {
            depResult.Products.Add(new ProductDto() { 
                productId = department.productId, 
                deptId = department.deptId, 
                ProductName = department.ProductName 
            });
        } 
        return Ok(depResult);
    }
    var departmentWithoutProductResult = new DepartmentsWithoutProductsDto() { DeptId = dept.deptId, DeptName = dept.DeptName};
    return Ok(departmentWithoutProductResult);
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
3

What I can't tell from your code is the datatype of what GetDepartments returns. My guess is that you are using EF Core and GetDepartments returns a DbSet or a LINQ query against a DbSet. If that is the case, then after the line where your depEntities variable is set, that variable points to a deferred object (an expression tree that has not been evaulated yet). Or in other words, the actual query has not been sent to the database yet. When you loop over the depEntities (with your foreach loop), you are causing the actual potentially long-running work to occur (database access). That's what you want to await on. So, yes, you could make an async version of GetDepartments or you could also probably change your code to be:

var depEntities = await _depRepository.GetDepartments().ToListAsync();

The call to ToListAsync will enumerate the deferred object and perform the database access. Your return statement would just return results. Behind the scenes, the method actually returns on your await statement and resumes after the work you're awaiting on completes.

One last note.. any database exceptions will occur at the point where the deferred object is enumerated.

Jason
  • 311
  • 3
  • 8
  • 1
    Thank you for your response. Now I have updated with similary detailed code, would you please check my complitly codes please? – Helen Tekie Jul 02 '17 at 15:18
2

You should not do any await on already-prepared results list. It's already contain required data - what you want to wait to?

You should make new async version of your GetDepartments() method and await while obtaining data from repository:

var depEntities = await _depRepository.GetDepartmentsAsync();
Dmitry
  • 16,110
  • 4
  • 61
  • 73
  • Thank you for your response. Now I have updated with similary detailed code, would you please check my complitly codes please? – Helen Tekie Jul 02 '17 at 15:18
  • 1
    Now you should await in `var dept = await _deptInfoRepository.GetDepartmentWithOrWithout...`, without this your code will not compile/work (`dept` is `Task`, not `Department`). And add `Async` suffix to method name (if possible) to clarify that it's async. – Dmitry Jul 03 '17 at 07:51