0

У меня есть два объекта домена, как идентичные, но с разными свойствами PK:Объект объект не может ссылаться несколько экземпляров IEntityChangeTracker при сохранении изменений

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

} 

Я подозреваю, что это потому что у меня есть экземпляры репозиториев (iMaintenanceRepository & iMyMaintenanceRepository) для обоих доменных объектов в том же методе постконтроллера, и оба имеют ссылку на сущность Employee.

Например, когда я располагаю iMyMaintenanceRepository и создаю новый экземпляр (перед обновлением представления в конце), я получаю ошибку en вставки нулевого значения в таблицу Employee, которую я не вставляю. Именно по этой причине я подозреваю, что сущность Employee существует в двух разных контекстах данных. Я не уверен, как его решить. Ни одно из решений, которые я нашел, похоже, применяется, и я думаю, что это больше проблема реализации с моей стороны.

EDIT: Хранилища

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

ответ

2

Вы абсолют правильно о проблеме. Фактически, в частности, это связано с тем, что каждый репозиторий имеет свой собственный экземпляр вашего объекта контекста, и вы пытаетесь передать Employee, который был первоначально извлечен через один экземпляр и сохранил его через другой экземпляр.

Самое легкое решение - отслеживать все подобные вещи в одном хранилище. Другими словами, просто используйте один MaintenanceRepository можете позвонить, как Maintenance, так и MyMaintenance. Хотя это немного напоминает идею «репозитория». Вот почему хранилища обычно сочетаются с классом Unit of Work, в котором будет размещен контекст для размещения репозиториев. Однако в этот момент вы в основном просто воссоздаете структуру, которая уже реализует Entity Framework. Таким образом, хранение всего в одном «хранилище» имеет больше смысла, но теперь вы действительно говорите об «сервисном» шаблоне, а не о шаблоне репозитория. Однако это просто семантика.

UPDATE

Отказ от ответственности: Это то, что я использую в настоящее время в проекте, и это работает для меня. Это может быть не самая лучшая практика, и разумные люди вполне могут не согласиться с моим подходом.

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

Service, реализация IService

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(); 
+0

Спасибо, Крис. Мне нужно будет рассмотреть комбинацию двух репозиториев. Я отредактировал мое сообщение, включив два репозитория и общий, который они реализуют. Если у вас есть предложения по их объединению, я был бы благодарен. – steveareeno

+0

Удивительный! Еще раз спасибо Крису. Я посмотрю ваше решение. – steveareeno

0

Так что я искал в Интернете для полного примера кода первой реализации MVC с хранилищами, единицы работы, ViewModels и т.д., и я нашел именно то, что я искал здесь:

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

Это большой демонстрационный веб-приложение, которое делает все, что нужно с архитектурной точки зрения. Тем не менее, я не понимаю его половины (пока), и мне потребовалось около 4 часов переоборудования моего приложения, но человеку это стоило! Он также решил мою ошибку IEntityChangeTracker.

Смежные вопросы