Entity Framework Attach error: An object with the same key already exists in the ObjectStateManager - entity-framework

I'm a newbie with Entity Framework, and I have looked at the questions with the same title, but I haven't found a satisfying answer yet.
Here is my class:
public class MyUser
{
public string FirstName { get; set; }
public virtual ICollection<ProfileSkillEdu> Skills { get; set; }
}
And in the controller I have:
[HttpPost]
public ActionResult EditProfile(MyUser user, string emailAddress)
{
try
{
if (ModelState.IsValid)
{
_unitOfWork.GetMyUserRepository().Update(user);
_unitOfWork.Save();
return View(user);
}
}
catch (DataException)
{
//Log the error
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View(user);
}
In my user repository:
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
I keep getting An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. at dbSet.Attach(entityToUpdate). I watched the variables and I found that if MyUser has only 1 Skill object, it's fine because when it does Attach, the key is unique (value is 0). But if MyUser has 2 skill objects, both primary key have the value of 0 and therefore the error.
Can someone explain why all the Skill object's primary key have the values of 0? Also, is there a simple solution to this problem? I thought it's supposed to be straight forward but I have been struggling with this for hours.
Edit:
I wonder if the problem is because of how I use the context.
In the controller definition I have:
public class MyAccountController : Controller
{
IUnitOfWork _unitOfWork;
public ActionResult EditProfile()
{
if (_user == null)
{
MembershipUser currentUser = Membership.GetUser();
if (currentUser == null)
{
return RedirectToAction("Logon", "Account");
}
Guid currentUserId = (Guid)currentUser.ProviderUserKey;
MyUserService svc = new MyUserService();
MyUser user = svc.GetUserLoaded(currentUserId); //this uses include/eager loading to get the Skills too.
if (user == null)
{
return RedirectToAction("Logon", "Account");
}
else
_user = user;
}
return View(_user);
}
}
In my UnitOfWork I have:
public class UnitOfWork:IUnitOfWork
{
private GlobalContext context = new GlobalContext();
private GenericRepository<MyUser> myUserRepository;
private GenericRepository<Skill> skillRepository;
.. and implementation of Save() and Dispose()
}

Related

Get current user id in dbcontext

I am working on an ASP.NET Core web application. This application works with a SQL Server database and I am using Entity Framework 6.
I want to automatically add, on each entity, a last modification date and user id.
My question is: how can I automatically set the user id to each entity?
I have tried to override SaveChanges() in the DbContext. It works fine, but I can't access the Microsoft identity classes there...
Thanks
(Apologies in advance if the method signatures look a bit odd, I'm one of the rare breed that uses explicit interface implementation to help avoid stale code dust-bunnies littering code as I re-factor)
An example on accessing a session state to get the user from within a DbContext using a session token from a Forms Authentication web application:
[Serializable]
public class UserDetails
{
public int UserId { get; set; }
public string DisplayName { get; set; }
}
public interface ISessionHelper
{
UserDetails CurrentUser { get; set; }
void Clear();
bool IsAuthenticated();
}
public class SessionHelper : ISessionHelper
{
private const string UserDetailsKey = "YourUniqueSessionIdKey";
private ISessionHelper This => this;
UserDetails ISessionHelper.CurrentUser
{
get
{
try
{
var token = (UserDetails)HttpContext.Current.Session[UserDetailsKey];
return token;
}
catch
{
throw new ApplicationException("The current session could not be resolved.");
}
}
set
{
try
{
HttpContext.Current.Session[UserDetailsKey] = value;
}
catch
{
throw new ApplicationException("The current session state could not be set.");
}
}
}
/// <summary>
/// <see cref="ISessionHelper.Clear"/>
/// </summary>
void ISessionHelper.Clear()
{
HttpContext.Current.Session.Clear();
}
/// <summary>
/// <see cref="ISessionHelper.IsAuthenticated(string)"/>
/// </summary>
bool ISessionHelper.IsAuthenticated()
{
try
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var userDetails = This.CurrentUser;
return (userDetails != null && userDetails.UserId != 0);
}
catch
{
return false;
}
}
}
public interface ICurrentUserLocator
{
bool IsUserLoggedIn { get; }
int CurrentUserId { get; }
string CurrentUserDisplayName { get; }
}
public sealed class FormsAuthenticationUserLocator : ICurrentUserLocator
{
private const string UnauthorizedAccessExceptionMessage = "No user is currently logged in.";
private readonly ISessionHelper _sessionHelper = null;
[ExcludeFromCodeCoverage]
private ICurrentUserLocator This => this;
private UserDetails _userDetails = null;
[ExcludeFromCodeCoverage]
private UserDetails CurrentUser
{
get { return _userDetails ?? (_userDetails = SessionHelper.UserDetails); }
}
[ExcludeFromCodeCoverage]
bool ICurrentUserLocator.IsUserLoggedIn => CurrentUser != null;
[ExcludeFromCodeCoverage]
int ICurrentUserLocator.CurrentUserId => CurrentUser?.UserId ?? throw new UnauthorizedAccessException(UnauthorizedAccessExceptionMessage);
[ExcludeFromCodeCoverage]
string ICurrentUserLocator.CurrentUserDisplayName => CurrentUser?.DisplayName ?? throw new UnauthorizedAccessException(UnauthorizedAccessExceptionMessage);
public FormsAuthenticationUserLocator(ISessionHelper sessionHelper)
{
_sessionHelper = sessionHelper ?? throw new ArgumentNullException("sessioNHelper");
}
}
Assuming you are using dependency injection with a container, register the two classes against their respective interfaces in the container on a per-request lifespan. Then add a dependency on ICurrentUserLocator into your DbContext:
public class AppDbContext : DbContext
{
private readonly ICurrentUserLocator _currentUserLocator = null;
public AppDbContext(ICurrentUserLocator currentUserLocator)
{
_currentUserLocator = currentUserLocator ?? throw new ArgumentNullException("currentUserLocator");
}
}
Now in your overridden SaveChanges you can fetch the current user ID:
var currentUser = Users.Single(x => x.UserId == _currentUserLocator.CurrentUserId);
var updatedEntities = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified)
.Select(x => x.Entity)
.Cast<EditableEntityBase>();
foreach (var entity in updatedEntities)
{
entity.LastModifiedBy = currentUser;
entity.LastModifiedAt = DateTime.Now;
}
var insertedEntities = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added)
.Select(x => x.Entity)
.Cast<EditableEntityBase>();
foreach (var entity in insertedEntities)
{
entity.CreatedBy = entity.LastModifiedBy = currentUser;
entity.CreatedAt = entity.LastModifiedAt = DateTime.Now;
}
If you only expose the FKs rather than user navigation properties then you can just set the ID. If your app has a Log Out feature be sure to call the SessionHelper.Clear() method. In this example I modified the session helper I typically use to just focus on the current user details to keep it simple. It normally tracks a few other details, hence why the Clear() method wipes the entire session. It's a standardized wrapper for handling known session state rather than littering code with Session["somestring"] everywhere.

EF Core 2.0 update - tracking issue

I am getting changed entity from fronted, mapping it on backend side and simply want to update it in database.
Update is performing like this:
[HttpPut("{id}")]
public IActionResult Update(string id, [FromBody]Worker worker)
{
using (var dbContext= new MyDbContext())
{
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var entity = dbContext.Workers.FirstOrDefault(r => r.Id == worker.Id);
if (entity == null) return BadRequest();
dbContext.Workers.Update(worker);
dbContext.SaveChanges();
return Ok();
}}
Before this action, i am getting the list of users and sending it to frontend.
Although I set QueryTrackingBehavior to NoTracking, i am getting exception:
System.InvalidOperationException: 'The instance of entity type 'Contract' cannot be tracked because another instance with the key value 'Id:4' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'
Where Contract is related entity for Worker which is updated...
Any idea what i am doing wrong here?
UPDATE:
Worker - Contract relation:
public class Worker: IId
{
public int Id { get; set; }
public ICollection<Contract> Contracts{ get; set; }
}
public class Contract: IId
{
public int Id { get; set; }
public int WorkerId { get; set; }
public Worker Worker { get; set; }
}
Okay! got the problem in your code. You didn't map the updated entity to the existing entity that you pulled from the database. You have to map the updated entity to the existing entity. To do so you can use AutoMapper or explicit mapping as follows:
You can solve the problem as follows:
[HttpPut("{id}")]
public IActionResult Update(string id, [FromBody]Worker worker)
{
using (var dbContext= new MyDbContext())
{
var entityToBeUpdated = dbContext.Workers.AsNoTracking().FirstOrDefault(r => r.Id == worker.Id);
if (entity == null) return BadRequest();
entityToBeUpdated.Property1 = worker.Property1;
entityToBeUpdated.Property2 = worker.Property2;
// Do the same for the other changed properties as well
dbContext.Workers.Update(entityToBeUpdated);
dbContext.SaveChanges();
return Ok();
}
}
Alternatively you can try as follows:
[HttpPut("{id}")]
public IActionResult Update(string id, [FromBody]Worker worker)
{
using (var dbContext= new MyDbContext())
{
var entityToBeUpdated = dbContext.Workers.FirstOrDefault(r => r.Id == worker.Id);
if (entity == null) return BadRequest();
entityToBeUpdated.Property1 = worker.Property1;
entityToBeUpdated.Property2 = worker.Property2;
// Do the same for the other changed properties as well.
dbContext.SaveChanges();
return Ok();
}
}

