Jake
Jake

Reputation: 743

Removing a navigation property doesn't update the database

I have 2 POCO classes (below) and wish to remove the link between two of their records. According to this EF 5.0 should be able to handle the removal without loading the User class like so:

context.Computers.Find("test").User = null;
context.SaveChanges();

This doesn't work, but using the .net 4 approved method it works:

en = context.Computers.Find("test");
context.Entry(en).Reference(e => e.User).Load();
en.User = null;
context.SaveChanges();

My EF reference is EntityFramework.dll version 5.0.0.0. Am I missing something obvious here?

Here are the classes:

public class Computer
{
    public string Id { get; set; }
    public Nullable<int> UserId { get; set; }
    public virtual User User { get; set; }
}
public class User
{
    public int Id { get; set; }
    public virtual ICollection<Computer> Computers { get; set; }
}

Edit: Here is the specific lines in the above linked article that don't seem to agree with the functionality I'm seeing:

To delete the relationship, set the navigation property to null. If you are working with the Entity Framework that is based on .NET 4.0, then the related end needs to be loaded before you set it to null. For example:

context.Entry(course).Reference(c => c.Department).Load();
course.Department = null;

Starting with the Entity Framework 5.0, that is based on .NET 4.5, you can set the relationship to null without loading the related end.

Upvotes: 7

Views: 4369

Answers (2)

Pawel
Pawel

Reputation: 31620

You don't see the relationship being deleted since your proxy is not a change tracking proxy but lazy loading proxy only. To make it a change tracking proxy you need to set all the properties to be virtual. Below please find an example that resets a navigation property without loading it first. Now a question is whether or not to use change tracking proxies - see this post as it contains an interesting discussion on this.

public class Computer
{
    public virtual string Id { get; set; }
    public virtual Nullable<int> UserId { get; set; }
    public virtual User User { get; set; }
}

public class User
{
    public int Id { get; set; }
    public virtual ICollection<Computer> Computers { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Computer> Computers { get; set; }
    public DbSet<User> Users { get; set; } 
}

class Program
{
    static void Main(string[] args)
    {
        using (var ctx = new MyContext())
        {
            if (!ctx.Computers.Any())
            {
                var user = ctx.Users.Add(new User());
                ctx.Computers.Add(new Computer() { Id = "MyComputer", User = user });
                ctx.SaveChanges();
            }
        }

        using (var ctx = new MyContext())
        {
            var computer = ctx.Computers.Single();
            computer.User = null;
            ctx.SaveChanges();
        }

        using (var ctx = new MyContext())
        {
            var computer = ctx.Computers.Include("User").Single();
            Console.WriteLine(computer.User == null);
        }
    }
}

Upvotes: 7

undefined
undefined

Reputation: 34299

EDIT:

I just read through the comments (i probably should have read them first but i already wrote this so ill leave it) and see you guys came to a pretty similar conclusion, hopefully this still helps explain a bit of the why. Also on a side note I think that you are better off to use the FK id property to null the relationship if its available as this means you wont have to actually load the remote entity at all.


Hey so i think this is whats going on:

You loading your Computer entity

  • at this point the User navigation property is null
  • The tracking graph has the initial state of this entity set to null

You set the User property to null

  • the tracking graph is still set to null so cant tell the difference between your intentional null and the not-loaded-yet null

You save changes

  • The tracker examines the initial graph, sees you havent loaded the user property yet and treats null as unloaded initial state
  • no changes are persisted

If this is the case as i see it there are two different ways you can make EF detect this as a change (and hence delete your relationship):

  1. Force loading of the user before setting the relationship to null, you can do this either by accessing it as you have lazy loading enabled, or using the .Include syntax in your query.
  2. Set the UserId property to null instead

The second is far easier and should work as the user ID property isn't on a remote entity. EF will treat either a null nav property or a null FK as a change and perform a delete.

Upvotes: 1

Related Questions