简体   繁体   English

在WebApi项目上使用Autofac依赖注入时,将处置DbContext

[英]DbContext is Disposed When Using Autofac Dependency Injection on WebApi project

I have a WebApi project using Entity Framework 6.0, Autfac for DI and CQRS architecture. 我有一个使用Entity Framework 6.0,用于DI和CQRS架构的Autfac的WebApi项目。 The problem I have that DbContext isn't disposing how it supposed to. 我的问题是DbContext没有按预期方式处理。 The action I take: 我采取的行动:

  • I run two quick requests, eg send request from Postman to one endpoint, runtime stops on breakpoint in controller method, I send second request to another endpoint in different controller. 我运行两个快速请求,例如,将请求从Postman发送到一个端点,运行时在控制器方法中的断点处停止,我将第二个请求发送到不同控制器中的另一个端点。
  • Resume Runtime 恢复运行时
  • if the second request finished before the first one is done, the first one throws and error that dbcontext was disposed and it cannot run whatever it was supposed to do 如果第二个请求在第一个请求完成之前完成,则第一个请求将引发并抛出dbcontext错误,并且它无法运行应做的事情

Originally problem appeared when I posted and patched from frontend one after another. 当我从前端一个接一个地发布并修补时,最初出现了问题。

It seems like lifetime scope is not really per-request. 似乎生命周期范围并不是真正的每个请求。 It seems like all dbcontexts are disposed on one of the request's end. 似乎所有dbcontexts都放置在请求的一端。 The other one does not have anything to work with. 另一个没有任何可用的东西。

How is it configured? 如何配置?

Starting from the highest layer - controller: 从最高层开始-控制器:

public class UsersController : BaseController, IUsersApi

{
    private readonly IUserService _userService;

    public UsersController(IUserService userService, ILogging logging) : base(logging)
    {
        _userService = userService;
    }

    [HttpGet]
    [Route("api/users")]
    public IList<UserDto> GetUsers()
    {
        try
        {
            return _userService.GetAllUsers();
        }
        catch (Exception e)
        {
            _logger.Error(e);
            _logger.Trace(e);
            throw;
        }

    }


    [HttpPatch]
    [Route("api/users/")]
    public IHttpActionResult EditUsers(ICollection<UserEditDto> model)
    {
        try
        {
            _userService.EditUsers(model);
            return Ok();
        }
        catch (Exception e)
        {
            _logger.Error(e);
            _logger.Trace(e);
            return BadRequest("Error");
        }
    }
}

Service layer: 服务层:

 public class UserService : IUserService
{
    private readonly IServiceTools _serviceTools;
    private readonly IUserQuerier _userQuerier;

    public UserService(IServiceTools serviceTools, IUserQuerier userQuerier)
    {
        _serviceTools = serviceTools;
        _userQuerier = userQuerier;
    }


    public void EditUsers(ICollection<UserEditDto> model)
    {
        var mapper = _serviceTools.AutoMapperConfiguration.Configure().CreateMapper();
        var userEditCommands = mapper.Map<ICollection<UserEditDto>, ICollection<EditUserCommand>>(model);
        foreach (var command in userSaveCommands)
        {
            _serviceTools.CommandBus.SendCommand(command);
            CacheHelper.Clear(command.Id.ToString());
        }
    }

    public IList<UserDto> GetAllUsers()
    {
        var allUsers = _userQuerier.GetAllUsers();
        var result = allUsers.Select(x => new UserDto()
        {
            ...
        }).ToList();
        return result;
    }        
}

Service Tools interface where command bus sits: 命令总线所在的服务工具界面:

public interface IServiceTools
{
    ICommandBus CommandBus { get; }
    IAutoMapperConfiguration AutoMapperConfiguration { get; }
    IIdentityProvider IdentityProvider { get; }
}

public class ServiceTools : IServiceTools
{
    public ServiceTools(ICommandBus commandBus, IAutoMapperConfiguration autoMapperConfiguration, IIdentityProvider identityProvider)
    {
        CommandBus = commandBus;
        AutoMapperConfiguration = autoMapperConfiguration;
        IdentityProvider = identityProvider;
    }

