Projection with asynchronous programming - c#

I'm introducing asynchronous programming to my existing code base and having some trouble with calling Select() on the result of GetStudents() - the error message received is as follows "Task<List<ApplicationUser>> does not contain a defintion for Select". I think it'll be due to incorrect syntax, but any guidance would be appreciated - thanks.
public async Task<List<ApplicationUser>> GetStudents()
{
return await Task.Run(() => _context.Users.ToList());
}
public async Task<StudentIndexViewModel> CreateStudentRegisterViewModel()
{
var model = new StudentIndexViewModel();
var students = await _studentRepo.GetStudents().
Select(x => new StudentViewModel
{
Forename = x.Forename,
Surname = x.Surname
}).ToListAsync();
model.Students = students;
return model;
}

As it was mentioned, the error comes from trying to call Select on a Task<T>, which is not valid. However, the problem is much bigger than that. The code is currently getting the entire table from the database just to get a few values from the result in-memory. This is a waste of processing time in both the database and the application server.
Not only that, but also using a thread pool thread just to wait on an I/O operation is another waste.
Overall, the code should be something like this.
public async Task<List<ApplicationUser>> GetApplicationUsersAsync()
{
// use Entity Framework properly with ToListAsync
// this returns the entire table
return await _context.Users.ToListAsync();
}
public async Task<List<StudentViewModel>> GetStudentsAsync()
{
// use Entity Framework properly with ToListAsync
return await _context.Users
// this only returns the 2 needed properties
.Select(x => new StudentViewModel
{
Forename = x.Forename,
Surname = x.Surname
})
.ToListAsync();
}
public async Task<StudentIndexViewModel> CreateStudentRegisterViewModel()
{
var model = new StudentIndexViewModel();
model.Students = await _studentRepo.GetStudentsAsync();
return model;
}

_studentRepo.GetStudents() returns a Task<List<...>>.
As the error is telling you, Task isn't a collection and doesn't have a Select() method.
You can use await to get the collection inside the task, but you need to call Select() on the awaited value (your code currently awaits Select()).
You need to add parentheses:
(await ...).Select(...);

Related

System.InvalidOperationException: 'Invalid operation. The connection is closed.'

I am using .net core 6.0. I am getting this error when I am calling same method from different places. I tried calling this method GetEmployeeByEmployeeNumber from inside Index and I dont get any error and the method returns the object employee with value values populated in employee
public async Task<IActionResult> Index()
{
EmployeeInfo employee = await _employeeService.GetEmployeeByEmployeeNumber(up.EmployeeId);
PopulatePDFDoc();
return View();
}
public async Task<EmployeeInfo?> GetEmployeeByEmployeeNumber(string employeeId)
{
List<int> emplyoeeInfoId = new List<int>();
UserPrincipal up = utilities.UserADIdentity.GetADUserInfo(WindowsIdentity.GetCurrent().Name.ToString());
emplyoeeInfoId = _ackContext.EmployeeInfos.Where(e => e.EmployeeNumber == employeeId).OrderBy(e => e.DateFormFilled).Select(e => e.EmployeeInfoId).ToList();
var employee = await _ackContext.EmployeeInfos.Include(e => e.EmergencyInfos.Where(p => p.EmployeeInfoId.Equals(emplyoeeInfoId.LastOrDefault()))).Where(e=>e.EmployeeInfoId.Equals(emplyoeeInfoId.LastOrDefault())).FirstOrDefaultAsync();
return employee;
}
If I call the same method GetEmployeeByEmployeeNumber from inside PopulatePDFDoc(); then I get an error saying System.InvalidOperationException: 'Invalid operation. The connection is closed.'
below is my PopulatePDFDoc
public async void PopulatePDFDoc()
{
AckPackage.Data.PDFPopulate.DocPDF doc = new Data.PDFPopulate.DocPDF();
EmployeeInfo employee = await _employeeService.GetEmployeeByEmployeeNumber(up.EmployeeId);
}
below is the screen shot of the error:
I am new to .net core. Any help on this will be highly appreciated.
You need to await the call to PopulatePDFDoc() inside the Index method.
Like this:
await PopulatePDFDoc();
Always use await when calling an async method!
The reason you’re getting a “connection closed” error, is because the call to PopulatePDFDoc() is not being “awaited”, and the server request ends before the asynchronous method can run.
Also, PopulatePDFDoc() should return Task instead of void, like this:
public async Task PopulatePDFDoc()
Another thing I noticed that may cause you issues is your _ackContext which looks like it's a class-wide member variable based on the snippet you shared, meaning that same context-instance is shared between multiple methods.
However the context itself is actually not "thread safe", as can be read in Microsofts documentation here: https://learn.microsoft.com/en-us/ef/ef6/fundamentals/working-with-dbcontext which means that if multiple async methods use the same context - which they do in your example - you may run into issues.
The recommended approach is to create the context when you need it, and use the using syntax to make sure it's disposed properly after your work is finished. Like this:
using (var ackContext = new EmployeeContext())
{
// Perform data access using the context here
}
Try using that in your GetEmployeeByEmployeeNumber method and see if it helps as well :)

Is this function Asynchronous?

I'm not sure if this code is Asynchronous. I call this function using await from my main controller and within the function I use await on the LINQ query and .ToListAsync() - but after the query I have the foreach loop which may defeat the purpose of async on the query.
Main Controller Call:
case "getassets":
reply = await GetAssets();
break;
Function:
public async Task<ReplyObj> GetAssets()
{
ReplyObj obj = new ReplyObj();
obj.Result = new List<dynamic>();
dynamic AssetRecords = await _context.Asset.FromSql("SELECT * FROM Asset").ToListAsync();
foreach (var objAsset in AssetRecords)
{
obj.Result.Add(new Asset()
{
AssetId = objAsset.AssetId,
Name = objAsset.Name,
Description = objAsset.Description,
PriceDecimals = objAsset.PriceDecimals
});
}
obj.Success = true;
obj.Message = "";
return obj;
}
This call will have many requests hitting it, I want to know for sure that its using async correctly. Thank you!
To begin, here's a couple of references for async/await in C# that I'd suggest reviewing:
Microsoft Docs
SO Community Answer
The simple (high-level) answer is that awaiting your sql call will return control up the call stack and continue execution. In this case, that means it will go up to:
reply = await GetAssets();
Which will in turn return control to whatever function called that, etc. etc..
With that said, if all of your async calls in your call stack are immediately being awaited, then async won't end up buying you anything/changing the flow of control. To say, keep in mind that async != threading.
Few things that I want to point out:
dynamic AssetRecords = await _context.Asset.FromSql("SELECT * FROM Asset").ToListAsync(); When you are using _context.Asset it will return all the Asset rows that you have. Why would you execute another query on the table when the Asset it self is giving all that you need? Hence, to me this is redundant thing.
And if you use select method and then get the list asyncronously then you will have eliminated the the foreach loop and it will cost only one await call and query processing. See code sample below:
public async Task<ReplyObj> GetAssets()
{
ReplyObj obj = new ReplyObj();
return obj.Result = await _context.Asset.Select(s => new
{
AssetId = s.AssetId,
Name = s.Name,
Description = s.Description,
PriceDecimals = s.PriceDecimals
}).ToListAsync();
}
This could now be your true async method.
PS:
If you want to show your code to experts for review, I would suggest joining StackExchange CodeReview Community

How does parallelization work on async/await?

