The ObjectStateManager cannot track multiple objects with the same key - c#

I'm aware many questions like this one have already been asked, but I just can't seem to understannd what is wrong. This is my code:
[HttpGet]
public ViewResult Edit(int id)
{
User user = userRepository.GetAll().FirstOrDefault(x => x.ID == id);
return View("Edit", user);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(User user)
{
if (ModelState.IsValid)
{
user.Password = HashHelper.GetHash(user.Password);
if (user.ID == 0) // new user
{
User testUser = userRepository.GetAll().FirstOrDefault(x => x.Name.Equals(user.Name));
if (testUser == null)
userRepository.AddEntity(user);
else
{
ModelState.AddModelError("", "Deze gebruikersnaam bestaat al");
return View(user);
}
}
else // edit existing user
{
User tempUser = userRepository.GetAll().First(x => x.ID == user.ID);
if (!user.Name.Equals(tempUser.Name))
{
// naam werd aangepast
int count = userRepository.GetAll().Count(x => x.Name.Equals(user.Name));
if (count > 0)
{
ModelState.AddModelError("", "Deze gebruikersnaam bestaat al");
return View(user);
}
}
userRepository.UpdateEntity(user);
}
userRepository.SaveChanges();
return RedirectToAction("Index");
}
else
{
return View(user);
}
}
UpdateEntity:
public void UpdateEntity(T entity)
{
var entry = context.Entry(entity);
if (entry.State == EntityState.Detached)
context.Set<T>().Attach(entity);
context.Entry<T>(entity).State = EntityState.Modified;
}
This results in this error:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
I don't get it. Why doesn't this work and how can I fix it?

there are multiple problems;
Do not call GetAll() if you want to fetch a single entity, what if you have thousands of entities in the database. Just implement a SingleOrDefault in the repository.
Use :
User testUser = userRepository.FirstOrDefault(x => x.Name.Equals(user.Name));
instead of :
User testUser = userRepository.GetAll().FirstOrDefault(x => x.Name.Equals(user.Name));
After fetching an existing user from database, just update this item using new values instead of trying to persist the one returned from page;
use
User tempUser = userRepository.GetAll().First(x => x.ID == user.ID);
tempUser.UserName = user.UserName;
....
SaveChanges();
instead of trying to persist user retrieved from page.
you need to decide the key of your entity; is it name, is it Id or both.

Related

The instance of entity type 'Bot' cannot be tracked because another instance with the same key value for {'Id'}

The instance of entity type 'Bot' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider us...
I know what this problem means. It is happening right here _context.Bots.Update(bot);.
The question is: is that a good way to solve this by adding .AsNoTracking() to all GetByXXX methods? Any suggestions?
[HttpPut("{id}")]
public async Task<ActionResult> UpdateAsync([FromRoute] int id, [FromBody] BotCreateUpdateDto botCreateUpdateDto)
{
if (id != botCreateUpdateDto.Id)
{
return BadRequest(new { Error = "Invalid ID." });
}
var user = await _userService.GetByEmailAsync(botCreateUpdateDto.Email);
if (user == null)
{
return BadRequest(new { Error = "Invalid e-mail." });
}
var cryptoPair = await _cryptoPairService.GetBySymbolAsync(botCreateUpdateDto.Symbol);
if (cryptoPair == null)
{
return BadRequest(new { Error = "Invalid crypto pair." });
}
var timeInterval = await _timeIntervalService.GetByIntervalAsync(botCreateUpdateDto.Interval);
if (timeInterval == null)
{
return BadRequest(new { Error = "Invalid time interval." });
}
var bot = new Bot
{
Id = botCreateUpdateDto.Id,
Name = botCreateUpdateDto.Name,
Status = botCreateUpdateDto.Status,
UserId = user.Id,
CryptoPairId = cryptoPair.Id,
TimeIntervalId = timeInterval.Id
};
bool updated;
try
{
updated = await _botService.UpdateAsync(bot);
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
if (updated)
{
return NoContent();
}
return NotFound();
}
public async Task<User> GetByEmailAsync(string email)
{
return await _context.Users
.Include(e => e.UserRoles)
.ThenInclude(e => e.Role)
.Include(e => e.Bots)
.SingleOrDefaultAsync(e => e.Email == email);
}
public async Task<CryptoPair> GetBySymbolAsync(string symbol)
{
return await _context.CryptoPairs
.Include(e => e.Bots)
.AsNoTracking()
.SingleOrDefaultAsync(e => e.Symbol == symbol);
}
public async Task<TimeInterval> GetByIntervalAsync(KlineInterval interval)
{
return await _context.TimeIntervals
.Include(e => e.Bots)
.AsNoTracking()
.SingleOrDefaultAsync(e => e.Interval == interval);
}
public async Task<bool> UpdateAsync(Bot bot)
{
_context.Bots.Update(bot);
var updated = await _context.SaveChangesAsync();
return updated > 0;
}
Sorry, I really didn't realise that I was creating a new Bot object instead of just getting it by id. There is also no point of .AsNoTracking.
var bot = await _botService.GetByIdAsync(id);
bot.Name = botCreateUpdateDto.Name;
bot.Status = botCreateUpdateDto.Status;
bot.CryptoPairId = cryptoPair.Id;
bot.TimeIntervalId = timeInterval.Id;
This solves the problem for anyone having same problem as me. Keep in mind that my all services are scoped as well as the DbContext. If yours are singleton, that will cause this problem as well.
I got same issue like :
The instance of entity type 'ClassName' cannot be tracked because another instance with the key value '{Id: 1}'
is already being tracked. When attaching existing entities, ensure that only one entity instance
with a given key value is attached.
I resolved with remove this .AsNoTracking() when fetch data from DB.

ASP.NET MVC: Get user id of currently logged in user

I am new to ASP.NET MVC and am trying to create a web app.
The problem I have is that in the controller class I need to get the UserID of the current user, but I am very confused about how one would do that.
Also, it seems that the user is not authenticated after logging in, because if I use the [Authorize] annotation it throws an HTTP Error 401.0 - Unauthorized error.
This is my Authentication.cs class:
public static class Authentication
{
public static bool CreateNewTicket(User user, bool rememberMe)
{
try
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
user.Email,
DateTime.Now,
DateTime.Now.AddDays(5),
rememberMe,
user.ID.ToString(),
FormsAuthentication.FormsCookiePath
);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.HttpOnly = true;
if (!HttpContext.Current.Request.IsLocal)
cookie.Secure = true;
HttpContext.Current.Response.Cookies.Add(cookie);
return true;
}
catch
{
return false;
}
}
public static bool AuthUser(string Email, string Password)
{
using (var db = new AntContext())
{
string password = Password;
string email = Email;
string hashedPW = GetHash(password);
bool userValid = db.Users.Any(user => user.Email == email && user.Password == hashedPW);
if (userValid)
{
var actUser = db.Users.FirstOrDefault(u => u.Email == Email && u.Password == hashedPW);
if (!actUser.IsLocked)
{
if (CreateNewTicket(actUser, false))
{
return true;
}
else
{
return false;
}
}
else if (actUser.IsLocked)
{
}
}
return false;
}
}
The actual problem happens when I try to store data in a database.
[HttpPost]
public ActionResult Q_FirstPage(ViewModels.Q1_Answer_VM vm)
{
vm.Qst = new Models.Questionnaire();
vm.Qst.NumericAnswers = new List<Models.NumericAnswer>();
vm.Qst.TextAnswers = new List<Models.TextAnswer>();
vm.Qst.IsComplete = false;
vm.Qst.StartedOn = DateTime.Now;
vm.Qst.NumericAnswers.Add(vm.Breite);
vm.Qst.NumericAnswers.Add(vm.Tiefe);
vm.Qst.NumericAnswers.Add(vm.Hoehe);
vm.Qst.TextAnswers.Add(vm.Sonstiges);
//vm.qst.User_ID = 22; if I set the User ID manually, it works
db.Questionnaires.Add(vm.Qst);
db.SaveChanges();
return View();
}
The Viewmodel works fine and returns the data input, but the UserID is null. The data table "Questionnaire" uses the UserID as a foreign key, which makes it throw an error when it comes to the savedata() part because I guess it expects the correct UserID. So I guess I need to get the current UserID, pass it to the instantiated object which is then passed to the data context and then saved into the database.
Unfortunately, I find it very hard to find complete information about how user authentication works in ASP.NET.
If you need more information, please let me know.
This is my Login method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(Login_VM login_vm)
{
if (!ModelState.IsValid)
{
return View(login_vm);
}
if (Authentication.AuthUser(login_vm.Email, login_vm.Password) == true && (login_vm.Email != null || login_vm.Password != null))
{
Classes.Authentication.CreateNewTicket(login_vm.usr, true);
return RedirectToAction("Login");
}
else
return View("~/Views/Home/Index.cshtml");
}
And this is my registration method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddUser(User model)
// add new User to db
{
if (ModelState.IsValid)
{
User usr = new Models.User();
usr = model;
model.Password = Authentication.GetHash(model.Password);
db.Users.Add(model);
db.SaveChanges();
}
return View();
}
Solved the problem by following this link: howto get the user id from a FormsAuthentication page in asp.net MVC? posted by https://stackoverflow.com/users/2516718/derloopkat
The System.Web.HttpContext.Current.User.Identity.Name Function returns the "name" attribute in the Authentication Ticket, which in my case was the email address. I then got the User ID by having a query to the Users database.
db.Users.Where(x => x.Email == System.Web.HttpContext.Current.User.Identity.Name).FirstOrDefault().ID;
Thanks for everybody's help.
Update in 2020: The query can be simplified to:
db.Users.FirstOrDefault(x => x.Email == System.Web.HttpContext.Current.User.Identity.Name).ID;
There are two simple ways to get current user in MVC 5.
If you are inside the controller class,the current user can be fetched as follows,
string userId = User.Identity.GetUserId();
Do not forget to add namespace:
using Microsoft.AspNet.Identity;
Other scenario could be that you are not inside the controller class and want to fetch the user information. You can fetch that using HttpContext class.
HttpContext.Current.User.Identity.GetUserId();

Form Base Authentication not working when I use random Database column

I am using MVC form base custom authentication using SQL database. I've Column with CustomerRole name.
I am checking Authorization as per following:
TestController.CS
[Authorize]
public ActionResult Index()
{
return View();
}
[Authorize(Roles="admin")]
public ActionResult AdminPage()
{
return View();
}
AccountController.cs
[HttpPost]
public ActionResult Login(UserModel model, string returnUrl)
{
// Lets first check if the Model is valid or not
if (ModelState.IsValid)
{
using (userDbEntities entities = new userDbEntities())
{
string username = model.username;
string password = model.password;
// Now if our password was enctypted or hashed we would have done the
// same operation on the user entered password here, But for now
// since the password is in plain text lets just authenticate directly
bool userValid = entities.Tbl_UserMast.Any(user => user.UserName == username && user.UserPassword == password);
// User found in the database
if (userValid)
{
FormsAuthentication.SetAuthCookie(username, false);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
So when I go AdminPage Action. It shows me I am not Authorized.
If I change my column name as Roles, it is working. But I am not allowed to change column name. Is there any other alternative, where I can use Authorization with same column name
You should Try Custom Authentication Filer
Try this:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//let us take out the username now
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
using (userDbEntities entities = new userDbEntities())
{
var user = entities.Users.SingleOrDefault(u => u.username == UserName);
roles = user.UserRole;
}
//let us extract the roles from our own custom cookie
//Let us set the Pricipal with our user specific details
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
}
catch (Exception)
{
//somehting went wrong
}
}
}
}

Saving Entity Framework object is deleting existing one

I have an information page which is being posted to database using Entity Framework. Everything is fine but when I initially enter a record and hit save twice, the record is deleted from database (it actually saves after first save click). This problem doesn't reflect for modifying and occurs for new entry.
Here is my code
public async Task<IHttpActionResult> Put(int id, [FromBody]CandidateLanding landing)
{
var result = await candidateContext.CandidateLanding
.Where(x => x.UserID == id)
.AsNoTracking()
.SingleOrDefaultAsync();
if (result != null)
{
if (landing.ID == 0)
{
landing.ID = result.ID;
}
}
if (ModelState.IsValid)
{
if (landing.ID > 0)
{
candidateContext.Entry(landing).State = EntityState.Modified;
}
else
{
landing.UserID = id;
candidateContext.CandidateLanding.Add(landing);
}
await candidateContext.SaveChangesAsync();
}
return CreatedAtRoute("DefaultApi", new { id = landing.ID }, landing);
}

How to add roles from database

I have my ASP.NET MVC 4 project and database (SQL Server 2008)
And I've created an entity framework model, with auto-generated models.
And in the database there is a table called Roles (2 fields, Id and name)
There are 3 roles: admin, moderator, user.
Plus Account controller:
public class AccountController : Controller
{
private korovin_idzEntities db = new korovin_idzEntities();
//
// GET: /Account/LogOn
public ActionResult LogOn()
{
return View();
}
//
// POST: /Account/LogOn
[HttpPost]
public ActionResult LogOn(LogOnModel model/*, string returnUrl*/)
{
if (ModelState.IsValid)
{
var user = db.Users.Where(x => x.username == model.UserName && x.password == model.Password).FirstOrDefault();
if (user != null)
{
user.isRemember = model.RememberMe;
db.SaveChanges();
ViewBag.UserName = model.UserName;
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
FormsAuthentication.RedirectFromLoginPage(model.UserName, false);
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
return View(model);
}
}
Where and how can i initialize roles in my asp.net mvc application? I've tried to check whether role exists and to initialize role by rolemanager in account controller, but i think it's not a good solution.
Is it possible to initialize roles in global.asax.cs?
I know that I should attach roles to user in log on function.
Thanks in advance :)
Here is my solution, I thought that there is some kind of a structure for storing a names of roles and there is needed to initialize this structure, but i was wrong, and after googling, I've found the solution:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
var context = HttpContext.Current;
if (context.Request.IsAuthenticated)
{
string[] roles = LookupRolesForUser(context.User.Identity.Name);
var newUser = new GenericPrincipal(context.User.Identity, roles);
context.User = Thread.CurrentPrincipal = newUser;
}
}
#region helper
private string[] LookupRolesForUser(string userName)
{
string[] roles = new string[1];
CosmosMusic.Models.korovin_idzEntities db = new CosmosMusic.Models.korovin_idzEntities();
var roleId = db.Users.Where(x => x.username == userName).FirstOrDefault().id_role;
roles[0] = db.Role.Where(y => y.id_role == roleId).FirstOrDefault().name;
return roles;
}
#endregion

Categories

Resources