    public ICommandBus CommandBus { get; }
    public IAutoMapperConfiguration AutoMapperConfiguration { get; }

    public IIdentityProvider IdentityProvider { get; }
}

And whatever handler for command: 以及任何用于命令的处理程序:

public class EditUserHandler : IHandleCommand<EditUserCommand>
{
    private readonly ICommandsContext _commandsContext;

    public SaveUserHandler(ICommandsContext commandsContext)
    {
        _commandsContext = commandsContext;
    }

    public void Handle(EditUserCommand command)
    {
        ... using dbcontext here...
    }
}

} }

For DI I use Autofac, all resources are set to per-request lifetime, split into modules, eg module for data access 对于使用Autofac的DI,所有资源均设置为每个请求生存期,分为多个模块,例如用于数据访问的模块

public class DataModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<AppNameDbContext>().As<ICommandsContext>().InstancePerRequest();
        builder.RegisterType<AppNameDbContext>().As<IQueryContext>().InstancePerRequest();
        base.Load(builder);
    }
}

The difference between both interfaces is that IQueryContext cannot change entity states and use SaveChagnes() method. 两个接口之间的区别在于IQueryContext无法更改实体状态,而不能使用SaveChagnes()方法。 IQueryContext have all DbSets in it, while ICommandsContext inherits from it and adds SettingState methods (added, modified, deleted) and SaveChanges() method. IQueryContext中具有所有DbSet,而ICommandsContext继承自它,并添加SettingState方法(添加,修改,删除)和SaveChanges()方法。 IQueryContext is injected into queries and ICommandsContext into commands as seend in example aboove. 如示例aboove所示,将IQueryContext注入查询中并将ICommandsContext注入命令中。 Now the Autofac config for command bus looks like that: 现在,命令总线的Autofac配置如下所示:

public class InfrastractureModule : Module
{
    private ICommandsContext _commandsContext;
    private ITranslationsCommandsContext _translationsCommandsContext;

    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<AutoMapperConfiguration>().
                         As<IAutoMapperConfiguration>().InstancePerRequest();
        builder.RegisterType<ServiceTools>().As<IServiceTools>().InstancePerRequest();
        builder.Register(c =>
        {
            _commandsContext = c.Resolve<ICommandsContext>();
            _translationsCommandsContext = c.Resolve<ITranslationsCommandsContext>();
            return new CommandBus(CreateHandlersFactory);
        })
        .As<ICommandBus>().InstancePerRequest();
        base.Load(builder);
    }

    private IHandleCommand CreateHandlersFactory(Type type)
    {
        if (type == typeof(XXXCommand))
        {
            return new XXXHandler(_commandsContext);
        }
    }

While the command bus looks like that 虽然命令总线看起来像那样

public class CommandBus : ICommandBus
{
    private readonly Func<Type, IHandleCommand> _handlersFactory;

    public CommandBus(Func<Type, IHandleCommand> handlersFactory)
    {
        _handlersFactory = handlersFactory;
    }

    public void SendCommand<T>(T command) where T : ICommand
    {
        var handler = (IHandleCommand<T>) _handlersFactory(typeof(T));
        handler.Handle(command);
    }
}

There is completely separate context used for translations for the app, but I do not thing that is important here. 该应用程序的翻译使用了完全独立的上下文,但是我在这里并不重要。

I did not find any posts with similar problem. 我没有发现类似问题的任何帖子。 It only occurs when where two requests processed at the same time. 它仅在两个请求同时处理的情况下发生。 I do not know if the configuration is wrong or Autofac messes things up, because it should not technically dispose dbcontext which was allocated for another request. 我不知道配置是否错误或Autofac搞砸了,因为从技术上讲,它不应处置分配给另一个请求的dbcontext。

Sorry for the wall of text ;) I hope someone can help with that. 抱歉,文本墙;)我希望有人可以提供帮助。

Obiously changing dbcontext's lifetime to SingleInstance fixed the problem, but we do not want that :) 辛苦地将dbcontext的生存期更改为SingleInstance可以解决此问题,但我们不希望这样:)

SOLUTION EDIT: 解决方案编辑:

As @ZeljkoVujaklija noticed CommandsDbContext declarations in InfrastractureModule seemed strange. @ZeljkoVujaklija注意到InfrastractureModule中的CommandsDbContext声明似乎很奇怪。 I removed whole CommandBus registration from InfrastractureModule. 我从InfrastractureModule中删除了整个CommandBus注册。 Instead I created CommandsModule in the assembly where all the commands sit. 相反,我在所有命令所在的程序集中创建了CommandsModule。 It looks like that: 看起来像这样:

public class CommandsModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        base.Load(builder);

        builder.RegisterAssemblyTypes(ThisAssembly)
            .Where(x => x.IsAssignableTo<IHandleCommand>())
            .AsImplementedInterfaces();

        builder.Register<Func<Type, IHandleCommand>>(c =>
        {
            var ctx = c.Resolve<IComponentContext>();

            return t =>
            {
                var handlerType = typeof(IHandleCommand<>).MakeGenericType(t);
                return (IHandleCommand)ctx.Resolve(handlerType);
            };
        });

        builder.RegisterType<CommandBus>()
            .AsImplementedInterfaces();
    }

}

Not only it fixes the problem but also gets rid of huge factory. 它不仅解决了问题,而且摆脱了庞大的工厂。

If you are running within ASP.NET Core you should run InstancePerLifetimeScope instead of InstancePerRequest 如果您在ASP.NET Core中运行,则应运行InstancePerLifetimeScope而不是InstancePerRequest

Use InstancePerLifetimeScope instead of InstancePerRequest. 使用InstancePerLifetimeScope而不是InstancePerRequest。 In previous ASP.NET integration you could register a dependency as InstancePerRequest which would ensure only one instance of the dependency would be created per HTTP request. 在以前的ASP.NET集成中,您可以将依赖项注册为InstancePerRequest,以确保每个HTTP请求仅创建一个依赖项实例。 This worked because Autofac was in charge of setting up the per-request lifetime scope. 之所以可行,是因为Autofac负责设置每个请求的生命周期范围。 With the introduction of Microsoft.Extensions.DependencyInjection, the creation of per-request and other child lifetime scopes is now part of the conforming container provided by the framework, so all child lifetime scopes are treated equally - there's no special “request level scope” anymore. 随着Microsoft.Extensions.DependencyInjection的引入,每个请求和其他子生存期范围的创建现在已成为框架提供的一致容器的一部分,因此所有子生存期范围均得到平等对待-没有特殊的“请求级别范围”不再。 Instead of registering your dependencies InstancePerRequest, use InstancePerLifetimeScope and you should get the same behavior. 与其注册您的依赖项InstancePerRequest,不如使用InstancePerLifetimeScope,您应该获得相同的行为。 Note if you are creating your own lifetime scopes during web requests, you will get a new instance in these child scopes. 请注意,如果要在Web请求期间创建自己的生存期作用域,则会在这些子作用域中获得一个新实例。

http://autofaccn.readthedocs.io/en/latest/integration/aspnetcore.html#differences-from-asp-net-classic http://autofaccn.readthedocs.io/en/latest/integration/aspnetcore.html#differences-from-asp-net-classic

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在WebApi项目上使用Unity依赖注入时处理DbContext - DbContext is Disposed When Using Unity Dependency Injection on WebApi project 实体框架 DbContext 不使用依赖注入进行处置 - Entity Framework DbContext is NOT disposed using dependency injection 使用InjectionConstructor时会弃用Unity WebApi DbContext,但如果没有,它会很好地工作 - Unity WebApi DbContext disposed when using InjectionConstructor, but works fine without it 将 DbContext 与依赖注入一起使用 - Using DbContext with dependency injection 使用DBContext和依赖注入的分歧 - Ramifications of using DBContext with Dependency Injection Autofac DbContext 已被释放 - Autofac DbContext has been disposed 对 DBContext 使用依赖注入时 using 语句应该如何看? - How should using statement look when using Dependency Injection for DBContext? Autofac 的通用 DBContext 注入 - Generic DBContext Injection with Autofac 使用 Repository 模式、工作单元和 autofac 依赖注入实现 webapi - Implement webapi with Repository pattern, unit of work and autofac dependency injection 具有依赖项注入的单独类库项目中的dbContext - DbContext in separate class library project with dependency injection
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM