AutoMapper sets EF Core entities to detach state - c#

When I map my input model with my database entities using AutoMapper, entity states are changes to 'detached' which means, that the changes aren't saved to the database.
CreateMap<User, UserInput>().ReverseMap();
Add/Update:
[BindProperty]
public IList<UserInput> UsersInput { get; set; }
public async Task<PageResult> OnGetAsync()
{
var users = await _dbContext.Users.ToListAsync();
UsersInput = _mapper.Map<List<UsersInput>>(signers);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var users = await _dbContext.Users.ToListAsync();
foreach (var u in users)
{
Console.WriteLine(_dbContext.Entry(u).State); // => Unchanged
}
users = _mapper.Map(UsersInput, users);
foreach (var u in users)
{
Console.WriteLine(_dbContext.Entry(u).State); // => Detached
}
// Save and return
await _dbContext.SaveChangesAsync(); // => Nothing is saved because entities are Detached
return Page();
}
Has it something to do with the way I map the data?

As long as UsersInput has an id that corresponds to the id in the Users table, then there's no reason to load anything from the DB. Just use the EF Core UpdateRange() method:
public async Task<IActionResult> OnPostAsync()
{
var users = _mapper.Map<List<User>>(UsersInput);
_dbContext.Users.UpdateRange(users);
await _dbContext.SaveChangesAsync();
return Page();
}

Related

When user table gets a new record, create record in another table with user ID in .NET Core identity

I have to code, When user table gets a new record, then automatically create record in siteUSer table with userID and siteCodeId in .net core.
user table does not have siteCodeId as a column. I need to add a record of userId with corresponding siteCodeID into siteUSer table.
public class SiteUsers
{
public int SiteCodeId { get; set; }
public SiteCode SiteCode { get; set; }
public string UserId { get; set; }
public User User { get; set; }
}
This is usersController.cs:
[HttpPost]
public async Task<IActionResult> Create(UserBO userBO)
{
try
{
await _userService.CreateUserAsync(userBO);
return Created(nameof(Get), userBO);
}
catch (Exception ex)
{
return HandleException(ex);
}
}
This is usersService.cs:
public async Task<UserBO> CreateUserAsync(UserBO userBO)
{
var user = new User
{
UserName = userBO.UserName,
Email = userBO.Email,
EmailConfirmed = true,
RecordState = Enums.RecordState.Active,
};
var result = await _userManager.CreateAsync(user, userBO.Password);
if (userBO.Roles.Count > 0)
{
// superadmin users can be created manually
userBO.Roles = userBO.Roles.Where(i => i != "SuperAdmin").ToList();
}
foreach (var item in userBO.Roles)
{
await _userManager.AddToRoleAsync(user, item);
}
return userBO;
}
You are performing three operations:
Create a new user
Add the user to some roles
Create a SiteUsers for the user
Add the following methods for each of these operations in UserService.cs.
Inject the DbContext(assuming you are using EF Core; if not, inject the equivalent service to add SiteUsers to the database). Use this service to add a new SiteUsers to the database.
Also, you can use the email from the UserBO to look up an already created user.
You don't even need to use a for loop to add a user to multiple roles, you can use AddToRolesAsync() that takes in an IEnumerable<string>:
public async Task<IdentityResult> CreateUserAsync(UserBO userBO)
{
var user = new User
{
UserName = userBO.UserName,
Email = userBO.Email,
EmailConfirmed = true,
RecordState = Enums.RecordState.Active,
};
return await _userManager.CreateAsync(user, userBO.Password);
}
public async Task CreateSiteUsersAsync(UserBO userBO, User user)
{
SiteUsers siteUsers = new SiteUsers { UserId = user.Id, User = user };
await _context.AddAsync(siteUsers);
await _context.SaveChangesAsync();
}
public async Task<IdentityResult> AddToRolesAsync(UserBO userBO, User user)
{
if (userBO.Roles.Count > 0)
{
userBO.Roles = userBO.Roles.Where(i => i != "SuperAdmin").ToList();
}
return await _userManager.AddToRolesAsync(user, userBO.Roles);
}
public async Task<User> FindByEmailAsync(string email) => _userManager.FindByEmailAsync(email);
Now, in your controller action, call each method:
public async Task<IActionResult> Create(UserBO userBO)
{
try
{
//Create user
IdentityResult createUserResult = await _userService.CreateUserAsync(userBO);
if(!createUserResult.Succeeded)
{
//Handle error
}
//Find created user
User user = await _userService.FindByEmailAsync(userBO.Email);
if(user is null)
{
//Handle error
}
//Add to roles
IdentityResult addToRolesResult = await _userService.AddToRolesAsync(userBO, user);
if(!addToResult.Succeeded)
{
//Handle error
}
await CreateSiteUsersAsync(userBO, user)
return Created(nameof(Get), userBO);
}
catch (Exception ex)
{
return HandleException(ex);
}
}
Please try the following code , it should work
var userName=UserBPO.email;
var user=_userManager.FindByNameAsync(userName);// You can use FindByEmailAsync as well
if(user!=null)
{
//Assign the role and populate SiteUsers
//Save SiteCode to database and get its ID in siteId
SiteUsers siteUsers = new SiteUsers { UserId = user.Id, SiteCodeId=siteId };
await _context.AddAsync(siteUsers);
}
You can try something like this, which would also fix the problem of getting the user id from the web request context.
Create an API service SiteUserService, that allows you to add site users.
Inject the data context and HTTP context.
Create a method CreateSiteUser() that takes the site code id and generates the SiteUser record.
Call API service SiteUserService method CreateSiteUser() from the web client.
The CreateSiteUser() method can be included within an existing or new controller method.
The service class SiteUserService is shown below:
public class SiteUserService
{
private readonly HttpContext _context;
private ApplicationDbContext _db;
public SiteUserService(IHttpContextAccessor httpContextAccessor,
ApplicationDbContext db)
{
_context = httpContextAccessor.HttpContext;
_db = db;
}
public async Task CreateSiteUser(int siteCodeID)
{
var siteUser = new SiteUsers
{
SiteCodeId = siteCodeID,
SiteCode = new SiteCode()
{
// set your site code properties in here..
},
UserId = _context.Request.HttpContext.User.Id,
User = new User()
{
// set your user properties in here
// e.g. _context.Request.HttpContext.User.Identity.Name
}
};
_db.Add(siteUser);
await _db.SaveChangesAsync();
}
...
}
The SiteCode and User object properties can be populated as needed.

Updating multiple records with context, ASP.NET C#

I want to find everything with a certain matching variable, and then update that variable.
Updating that exact variable for every record that was found.
I know context is used to query the database.
How could I do this?
Its not working:
public async Task<IActionResult> Checkout()
{
IEnumerable<Orders> orders = _ocontext.GetAllOrders();
var movingcart = orders.Where(p => p.Cart == 1);
foreach (var item in movingcart)
{
item.Cart++;
}
_context.Update(movingcart);
await _context.SaveChangesAsync();
return View();
}
EDIT::::
I FIGURED IT OUT!:
public async Task<IActionResult> Checkout()
{
var Temp = (from d in _context.Orders
where d.Cart == 1
select d).Single();
Temp.Cart++;
_context.Update(Temp);
await _context.SaveChangesAsync();
return View();
}
I missed this part where you said "I want to find everything with a certain matching variable, and then update that variable."
To do that, you could do this:
var cartItems = _context.Orders.Where(x => x.Cart == 1).ToList();
foreach (var item in cartItems)
{
item.Cart++;
_context.Update(item);
await _context.SaveChangesAsync();
}
public async Task<IActionResult> Checkout()
{
var temp= _context.Orders.FirstOrDefault(x => x.Cart == 1);
temp.Cart++;
_context.Update(temp);
await _context.SaveChangesAsync();
return View();
}

Entity Framework Core Assign role to user after created

i would like to know how can i associate a specific role to a user when he is created
I have this on my create User page
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Users.Add(User);
await _context.SaveChangesAsync();
_toastNotification.AddSuccessToastMessage("Utilizador Adicionado com sucesso");
return RedirectToPage("./Index");
}
I have tried on the model
public User()
{
IsActivo = true;
UserRoles.Add(new UserRole { RoleId = 4 });
}
this does not work unfortunely.
For both users and roles you should be using the associated managers: UserManager<TUser> and RoleManager<TRole>. There's a number of things that need to happen besides simple persistence that the managers handle and that adding directly to the context does not. Here, though, you only need UserManager<TUser> to handle everything.
public class RegisterModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
public RegisterModel(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
...
}
Then:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
await _userManager.CreateUserAsync(User);
await _userManager.AddToRoleAsync(User, "foo"); // where "foo" is the name of your role
_toastNotification.AddSuccessToastMessage("Utilizador Adicionado com sucesso");
return RedirectToPage("./Index");
}
Ok I took some time rereading entity framework tutorials and this worked for me
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Users.Add(User);
_context.UserRoles.Add(new UserRole { User = User, Role = _context.Roles.FirstOrDefault(r => r.Nome == "User")});
await _context.SaveChangesAsync();
_toastNotification.AddSuccessToastMessage("Utilizador Adicionado com sucesso");
return RedirectToPage("./Index");
}

.NET Core 2.0 MVC - Programmatically change values before calling TryUpdateModelAsync

