EF5删除实体时出现问题。之前有用......我哪里出错了?

时间:2014-10-27 14:31:19

标签: c# asp.net-mvc entity-framework asp.net-mvc-4

我研究过包含相同错误的其他答案。所以我要感谢别人不要说这是重复的。其他海报显然是故意使用多种情境。我至少不是故意的。

...方案

我开始了一个标准的MVC项目,并将模型提取到模型层。经过全面测试,一切正常。

接下来,我从控制器中提取了存储库。除了我创建了一个通用接口并实现它之外,没有更改代码。这一切都奏效了。但是我正在努力在MVC中应用设计模式,经过多次阅读后,我想将我的存储库分成不同的类,以用于不同的CRUD操作。单一责任原则。

所以我根据需要制作了单独的接口,并开始基于它们实现单独的存储库。所有删除存储库都有效。

字面上发生的所有事情都是代码dbset.remove从控制器中取出,并放入一个只包含一个方法的repo层。删除。这是我的代码。

控制器:

    ReadOneRepository<Course> readOneRepo = new ReadOneRepository<Course>(new SchoolDemoEntity());
    DeleteRepository<Course> deleteRepo = new DeleteRepository<Course>(new SchoolDemoEntity());

    // GET: /Course/Delete/5
    public ActionResult Delete(int id = 0)
    {
        Course course = readOneRepo.Read(id);

        if (course == null) return HttpNotFound();

        return View(course);
    }

    // POST: /Course/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(int id)
    {
        Course course = readOneRepo.Read(id);
        deleteRepo.Delete(course);
        return RedirectToAction("Index");
    }

回购接口:

namespace SchoolDemo.Repository.Interface
{
    public interface IDeleteRepository<TEntity>
    {
        int Delete(TEntity entity);
    }
}

上述接口的Repo实现: - 从所有CRUD存储库通用的基类开始:

使用System.Data.Entity;

namespace SchoolDemo.Repository
{
    public class BaseRepository<TEntity> where TEntity : class
    {
        protected DbSet<TEntity> dbSet;
        protected readonly DbContext dbContext;

        public BaseRepository() { }

        public BaseRepository(DbContext dbContext)
        {
            this.dbContext = dbContext;
            dbSet = dbContext.Set<TEntity>();
        }
    }
}

最后是存储库......它继承了基类并实现了接口:

using SchoolDemo.Repository.Interface;
using System.Data.Entity;

namespace SchoolDemo.Repository
{
    public class DeleteRepository<TEntity>
        : BaseRepository<TEntity>
        , IDeleteRepository<TEntity>
          where TEntity : class
    {

        public DeleteRepository(DbContext dbContext) 
            : base(dbContext) { }

        public int Delete(TEntity entity)
        {
            //**** THIS IS THE IMPORTANT PART ****

            dbSet.Remove(entity);
            return dbContext.SaveChanges();
        }
    }
}

上面标记的重要部分是直接从控制器中提取的代码,以及当所有内容都是基本repo类的一部分时从旧的repo中提取的代码。这一切都奏效了。

但是现在我收到以下错误:

  

无法删除该对象,因为在该对象中找不到该对象   ObjectStateManager。

在阅读其他问题后,我尝试在重要评论下面添加以下行...

        dbSet.Attach(entity);

EF现在回复:

  

实体对象不能被多个实例引用   IEntityChangeTracker。

在研究这个错误时,我得到了一些非常复杂的答案,但它们似乎并不适用于我提到的情况,我没有使用我所知道的多个上下文。此外,我试图删除,其他人试图保存或做一些其他功能。如果有人回答类似的问题并且能够理解它们如何适用于我的情况,我会很感激如何解释它。但是,请原谅我这么直言,我的意思是没有不尊重,他们似乎有点复杂的简单的通用删除回购,特别是考虑到其他所有操作都有效。我想我一定是犯了一些错字或简单的逻辑错误。

当我使用通用存储库时,我还为学生控制器使用了一组单独的代码,它使用相同的存储库......所以我会发布这个包含它的任何线索。

学生管理员:

    ReadOneRepository<Student> readOneRepo = new ReadOneRepository<Student>(new SchoolDemoEntity());
    DeleteRepository<Student> deleteRepo = new DeleteRepository<Student>(new SchoolDemoEntity());

    // GET: /Student/Delete/5
    public ActionResult Delete(int id = 0)
    {
        Student student = readOneRepo.Read(id);

        if (student == null) return HttpNotFound();

        return View(student);
    }

    // POST: /Student/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(int id)
    {
        Student student = readOneRepo.Read(id);
        deleteRepo.Delete(student);
        return RedirectToAction("Index");
    }

我为学生犯了同样的错误。所以同样的问题也适用。我已经调试并验证了readOneRepo.Read(id)方法实际上正在返回正确的记录。

如果有人有任何想法,我会很感激帮助...

3 个答案:

答案 0 :(得分:3)

您不应将操作分成不同的存储库。您应该有一个存储库来执行所有CRUD操作,并在这些方法之间共享一个上下文实例。

public class BaseRepository<TEntity> where TEntity : class
{
    protected DbSet<TEntity> dbSet;
    protected readonly DbContext dbContext;

    public BaseRepository() { }

    public BaseRepository(DbContext dbContext)
    {
        this.dbContext = dbContext;
        dbSet = dbContext.Set<TEntity>();
    }

    // read, update, delete methods here that all use the same dbContext instance
    public int Delete(TEntity entity)
    {
        dbSet.Remove(entity);
        return dbContext.SaveChanges();
    }

    public IQueryable<TEntity> Read(Expression<Func<TEntity, bool>> filter)
    {
        return dbContext.Table<TEntity>().Where(filter); //Not sure what the generic method is to get a table in EF, but it's something like this
    }
}

