Blank values in DB from async function with multiple awaits - c#

I have a model as so
public class Action ()
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool Completed { get; set; }
public DateTime CompletedOn { get; set; }
public virtual User CompletedBy { get; set; }
}
And an async function as so:
public async Task<ActionResult> MarkActionCompleted(int id)
{
using(MyDbContext db = new MyDbContext())
{
UserManager userManager = new UserManager(new UserStore(db));
var user = await userManager.FindByIdAsync(User.Identity.GetUserId());
var action = await db.Actions.SingleOrDefaultAsync(x => x.Id == id);
if (action == null)
{
return new HttpNotFoundResult();
}
action.Completed = true;
action.CompletedOn = DateTime.Now;
action.CompletedBy = user;
await db.SaveChangesAsync();
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
}
I've been noticing, by looking at all the entries on my DB that about 2% (441/21547) of records are marked completed but both the CompletedBy and CompletedOn are null. I've been trying to wrap my head around how this can be. This is the only function that modifies this table.
So my question is, Am i using async/await incorrectly? Is it possible that SaveChangesAsync is being called before the user is retrieved? Or is perhaps an issue with Context? Should I be using ConfigureAwait(false) somewhere?

Related

Saving a List to SQLite.NET using SQLiteAsyncConnection

Working on a project thats Stores items to my sqlDb created it following this video by James Montemagno https://www.youtube.com/watch?v=XFP8Np-uRWc&ab_channel=JamesMontemagno my issue now comes when I'm trying to save a list to the sqlDb it shows that it was added however when i retrieve my data my List prop is null.
public class UserTask{
[PrimaryKey]
public string ID { get; set; }
public string Title { get; set; }
[TextBlob("TagBlobbed")]
public List<string> TagsList { get; set; }
public string TagBlobbed { get; set; }
public string Details { get; set; }
[Ignore]
public string Comment { get; set; }
public UserTask()
{
TagsList = new();
}
}
public static class PlannerDataService
{
static SQLiteAsyncConnection db;
static async Task Init()
{
if (db != null) return;
var databasePath = Path.Combine(FileSystem.AppDataDirectory, "DbTasks.db");
db = new SQLiteAsyncConnection(databasePath);
await db.CreateTableAsync<UserTask>();
}
public static async Task AddNewTask(UserTask t)
{
await Init();
var task = new UserTask()
{
ID = t.ID,
TagsList = t.TagsList,
Details = t.Details,
Title = t.Title
};
await db.InsertAsync(task);
}
public static async Task<List<UserTask>> GetUserTasks()
{
await Init();
var tasks = await db.Table<UserTask>().ToListAsync();
var t = tasks.OrderByDescending(a => a.ID).ToList();
return t;
}
public static async Task RemoveTask(string id)
{
await Init();
await db.DeleteAsync<UserTask>(id);
}
public static async Task UpdateTask(UserTask t)
{
await Init();
var task = new UserTask()
{
ID = t.ID,
TagsList = t.TagsList,
Details = t.Details,
Title = t.Title
};
await db.UpdateAsync(task);
}
}
I've seen + read questions similar to this and I've tried following their advice to no luck which is why I'm posting for a better solution without changing much of my code.

How can i create column with information about who Created an element

Can you help me. How can i create column with information about who Created an element(column CreatedBy) I created asp net core 2.0 MVC Web-Application with Windows authentication. I implemented information about who was modifying last, sucessfuly, but i dont get it with CreatedBy.
My model is
public class TestModel
{
public int Id { get; set; }
public string Description { get; set; }
public string CreatedBy { get; set; }
[DataType(DataType.Date)]
public DateTime Created { get;}
public TestModel()
{
Created = DateTime.Now;
}
public string ModifiedBy { get; set; }
public DateTime Modified { get; set; }
}
My controller of Create
public async Task<IActionResult> Create([Bind("Id,Description")] TestModel testModel)
{
if (ModelState.IsValid)
{
_context.Add(testModel);
testModel.CreatedBy = this.User.Identity.Name;
// testModel.ModifiedBy = this.User.Identity.Name;
testModel.Modified = DateTime.Now;
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(testModel);
}
Edit controller
public async Task<IActionResult> Edit(int id, [Bind("Id,Description")] TestModel KestModel)
{
if (id != KestModel.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TestModelExists(KestModel.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View();
}
As per your comment I understood that you want to update modifyby on update request and assign createdby at the create request,
For this you should check the Id which is already assign or not, If id is already assign than it is update request else it is create request
Try below code changes
public async Task<IActionResult> Create([Bind("Id,Description")] TestModel testModel)
{
if (ModelState.IsValid)
{
if(testModel.Id > 0){
// the entity is already created and it is modify request
_context.Entry(testModel).State = EntityState.Modified;
testModel.ModifiedBy = this.User.Identity.Name;
testModel.Modified = DateTime.Now;
}
else{
// it is create request
_context.Entry(testModel).State = EntityState.Added;
testModel.CreatedBy = this.User.Identity.Name;
testModel.Created = DateTime.Now;
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(testModel);
}

Dependent Object Creation

Environment:
I am working in Webapi. There is 2 entity classes which are follows;
public class Class1
{
public Class1()
{
this.items = new HashSet<Class2>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Class2> items { get; set; }
}
public class Class2
{
public int Id { get; set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public virtual Class1 class1 { get; set; }
}
Business Layer:
The buniess layer have the following codes;
public class Class1Logic : IClass1Logic
{
private readonly IClass1Repository _repo;
public Class1Logic(IClass1Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass1ItemAsync(Class1 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class1> GetClass1ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
public class Class2Logic : IClass1Logic
{
private readonly IClass2Repository _repo;
public Class2Logic(IClass2Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass2ItemAsync(Class2 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class2> GetClass2ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
ViewModels:
public class Class1Model
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Class2Model
{
public int Id { get; internal set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public string Class1Name { get; internal set; }
}
Controllers:
There are 2 contrtollers like Class1Controller and Class2Controller. Both have all CRUD operations.
[RoutePrefix("api/class1items")]
public class Class1Controller : ApiController
{
private readonly IClass1Logic _class1Logic;
private ModelFactory TheFactory;
public Class1Controller(IClass1Logic class1Logic)
{
_class1Logic = class1Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class1Model class1Model)
{
var item = TheFactory.Parse(class1Model);
bool result = await _class1Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetLabById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
[Route("{id:int}", Name = "GetClass1ItemById")]
public async Task<IHttpActionResult> GetClass1Item(int id)
{
Class1 item = await _class1Logic.GetClassItemAsync(id);
if (item == null)
{
return NotFound();
}
return Ok(TheFactory.Create(item));
}
}
[RoutePrefix("api/class2items")]
public class Class2Controller : ApiController
{
private readonly IClass2Logic _class2Logic;
private ModelFactory TheFactory;
public Class2Controller(IClass2Logic class2Logic)
{
_class2Logic = class2Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class2Model class2Model)
{
var item = TheFactory.Parse(class2Model);
***//Here item should include Class1 object even if user give ClassId in class2Model***
bool result = await _class2Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetClass2ItemById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
}
There is not dependecies in Class1. So all operations are fine. In Class2Controller post method, I got the model object as following to create Class2.
{
"id": 0,
"name": "string",
"class1Id": 1
}
Understanding:
I need to return this viewmodel to user after the create the record. The record created successfully but when mapping to viewmodel i got null exception as Class1 object not in the Class2 object.
In order to get the Class2 object including class1 object, I need to give the class1Object in the request object.
For this i need to find the Class1 object with Class1Id in the request object.
ViewMapper Code:
public class ModelFactory
{
public Class1Model Create(Class1 item)
{
return new Class1Model
{
Id = item.Id,
Name = item.Name
};
}
public Class2Model Create(Class2 item)
{
return new Class2Model
{
Id = item.Id,
Name = item.Name,
Class1Id = item.class1.Id,
Class1Name = item.class1.Name
};
}
public Class1 Parse(Class1Model modelItem)
{
return new Class1
{
Id = modelItem.Id,
Name = modelItem.Name
};
}
public Class2 Parse(Class2Model modelItem)
{
return new Class2
{
Id = modelItem.Id,
Name = modelItem.Name,
Class1Id = modelItem.Class1Id,
***/*Issue Place*/
//class1 = Need to set property by getting object using modelItem.Class1Id***
};
}
}
Issue:
Now i need to call get method of Class1Controller by passing Class1Id.
How to call and is this correct? or my design is bad?
This is initial case. If my Class3 have both Class1 and Class2 again i need to call methods of Class1 and Class2.
Please help to find the correct solution in this case
Note: I added comments the issue area to understand
Well, just to fix this issue you need to manually call _class1Logic.GetClass1ItemAsync after saving. However this doesn't look good.
More elegant ways to fix it:
1) If you always need Class2.Class1 field to be filled use Include when you fetch data (in repository): dbContext.Set<Class2>().Include(c => c.class1).
2) Also you can turn on LazyLoading for EF - I assume it should work in your case.
3) Inject class1Repo to class2Logic and fix up class1 reference after saving - in case if you don't want to enable lazy loading or item was detached from context after save method
Thoughts about design:
I suggest you to look at Automapper or simular libraries instead of ModelFactory where you going to have all mapping logic
Edit: About generic repository: you can modify you GetAsync method
public async Task<T> GetAsync<T>(int id, params Expression<Func<T, object>>[] includes)
where T: class, IEntity
{
var query = context.Set<T>().AsQueryable();
if (includes.Length > 0)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
return await query.FirstOrDefaultAsync(x => x.Id == id);
}
IEntity interface:
interface IEntity
{
int Id { get; }
}
With this implementation you can use
await _repo.GetAsync<Class2>(id, x => x.class1);

Async lambda expressions in Xunit Assert.Throws

I have some test code asserting duplicate Users cannot be created through my UserRepository.
User.cs:
public class User
{
public int Id { get; set; }
public string AccountAlias { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public bool IsActive { get; set; }
}
UserRepository.cs:
public class UserRepository
{
public virtual async Task<User> CreateAsync(User entity)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
if (await GetDuplicateAsync(entity) != null)
{
throw new InvalidOperationException("This user already exists");
}
return Create(entity);
}
public async Task<User> GetDuplicateAsync(User user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
return await (from u in Users
where u.AccountAlias == user.AccountAlias &&
u.Id != user.Id &&
u.IsActive
select u).FirstOrDefaultAsync();
}
}
UserRepositoryTests.cs:
public sealed class UserRepositoryTests : IDisposable
{
public UserRepositoryTests()
{
UserRepository = new UserRepository(new FooEntities()); // DbContext
// from EF
}
private UserRepository UserRepository { get; set; }
[Fact]
public void DuplicateUserCannotBeCreated()
{
var testUser = new User // This test user already exists in database
{
Id = 0,
AccountAlias = "domain\\foo",
DisplayName = "Foo",
Email = "foo#bar.com",
IsActive = true
};
Assert.Throws<InvalidOperationException>(async () =>
await UserRepository.CreateAsync(testUser));
}
public void Dispose()
{
if (UserRepository != null)
{
UserRepository.Dispose();
}
}
}
When I run this unit test, Xunit.Sdk.ThrowsException is thrown (i.e. my InvalidOperationException was not thrown):
Assert.Throws() Failure
Expected: System.InvalidOperationException
Actual: (No exception was thrown)
From the debugger, GetDuplicateAsync() was evaluated but when the LINQ query was executed, the result was never returned and thus no exception was thrown. Can anyone help?
xUnit's Assert.Throws (at least on version 1.9.2) is not async-aware. This was fixed in version 2, which now has an Assert.ThrowsAsync method.
So, you can either upgrade to xUnit 2 or create your own method to get it working:
public async static Task<T> ThrowsAsync<T>(Func<Task> testCode) where T : Exception
{
try
{
await testCode();
Assert.Throws<T>(() => { }); // Use xUnit's default behavior.
}
catch (T exception)
{
return exception;
}
return null;
}
await ThrowsAsync<InvalidOperationException>(async () => await UserRepository.CreateAsync(testUser));
From Haacked's gist.
XUnit now handle Assert.ThrowAsync by default
This works for me:
Assert.Throws<AbpValidationException>(() => _personAppService.CreatePersonAsync(new CreatePersonInput { Name = null }));
Just don't use async/await.

Adding value to base class property in ActionFilter

I'm creating a simple Private Message system for my website. Here is the model:
public class PrivateMessage : GlobalViewModel
{
[Key]
public int MessageId { get; set; }
public bool IsRead { get; set; }
public DateTime CreatedDate { get; set; }
[MaxLength(100)]
public string Subject { get; set; }
[MaxLength(2500)]
public string Body { get; set; }
public virtual UserProfile Sender { get; set; }
public virtual UserProfile Receiver { get; set; }
}
I want to check on every page request if you have any new messages, so I can notify the user. Therefore I made a base viewmodel, which contains:
public class GlobalViewModel
{
[NotMapped]
public virtual int NewMessages { get; set; }
}
All other viewmodels inherit from this class. To get the amount of new private messages for the user, I do this:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
DBContext db = new DBContext();
int userID = (int)Membership.GetUser().ProviderUserKey;
int newMessages = db.PrivateMessages.Where(a => a.Receiver.UserId == userID && a.IsRead == false).Count();
base.OnActionExecuted(filterContext);
}
I came to this and the OnActionExecuting is indeed called on every Action. But my question is:
How can I add the newMessages to the GlobalViewModel?
What I want to eventually do, is call this in the 'master' view
You have #Model.NewMessages new messages
You could override the OnActionExecuted event which runs after your action has finished running and which would allow you to inspect the model being passed to the view and potentially modify it by setting some properties on it:
public class PrivateMessageFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutedContext filterContext)
{
GlobalViewModel model = null;
var viewResult = filterContext.Result as ViewResultBase;
if (viewResult != null)
{
// The action returned a ViewResult or PartialViewResult
// so we could attempt to read the model that was passed
model = viewResult.Model as GlobalViewModel;
}
if (model == null)
{
var jsonResult = filterContext.Result as JsonResult;
if (jsonResult != null)
{
// The action returned a JsonResult
// so we could attempt to read the model that was passed
model = jsonResult.Data as GlobalViewModel;
}
}
if (model != null)
{
// We've managed to read the model
// Now we can set its NewMessages property
model.NewMessages = GetNewMessages();
}
}
private int GetNewMessages()
{
int userId = (int)Membership.GetUser().ProviderUserKey;
int newMessages = db.PrivateMessages.Where(a => a.Receiver.UserId == userId && a.IsRead == false).Count();
}
}
As an alternative to using a base view model you could write a custom HTML helper which will return this information.

Categories