【问题标题】:Cannot access a disposed context instance with N-layer architecture无法访问具有 N 层架构的已处置上下文实例
【发布时间】:2021-09-20 09:14:01
【问题描述】:

我正在尝试为我的 Telegram Bot 创建一个 N 层架构。我创建了 DAL、BLL 和 PL。我想将实体 News 添加到我的数据库中。但是我的上下文有些问题。

我的数据库上下文:

public class ApplicationContext : DbContext
    {
        public DbSet<News> News { get; set; }
        public DbSet<User> Users { get; set; }

        public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)
        {           
        }
               
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<News>().Property(tn => tn.Id).ValueGeneratedOnAdd();
            modelBuilder.Entity<User>().Property(tn => tn.Id).ValueGeneratedOnAdd();

            modelBuilder.Entity<News>().Property(tn => tn.Title).IsRequired();
            modelBuilder.Entity<News>().Property(tn => tn.Href).IsRequired();
            modelBuilder.Entity<News>().Property(tn => tn.Image).IsRequired();
            modelBuilder.Entity<News>().Property(tn => tn.Date).IsRequired();

            modelBuilder.Entity<User>().Property(tn => tn.UserId).IsRequired();
            modelBuilder.Entity<User>().Property(tn => tn.UserName).IsRequired();
            modelBuilder.Entity<User>().Property(tn => tn.DateOfStartSubscription).IsRequired();
            base.OnModelCreating(modelBuilder);
        }
    }

UoW 接口:

public interface IUnitOfWork : IDisposable
    {
        INewsRepository News { get; }
        IUserRepository Users { get; }
        int Complete();
    }

UoW 类:

 public class UnitOfWork : IUnitOfWork
    {       
        public IUserRepository Users { get; }
        public INewsRepository News { get; }
        private readonly ApplicationContext _context;

        public UnitOfWork(ApplicationContext context)
        {
            _context = context;
            Users = new UserRepository.UserRepository(_context);
            News = new NewsRepository.NewsRepository(_context);
        }

        public int Complete() =>  _context.SaveChanges();

        public void Dispose() => _context.Dispose();
    }

我的 DAL 通用存储库:

async Task IGenericRepository<T>.AddAsync(T entity) => await _context.Set<T>().AddAsync(entity);

DAL 注入:

 public static class DALInjection
    {
        public static void Injection(IServiceCollection services)
        {
            services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>));
            services.AddTransient<IUserRepository, UserRepository.UserRepository>();
            services.AddTransient<INewsRepository, NewsRepository.NewsRepository>();
            services.AddTransient<IUnitOfWork, UnitOfWork.UnitOfWork>();
        }
    }

我的 BLL 服务类:

 public class ParserService : IParser
    {
     private IUnitOfWork _unitOfWork;
     private readonly IMapper _mapper;

    public ParserService(IUnitOfWork unitOfWork, IMapper mapper)
        {
            _unitOfWork = unitOfWork;
            _mapper = mapper;
        }

 private async Task SaveArticles(IEnumerable<NewsDTO> articlesDTO)
        {            
            var articles = _mapper.Map<IEnumerable<NewsDTO>, IEnumerable<News>>(articlesDTO);
            await _unitOfWork.News.AddAsync(articles.First());
            _unitOfWork.Complete();
        }

BLL 注入:

public static class BLLInjection
    {
        public static void Injection(IServiceCollection services)
        {
            DALInjection.Injection(services);
            services.AddTransient<IParser, ParserService>();
            services.AddTransient<IArticleService, ArticleService>();
            services.AddAutoMapper(typeof(CommonMappingProfile));
        }
    }

我的 PL:

   private static async Task SendArticleAsync(long chatId, int offset, int count)
        {
            var articles = await _parser.MakeHtmlRequest(offset, count);
            foreach (var article in articles)
            {
                var linkButton = KeyboardGoOver("Перейти", article.Href);
                await _client.SendPhotoAsync(chatId: chatId, photo: article.Image,
                        caption: $"*{article.Title}*", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: linkButton);

            }
            await OnLoadMoreNewsAsync(chatId, offset + count, count);
        }

PL 启动类:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
           
            services.AddDbContext<ApplicationContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection"),
                    b => b.MigrationsAssembly(typeof(ApplicationContext).Assembly.FullName)));
            BLLInjection.Injection(services);
           
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "TelegramBot.WebApi", Version = "v1" });
            });
        }

当我尝试调试时,我遇到了这个错误,但我无法解决这个问题。

