ASP.Net核心(MVC6)存储库模式意外处置

时间:2022-12-04 18:45:51

When I try to add a comment, I get the following error:

当我尝试添加评论时,出现以下错误:

ObjectDisposedException: Cannot access a disposed object.

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

When the code runs the second line:

当代码运行第二行时:

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

Why is the context being disposed? If move the the TryAddComment method into the controller, it doesn't call Dispose early.

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

Here is what my Controller and Repository class look like (simplified).

这是我的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);
    }
}

Edit:

If I use a local CommentRepository, it works as expected. For example:

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

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

Edit2:

In Startup.cs, I registered the IcommentRepository as Scoped and works as expected. Originally it was Singleton. Why would a singleton cause this issue?

在Startup.cs中,我将IcommentRepository注册为Scoped并按预期工作。最初是Singleton。为什么单身人士会导致这个问题?

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

Edit3:

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

1 个解决方案

#1


7  

Neither your repository nor your DbContext should be singletons. The correct way to register them is services.AddScoped or services.AddTransient, as a DbContext shouldn't live longer than a request and the AddScoped is exactly for this.

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

AddScoped will return the same instance of a DbContext (and repository if you register it as such) for the lifetime of the scope (which in ASP.NET Core equals the lifetime of a request).

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

When you use AddScope you shouldn't dispose the context yourself, because the next object that resolves your repository will have an disposed context.

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

Entity Framework by default registers the context as scoped, so your repository should be either scoped (same lifetime as the context and request) or transient (each service instance gets it's own instance of the repository, but all repositories within a request still share the same context).

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

Making the context singleton causes serious issues, especially with the memory (the more you work on it, the more memory the context consumes, as it has to track more records). So a DbContext should be as short-lived as possible.

制作上下文单例会导致严重的问题,特别是对于内存(你对它的工作越多,上下文消耗的内存就越多,因为它必须跟踪更多的记录)。所以DbContext应尽可能短暂。

Duration of the context has the advantage that you can still roll back all operations during the request if something goes wrong and handle it as one single transaction.

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

#1


7  

Neither your repository nor your DbContext should be singletons. The correct way to register them is services.AddScoped or services.AddTransient, as a DbContext shouldn't live longer than a request and the AddScoped is exactly for this.

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

AddScoped will return the same instance of a DbContext (and repository if you register it as such) for the lifetime of the scope (which in ASP.NET Core equals the lifetime of a request).

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

When you use AddScope you shouldn't dispose the context yourself, because the next object that resolves your repository will have an disposed context.

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

Entity Framework by default registers the context as scoped, so your repository should be either scoped (same lifetime as the context and request) or transient (each service instance gets it's own instance of the repository, but all repositories within a request still share the same context).

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

Making the context singleton causes serious issues, especially with the memory (the more you work on it, the more memory the context consumes, as it has to track more records). So a DbContext should be as short-lived as possible.

制作上下文单例会导致严重的问题,特别是对于内存(你对它的工作越多,上下文消耗的内存就越多,因为它必须跟踪更多的记录)。所以DbContext应尽可能短暂。

Duration of the context has the advantage that you can still roll back all operations during the request if something goes wrong and handle it as one single transaction.

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