【问题标题】:ASP.Net Core MVC Repository Pattern Unexpectedly disposingASP.Net Core MVC Repository Pattern 意外处理
【发布时间】:2016-02-22 04:30:00
【问题描述】:

当我尝试添加评论时,我收到以下错误:

ObjectDisposedException:无法访问已处置的对象。

当代码运行第二行时:

m_context.Comments.Add(comment);
m_context.SaveChanges();

为什么要处理上下文?如果将 TryAddComment 方法移动到控制器中,它不会提前调用 Dispose。

这是我的 Controller 和 Repository 类的样子(简化)。

CommentsController.cs:

public class CommentsController : Controller
{

    private ICommentRepository m_commentRepository;

    public CommentsController(ICommentRepository commentRepository)
    {
        m_commentRepository = commentRepository;
    }

    // POST: api/Comments
    [HttpPost]
    public async Task<IActionResult> PostComment([FromBody] CommentAddViewModel commentVM)
    {
        Comment comment = new Comment
        {
            ApplicationUserId = User.GetUserId(),
            PostId = commentVM.PostId,
            Text = commentVM.Text
        };

        bool didAdd = m_commentRepository.TryAddComment(comment);

        if (!didAdd)
        {
            return new HttpStatusCodeResult(StatusCodes.Status409Conflict);
        }

        return CreatedAtRoute("GetComment", new { id = comment.CommentId }, comment);
    }

}

CommentRepository.cs:

public class CommentRepository : ICommentRepository, IDisposable
{

    public ApplicationDbContext m_context;

    public CommentRepository(ApplicationDbContext context)
    {
        m_context = context;
    }
    public bool TryAddComment(Comment comment)
    {
        m_context.Comments.Add(comment);
        m_context.SaveChanges();

        return true;
    }
    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                m_context.Dispose();
            }
        }
        this.disposed = true;
    }

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

编辑:

如果我使用本地 CommentRepository,它会按预期工作。例如:

    CommentRepository localCommentRepo = new CommentRepository(m_context);
    bool didAdd = localCommentRepo.TryAddComment(comment);

编辑2:

在 Startup.cs 中,我将 IcommentRepository 注册为 Scoped 并按预期工作。最初是辛格尔顿。为什么单例会导致这个问题?

services.AddSingleton<ICommentRepository, CommentRepository>(); //breaks
services.AddScoped<ICommentRepository, CommentRepository>(); //works

编辑3:

ApplicationDbContext.cs:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);

    }
    public DbSet<Post> Posts { get; set; }
    public DbSet<Comment> Comments { get; set; }
}

【问题讨论】:

  • 请出示服务注册
  • 添加服务注册
  • 我们能看到 ApplicationDbContext 类的定义吗?因为它有可能实现了 Dispose 方法,所以 Add() 方法本身最后会销毁上下文对象。
  • 添加了定义。

标签: c# asp.net asp.net-core asp.net-core-mvc


【解决方案1】:

您的存储库和DbContext 都不应该是单例的。注册它们的正确方法是services.AddScopedservices.AddTransient,因为DbContext 的寿命不应超过请求,而AddScoped 正是为此而设计的。

AddScoped 将在作用域的生命周期(在 ASP.NET Core 中等于请求的生命周期)返回相同的 DbContext 实例(以及存储库,如果您这样注册它)。

当您使用AddScope 时,您不应该自己处置上下文,因为解析您的存储库的下一个对象将具有处置的上下文。

默认情况下,实体框架将上下文注册为作用域,因此您的存储库应该是作用域的(与上下文和请求相同的生命周期)或瞬态(每个服务实例都有自己的存储库实例,但请求中的所有存储库仍然共享相同的上下文)。

使上下文单例会导致严重的问题,尤其是在内存方面(您处理的越多,上下文消耗的内存就越多,因为它必须跟踪更多记录)。所以DbContext 应该尽可能短。

上下文持续时间的优点是,如果出现问题,您仍然可以在请求期间回滚所有操作并将其作为单个事务处理。

【讨论】:

  • 谢谢!这确实有助于解决问题。你是说我应该从存储库中删除 IDisposable 实现,因为它已经被正确处理了吗?此外,当我在调试时,调试器会在第一次调用DBContext 之后退出TryAddComment,并且它是调用 Dispose 方法的控制器。您能否解释一下为什么控制器会在请求完成之前调用Dispose
  • 很难说没有看到整个代码。您在某处使用using 关键字吗?即using(context) { context.Add(...); }?还是使用多个请求?如果您触发两个请求,一个会在存储库上调用 dispose(或者 GC 在收集它时会这样做),另一个请求将尝试访问已处理的上下文。您最初的问题是,存储库是单例的,而您的上下文不是。因此,您的第一个请求可能会获取上下文并在最后处理它。您的第二个请求获取上下文已被释放的单例存储库。
  • 不,我没有在任何地方使用using。这正是我的想法,第二个请求是调用 Dispose 的请求,它正在处理单例存储库。我从概念上理解正在发生的事情。但是我仍然不明白为什么 Singleton 存储库会以这种方式运行。 DBContext 不会总是在初始化应用程序后的第一个请求之后被释放吗? Here 是指向重现此行为的测试项目的链接。
  • 好吧,在第一次请求之后,您的存储库处置被称为(断点)。此时,您的存储库包含对已处置上下文的引用。下一个请求返回相同的存储库实例,因为它是单例的。尝试访问上下文时,抛出异常
  • 这对我来说是有道理的,但如果是这样,为什么你可以成功地连续调用 GetComment?他们不应该在第一次请求之后有一个处置的上下文吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-17
  • 2011-06-01
  • 2022-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-31
相关资源
最近更新 更多