【问题标题】:An entity object cannot be referenced by multiple instances of IEntityChangeTracker when saving changes保存更改时,IEntityChangeTracker 的多个实例不能引用实体对象
【发布时间】:2013-08-15 14:41:28
【问题描述】:

我有两个域对象,它们相同但具有不同的 PK 属性:

public partial class Maintenance : MaintenanceBase
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int MaintenanceId { get; set; }

    public virtual Employee Employee { get; set; }
}

public partial class MyMaintenance : MaintenanceBase
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int RowId { get; set; }

    public virtual Employee Employee { get; set; }
}

其余属性继承自基类。我遇到的问题是当尝试在我的后控制器中调用保存更改时,我收到以下错误:

An entity object cannot be referenced by multiple instances of IEntityChangeTracker

这(基本上)是我的控制器方法:

    [HttpPost]
    public ActionResult SubmitMyMaintenance(IList<MyMaintenance> myMaintenanceList, string userName)
    {
        foreach (var result in myMaintenanceList)
    {
    var m = iMyMaintenanceRepository.GetSingle(result.RowId);
    Maintenance maintenance = new Maintenance();

    // Use Injector to handle mapping between viewmodel and model
    maintenance.InjectFrom(m);

    try
    {
        if (ModelState.IsValid)
        {
            // save the maintenance item
            iMaintenanceRepository.Add(maintenance);
            iMaintenanceRepository.Save();

            // delete the item in MyMaintenance
            iMyMaintenanceRepository.Delete(m);
            iMyMaintenanceRepository.Save();
        }
    }
    catch (DataException ex)
    {
        message = ex.InnerException.ToString();
    }
}

// refresh the view 
var mvm = new MyMaintenanceListViewModel
{
    MyMaintenanceList = iMyMaintenanceRepository.FindBy(v => v.CreatedBy.Equals(userName)).ToList(),
    Message = "Your maintenance items were successfully added."
};

return View("MyMaintenance", mvm);

}

我怀疑这是因为我在同一个控制器 post 方法中有两个域对象的存储库实例(iMaintenanceRepositoryiMyMaintenanceRepository),并且都引用了 Employee 实体。

例如,当我处置 iMyMaintenanceRepository 并创建一个新实例时(在最后刷新视图之前),我收到关于在 Employee 表中插入空值的错误,我没有插入任何内容。这就是我怀疑 Employee 实体存在于两个不同的数据上下文中的原因。我不知道如何解决它。我发现的解决方案似乎都不适用,我认为这更多的是我的实施问题。

编辑:存储库

namespace EMMS.Models.Interfaces
{
    public interface IMyMaintenanceRepository : IGenericRepository<MyMaintenance>
    {
        MyMaintenance GetSingle(int RowId);
    }
}

namespace EMMS.Models.Repositories
{
    public class MyMaintenanceRepository : GenericRepository<AppDBContext, MyMaintenance>, IMyMaintenanceRepository
    {
        public MyMaintenance GetSingle(int RowId)
        {
            var query = GetAll().FirstOrDefault(x => x.RowId == RowId);
            return query;
        }
    }
}

namespace EMMS.ViewModels.Repositories
{
    public class GenericRepository<C, T> : IDisposable, IGenericRepository<T>
        where T : class
        where C : DbContext, new()
    {
        private C _entities = new C();
        public C Context
        {
            get { return _entities; }
            set { _entities = value; }
        }

        public virtual IQueryable<T> GetAll()
        {
            IQueryable<T> query = _entities.Set<T>();
            return query;
        }

        public IQueryable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
        {
            IQueryable<T> query = _entities.Set<T>().Where(predicate);
            return query;
        }

        // enforce referential itegrity
        public bool ValueInUse(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
        {
            IQueryable<T> query = _entities.Set<T>().Where(predicate);
            int count = query.Count();
            return count > 0 ? true : false;
        }

        public virtual void Add(T entity)
        {
            _entities.Set<T>().Add(entity);
        }

        public virtual void Delete(T entity)
        {
            _entities.Entry(entity).State = System.Data.EntityState.Deleted;
        }

        public virtual void Edit(T entity)
        {
            _entities.Entry(entity).State = System.Data.EntityState.Modified;
        }

        public virtual void Save()
        {
            _entities.SaveChanges();
        }

        private bool disposed = false; // to detect redundant calls
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    if (_entities != null)
                    {
                        _entities.Dispose();
                    }
                }

                disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

namespace EMMS.ViewModels.Interfaces
{
    public interface IGenericRepository<T> where T : class
    {
        IQueryable<T> GetAll();
        IQueryable<T> FindBy(Expression<Func<T, bool>> predicate);
        bool ValueInUse(System.Linq.Expressions.Expression<Func<T, bool>> predicate);
        void Add(T entity);
        void Delete(T entity);
        void Edit(T entity);
        void Save();
        void Dispose();
    }
}

【问题讨论】:

    标签: asp.net-mvc controller ef-code-first repository http-post


    【解决方案1】:

    你对这个问题是绝对正确的。实际上,特别是,这是因为每个存储库都有自己的上下文对象实例,并且您试图传递最初通过一个实例检索并通过不同实例保存的 Employee

    最简单的解决方案是在一个存储库中跟踪所有类似的东西。换句话说,只需使用一个MaintenanceRepository 就可以调用返回MaintenanceMyMaintenance。虽然这有点延伸了“存储库”的概念。这就是存储库通常与工作单元类结合的原因,该类将容纳存储库共享的上下文。但是,此时,您基本上只是重新创建实体框架已经实现的结构。因此,将所有内容都保存在一个“存储库”中更有意义,但现在您实际上是在谈论“服务”模式而不是存储库模式。不过这只是语义。

    更新

    免责声明:这是我目前在项目中使用的,它对我有用。这可能不是最佳做法,理性的人很可能不同意我的做法。

    IService接口

    public interface IService<TContext, TEntity>
        where TContext : DbContext
        where TEntity : class
    {
        IEnumerable<TEntity> GetAll(
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "");
        IEnumerable<TEntity> Get(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "");
        TEntity GetById(int id, string includeProperties = "");
        TEntity GetOne(
            Expression<Func<TEntity, bool>> filter = null,
            string includeProperties = "");
        TEntity GetFirst(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "");
        TEntity GetLast(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "");
        void Create(TEntity entity);
        void Update(TEntity entity);
        void Delete(int id);
        void Delete(TEntity entity);
        int Count(Expression<Func<TEntity, bool>> filter = null);
        bool Any(Expression<Func<TEntity, bool>> filter = null);
    }
    

    ServiceIService的实现

    public class Service<TContext, TEntity> : IService<TContext, TEntity>
        where TContext : DbContext
        where TEntity : class
    {
        internal TContext context;
        internal DbSet<TEntity> dbSet;
    
        public Service(TContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
        }
    
        public virtual IEnumerable<TEntity> GetAll(
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
        {
            return Get(null, orderBy, includeProperties);
        }
    
        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.Distinct().ToList();
            }
        }
    
        public virtual TEntity GetById(int id, string includeProperties = "")
        {
            return dbSet.Find(id);
        }
    
        public virtual TEntity GetOne(
            Expression<Func<TEntity, bool>> filter,
            string includeProperties = "")
        {
            return Get(filter, null, includeProperties).SingleOrDefault();
        }
    
        public virtual TEntity GetFirst(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
        {
            return Get(filter, orderBy, includeProperties).FirstOrDefault();
        }
    
        public virtual TEntity GetLast(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
        {
            return Get(filter, orderBy, includeProperties).LastOrDefault();
        }
    
        public virtual void Create(TEntity entity)
        {
            dbSet.Add(entity);
        }
    
        public virtual void Delete(int id)
        {
            var entity = GetById(id);
            Delete(entity);
        }
    
        public virtual void Delete(TEntity entity)
        {
            if (context.Entry(entity).State == EntityState.Detached)
            {
                dbSet.Attach(entity);
            }
            dbSet.Remove(entity);
        }
    
        public virtual void Update(TEntity entity)
        {
            if (context.Entry(entity).State == EntityState.Detached)
            {
                dbSet.Attach(entity);
            }
            context.Entry(entity).State = EntityState.Modified;
        }
    
        public virtual int Count(Expression<Func<TEntity, bool>> filter = null)
        {
            return Get(filter).Count();
        }
    
        public virtual bool Any(Expression<Func<TEntity, bool>> filter = null)
        {
            return Count(filter) > 0;
        }
    }
    

    ServiceGroup,服务的抽象容器

    public abstract class ServiceGroup<TContext> : IDisposable
        where TContext : DbContext
    {
        protected TContext context;
    
        public virtual void Save()
        {
            try
            {
                context.SaveChanges();
            }
            catch (DbEntityValidationException validationException)
            {
                string validationErrorMessage = DbEntityValidationMessageParser.GetErrorMessage(validationException);
                Console.WriteLine(validationErrorMessage);
            }
    
        }
    
        #region Disposable
        private bool disposed = false;
    
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
        }
    
        public virtual void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
    

    所以,我使用这一切的方式是,每当我想创建一个可以使用的类似事物的集合时,我都将ServiceGroup 子类化,如下所示:

    public class SampleService : ServiceGroup<MyDbContext>
    {
        public SampleService()
        {
            this.context = new MyDbContext();
        }
    
        private Service<MyDbContext, SomeModel> someModels;
        public Service<MyDbContext, SomeModel> SomeModels
        {
            get
            {
                if (someModels == null)
                {
                    someModels = new Service<MyDbContext, SomeModel>(context);
                }
                return someModels;
            }
        }
    
        private Service<MyDbContext, AnotherModel> anotherModels;
        public Service<MyDbContext, AnotherModel> AnotherModels
        {
            get
            {
                if (anotherModels == null)
                {
                    anotherModels = new Service<MyDbContext, AnotherModel>(context);
                }
                return anotherModels;
            }
        }
    
        // rinse and repeat
    
    }
    

    这可以确保一切都使用相同的上下文实例。因此,要实际使用它,您只需:

    var service = new SampleService();
    
    someModels = service.SomeModels.GetAll();
    

    【讨论】:

    • 谢谢克里斯。我将不得不考虑合并这两个存储库。我编辑了我的帖子以包含两个存储库和它们实现的通用存储库。如果您对如何组合它们有任何建议,我将不胜感激。
    • 太棒了!再次感谢克里斯。我会看看你的解决方案。
    【解决方案2】:

    所以我在网上搜索了一个包含存储库、工作单元、视图模型等的代码优先 MVC 实现的完整示例,我在这里找到了我正在寻找的东西:

    EFMVC - ASP.NET MVC 4, Entity Framework 5 Code First and Windows Azure

    这是一个很棒的演示网络应用程序,从架构的角度来看,它可以满足我的一切需求。不过,我不明白其中的一半,(还)我花了大约 4 个小时来重新调整我的应用程序,但这是值得的!它还解决了我的 IEntityChangeTracker 错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-07
      • 1970-01-01
      • 2011-11-01
      • 2011-10-12
      • 1970-01-01
      相关资源
      最近更新 更多