【问题标题】:DbContext is Disposed When Using Autofac Dependency Injection on WebApi project在 Web Api 项目中使用 Autofac 依赖注入时 DbContext 被丢弃
【发布时间】:2018-03-13 22:02:11
【问题描述】:

我有一个使用 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...
    }
}

}

对于 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 被注入到命令中,如上例所示。 现在命令总线的 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();
    }

}

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

【问题讨论】:

  • 您是否尝试过使用派生类来实现 ICommandsContext 接口,而不是为两个接口使用同一个类?我对 AutoFac 没有任何经验,但我会尝试这样做
  • 刚试过。没有帮助。我还删除了 IQueryContext 并在各处注入了 ICommandsContext,但这也没有解决问题。
  • InfrastructureModule 中的变量 ICommandsContext _commandsContext 在类级别声明。这很奇怪。在我看来,您在 CreateHandlersFactory 中没有找到任何其他方式来访问它。为什么不在 CommandBus 中解析 ICommandsContext 和 ITranslationsCommandsContext ,即将它们作为 ctor 参数。 CreateHandlersFactory 可以将它们作为参数。
  • @ZeljkoVujaklija 就是这样!!!我改变了命令/命令总线的注册方式。我无法弄清楚您提出的方式,但我在原始帖子中添加了我的解决方案。谢谢你,先生:)

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


【解决方案1】:

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

【讨论】:

  • 我不使用 ASP.NET Core,但我尝试过。不幸的是它没有解决问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多