0

I have an API Controller for writing new users to a database, which looks like this:

public abstract class ApiBaseController : ApiController
{        
    protected UserManager<User, Guid> UserManager { get; set; }

    protected dbcontext Repo { get; private set; }

    public ApiBaseController()
    {
        UserManager = HttpContext.Current.GetOwinContext().GetUserManager<XUserManager>();
        Repo = HttpContext.Current.GetOwinContext().Get<dbcontext>();
    }
}

public class UsersController : ApiBaseController
{
    [HttpPost]
    [FeatureAuthorization(FeatureName = "Users", Permission = FeaturePermissions.Write)]
    public IHttpActionResult Post([FromBody]CreateUserRequest req)
    {
        if (!ModelState.IsValid)
        {
            return ResponseMessage(Request.CreateResponse(HttpStatusCode.BadRequest, ModelState.ToApiResponse()));
        }

        var user = UserManager.FindByName(req.UserName);

        //some validation happens here

        user = new User()
        {
            AuthenticationMethod = req.AuthenticationMethod,
            DomainLDAP = req.DomainLDAP,
            EmailAddress = req.EmailAddress,
            FirstName = req.FirstName,
            SecondName = req.SecondName,
            MentionHandle = req.MentionHandle,
            MobileNumber = req.MobileNumber,
            UserName = req.UserName,
            IsDeleted = false
        };

        IdentityResult createResult = null;
        if (req.AuthenticationMethod == AuthenticationMethod.Internal)
        {
            createResult = UserManager.Create(user);
        }
        else
        {
            createResult = UserManager.Create(user, req.Password);
        }

        if (!createResult.Succeeded)
        {
            return ResponseMessage(Request.CreateResponse(HttpStatusCode.BadRequest, new ApiResponse(createResult.Errors.ToArray())));
        }

        //Map user to user type
        user.UserType = Repo.UserTypes.Find(req.UserTypeId);

        //Map user to area
        user.Area = Repo.Areas.Find(req.AreaId);

        //Map user to role
        var roles = new List<Role>();
        roles.Add(role);
        user.Roles = roles;

        //Map user to retailers
        user.Retailers = retailers;

        UserManager.Update(user);
        Repo.SaveChanges();

        var dto = Mapper.Map<UserDto>(user);

        return ResponseMessage(Request.CreateResponse(HttpStatusCode.Created, new ApiResponse<UserDto>(new[] { dto })));
    }
}

When I debug this method and inspect the user object at

UserManager.Update(user);

it has all of it's properties set correctly, including the GUIDs of the user type, area, role, and retailers. However, after the line executes and I check my database, the user that has been inserted has different GUIDs for those properties, and EF has inserted new lines into the user type and area tables which correspond to my new user.

If I remove the line of code which performs the update and leave the

Repo.SaveChanges();

line, the user is inserted correctly with appropriate user type and area IDs, but the rows which should be inserted into my dbo.UserRoles and dbo.UserRetailers bridging tables are not inserted, meaning the link between the user and the role/retailers I've assigned to it are lost.

From my research on StackOverflow it seems as though the Area and User Type objects I'm fetching from the database are detached from the context, so the update creates new objects which have similar properties to the ones I fetched, but (obviously) with a different Id. However, I'm still not sure how to remedy the issue.

Luke Pothier
  • 1,030
  • 1
  • 7
  • 19
  • I haven't looked at the code but from your problem description it seems like you know you have to attach the object to the DbContext. Here's how you can do that: http://stackoverflow.com/a/29721938/1864167 If that solves your problem, let me know. – Jeroen Vannevel Dec 06 '15 at 13:34

1 Answers1

0

After dealing with something similar with our entities, we changed all the models to always have the ID's available on the model for Many to 1 relationships, and always used a managed table, for Many to Many relationships, rather than letting Entity Framework handle it.

We then updated the code to always apply the ID to the model, rather than the actual other object.

For example:

Models:

    public class Organisation
    {
      public Guid Id { get; set; }
      public String Name { get; set; }
      public String Description { get; set; }
      //etc etc

      public Guid OrgTypeId { get; set; }

      #region Navigation properties

      public virtual OrgType OrgType { get; set; }

      #endregion

   }    

    public class OrgType 
    {
        public Guid Id { get; set; }
        public String Name { get; set; }
        public string Description { get; set; } 

        #region Navigation properties

        public virtual ICollection<Organisation> Organisations { get; set; }              

        #endregion
   }

Mapping:

    public class OrganisationMap : EntityTypeConfiguration<Organisation>
    {
        public OrganisationMap()
        {
            HasKey(t=>t.Id);
            Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            Property(t => t.Name).HasMaxLength(256);
            Property(t => t.Description).IsMaxLength().HasColumnType("ntext");                
            HasRequired(t => t.OrgType).WithMany(t => t.Organisations).HasForeignKey(t => t.OrgTypeId);
        }
    }

This ensures all the mapping would be done correctly, exposes the foreign key to you, and allows you to create a new object with just the type Id, not the full type object.

I believe that even after SaveChanges() however, the OrgType object might still be null, however it'll be fine the next time you get it from the context.

It's a bit of work to change all of that, but in the long run having access to the primary/foreign keys makes things much easier.

Hope that helps!

Willisterman
  • 583
  • 3
  • 5