ASP.NET MVC Entity Framework 6 Navigation Property not saving on SaveChanges() Repository Pattern on Edit

asp.net asp.net-mvc-5 c# entity-framework-6 repository-pattern

Question

I am having a strange problem where a navigation property is not persisiting on the Update of a page, but is on a Create. I am using a Generic Repository pattern with Entity Framework 6, ASP.NET MVC 5.

Here's the skeleton code:

My context:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext() : base("DefaultConnection") { }       
}

My repository implementation:

public class EntityFrameworkRepository : IRepository, IDisposable
{
    private ApplicationDbContext _context;
    private readonly ConcurrentDictionary<Type, object> _dbSets =
        new ConcurrentDictionary<Type, object>();

    public EntityFrameworkRepository(ApplicationDbContext context)
    {
        _context = context;
    }

    public void Save<T>(T entity) where T : BaseModel
    {
        GetDbSet<T>().Add(entity);
        _context.SaveChanges();
    }

    public void Update<T> (T entity) where T: BaseModel
    {
        _context.Entry(entity).State = EntityState.Modified;
        _context.SaveChanges();
    }
}

Here the basis of Get of the Edit page uses AutoMapper to map between the domain model and the view model.

public ActionResult Edit(int menuId, int? id)
{
   MenuItem mi = _repository.Get<MenuItem>((int)id);
   Mapper.CreateMap<MenuItem, MenuItemViewModel>();
   MenuItemViewModel mivm = Mapper.Map<MenuItem, MenuItemViewModel>(mi);
///....///
}

This is the method that is not working. As you can see, the full object UseSiblingClaim is being retrieved by id and set to the MenuItem itemToSave. I check the object just before _repository.Update() is called, and the property correctly.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int menuId, [Bind(Include = "Id,Name,ControllerName,ActionName,Order,CustomUrl,Tooltip,IconClass,CanUseParentsClaim,DoesNotNeedClaim,UseSiblingClaim_Id")] MenuItemViewModel mivm)
{
    if (ModelState.IsValid)
    {
        mivm.Menu = _repository.Get<Menu>(menuId);

        if (mivm.UseSiblingClaim_Id != null)
        {
           mivm.UseSiblingClaim = _repository.Get<MenuItem>((int)mivm.UseSiblingClaim_Id);
        }

        MenuItem itemToSave = MapViewModelToModel(mivm, _repository);
        _repository.Update<MenuItem>(itemToSave);
        return RedirectToAction("Details", "Menu", new { Id = menuId });
     }
     return View(mivm);
 }

Here's the MenuItem class:

public class MenuItem : BaseModel
{
    public MenuItem()
    {
        Children = new HashSet<MenuItem>();
    }        

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }        
    public virtual MenuItem ParentMenuItem { get; set; }
    public virtual Menu Menu { get; set; }        
    public string Name { get; set; }
    ///.....///
    public virtual MenuItem UseSiblingClaim { get; set; }
}

The mapping method is here: UseSiblingClaim is being set.

private static MenuItem MapViewModelToModel(MenuItemViewModel mivm, IRepository _repository)
    {


        MenuItem itemToSave = new MenuItem()
        {
            UseSiblingClaim = mivm.UseSiblingClaim,
            Id = mivm.Id,
            ActionName = mivm.ActionName,
            CanUseParentsClaim = mivm.CanUseParentsClaim,
            ControllerName = mivm.ControllerName,
            CustomURL = mivm.CustomURL,
            DoesNotNeedClaim = mivm.DoesNotNeedClaim,
            IconClass = mivm.IconClass,
            Menu = mivm.Menu,
            Name = mivm.Name,
            Order = mivm.Order,
            ParentMenuItem = mivm.ParentMenuItem,
            Tooltip = mivm.Tooltip,                     
        };
        return itemToSave;
    }

Here's the view model:

public class MenuItemViewModel : BaseModel
    {
        public int Id { get; set; }
        public int ParentMenuItem_Id { get; set; }
        public MenuItem ParentMenuItem { get; set; }
        public int Menu_Id { get; set; }
        public Menu Menu { get; set; }
        public IEnumerable<SelectListItem> SiblingItems { get; set; }
        [DisplayName("Can Use Sibling's Claim")]
        public int? UseSiblingClaim_Id { get; set; }
        public MenuItem UseSiblingClaim { get; set; }
    }

The View part (this is the same for Create() and Update():

<div class="form-group">
    @Html.LabelFor(model => model.UseSiblingClaim_Id, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.DropDownListFor(x => x.UseSiblingClaim_Id, Model.SiblingItems, new { @class = "select2", @multiple = "multiple", @style = "width:100%" })
        @Html.ValidationMessageFor(model => model.UseSiblingClaim_Id)
    </div>
</div>

Model.SiblingItems is created and passed into the view.

Why won't the UseSiblingClaim navigation property be persisted? It works on Create of the Menu Item, but not on Update? Is it something to do with me uaing a generic repositroy pattern? Please help

1
1
6/1/2014 11:53:46 PM

Accepted Answer

I found a solution to this. I'm still not sure why it wasn't updating, but the way I got it working was to expose the ForeignKey on the MenuItem object explicitly as such:

public class MenuItem : BaseModel
{    
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }        
    // I didn't have this exposed before //
    public int? UseSiblingClaim_Id { get; set; }
    public virtual MenuItem UseSiblingClaim { get; set; }
}

Then change my mapping from:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<MenuItem>()
                .HasOptional(x=>x.UseSiblingClaim)                
                .WithOptionalDependent()
                .Map(m => m.MapKey("UseSiblingClaim_Id"));
    base.OnModelCreating(modelBuilder);
}

to:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{  
    modelBuilder.Entity<MenuItem>()
            .HasOptional(x => x.UseSiblingClaim)
            .WithMany()
            .HasForeignKey(x => x.UseSiblingClaim_Id);
    base.OnModelCreating(modelBuilder);
}

Then in my MenuItemController I only had to set the foreign key property (UseSiblingClaim_Id), and not the navigation property, which also saved a db trip.

The answer was buried in here: Cannot get relationship to update for navigation properties in entity framework

0
5/23/2017 12:16:19 PM

Popular Answer

I ran into this same issue. I solved it by changing

IEnumerable<Item>

to

ICollection<Item>


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow