且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

在EF代码中删除首先使导航属性被设置为空和空

更新时间:2022-11-16 10:34:52

这是EF的工作原理。问题是,您的播放列表与其他关系形成实体图,EF使用非常简单的规则来跟踪实体图:图中的所有实体必须被跟踪 - 不能参考未跟踪的实体。我不给你参考这个规则的描述,这只是我的观察,但是我没有发现这个规则的任何一个例外。



编辑:更新版本 - 我刚刚检查内部实现和关键确实在调用时删除



所以你的代码发生了什么。




  • 您将播放列表标记为已删除

  • EF将删除操作传递给执行fixup的状态管理器 - 它将会关闭所有关系

  • 将更改保存到数据库

  • 由于播放列表中没有级联删除所有相关对象仍未删除

  • 保存更改后,EF内部接受它们并将更改跟踪器设置为当前状态

  • 由于播放列表的当前状态不存在(在数据库中已删除),因此已从上下文

  • 分离已经破坏了实体图,EF修改了两端的导航属性。



负责从 System.Data.Objects.EntityEntry.Delete(doFixup) doFixup 是true) - 类是内部的:

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

在您的场景中,应该有简单的解决方法 - 在删除实体之前创建DTO。


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.