How to synchronize web api calls to a database - api

I have the following problem:
I have a async web api which updates certain fields of a mongo document. It first retrieves the document updates the fields and then saves the document asynchronously to the database. How do I ensure that if the api is called multiple times in succession, one call will not overwrite the results of a previous call ?
public async Task<ActionResult> UpdateAsync(string id , string b)
{
//this queries the mongo db and returns the document
var q = await _mongoService.FindAsync(id);
q.someField = b;
await _mongoService.UpdateAsync(q);
}
When called multiple times, I first get q, then when one of the calls saves q, but then another call to the api can overwrite the first q.
How do i synchronize the finds and updates ?
The following solution works but i am afraid that this is not scalable
static object _locker = new object();
public async Task<ActionResult> UpdateAsync(string id , string b)
{
await Task.Run(() =>
lock (_locker) {
var q = _mongoService.Find(id);
q.someField = b;
_mongoService.Update(q);
}
}
Another option is using a semaphore
private static SemaphoreSlim _mutex = new SemaphoreSlim(1);
public async Task<ActionResult> UpdateAsync(string id , string b)
{
_mutex.WaitAsync();
//this queries the mongo db and returns the document
var q = await _mongoService.FindAsync(id);
q.someField = b;
await _mongoService.UpdateAsync(q);
_mutex.Release()
}
Not sure if these are the best solutions

Related

EF Core not setting class variables automatically

I want to switch my code to an async implementation. When I want to do this then I notice that my related data gets not set automatically after I retrieve them like it used to do it.
This is the initial function that gets called from an API controller. I used the AddDbContext function to add the dbcontext class via dependency injection into my controller:
public async Task<Application> GetApplicationById(AntragDBNoInheritanceContext dbContext, int id)
{
List<Application> ApplicationList = await dbContext.Applications.FromSqlRaw("Exec dbo.GetApplication {0}", id).ToListAsync();
Application Application = ApplicationList.First();
if(Application != null)
{
await CategoryFunctions.GetCategoryByApplicationID(Application.Id);
}
}
The GetCategoryByApplicationId function loads the related category of an application which is a many to one relation between Category and Application:
public async Task<Category> GetCategoryByApplicationID(int applicationID)
{
var optionsBuilder = new DbContextOptionsBuilder<AntragDBNoInheritanceContext>();
optionsBuilder.UseSqlServer(ApplicationDBConnection.APPLICATION_CONNECTION);
using (var dbContext = new AntragDBNoInheritanceContext(optionsBuilder.Options))
{
List<Category> category = await dbContext.Categories.FromSqlRaw("Exec GetApplicationCategory {0}", applicationID).ToListAsync();
if (category.Any())
{
return category.First();
}
}
return null;
}
When I want to retrieve an application then the field Category is not set. When I did not use async/await it would set the category automatically for me. Of course I could just return the Category Object from the GetCategoryByApplicationId and then say:
Application.Category = RetrievedFromDbCategory;
But this seems a bit unmaintainable compared to the previous behaviour. Why does this happen now and can I do something about it? Otherwise I don't see much benefits on using async/await .

How to sync in memory data with disk in ASP NET

In a ASP NET Controller i have a service that returns a list of items.This service serves from the RAM the list to requesters.
The list can also be altered by a special group of users , so everytime it is altered i write the changes to disk and update my RAM from disk. (Reading my own writes this way)
From a JS client when i alter this list , the changes are written correctly on the disk , but when i forward a second request to get my list , i am served a stale list.I need to hit F5 for the client to get the right data.
I do not understand how does the RAM cache lags behind.
You can see in my service below that i have guarded the altering method with a lock.I have also tried without it to no avail.
Service
public class FileService : IADReadWrite {
private const int SIZE = 5;
private const string COMPUTER_FILE = #"computers.txt";
private List<Computer> computers = new List<Computer>();
private readonly object #filelock = new object();
private readonly Computer[] DEFAULT_COMPUTERS_LIST = new Computer[] {
new Computer(id:"W-CZC81371RS",Username:"A"),
new Computer(id:"W-CZC81371RQ",Username:"B"),
};
async Task<Computers> GetComputersAsymc() {
if (this.computers.Count == 0) {
var query = await Fetch();
this.computers = query.ToList();
}
var result = new Computers(this.computers);
return result;
}
public async Task<bool> AddComputerAsync(Computer computer) {
lock (filelock) {
if (this.computers.Any(x => x == computer)) {
return false;
}
this.computers.Add(computer);
File.WriteAllText(COMPUTER_FILE, JsonConvert.SerializeObject(this.computers, Formatting.Indented));
this.computers = JsonConvert.DeserializeObject<List<Computer>>(File.ReadAllText(COMPUTER_FILE));
}
return true;
}
---------------------Helpers --------------------------
private async Task<IEnumerable<Computer>> Fetch() {
if (!File.Exists(COMPUTER_FILE)) {
WriteComputersToDisk();
}
using (FileStream stream = new FileStream(COMPUTER_FILE, FileMode.Open, FileAccess.Read)) {
var raw = await File.ReadAllTextAsync(COMPUTER_FILE);
var comp = JsonConvert.DeserializeObject<List<Computer>>(raw);
return comp;
}
}
private void WriteComputersToDisk() {
var comps = DEFAULT_COMPUTERS_LIST;
var data = JsonConvert.SerializeObject(comps, Formatting.Indented);
File.WriteAllText(COMPUTER_FILE, data);
}
}
Controller
public class MyController:Controller
{
MyController(IADReadWrite service)
{
this.service=service;
}
IADReadWrite service;
[HttpGet]
public async Task<List<Computer>> GetAll()
{
return await service.GetComputersAsync();
}
[HttpPost]
public async Task AddComputer(Computer computer)
{
await service.AddComputerAsync(computer);
}
}
Scenario
Initial list : [0,1]
Client hits controller calling `AddComputer` {2}
I check the file , list is now: [0,1,2]
Client hits controller calling `GetComputers` -> it returns [0,1]
I hit F5 on the browser -> GetComputers gets hit again -> it returns [0,1,2]
P.S
I have not posted the Computer class since it does not matter in this scenario ( It implements IEquateable in case you are wondering if it is failing when i use the == operator.
The last 2 methods deal with the initialization of the Disk file.

Entity Framework Core: New transaction is not allowed because there are other threads running in the session

I have a database with a hierarchy of categories. Each category has a parentcategoryid. I call the following function to load the top level categories and then it recursively calls itself to load all the children.
However, I get the following error:
SqlException: New transaction is not allowed because there are other
threads running in the session.
public async Task LoadCategoriesAsync()
{
await LoadCategoriesByParentId(null);
}
private async Task LoadCategoriesByParentId(int? sourceParentId, int? parentId)
{
var sourceCategories = _dbContext.SourceCategory.Where(c => c.ParentCategoryId == sourceParentId);
foreach (var sourceCategory in sourceCategories)
{
var newCategory = new Category()
{
Name = sourceCategory.Name,
Description = sourceCategory.Description,
ParentCategoryId = parentId
};
_dbContext.Category.Add(newCategory);
await _dbContext.SaveChangesAsync();
//category.EntityId = newCategory.Id;
//_dbContext.SourceCategory.Update(category);
//await _dbContext.SaveChangesAsync();
await LoadCategoriesByParentId(sourceCategory.CategoryId, newCategory.Id);
}
}
Your Where() statement doesn't retrieve the data; just "opens the cursor" (in old-speak). So, you can't do SaveChange(). The simplest solution is to convert IEnumerable to List or Array:
var rootCategories = _dbContext.SourceCategory.Where(c => c.ParentCategoryId == parentId).ToList();
But I would strongly recommend you google the error and understand why it is happening. To do this recursively is begging for trouble

How can I successfully use HttpClient to access iTunes search service in MVC4 app

I am writing an MVC4 app, and in the controller, I need to be able to make a call to a web service to access the iTunes search service in order to get some cover art for a DVD.
I have the following very simple code
public static string GetAlbumArt(Movie movie) {
Task<string> lookupTask = LookupAlbumArt(movie.Title, movie.Year);
lookupTask.Wait(5000);
if (lookupTask.IsCompleted)
{
return lookupTask.Result;
}
else
{
return Timeout;
}
}
private static async Task<string> LookupAlbumArt(string title, int year)
{
using (var client = new HttpClient())
{
string response= await client.GetStringAsync(
"http://itunes.apple.com/search?term=hoosiers&media=movie"
);
return response;
}
}
When I run this and set breakpoints at the return Timeout line in GetAlbumArt and also at the return response line in LookupAlbumArt, the Timeout breakpoint is hit first, and then the LookupAlbumArt return statement is hit, and the correct content is in the response variable.
If I remove the timeout parameter from lookupTask.Wait(5000), the wait GetStringAsync call never returns, and the page never loads.
This feels like a pretty straightforward task, and I'm stumped as to what I'm doing wrong.

retrieve int value from async task

I have an async task in a separate project that I need to call from another project but Im not sure how to accomplish that.
The async method is
public async Task<int> InsertNewPasswordResetRequest(UserPasswordReset upr)
{
string commandText = "Insert Into passwordresetrequests (reset_username, ResetToken, ResetRequestTime, ResetRequestTimeout) values(#username,#rt,#rrt,#rrtout);";
var parameters = new Dictionary<string, object>() {
{ "#username", upr.ResetUsername },
{ "#rt", upr.ResetToken },
{ "#rrt", upr.ResetRequestTime },
{ "#rrtout", upr.ResetRequestTimeout }
};
return await Task.FromResult( _database.Execute(commandText, parameters, false));
}
I am trying to call it from another project like so
Dim success As Integer = prr.InsertNewPasswordResetRequest(upr).FromResult()
I know its returning a task but how do I extract the int value from it?
You can await of your task if you want to run it async
Dim success As Integer = Await prr.InsertNewPasswordResetRequest(upr)
or run task synchronously as
Dim success As Integer = prr.InsertNewPasswordResetRequest(upr).Result
The recommended way is to await the results of the InsertNewPasswordResetRequest method. You can also call the Result property on the returned Task<int> but that could lead to your code blocking if you're executing within a synchronization context.
int result = await InsertNewPasswordResetRequest(...);
An additional note: There is no need to await Task.FromResult in that method. Simply remove the async modifier from the method signature and the await keyword and return Task.FromResult directly.
An additional note: If possible, consider making InsertNewPasswordResetRequest non-async completely since it doesn't execute any asynchronous code.
Edit for VB.NET calling code:
Dim result As Integer = Await InsertNewPasswordResetRequest(...)