繁体   English   中英

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

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

我有一个使用Entity Framework 6.0,用于DI和CQRS架构的Autfac的WebApi项目。 我的问题是DbContext没有按预期方式处理。 我采取的行动:

  • 我运行两个快速请求,例如,将请求从Postman发送到一个端点,运行时在控制器方法中的断点处停止,我将第二个请求发送到不同控制器中的另一个端点。
  • 恢复运行时
  • 如果第二个请求在第一个请求完成之前完成,则第一个请求将引发并抛出dbcontext错误,并且它无法运行应做的事情

当我从前端一个接一个地发布并修补时,最初出现了问题。

似乎生命周期范围并不是真正的每个请求。 似乎所有dbcontexts都放置在请求的一端。 另一个没有任何可用的东西。

如何配置?

从最高层开始-控制器:

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

服务层:

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

命令总线所在的服务工具界面:

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

以及任何用于命令的处理程序:

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

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

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

}

对于使用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);
    }
}

两个接口之间的区别在于IQueryContext无法更改实体状态,而不能使用SaveChagnes()方法。 IQueryContext中具有所有DbSet,而ICommandsContext继承自它,并添加SettingState方法(添加,修改,删除)和SaveChanges()方法。 如示例aboove所示,将IQueryContext注入查询中并将ICommandsContext注入命令中。 现在,命令总线的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);
        }
    }

虽然命令总线看起来像那样

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

该应用程序的翻译使用了完全独立的上下文,但是我在这里并不重要。

我没有发现类似问题的任何帖子。 它仅在两个请求同时处理的情况下发生。 我不知道配置是否错误或Autofac搞砸了,因为从技术上讲,它不应处置分配给另一个请求的dbcontext。

抱歉,文本墙;)我希望有人可以提供帮助。

辛苦地将dbcontext的生存期更改为SingleInstance可以解决此问题,但我们不希望这样:)

解决方案编辑:

@ZeljkoVujaklija注意到InfrastractureModule中的CommandsDbContext声明似乎很奇怪。 我从InfrastractureModule中删除了整个CommandBus注册。 相反,我在所有命令所在的程序集中创建了CommandsModule。 看起来像这样:

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

}

它不仅解决了问题,而且摆脱了庞大的工厂。

如果您在ASP.NET Core中运行,则应运行InstancePerLifetimeScope而不是InstancePerRequest

使用InstancePerLifetimeScope而不是InstancePerRequest。 在以前的ASP.NET集成中,您可以将依赖项注册为InstancePerRequest,以确保每个HTTP请求仅创建一个依赖项实例。 之所以可行,是因为Autofac负责设置每个请求的生命周期范围。 随着Microsoft.Extensions.DependencyInjection的引入,每个请求和其他子生存期范围的创建现在已成为框架提供的一致容器的一部分,因此所有子生存期范围均得到平等对待-没有特殊的“请求级别范围”不再。 与其注册您的依赖项InstancePerRequest,不如使用InstancePerLifetimeScope,您应该获得相同的行为。 请注意,如果要在Web请求期间创建自己的生存期作用域,则会在这些子作用域中获得一个新实例。

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

暂无
暂无

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM