实体对象不能被 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;
}
并且每当您需要创建 UserService
或 RoleService
时,请使用相同的 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);
}
我的问题如下: 我有两个包含多对多 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;
}
并且每当您需要创建 UserService
或 RoleService
时,请使用相同的 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);
}