I have the following code, that I intend to run asynchronously. My goal is that GetPictureForEmployeeAsync() is called in parallel as many times as needed. I'd like to make sure that 'await' on CreatePicture does not prevent me from doing so.
public Task<Picture[]> GetPictures(IDictionary<string, string> tags)
{
var query = documentRepository.GetRepositoryQuery();
var employees = query.Where(doc => doc.Gender == tags["gender"]);
return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));
}
private Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
{
var base64PictureTask = blobRepository.GetBase64PictureAsync(employee.ID.ToString());
var documentTask = documentRepository.GetItemAsync(employee.ID.ToString());
return CreatePicture(tags, base64PictureTask, documentTask);
}
private static async Task<Picture> CreatePicture(IDictionary<string, string> tags, Task<string> base64PictureTask, Task<Employee> documentTask)
{
var document = await documentTask;
return new Picture
{
EmployeeID = document.ID,
Data = await base64PictureTask,
ID = document.ID.ToString(),
Tags = tags,
};
}
If I understand it correctly, Task.WhenAll() is not affected by the two awaited tasks inside CreatePicture() because GetPictureForEmployeeAsync() is not awaited. Am I right about this? If not, how should I restructure the code to achieve what I want?
I'd like to make sure that 'await' on CreatePicture does not prevent me from doing so.
It doesn't.
If I understand it correctly, Task.WhenAll() is not affected by the two awaited tasks inside CreatePicture() because GetPictureForEmployeeAsync() is not awaited. Am I right about this?
Yes and no. The WhenAll isn't limited in any way by the awaited tasks in CreatePicture, but that has nothing to do with whether GetPictureForEmployeeAsync is awaited or not. These two lines of code are equivalent in terms of behavior:
return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));
return Task.WhenAll(employees.Select(async employee => await GetPictureForEmployeeAsync(employee, tags)));
I recommend reading my async intro to get a good understanding of how async and await work with tasks.
Also, since GetPictures has non-trivial logic (GetRepositoryQuery and evaluating tags["gender"]), I recommend using async and await for GetPictures, as such:
public async Task<Picture[]> GetPictures(IDictionary<string, string> tags)
{
var query = documentRepository.GetRepositoryQuery();
var employees = query.Where(doc => doc.Gender == tags["gender"]);
var tasks = employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)).ToList();
return await Task.WhenAll(tasks);
}
As a final note, you may find your code cleaner if you don't pass around "tasks meant to be awaited" - instead, await them first and pass their result values:
async Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
{
var base64PictureTask = blobRepository.GetBase64PictureAsync(employee.ID.ToString());
var documentTask = documentRepository.GetItemAsync(employee.ID.ToString());
await Task.WhenAll(base64PictureTask, documentTask);
return CreatePicture(tags, await base64PictureTask, await documentTask);
}
static Picture CreatePicture(IDictionary<string, string> tags, string base64Picture, Employee document)
{
return new Picture
{
EmployeeID = document.ID,
Data = base64Picture,
ID = document.ID.ToString(),
Tags = tags,
};
}
The thing to keep in mind about calling an async method is that, as soon as an await statement is reached inside that method, control immediately goes back to the code that invoked the async method -- no matter where the await statement happens to be in the method. With a 'normal' method, control doesn't go back to the code that invokes that method until the end of that method is reached.
So in your case, you can do the following:
private async Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
{
// As soon as we get here, control immediately goes back to the GetPictures
// method -- no need to store the task in a variable and await it within
// CreatePicture as you were doing
var picture = await blobRepository.GetBase64PictureAsync(employee.ID.ToString());
var document = await documentRepository.GetItemAsync(employee.ID.ToString());
return CreatePicture(tags, picture, document);
}
Because the first line of code in GetPictureForEmployeeAsync has an await, control will immediately go right back to this line...
return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));
...as soon as it is invoked. This will have the effect of all of the employee items getting processed in parallel (well, sort of -- the number of threads that will be allotted to your application will be limited).
As an additional word of advice, if this application is hitting a database or web service to get the pictures or documents, this code will likely cause you issues with running out of available connections. If this is the case, consider using System.Threading.Tasks.Parallel and setting the maximum degree of parallelism, or use SemaphoreSlim to control the number of connections used simultaneously.

Why is my IEnumerable<T>.Where iterator not executing inside a using block while Async call?

I'm sure this is not a Dapper issue however I am finding, in the following snippet, that the predicate supplied to the Where function is never executed.
private async Task<IEnumerable<Product>> GetProducts()
{
using (var connection = await _connectionFactory.Create())
{
var products = await connection.QueryAsync<Product>("select * from Products");
return products.Where(p => p.Active);
}
}
However if I move the operation to outside the using it is executed.
private async Task<IEnumerable<Product>> GetProducts()
{
var products = Enumerable.Empty<Product>();
using (var connection = await _connectionFactory.Create())
{
products = await connection.QueryAsync<Product>("select * from Products");
}
return products.Where(p => p.Active);
}
Is there some sort of deferred execution going on?
In the first example, if you can make the following modification in the return statement:
return products.Where(p => p.Active).ToList();, then it will work as expected.
Case 1:
Issue here is Where clause applied on the IEnumerable<Product> is deferred execution, which is returned wrapped up in the Task as follows Task<IEnumerable<Product>>, but now you need to run the Task, which shall execute the predicate too, Not sure how are you executing the Task or may be there's an issue with wrapping the deferred execution in this manner, but end result is predicate is not coming in effect as expected, even when its applied on the Dapper result, which is buffered by default (no-streaming)
Case 2:
It works in the second case, since you are completely getting rid of deferred execution, Enumerable.Empty<Product>() is ensuring that memory is first allocated, so predicate is executed moment its applied there's no deferred execution. In fact predicate is any way applied outside the using block
In the Async method, you are disposing the connection with the using block, mostly since Dapper internally allocates memory that's why all the data is sent across, connection is then disposed, and predicate is never executed. I have similar sample, which doesn't rely on database connection and it works as expected, therefore we can deduce that connection dispose play a role here in predicate not executing. In second case predicate is applied outside using block, so connection dispose has no role and memory is already allocated.
Sample Code (using LinqPad):
async Task Main()
{
var result = await GetTest();
result.Dump();
}
public async Task<IEnumerable<Test>> GetTest()
{
var value = await GetTestDb();
return value.Where(x => x.Id == 1);
}
public async Task<IEnumerable<Test>> GetTestDb()
{
return await Task.FromResult(
new List<Test>
{
new Test{Id = 1, Name = "M"},
new Test{Id = 2, Name = "S"}
}
);
}
public class Test
{
public int Id { get; set; }
public string Name { get; set; }
}
Result:
Your predicate is not actually working as predicate. It is simply a LINQ call.
return products.Where(p => p.Active);
When above line execute, products is already filled by all the rows from the table based on your query and QueryAsync call in earlier line.
Good thing about Dapper is that, it provides complete control of query writing to you. So, if you want to filter the records, why not write the query that way?
using(var connection = ....)
{
var param = new DynamicParameters();
param.Add("#Active", 1);
var products = await connection.QueryAsync<Product>("select * from Products where Active = #Active", param);
return products;
}
You should then remove the products.Where line.
About actual problem you asked in question:
I could not reproduce the problem. When I run following code to read the output in console application, it returns expected results.
DbDataReader dbDataReader = new DbDataReader();
IEnumerable<Product> activeProducts = dbDataReader.GetProducts().Result;
Console.WriteLine(activeProducts.Count());
Your method is little modified as below:
public class DbDataReader
{
string connectionString = #"....";
public async Task<IEnumerable<Product>> GetProducts()
{
using(var connection = await GetOpenConnection())
{
var products = await connection.QueryAsync<Product>("select * from Products;WAITFOR DELAY '00:00:05'");
return products.Where(p => p.Active);
}
}
private async Task<SqlConnection> GetOpenConnection()
{
SqlConnection sqlConnection = new SqlConnection(connectionString);
await sqlConnection.OpenAsync();
return sqlConnection;
}
}
Note that I have intentionally delayed the QueryAsync call with WAITFOR.

