In entity framework, I can't get a relationship to update for navigation properties.

code-first entity-framework

Question

I'm now working with Code First and EF4.3. Creation of my objects works (via my views - just using the auto-generated Create), but when I attempt to edit an object, it does not save any changes that, utlimately, tie back to my navigation properties. I have been reading about partnerships, but I don't understand how to tell my context that the relationship has changed.

Here is a sample of my implementation's code.

@* Snippet from my view where I link into my ViewModel. *@
<div class="row">
    <div class="editor-label">
        @Html.LabelFor(model => model.ManagerID)
    </div>
    <div class="editor-field">
        @Html.DropDownListFor(model => model.ManagerID, ViewBag.Manager as SelectList, String.Empty)
        @Html.ValidationMessageFor(model => model.ManagerID)
    </div>
</div>

Here is how I implemented my controller (for my edit's POST):

    [HttpPost]
    public ActionResult Edit(ProjectViewModel projectViewModel)
    {
        if (ModelState.IsValid)
        {
            Project project = new Project();
            project.ProjectID = projectViewModel.ProjectID;
            project.Name = projectViewModel.Name;
            project.ProjectManager = repository.GetUser(projectViewModel.ManagerID);
            repository.InsertOrUpdateProject(project);
            repository.Save();
            return RedirectToAction("Index");
        }
        ViewBag.Manager = new SelectList(repository.GetUsers(), "UserID", "FullName", projectViewModel.ManagerID);
        return View(projectViewModel);
    }

My Project object is:

public class Project
{
    public int ProjectID { get; set; }
    [Required]
    public string Name { get; set; }

    // Navigation Properties
    public virtual User Manager { get; set; }
}

The associated method from the repository (where my context is stored) is shown below:

public void InsertOrUpdateProject(Project project)
    {
        if (program.ProjectID == default(int))
        {
            context.Projects.Add(project);
        }
        else
        {
            context.Entry(project).State = EntityState.Modified;
        }
    }

Just to be clear, this does work to update my properties, but it does not update my navigation properties (in this case, Manager). Appreciate any assistance.

1
6
3/7/2012 6:11:24 PM

Accepted Answer

The state being set toModified Navigation properties are not marked as changed; only scalar properties are. You have a number of choices:

  • (You won't like it) A hack

    //...
    else
    {
        var manager = project.Manager;
        project.Manager = null;
        context.Entry(project).State = EntityState.Modified;
        // the line before did attach the object to the context
        // with project.Manager == null
        project.Manager = manager;
        // this "fakes" a change of the relationship, EF will detect this
        // and update the relatonship
    }
    
  • Reload the project for the current manager from the database at including. The properties are then set. If the manager changes again, change tracking will notice and submit a UPDATE.

  • Make a foreign key property available to theManager Your model's navigation attribute is:

    public class Project
    {
        public int ProjectID { get; set; }
        [Required]
        public string Name { get; set; }
    
        public int ManagerID { get; set; }
        public virtual User Manager { get; set; }
    }
    

    Now ManagerID setting the state to is a scalar attribute.Modified will contain this attribute. Additionally, you may assign the ID you got from your view rather than loading the Manager user from the database:

    Project project = new Project();
    project.ProjectID = projectViewModel.ProjectID;
    project.Name = projectViewModel.Name;
    project.ManagerID = projectViewModel.ManagerID;
    repository.InsertOrUpdateProject(project);
    repository.Save();
    
11
3/7/2012 6:45:13 PM

Popular Answer

There are a number of possibilities here; I'll mention three of them:

First choice: GraphDiff

This requires theConfiguration.AutoDetectChangesEnabled if the context setting is true.

Just install GraphDiff with NuGet

Install-Package RefactorThis.GraphDiff

Then

using (var context = new Context())
{
    var customer = new Customer()
    {
        Id = 12503,
        Name = "Jhon Doe",
        City = new City() { Id = 8, Name = "abc" }
    };

    context.UpdateGraph(customer, map => map.AssociatedEntity(p => p.City));
    context.Configuration.AutoDetectChangesEnabled = true;

    context.SaveChanges();
}

Look at here for further information about GraphDiff.

Choice 2: Lookup and Edit

using EF to search your object and trace it to the context. Edit the properties after that.

This requires theConfiguration.AutoDetectChangesEnabled if the context setting is true.

var customer = new Customer()
{
    Id = 12503,
    Name = "Jhon Doe",
    City = new City() { Id = 8, Name = "abc" }
};

using (var context = new Contexto())
{
    var customerFromDatabase = context.Customers
                                      .Include(x => x.City)
                                      .FirstOrDefault(x => x.Id == customer.Id);

    var cityFromDataBase = context.Cities.FirstOrDefault(x => x.Id == customer.City.Id);

    customerFromDatabase.Name = customer.Name;
    customerFromDatabase.City = cityFromDataBase;                

    context.Configuration.AutoDetectChangesEnabled = true;
    context.SaveChanges();
}

Using a scalar property is option three.

This is the greatest method in terms of speed, but it complicates your class with database issues. Because the Id must be mapped using a scalar attribute of type primitive.

This eliminates the need to establish theConfiguration.AutoDetectChangesEnabled a fact. Additionally, unlike the previous two alternatives (GraphDiff handles it automatically! ), you won't need to run a database query to find the entities.

var customer = new Customer()
{
    Id = 12503,
    Name = "Jhon Doe",
    City_Id = 8,
    City = null
};

using (var contexto = new Contexto())
{
    contexto.Entry(customer).State = EntityState.Modified;
    contexto.SaveChanges();
}


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