REST API - CreatedAtRoute method doesn't return a value

I'm building some REST API server in .NET Core and using Postman software to test it. I have a problem with POST method which doesn't return me any value ("Could not get any response") when I try to perform second Add operation on my DBContext class inside CreateUser method. My code :
UsersController :
[Produces("application/json")]
[Route("api/[controller]")]
public class UsersController : Controller
{
private readonly DBContext _context;
#region CONSTRUCTOR
public UsersController(DBContext context)
{
_context = context;
}
#endregion
#region HTTP GET
// GET: api/users || api/users?cardnr=xxx
[HttpGet]
public async Task<IActionResult> GetUsers(string cardNr)
{
if (String.IsNullOrEmpty(cardNr))
{
try
{
var users = await _context.Users.ToListAsync();
if (users.Any())
{
return Json(users);
}
else
{
return NotFound();
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
else
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.Cards.Any(c => c.CardNumber.Equals(cardNr)));
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
//GET: api/users/1
[HttpGet("{id}", Name = "GetUserByID")]
public async Task<IActionResult> GetUserByID(Int32 id)
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.IDUser == id);
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
#endregion
#region HTTP POST
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] Models.User userToCreate, string userGroupID)
{
if (userToCreate == null)
{
return BadRequest();
}
else
{
try
{
_context.Users.Add(userToCreate);
int parsingResult;
// if user passed userGroupID
if (userGroupID != null)
{
// parsing if userGroupID is a number
if (!int.TryParse(userGroupID, out parsingResult))
{
return BadRequest();
}
else
{
// if client want to assign a new user to some group
if (parsingResult > 0)
{
// creating new record in UserGroup table - assigning a user to group
var userGroup = new Models.UserGroup();
_context.Entry(userGroup).Property("IDGroup").CurrentValue = parsingResult;
_context.Entry(userGroup).Property("IDUser").CurrentValue = userToCreate.IDUser;
_context.UserGroups.Add(userGroup); // NOTE HERE
}
}
}
await _context.SaveChangesAsync();
return CreatedAtRoute("GetUserByID", new { id = userToCreate.IDUser }, userToCreate);
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
#endregion
}
User model :
public class User
{
[Key]
public int IDUser { get; set; }
[Required]
public string Name { get; set; }
public List<UserGroup> UsersGroups { get; set; }
}
UserGroup model :
public class UserGroup
{
public Group Group { get; set; }
public User User { get; set; }
}
DBContext class :
public class DBContext : DbContext
{
public DBContext(DbContextOptions<DBContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDUser");
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasKey( new string[]{ "IDUser", "IDGroup" });
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.Group)
.WithMany(g => g.UsersGroups)
.HasForeignKey("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.User)
.WithMany(u => u.UsersGroups)
.HasForeignKey("IDUser");
base.OnModelCreating(modelBuilder);
}
public DbSet<Group> Groups { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserGroup> UserGroups { get; set; }
}
The problem lies in HttpPost method in UsersController.
When I do "normal" POST and pass JSON object which contain a user to add without assigning it to group (empty userGroupID parameter) everything is ok - user gets added to the DataBase and Postman returns me a user with its ID.
screen :
and when I try to add a new user but with adding it to specific group I always get an error :
screen :
Even despite that error new user gets properly added to DB and associated with its group (record gets added to UserGroup table; UserGroup is join table between Users and Groups table). So I have proper data in my DB but I always get this error and I can't return new added user to client who called API and can't get his ID. Am I doing something wrong in my CreateUser method ?
UPDATE :
I have added a comment line in "NOTE HERE" in CreateUser method in UsersController. If I comment whole this line I don't get an error from Postman but obviously I don't get my User associated with its group (I don't get new record added to UserGroup join table). So it seems like another Add method on context object causing an error ... Does it make sense ?
Did you try to debug it?
Set a breakpoint on the row:
if (userToCreate == null)
Send again the request with Postman and debug your app. There you can see what and where it goes wrong.
Please let me know how it is going so I know how can I help you:)

ASP MVC EF6 Multi Tenant based on host

Sorry, another multi tenancy post. I can't find a good solution to site, I have read tons of great posts on multi tenancy for ASP MVC but I still need some good advice.
I have an ASP MVC Entity Framework 6 Code First web application. This app has to work for many different clients using a single database for all of them.
I have an entity for all the clients, and each client can have different hosts.
public class Client
{
public int ClientId { get; set; }
public string Name { get; set; }
...
public ICollection<ClientHost> Hosts { get; set; }
}
public class ClientHost
{
public int ClientId { get; set; }
public Client Client { get; set; }
public string Name { get; set; }
}
I have added a column "ClientId" to all the entities I need to filter, so I can separate data from different clients.
public class SomeEntity
{
public int Id { get; set; }
...
public int ClientId { get; set; }
}
First thing I need is, base on the host, retrieve the ClientId to work with.
private static int GetClientId()
{
var currentClient = Convert.ToInt32(HttpRuntime.Cache[CacheClient]);
if (currentClient != null) return currentClient;
lock (Synclock)
{
using (var dataContext = new MyDataContext())
{
var urlHost = HttpContext.Current.Request.Url.Host;
currentClient = dataContext.Clients
.FirstOrDefault(p => p.Hosts.Any(h => h.Name == urlHost));
if (currentClient == null) return null;
HttpRuntime.Cache.Insert(CacheClient, currentClient, null, Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(0), CacheItemPriority.Default, null);
return currentClient;
}
}
}
QUESTION 1
As you see I get the clientId from DB and store it in cache, so I don't have to call DB every time I need it.
I don't know if there is a better approach to get the client Id or, better, to store it.
EDIT
After investigation I have created a variable in DbCOntext and initialize it in the Startup.cs file.
public class MyDataContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public static string ClientId { get; set; }
public MyDataContext() : base("MyDataBase") { }
public static MyDataContext Create()
{
return new myDataContext();
}
....
}
In Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
MyDataContext.ClientId = ClientConfiguration.GetCurrentClientId();
ConfigureAuth(app);
}
}
QUESTION 2
Once I have the ClientId, I need to add a filter to every query that needs it. Doing this manually can take you to make many errors or forget to do it in some places.
I need a way that the application can add the filter to all queries automatically (only those entities that need it), so I don't have to worry about a client getting other client's data. Also I need to add the ClientId to all the Insert and Update commands.
I have read about filtering and/or use EF Interceptors, but after reading some posts about that I can't figure out how to do it. Need some help here.
Thanks in advance.
EDIT
In order to solve QUESTION 2 I have followed this great post by Xabikos:
http://xabikos.com/2014/11/17/Create-a-multitenant-application-with-Entity-Framework-Code-First-Part-1/
I have changed it a little bit, since I don't use Users to get the current tenant and instead I use the host. This is part of the program I don't know yet how I'm going to solve but, assuming I already have the ClientId I can add filters to all the queries without realizing that is happening:
I have replaced all the user logic:
private static void SetTenantParameterValue(DbCommand command)
{
if (MyDataContext.ClientId == 0) return;
foreach (DbParameter param in command.Parameters)
{
if (param.ParameterName != TenantAwareAttribute.TenantIdFilterParameterName)
continue;
param.Value = MyDataContext.ClientId;
}
}
Same in all the places...
Than I only have to mark the entities that have to filter with TenantAware, indicating the property. In this case I do in my base class and then apply that base class to all the entities I need.
[TenantAware("ClientId")]
public abstract class ClientEntity : Entity, IClientEntity
{
public int ClientId { get; set; }
public Client Client { get; set; }
}
Here are a couple of things I have done in the past that might help.
Question 1:
I am not a big fan of session as the web is supposed to be stateless. However, it is sometimes necessary. Your approach is reasonable. You could also use cookies as well. What I use are Json Web Tokens (JWT) via my authentication provider (Auth0.com). For each request as it is authenticated, I look for this client id. Here is an example. This is MVC 6 as well. You could do the same type of things w/ cookies.
public class Auth0ClaimsTransformer : IClaimsTransformer
{
private string _accountId = AdminClaimType.AccountId.DefaultValue;
private string _clientId = AdminClaimType.ClientId.DefaultValue;
private string _isActive = AdminClaimType.IsActive.DefaultValue;
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
foreach (var claim in context.Principal.Claims)
{
switch (claim.Type)
{
case "accountId":
_accountId = claim.Value ?? _accountId;
break;
case "clientId":
_clientId = claim.Value ?? _clientId;
break;
case "isActive":
_isActive = claim.Value ?? _isActive;
break;
}
}
((ClaimsIdentity)context.Principal.Identity)
.AddClaims(new Claim[]
{
new Claim(AdminClaimType.AccountId.DisplayName, _accountId),
new Claim(AdminClaimType.ClientId.DisplayName, _clientId),
new Claim(AdminClaimType.IsActive.DisplayName, _isActive)
});
return Task.FromResult(context.Principal);
}
Then in my Startup.cs Configure method I plug in my claims transformer.
app.UseJwtBearerAuthentication(options);
app.UseClaimsTransformation(new ClaimsTransformationOptions
{
Transformer = new Auth0ClaimsTransformer()
});
Next I use a base authentication controller that parses out my claims into properties I can use in my controller.
[Authorize]
[Route("api/admin/[controller]")]
public class BaseAdminController : Controller
{
private long _accountId;
private long _clientId;
private bool _isActive;
protected long AccountId
{
get
{
var claim = GetClaim(AdminClaimType.AccountId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _accountId);
return _accountId;
}
}
public long ClientId
{
get
{
var claim = GetClaim(AdminClaimType.ClientId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _clientId);
return _clientId;
}
}
public bool IsActive
{
get
{
var claim = GetClaim(AdminClaimType.IsActive);
if (claim == null)
return false;
bool.TryParse(claim.Value, out _isActive);
return _isActive;
}
}
public string Auth0UserId
{
get
{
var claim = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
return claim == null ? string.Empty : claim.Value;
}
}
private Claim GetClaim(AdminClaimType claim)
{
return User.Claims.FirstOrDefault(x => x.Type == claim.DisplayName);
}
Finally in my controller it is trivial to extract which tenant is making the call. e.g.
public FooController : BaseController
{
public async Task<IActionResult> Get(int id)
{
var foo = await _fooService.GetMultiTenantFoo(ClientId, id);
return Ok(foo);
}
}
Question 2:
One of the ways I have used in the past is create a BaseMultiTenant class.
public class BaseMultiTenant
{
public int ClientId {get;set;}
public virtual Client Client {get;set;}//if you are using EF
}
public class ClientHost : BaseMultiTenant
{
public string Name {get;set;}
//etc
}
Then simply create an extension method for multi-tenant based entities. I know this doesn't "do it automatically" but it is an easy way to ensure each multi-tenant entity is being called only by its owner.
public static IQueryable<T> WhereMultiTenant<T>(this IQueryable<T> entity, int clientId, Expression<Func<T, bool>> predicate)
where T : BaseMultiTenant
{
return entity.Where(x => x.ClientId == clientId)
.Where(predicate);
}
Then when someone calls for their resource you can:
var clientHost = _myContext.ClientHosts
.WhereMultiTenant(ClientId,
x => x.Name == "foo")
.FirstOrDefault();
Hope this is helpful.
Also found a similar example using an interface.

Why does Entity Framework create a new entity when I try to associate an existing one?

I have a many-to-many relationship (Users to skills), and when I try to associate an existing skill to a user, it always creates a new one.
User:
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public virtual ICollection<Skill> Skills { get; set; }
}
Skill:
public class Skill
{
public int Id { get; set; }
public string Name { get; set; }
}
OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>()
.HasMany<Skill>(user => user.Skills)
.WithMany();
}
Creating the association:
public ActionResult Skills(SkillsViewModel viewModel)
{
var user = UserManager.FindById(User.Identity.GetUserId());
if(!string.IsNullOrWhiteSpace(viewModel.NewSkill)
&& !user.Skills
.Where(sk => sk.Name == viewModel.NewSkill)
.Any())
{
var foundSkill = this.db.Skills
.Where(sk => sk.Name == viewModel.NewSkill)
.FirstOrDefault();
if(foundSkill != null)
{
user.Skills.Add(foundSkill);
}
else
{
user.Skills.Add(new Skill()
{
Name = viewModel.NewSkill
});
}
}
if(viewModel.SelectedSkillId > 0)
{
var foundSkill = this.db.Skills.Find(viewModel.SelectedSkillId);
user.Skills.Add(foundSkill);
}
this.UserManager.Update(user);
return RedirectToAction("skills");
}
I've stepped through, and verified that I do indeed get a 'foundSkill' from the database, but after I add it to the user, and save the user, the skill associated to the user is not the one I found, but a new one with the same name and different ID.
I figured it out. The UserManager was being loaded with one DbContext, and I was trying to associate a Skill loaded from a different DbContext.
Quick hack to test and fix this was to load the user from the same DbContext as the Skills, update the user, save the DbContext.
Longer term solution would be to ensure that everything uses the same DbContext per request.