为什么我创建的工作单元存储库在插入新项目时导致 System.InvalidOperationException

Why is the unit of work repository i created causing a System.InvalidOperationException when inserting a new item

在为我的数据访问编写了许多存储库和接口之后,我了解到我一直在一遍又一遍地重写许多代码,因此我试图了解通用存储库模式和工作单元。我遵循了教程 here。 在实施示例并将需要的部分合并到我的项目中之后。我遇到了

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

根据我的研究,我了解到我可能正在使用两个数据库上下文,因此出现了错误。我有这个 GenericRepository class 和工作单元 class,我在下面注册了我的所有存储库

public class GenericRepository<TEntity> where TEntity : class
{
    internal ApplicationDbContext context;
    internal DbSet<TEntity> dbSet;
    public GenericRepository(ApplicationDbContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }
    public virtual IEnumerable<TEntity> Get(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = "")
    {
        IQueryable<TEntity> query = dbSet;
        if (filter != null)
        {
            query = query.Where(filter);
        }
        foreach (var includeProperty in includeProperties.Split(new char[]{','}, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }
        if (orderBy != null)
        {
            return orderBy(query).ToList();
        }
        else
        {
            return query.ToList();
        }
    }
    public virtual TEntity GetByID(object id)
    {
        return dbSet.Find(id);
    }
    public virtual void Insert(TEntity entity)
    {
        if (dbSet != null) dbSet.Add(entity);
    }
    public virtual void Delete(object id)
    {
        TEntity entityToDelete = dbSet.Find(id);
        Delete(entityToDelete);
    }
    public virtual void Delete(TEntity entityToDelete)
    {
        if (context.Entry(entityToDelete).State == EntityState.Detached)
        {
            dbSet.Attach(entityToDelete);
        }
        dbSet.Remove(entityToDelete);
    }
    public virtual void Update(TEntity entityToUpdate)
    {
        dbSet.Attach(entityToUpdate);
        context.Entry(entityToUpdate).State = EntityState.Modified;
    }
}

这里是工作单元class

public class UnitOfWork : IDisposable
{
    private ApplicationDbContext context = new ApplicationDbContext();
    private GenericRepository<Transactions> transactionRepository;
    private GenericRepository<PendingReason> penReasonRepository;
    private GenericRepository<DeclineReason> decReasonRepository;
    private GenericRepository<LoanStatus> loanStatusRepository;
    private GenericRepository<SalesAgent> salesAgentRepository;
   public GenericRepository<Transactions> TransactionRepository
    {
        get
        {
            if (this.transactionRepository == null)
            {
                this.transactionRepository = new GenericRepository<Transactions>(context);
            }
            return transactionRepository;
        }
    }
    public GenericRepository<PendingReason> PenReasonRepository
    {
        get
        {
            if (this.penReasonRepository == null)
            {
                this.penReasonRepository = new GenericRepository<PendingReason>(context);
            }
            return penReasonRepository ;
        }
    }
    public GenericRepository<DeclineReason> DecReasonRepository
    {
        get
        {
            if (this.decReasonRepository == null)
            {
                this.decReasonRepository = new GenericRepository<DeclineReason>(context);
            }
            return decReasonRepository;
        }
    }
    public GenericRepository<LoanStatus> LoanStatusRepository
    {
        get
        {
            if (this.loanStatusRepository == null)
            {
                this.loanStatusRepository = new GenericRepository<LoanStatus>(context);
            }
            return loanStatusRepository;
        }
    }
    public GenericRepository<SalesAgent> SalesAgentRepository
    {
        get
        {
            if (this.salesAgentRepository == null)
            {
                this.salesAgentRepository = new GenericRepository<SalesAgent>(context);
            }
            return salesAgentRepository;
        }
    }
    public void Save()
    {
        context.SaveChanges();
    }
    private bool disposed = false;
    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                context.Dispose();
            }
        }
        this.disposed = true;
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