Parallel Task Code to make multiple db accesses

I am trying to speed up some code of mine,
I make 2 or 3 reads to a slow database, and I want to make these calls run in paralle.
FSKWebInterfaceEntities dbSrc = new FSKWebInterfaceEntities();
public void main()
{
var TaskUsr = GetUserAsync(dev);
var TaskOldCompany = GetOldCompanyAsync(dev);
await Task.WhenAll();
var usr = TaskUsr.Result;
var oldCompanyName = TaskOldCompany.Result;
.....
use my two variables to insert a new entry into my localdb
}
private async Task<ut_User> GetUserAsync(ut_UserAPNdevices dev)
{
return dbSrc.ut_User.FirstOrDefault(x => x.UserID == dev.UserID);
}
private async Task<String> GetOldCompanyAsync(ut_UserAPNdevices dev)
{
return dbSrc.ut_User.FirstOrDefault(x => x.UserID == dev.UserID).Company;
}
On my two helper methods its is underlined green and said that there is no await, and therefore will run synchronously. However I cant return return await dbSrc.ut_User.FirstOrDefault(x => x.UserID == dev.UserID);
How should I modify my code to make the two reads in parallel?
You can await that line because it is not an async method. You can't really await database calls as they are not thread safe. See this thread for an example of a way to do it, and a better explanation as to why not. You can consider using SqlCommands that have async methods as well.
You cannot use the same EF DbContext concurrently. From the EF Team's statement about thread safety:
For the moment, EF will detect if the developer attempts to execute
two async operations at one time and throw.
You still can call them asynchronously, but sequentially:
public async Task main()
{
var sr = await GetUserAsync(dev);
var oldCompany = await GetOldCompanyAsync(dev);
.....
use my two variables to insert a new entry into my localdb
}
Updated to address the comment:
Could you possible post a simple example of how I would change my
above code to use the multiple dbcontext, As I don't really know the
correct way to do it, I would be coding in the dark.
Something like below. I'm not an expert in EF to assure you this will actually improve the processing time. There may be significant overhead of opening the database connection for each context.
public async Task main()
{
var TaskUsr = GetUserAsync(dev);
var TaskOldCompany = GetOldCompanyAsync(dev);
await Task.WhenAll(TaskUsr, TaskOldCompany);
var usr = TaskUsr.Result;
var oldCompanyName = TaskOldCompany.Result;
.....
use my two variables to insert a new entry into my localdb
}
private async Task<ut_User> GetUserAsync(ut_UserAPNdevices dev)
{
using (var dbSrc = new FSKWebInterfaceEntities())
return dbSrc.ut_User.FirstOrDefault(x => x.UserID == dev.UserID);
}
private async Task<String> GetOldCompanyAsync(ut_UserAPNdevices dev)
{
using (var dbSrc = new FSKWebInterfaceEntities())
return dbSrc.ut_User.FirstOrDefault(x => x.UserID == dev.UserID).Company;
}

Categories