单例Repository与Unit of Work模式下附加实体报错的解决方法
解决EF中实体重复跟踪的异常问题
首先咱们得先捋清楚问题根源:你的Validate方法里通过GetById获取了Author实体,而EF的默认查询是跟踪查询——这意味着这个Author已经被上下文(ObjectStateManager)纳入跟踪了。之后你在AddBook里又尝试Attach同一个ID的Author实体,EF自然会抛出异常,因为它不允许同时跟踪两个拥有相同主键的实体。
下面给你几个不用修改验证逻辑(毕竟你提到没法预知他人编写的验证代码)的可行解决方案:
方案一:Attach前先检查上下文是否已跟踪该实体
在执行Attach操作前,先去上下文的本地缓存里排查是否已有同ID的Author实体,有就直接复用,没有再执行Attach:
// 从上下文本地缓存获取已被跟踪的Author var trackedAuthor = _uow.Repository<Author>().Context.Set<Author>().Local .FirstOrDefault(a => a.Id == addBook.Author.Id); if (trackedAuthor == null) { // 未找到已跟踪实体时才执行Attach _uow.Repository<Author>().Attach(addBook.Author); } else { // 替换newBook的Author引用为已跟踪实体,避免后续状态冲突 newBook.Author = trackedAuthor; }
这个方案完全不需要改动验证代码,只需要在你的AddBook方法里做前置检查即可。
方案二:通过Entry判断实体状态后再操作
利用EF的Entry方法直接获取实体的跟踪状态,根据状态决定是否执行Attach:
var authorEntry = _uow.Repository<Author>().Context.Entry(addBook.Author); // 仅当实体处于未跟踪状态时才执行Attach if (authorEntry.State == EntityState.Detached) { authorEntry.Attach(); } // 如需重载最新数据,直接调用Reload即可,不会触发冲突 authorEntry.Reload();
这个写法更简洁,直接借助EF的状态管理机制从根源避免重复Attach的问题。
方案三:换个思路——不附加实体也能拿到Country
其实你只需要Author.Country这个字段,不一定非要附加整个实体。既然验证方法已经查询过一次Author,你可以考虑把Country信息传递到AddBook方法中;或者直接单独查询Country(使用无跟踪查询):
// 仅查询Country字段,用AsNoTracking避免跟踪实体 var authorCountry = _uow.Repository<Author>().Context.Set<Author>() .AsNoTracking() .Where(a => a.Id == addBook.Author.Id) .Select(a => a.Country) .FirstOrDefault();
这样既拿到了你需要的信息,又不会触发实体跟踪冲突,一举两得。
如果有机会修改验证方法,还可以在验证时使用AsNoTracking查询Author,这样实体不会被上下文跟踪,后续Attach就不会有问题了:
// 验证方法中的查询改为无跟踪模式 var author = _uow.Repository<Author>().Context.Set<Author>() .AsNoTracking() .FirstOrDefault(a => a.Id == addBook.Author.Id);
不过这个得看你是否有权限修改他人编写的验证代码。
内容的提问来源于stack exchange,提问作者Okan Kocyigit
相关产品推荐
相关产品推荐

