开发者

Deleting in EF Code first causes navigational properties to be set to null and empty

I noticed something interesting when I was performing a delete using EF code first. I use the following domain model:

public class User
{
    public virtual long Id { get; set; }
    public virtual string Name { get; set; }
    public virtual ICollection<Playlist> Playlists { get; set; }
}

public class Playlist
{
    public virtual long Id { get; set; }
    public virtual string Title { get; set; }
    public virtual User User { get; set; }
    public virtual ICollection<Track> Tracks { get; set; }
}

public class Track
{
    public virtual long Id { get; set; }
    public virtual string Title { get; set; }
    public virtual Playlist Playlist { get; set; }
}

The model is configured using:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    开发者_开发百科modelBuilder.Entity<User>().HasMany(x => x.Playlists).WithRequired(x => x.User).Map(x => x.MapKey("UserId"));
    modelBuilder.Entity<Playlist>().HasMany(x => x.Tracks).WithRequired(x => x.Playlist).Map(x => x.MapKey("PlaylistId"));
}

I use a generic repository:

public virtual void Delete(T entity)
{
    Database.Set<T>().Remove(entity);
}

I also have a dto that looks like:

public class PlaylistDTO
{
    public PlaylistDTO(Playlist playlist)
    {
        Id = playlist.Id;
        Title = playlist.Title;
        User = playlist.User.Name;
    }
}

In one of my services I am trying to do the following:

public PlaylistDTO Delete(long id)
{
    Playlist playlist = playlistRepository.GetById(id);
    playlistRepository.Delete(playlist);
    unitOfWork.Commit();

    return PlaylistDTO(playlist);
}

This code fails. When I stepped through the debugger I noticed something interesting. The moment I call playlistRepository.Delete the navigational properties (User and Tracks) get set to null and empty respectively. Playlist however stays in memory. So when I pass in the playlist to the DTO the code will fail when it is trying to access playlist.User.Name. I wanted to pass this data to the client to display a verification.

Is this behavior correct? Is this by design?


This is how EF works. The problem is that your Playlist forms entity graph with other relations and EF uses very simple rule for tracking entity graphs: All entities in the graph must be tracked - there cannot be reference to entity which is not tracked. I don't give you reference to description of this rule, it is just my observation but I didn't find any single exception to this rule.

Edit: Updated version - I just checked internal implementation and relations are indeed nulled during calling Delete

So what happened in your code.

  • You marked your Playlist as deleted
  • EF passes delete operation to the state manager which does the fixup - it will null all relations
  • You saved changes to the database
  • Because there are no cascade deletes from Playlist all related objects remain undeleted
  • Once you saved changes EF internally accepted them and set change tracker to current state
  • Because the current state of Playlist is non existing (deleted in the database) it was detached from the context
  • Detaching has broken entity graph and EF fixed it by modifying navigation properties on both ends

The code responsible for nulling from System.Data.Objects.EntityEntry.Delete(doFixup) (doFixup is true) - the class is internal:

if (doFixup && (base.State != EntityState.Deleted))
{
    this.RelationshipManager.NullAllFKsInDependentsForWhichThisIsThePrincipal();
    this.NullAllForeignKeys();
    this.FixupRelationships();
}

In your scenario this should have simple workaround - create DTO before you delete entity.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