答案 1 :(得分:0)

我已经接受并非常感谢DLeh的回答。我认为这是大多数情况下的正确方法。然而,按照建议重新组合我所有的回购后,所以我只有一个回购,我记得为什么我这样做了。因此,我可以在需要的地方轻松添加横切问题,例如:

  • 测试(我可以使用TDD / BDD分别测试回购的每个部分)
  • 缓存读取,(因为缓存通常仅适用于读取)。
  • 池化写入(因为池和事务通常仅适用于写入)。
  • 记录,(通常您只想记录更改)。
  • 验证
  • 错误处理。
  • 安全等。

我希望能够在&#34; As Needed&#34;基础而不是覆盖我的整个回购并迫使一切使用天气它需要与否。所以我会建议和替代解决方案,我应用了几个脑细胞后想出来。

我不是说这是对的,它只是我对上述错误阅读的更为复杂的答案的替代方案......它可能会或可能不适用于我读过的其他问题。但它完全符合我的需求。

如果您将我的代码组织成单独的回购,那么由于关注点分离。围绕多个上下文错误的方法如下:

我记得我有一个看起来像这样的ReadOne Repo:

接口:

namespace SchoolDemo.Repository.Interface
{
    public interface IReadOneRepository<TEntity>
    {
        TEntity Read(int id);
    }
}

实现:

using SchoolDemo.Repository.Interface;
using System.Data.Entity;

namespace SchoolDemo.Repository
{
    public class ReadOneRepository<TEntity>
        : BaseRepository<TEntity>
        , IReadOneRepository<TEntity>
          where TEntity : class
    {
        //public ReadOneRepository() : base() { }

        public ReadOneRepository(DbContext dbContext)
            : base(dbContext) { }

        public TEntity Read(int id)
        {
            return dbSet.Find(id);
        }
    }
}

它继承自基类,所以我选择更改我的删除仓库,从而继承它,因为它需要读取功能......就像这样:

using SchoolDemo.Repository.Interface;
using System.Data.Entity;

namespace SchoolDemo.Repository
{
    public class DeleteRepository<TEntity>
        : ReadOneRepository<TEntity>        //This was the 1st little change made, changed from BaseRepo
        , IDeleteRepository<TEntity>
          where TEntity : class
    {

        public DeleteRepository(DbContext dbContext) 
            : base(dbContext) { }

        public int Delete(TEntity entity)
        {
            dbSet.Remove(entity);
            return dbContext.SaveChanges();
        }
    }
}

现在我可以在删除控制器中成功使用以下内容:

    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(int id)
    {
        Student student = deleteRepo.Read(id); //Here is the 2nd change (from ReadOneRepo.Read() to the inherited deleteRepo.Read()).
        deleteRepo.Delete(student);
        return RedirectToAction("Index");
    }

通过这样做,它强制单个上下文没有任何复杂的编码来检测是否附着和附着,如果不是等。

关注...

Liskov替换原则没有被违反,因为删除子类仍然可以执行其基类ReadOneRepo可以执行的操作。它只是根据需要添加删除来扩展现有功能。

不违反单一责任原则,因为它不直接包含Read的另一个实现。它继承了它。仍然留下一个地方(如果代码需要改变,则返回Read基类)。

现在可以满足打开/关闭原则,因为我可以扩展其中一个或两个类而不影响delete类,就像我扩展readOne以适应缓存一样,例如,我可以在一个单独的继承行中满足OCP。

我不认为我违反了我(因为我的课程除了基础上的继承之外没有任何直接的依赖关系,因此可以轻松使用IOC)或D(我认为它满足D因为我的所有回购都没有使用其他SOLID原则的接口。如果违反任何设计模式或最佳实践,则不确定。我很乐意听到有关它的意见。

至于我,这似乎是一个非常优雅的解决方案,它允许我将每个CRUD操作分开(如果需要),并且仍然克服了在不使用复杂的编码解决方案的情况下消除错误的问题。只有2行代码发生了变化,没有添加任何内容。

我希望这有助于其他人,但我想在其他许多SO问题中提到它,我也使用SO作为参考,所以如果我再次遇到这个问题,我知道在哪里修复它。

再次感谢大家的任何帮助和建议。

答案 2 :(得分:0)

删除整个类BaseRepository&lt; TEntity&gt;

DbContext已经是一个通用存储库。将一个已经通用的存储库包装在另一个通用存储库中是徒劳的。

如果您确实拥有需要重复使用的代码,例如实体审核信息(由创建的,由内容更新),您可以注入 GenericRepository<T>。虽然看起来我很迂腐,但事实并非如此。主要区别在于使用委托而不是继承。

C#不允许多重继承,因此您永远不能将BaseRepository<City>BaseRepository<State>组合在一起。要将这些组合在一起,您必须实现一个第四个存储库,它包含那些2.我们如何在那里获得4个存储库? DbContext,City,State,现在是CityAndState。

相反,如果必须重复使用代码,您的存储库应如下所示:

public class CityAndStateRepository {

    readonly GenericRepository<City> _cities;
    readonly GenericRepository<States> _states;

    public CityAndStateRepository(GenericRepository<City> cities,
                                  GenericRepository<States> states)
    {
        _cities = cities;
        _states = states;    
    }

    public virtual Tuple<City, State> GetCityState(string city, string state) {

        var city = _cities.DoStuff
        var state = _states.DoStuff

        return new Tuple(city, state)
    }
}

注意我对元组的使用仅用于说明目的,我不建议创建常规返回元组的API。

如果GetCityState标记为虚拟,则此类在依赖于CityAndStateRepository进行集成测试的任何方面都是完全可测试的。