0

2 つのドメイン オブジェクトがあります。どちらも同じですが、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 メソッド内に両方のドメイン オブジェクトのリポジトリ ( iMaintenanceRepository& )のインスタンスがあり、両方が Employee エンティティへの参照を持っているためだと思われます。iMyMaintenanceRepository

たとえば、iMyMaintenanceRepository を破棄して新しいインスタンスを作成すると (最後にビューを更新する前に)、何も挿入していない Employee テーブルに null 値を挿入するとエラーが発生します。これが、Employee エンティティが 2 つの異なるデータ コンテキストに存在すると思われる理由です。私はそれを解決する方法がわかりません。私が見つけた解決策はどれも当てはまらないようで、それは私の側の実装上の問題であると考えています。

編集:リポジトリ

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

2 に答える 2

2

あなたはこの問題について絶対に正しいです。Employee実際には、特に、各リポジトリにはコンテキスト オブジェクトの独自のインスタンスがあり、最初に 1 つのインスタンスを介して取得され、別のインスタンスを介して保存されたを渡そうとしているためです。

最も簡単な解決策は、1 つのリポジトリで同様のものをすべて追跡することです。つまり、 one を使用するだけで、と のMaintenanceRepository両方を返す呼び出しを持つことができMaintenanceますMyMaintenance。それは「リポジトリ」の考えを少し広げますが。これが、リポジトリが通常、リポジトリが共有するコンテキストを収容する Unit of Work クラスと組み合わされる理由です。ただし、その時点では、基本的に、Entity Framework が既に実装している構造を再作成しているだけです。したがって、すべてを 1 つの「リポジトリ」に保持する方が理にかなっていますが、実際には、リポジトリ パターンではなく「サービス」パターンについて話しているのです。ただし、それは単なるセマンティクスです。

アップデート

免責事項:これは私が現在プロジェクトで使用しているものであり、私にとってはうまくいきます。これはベスト プラクティスではない可能性があり、合理的な人々は私のアプローチに反対する可能性があります。

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();
于 2013-08-15T15:50:55.047 に答える
0

そのため、リポジトリ、作業単位、ビューモデルなどを使用したコード ファーストの MVC 実装の完全な例を Web で検索していたところ、探していたものがまさにここで見つかりました。

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

これは、アーキテクチャの観点から私が必要とするすべてを実行する優れたデモ Web アプリです。私はそれの半分を理解していませんが、(まだ)アプリを改造するのに約4時間かかりましたが、それだけの価値がありました! IEntityChangeTracker エラーも解決しました。

于 2013-08-16T04:24:41.580 に答える