_context = Database = {"无法访问已释放的上下文实例。此错误的常见原因是释放从依赖注入中解析的上下文实例,然后尝试在应用程序的其他地方使用相同的上下文实例。这可能哦...

有人可以帮我解决这个问题吗?

【问题讨论】:

  • 错误出现在您未发布的代码中。不要一开始就使用“通用存储库”反模式。 DbContext 已经是一个工作单元,一个 DbSet 已经是一个存储库。像 EF Core 这样的 ORM 已经抽象了持久性机制。 AddAsync 没有做你认为它做的和不需要的事情。在这种情况下,丢失的代码以某种方式尝试使用单例 DbContext 实例。
  • 从普通的旧 EF Core 代码开始,而不是尝试使用“最佳实践”。现在没有人知道发生了什么,除了说“存储库”和“工作单元”类不工作。正确配置的 DbContest 可以正常工作。使用AddDbContext 工作正常。如果您将 DbContext 作为依赖项添加到控制器,它将正常工作,并且仅在您在操作结束时调用 SaveChanges 一次时才会持久更改。如果您不这样做,所有更改都将被丢弃。这是开箱即用的 U-o-W
  • 您能解释一下为什么“通用存储库”是反模式吗?如果我有很多存储库,那么不要重复我的代码会很有用。我可以发布哪些代码可以帮助您理解我的问题?
  • 为什么你甚至需要一个存储库? DbSet 是存储库。 DbContesxt 是工作单元。为什么要写 AddAsync 方法呢?至于您需要发布的代码,是将DbContext 实例存储到_context 的代码。显然,您不应该使用 Singleton 实例。您可能需要发布完整的 UoW 和操作代码,因为无论该代码做什么,都不是标准或常见的

标签: c# asp.net-core entity-framework-core


【解决方案1】:

您的代码中存在一些问题。

  1. 控制器是作用域实体,它们的实例根据 http 请求创建,并在请求完成后释放。这意味着控制器不是订阅事件的好地方。当您调用/start 端点时,您会创建TelegramControllerTelegramBotClient 的实例,但是一旦请求完成,控制器及其所有非单例依赖项(在您的情况下为IParser)都会被释放。但是您订阅了捕获对IParser 的引用的TelegramBotClient 事件。这意味着请求完成后将到达的所有事件都将尝试访问 disposed IParser 实例,这就是您的异常的原因。
    对于基于事件的消息,最好使用IHostedService。您将需要使用 IServiceScopeFactory 为每条消息创建一个范围,并从该范围解决您的依赖关系。
public class TelegramHostedService : IHostedService
{
    private IServiceScopeFactory _scopeFactory;

    public TimedHostedService(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _client = new TelegramBotClient(_token);
        _client.OnMessage += OnMessageHandlerAsync;
        _client.OnCallbackQuery += OnLoadCallBackAsync;
        _client.StartReceiving(); 

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        // TODO: Unsubscribe from events
        return Task.CompletedTask;
    }

    public static async void OnMessageHandlerAsync(object sender, MessageEventArgs e)
    {
        using var scope = _scopeFactory.CreateScope();
        var handler = scope.ServiceProvider.GetRequiredService<MessageHandler>();
        await handler.Handle(TODO: pass required args); // Move the logic to separate handler class to keep hosted service clean
    }

    ...
}

我在事件订阅后将呼叫转移到_client.StartReceiving();,否则当您收到事件但您还没有订阅者时可能会出现竞争条件,并且此事件将丢失。

  1. 第二个问题正如@PanagiotisKanavos 所说:async void 不能等待,因此一旦您的代码第一次遇到 true 异步方法(如 DB 访问、http 请求、文件读取或任何其他I/O 操作)控制返回到调用async void 方法的点并继续执行而不等待操作完成。如果您从此类方法中抛出未处理的异常,整个应用程序甚至可能崩溃,因此应避免使用async void。为防止出现这些问题,请使用同步方法包装您的异步事件处理程序,这将使用 Wait() 方法阻止执行:
public class TelegramHostedService : IHostedService
{
    private IServiceScopeFactory _scopeFactory;

    public TimedHostedService(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _client = new TelegramBotClient(_token);
        _client.OnMessage += OnMessageHandler;
        _client.OnCallbackQuery += OnLoadCallBack;
        _client.StartReceiving(); 

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        // TODO: Unsubscribe from events
        return Task.CompletedTask;
    }

    public static void OnMessageHandler(object sender, MessageEventArgs e)
    {
        OnMessageHandlerAsync(sender, e).Wait();
    }

    public static async Task OnMessageHandlerAsync(object sender, MessageEventArgs e)
    {
        using var scope = _scopeFactory.CreateScope();
        var handler = scope.ServiceProvider.GetRequiredService<MessageHandler>();
        await handler.Handle(TODO: pass required args); // Move the logic to separate handler class to keep hosted service clean
    }

    ...
}

【讨论】:

    猜你喜欢
    • 2023-02-20
    • 2021-03-29
    • 2022-10-13
    • 1970-01-01
    • 2021-06-29
    • 1970-01-01
    • 1970-01-01
    • 2021-06-24
    • 2016-11-05
    相关资源
    最近更新 更多