I have been following this tutorial on .NET Core CRUD operations https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/crud Below is my Edit method
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditUser(string id, ApplicationUser applicationUser)
{
if (id != null)
{
return NotFound();
}
var userToUpdate = await _context.ApplicationUser.SingleOrDefaultAsync(u => u.Id == id);
if(await TryUpdateModelAsync<ApplicationUser>(
userToUpdate,"",m => m.DateUpdated,m=> m.Name, m=>m.Email))
{
try
{
if (applicationUser.IsAdmin)
{
var x = await _userManager.AddToRoleAsync(applicationUser, "Admin");
if (!x.Succeeded)
{
}
}
else
{
await _userManager.AddToRoleAsync(applicationUser, "User");
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch(DbUpdateException ex)
{
//TODO: err
}
return View(applicationUser);
}
}
However, there is no clear documentation on how to then update the fields programmatically before calling the Update using the TryUpdateModelAsync method. For example, I want to set the DateUpdated. Or what if I wanted to make some other field change based on the form value?
The method TryUpdateModelAsync just updates properties of the userToUpdate and doesn't save to database anything. The properties listed in method params should be in the controller's current IValueProvider(e.g. in FormValueProvider)
I want to set the DateUpdated
If you want to change additional properties that are not exist in the form you can do it anywhere but before the entity will be saved by await _context.SaveChangesAsync();:
userToUpdate.DateUpdated = DateTime.Now;
Or what if I wanted to make some other field change based on the form value?
you can get form values from HttpContext.Request.Form:
userToUpdate.SomeProperty = HttpContext.Request.Form["SomeProperty"];

Registering and update model in a single API call

I have an ASP.net API 2 application with a register method, which basically does the following:
var user = new ApplicationUser() { UserName = user.email, Email = user.email };
UserManager.AddToRole(user.Id, "SomeRole");
In the same controller, I would like to assign a model class to the application user, for example:
var parent = db.Parents.Find(parentToCreate.id);
db.SaveChanges();
This gives me an error that the user name is already taken. I know that the issue is relating to there being two model trackers, one for the UserManager.CreateAsync and one for updating the db. Will it be alright to create users without using CreateAsync or is there another way to avoid this error?
Note that I think that this could be achieved by putting a Parent property on the account property, but not all accounts are parents, so I do not want to do this solution. A parent has an account, so there is an account property on the parent.
As requested, the full register method is as follows:
[AllowAnonymous]
[Route("RegisterParent")]
public async Task<IHttpActionResult>RegisterParent(ParentRegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.email, Email = model.email };
Parent parentToCreate = new Parent();
db.Parents.Add(parentToCreate);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.SaveChanges();
try
{
IdentityResult result = await UserManager.CreateAsync(user, model.password);
// The following two lines give an error
parentToCreate.account = user;
// the above two lines give an error
UserManager.AddToRole(user.Id, "ParentRole");
db.SaveChanges();
}
catch (Exception e)
{
Console.Write(e);
// e returns here with message
}
return Ok(200);
}
This is a simplified example based on minimal example provided in OP.
Based on conversation to clarify current design the Parent model would need to be updated to have a proper code first Foreign Key relationship
public class Parent {
//...other properties
//Foreign key
public string accountid { get; set; }
//navigation property
[ForeignKey("accountid")]
public ApplicationUser account { get; set; }
}
With that then you only need to assign the user id when creating the parent.
Refactoring/abstracting out specific responsibilities.
public interface IParentService {
Task AddParentAsync(ApplicationUser user);
}
public class ParentService : IParentService {
ApplicationDbContext db;
public ParentService(ApplicationDbContext db) {
this.db = db;
}
public async Task AddParentAsync(ApplicationUser user) {
Parent parentToCreate = new Parent() {
//...set other properties
accountid = user.Id
};
db.Parents.Add(parentToCreate);
await db.SaveChangesAsync();
}
}
Next separating the action into distinct processes to avoid concurrency issues.
public class AccountController : ApiController {
ApplicationUserManager userManager;
IParentService parentService;
public AccountController(ApplicationUserManager userManager, IParentService parentService) {
this.userManager = userManager;
this.parentService = parentService;
}
[AllowAnonymous]
[Route("RegisterParent")]
public async Task<IHttpActionResult> RegisterParent(ParentRegisterBindingModel model) {
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.email, Email = model.email };
var result = await userManager.CreateAsync(user, model.password);
if (result.Succeed) {
try {
await userManager.AddToRole(user.Id, "ParentRole");
await parentService.AddParentAsync(user);
return Ok();
} catch (Exception e) {
userManager.Delete(user.Id);
Console.Write(e);
// e returns here with message
return BadRequest(); //OR InternalServerError();
}
} else {
foreach (var error in result.Errors) {
ModelState.AddModelError("", error);
}
return BadRequest(ModelState);
}
}
}
You would obviously register dependencies with the DI framework to allow for proper injection.

Categories

Resources