在我的控制器的创建操作中,我有一点使用 usermanager 对象来获取当前用户的 ID。在那一点上有一个对 ApplicationDbContext 的引用,我认为这是导致问题的原因。但是我可能是错的。下面是我的控制器动作

    [HttpPost]
    public ActionResult Create(Transactions transactions)
    {
        using (var unit = new UnitOfWork())
        {
            if (ModelState.IsValid)
            {
                MapTransactions(new CreateTrnVM(), transactions);
                unit.TransactionRepository.Insert(transactions);
                unit.Save();
                return RedirectToAction("Index");
            }
            ViewBag.DeclineReasonId = new SelectList(unit.DecReasonRepository.Get(), "DeclineReasonId", "DecReason");
            ViewBag.PendingReasonsId = new SelectList(unit.PenReasonRepository.Get(), "PendingReasonId", "PenReason");
            ViewBag.StatusId = new SelectList(unit.LoanStatusRepository.Get(), "StatusId", "Status");
            return View(new CreateTrnVM());
        }

这是 MapTransactions 方法。

public void MapTransactions(CreateTrnVM model, Transactions source)
    {
        source.TrnDate = DateTime.Now;
        ApplicationUser currentUser;
        using (var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
        {
            currentUser = manager.FindById(User.Identity.GetUserId());
        }
        source.Agent = currentUser.SalesAgent;
    }

尝试创建交易时,此错误不断出现

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

进一步的研究使我得出了关于此 link 的声明。

In a real application you’ll have to decide if you want to mingle your data context with IdentityDbContext. One issue to be aware of is that the UserStore class does not play well when using the unit of work design pattern. Specifically, the UserStore invokes SaveChanges in nearly every method call by default, which makes it easy to prematurely commit a unit of work. To change this behavior, change the AutoSaveChanges flag on the UserStore.

var store = new UserStore<ApplicationUser>(new ApplicationDbContext());
store.AutoSaveChanges = false;

这仍然没有用。或者可能是其他问题。

问题出在这一行source.Agent = currentUser.SalesAgent;

您设置的实体 SalesAgentApplicationDbContext 的一个实例检索,该实例与用于获取 source.[=14= 的实例不同]

从各种帖子和研究中,我终于知道问题是我使用了一个单独的 ApplicationDbContext 实例,因为它与工作单元 class 中实例化的实例发生冲突。这发生在我试图通过使用 UserManager class 获取当前登录用户的用户 ID 时。 K Scot Allen 博客

的一些研究向我展示了这一点

In a real application you’ll have to decide if you want to mingle your data context with IdentityDbContext. One issue to be aware of is that the UserStore class does not play well when using the unit of work design pattern. Specifically, the UserStore invokes SaveChanges in nearly every method call by default, which makes it easy to prematurely commit a unit of work. To change this behavior, change the AutoSaveChanges flag on the UserStore.

var store = new UserStore<ApplicationUser>(new ApplicationDbContext());
store.AutoSaveChanges = false;

上面的建议对我没有帮助,但它促使我找到更多关于问题的信息,我得到了这个 Whosebug link,它建议我在工作单元 class 中创建 UserManager class。修改我上面的代码以包含

private UserManager<ApplicationUser> _userManager;
public UserManager<ApplicationUser> UserManager
{
    get
    {

        if (this._userManager == null)
        {
            this._userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
        }
        return _userManager;
    }
}

我能够在不直接使用 ApplicationDbContext 的情况下访问控制器中的 UserManager class。我这样修改了我的控制器

public ActionResult Create(Transactions transactions)
        {
            using (var unit = new UnitOfWork())
            {
                if (ModelState.IsValid)
                {
                    transactions.TrnDate = DateTime.Now;
                    var manager = unit.UserManager;
                    var currentUser = manager.FindById(User.Identity.GetUserId());
                    transactions.Agent = currentUser.SalesAgent;
                    unit.TransactionRepository.Insert(transactions);
                    unit.Save();
                    return RedirectToAction("Index");
                }
                ViewBag.StatusId = new SelectList(unit.LoanStatusRepository.Get(), "StatusId", "Status");
                return View(new CreateTrnVM());
            }

注意这三行

var manager = unit.UserManager;
var currentUser = manager.FindById(User.Identity.GetUserId());
transactions.Agent = currentUser.SalesAgent;

希望对其他人有所帮助。