在WebApi项目上使用Autofac依赖注入时处理DbContext

时间:2018-03-12 13:13:59

标签: c# asp.net entity-framework autofac cqrs

我有一个使用Entity Framework 6.0的WebApi项目,用于DI和CQRS架构的Autfac。我有DbContext没有处理它应该如何的问题。我采取的行动:

  • 我提出两个快速请求,例如从Postman向一个端点发送请求,运行时在控制器方法中停止在断点上,我将第二个请求发送到不同控制器中的另一个端点。
  • 恢复运行时
  • 如果第二个请求在第一个请求完成之前完成,则第一个请求抛出并且错误表明dbcontext已被处理,并且它无法运行它应该执行的任何操作

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

看起来生命范围并不是真正的每个请求。似乎所有dbcontex都被置于请求的一端。另一个没有任何可用的东西。

如何配置?

从最高层开始 - 控制器:

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

}

对于DI我使用Autofac,所有资源都设置为按请求生命周期,分成模块,例如数据访问模块

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()方法。 IQueryContext被注入查询和ICommandsContext到命令中,如下例中的seend。 现在,命令总线的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();
    }

}

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

1 个答案:

答案 0 :(得分:0)

如果您在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