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

时间:2016-12-09 00:21:35

标签: c# asp.net-mvc entity-framework dbcontext invalidoperationexception

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

  

发生了'System.InvalidOperationException'类型的异常   DAL.dll但未在用户代码中处理
  附加信息:无法引用实体对象   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;
         }
     }
 }

// UserService代码

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上,但在阅读了所有这些意见和建议之后,我还没有找到解决方案或我收到此错误的原因。

1 个答案:

答案 0 :(得分:0)

您的Update中有一个方法BaseRepository,它接收一个实体,并以某种方式将其附加到在此基本存储库的构造函数内创建的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都不会使用DbContextUpdate()方法中使用的同一BaseRepository个实例。

我可以肯定这一点,因为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);

您可以始终使用DbContext

中的UnitOfWork来修复代码
// 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实例。此外,您可能需要设置db公开的UnitOfWork属性(或创建公共获取者)。

因此,您还需要修复此方法:

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();
}

Controller中的示例方法:

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);

}