实体对象不能被 IEntityChangeTracker 的多个实例引用

An entity object cannot be referenced by multiple instances of IEntityChangeTracker

我的问题如下: 我有两个包含多对多 relationship:Users、UserRoles 和 Roles 的表。我正在尝试在编辑用户时实现为用户设置角色的可能性。我将 ASP.NET MVC 与 EF 一起使用,在视图中我使用复选框来设置新角色。在 UserController 中,我正在使用 UserService,我在其中向已存在的用户角色添加新角色。该服务正在使用存储库。我有一个通用存储库,它与工作单元一起工作。这些值是从视图到模型的映射。当我尝试同时更新数据库中的角色和用户时,出现以下错误:

An exception of type 'System.InvalidOperationException' occurred in DAL.dll but was not handled in user code
Additional information: An entity object cannot be referenced by multiple instances of IEntityChangeTracker.

  public class BaseRepository<T> where T: BaseEntity, new()
  {
      private DbContext db;
      private DbSet<T> dbSet;
      private UnitOfWork unitOfWork;

      // constructors, where I make an instance of Unit of work
      public BaseRepository()
      {
          db = new BookATableContext();
          dbSet = db.Set<T>();          
      }

      public BaseRepository(UnitOfWork unitOfWork)
      {                   
          db = new BookATableContext();
          dbSet = db.Set<T>();
          this.unitOfWork = unitOfWork;        
      }
     //Get User by id
public virtual T GetById(int id)
    {
        return dbSet.Find(id);
    }
      // Update method
      protected virtual void Update(T entity)
      {
          try
          {
              entity.UpdatedAt = DateTime.Now;
              db.Entry(entity).State = EntityState.Modified;                             
              db.SaveChanges();
          }
          catch (Exception e)
          {
              throw e;
          }     
      }
  }

// my implementation of Unit of work
public class UnitOfWork : IDisposable
{
     private BookATableContext context;
     private DbContextTransaction transaction;

     public UnitOfWork()
     {
         this.context = new BookATableContext();
         this.transaction = this.context.Database.BeginTransaction();
     }

     public void Commit()
     {
         if (transaction != null)
         {
             transaction.Commit();
             transaction = null;
         }
     }

     public void RollBack()
     {
         if (transaction != null)
         {
             transaction.Rollback();
             transaction = null;
         }
     }

     public void Dispose()
     {
         if (transaction != null)
         {
             transaction.Dispose();
             transaction = null;
         }
     }
 }

//用户服务代码

public class UserService:BaseService<User>
{
    public UserService() : base()
    {

    }
    public UserService(UnitOfWork unitOfWork) : base(unitOfWork)
    {

    }

    public List<SelectItem> GetSelectedRoles(List<Role> roles)
    {
        roles = roles ?? new List<Role>();
        return new RoleService()
        .GetAll()
        .Select(r => new SelectItem
        {
            Text = r.Name,
            Value = r.Id.ToString(),
            Selected = roles.Any(ar => ar.Id == r.Id)
        })
        .ToList();
    }
    public List<Role> GetUpdatedUserRoles(List<Role> roles, string[] selectedRoles)
    {
        selectedRoles = selectedRoles ?? new string[0];
        roles = roles ?? new List<Role>();
        return roles = new RoleService(unitOfWork)
        .GetAll()
        .Where(r => selectedRoles.Any(sr => r.Id == Convert.ToInt32(sr)))
        .ToList();
    }
}

//UserService继承BaseService,它有如下构造函数

public class Base`enter code here`Service<T> where T: BaseEntity, new()
{
    private BaseRepository<T> Repository;
    protected UnitOfWork unitOfWork;

    public BaseService()
    {

        this.Repository = new BaseRepository<T>();
    }

    public BaseService(UnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork;
        this.Repository = new BaseRepository<T>(this.unitOfWork);

    }
public T GetById(int id)
    {
        return this.Repository.GetById(id);
    }

}

即使在 SO 上,网上也有很多关于此错误的参考资料,但在阅读了所有这些评论和建议后,我还没有找到解决方案或出现此错误的原因。

您的 BaseRepository 中有一个方法 Update 接收实体,并以某种方式将其附加到在此基础存储库的构造函数中创建的 DbContext

您还有一个 GetById 方法,您可以调用该方法来获取该用户、添加角色,然后传递给 Update 方法。

问题是你有两个服务类:UserService和RoleService,都继承了BaseService

那你就这样称呼:

return new RoleService()
.GetAll()
.Select(r => new SelectItem
{
    Text = r.Name,
    Value = r.Id.ToString(),
    Selected = roles.Any(ar => ar.Id == r.Id)
})
.ToList();

或者这个:

return roles = new RoleService(unitOfWork)
    .GetAll()
    .Where(r => selectedRoles.Any(sr => r.Id == Convert.ToInt32(sr)))
    .ToList();

在这两种情况下 RoleService 不会使用 BaseRepository.

Update() 方法中使用的相同 DbContext 实例

我可以肯定这一点,因为您的 RoleService 使用 DbContext 获得角色的方式有两种:

由于继承了BaseService,BaseService总是创建一个新的BaseRepository实例,DbContext创建一个新实例。

由于您使用 UserService 通过 ID 获取用户,并使用 RoleService 获取角色,因此这两个服务 类 没有使用相同的 DbContext, 每个人都有自己的 BaseRepository 和自己的 DbContext.

TL;DR

您无法从一个 DbContext 获取角色并将其添加到从另一个 DbContext 查询的用户。这行不通:

var userService = new UserService();
var user = userService.GetById(1);

var roles = userService.GetSelectedRoles(null);
user.AddRoles(roles);

userService.Update(user);

您可以通过始终使用 UnitOfWork 中的 DbContext 来修复您的代码:

// Never use this, unless you are using only one entity (e.g user)
// and not using two or more related (e.g users and roles)
// public BaseRepository()
// {
//     db = new BookATableContext();
//     dbSet = db.Set<T>();          
// }

public BaseRepository(UnitOfWork unitOfWork)
{         
    // Don't create a new DbContext instance, use a unique shared instance
    // from UnitOfWork          
    // db = new BookATableContext();
    dbSet = db.Set<T>();
    this.unitOfWork = unitOfWork;   
    this.db = unitOfWork.db;
}

并且每当您需要创建 UserServiceRoleService 时,请使用相同的 UnitOfWork 实例以确保没有 2 个 DbContext 实例。此外,您可能需要制作 UnitOfWork public 的 db 属性(或创建 public getter)。

所以,你也需要修正这个方法:

public class Base`enter code here`Service<T> where T: BaseEntity, new()
{
    private BaseRepository<T> Repository;
    protected UnitOfWork unitOfWork;

    // Must not use this constructor, we always need the unitofWork to share DbContext
    // public BaseService()
    // {
    //     this.Repository = new BaseRepository<T>();
    // }

    public BaseService(UnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork;
        this.Repository = new BaseRepository<T>(this.unitOfWork);

    }
}

所以所有 类 继承 BaseService 的构造函数必须带有 UnitOfWork,不能使用无参数构造函数...还解决了这个问题:

public List<SelectItem> GetSelectedRoles(List<Role> roles)
{
    roles = roles ?? new List<Role>();
    return new RoleService(this.unitOfWork) // always pass unitOfWork
    .GetAll()
    .Select(r => new SelectItem
    {
        Text = r.Name,
        Value = r.Id.ToString(),
        Selected = roles.Any(ar => ar.Id == r.Id)
    })
    .ToList();
}

控制器中的示例方法:

public ActionResult AddRoleToUser()
{
    var unitOfWork = new UnitOfWork();

    var userService = new UserService(unitOfWork); // This unitOfWork will be passed to RoleService and BaseRepository as well...
    var user = userService.GetById(1);

    var roles = userService.GetSelectedRoles(null);
    user.AddRoles(roles);

    userService.Update(user);

}