c# - Quartz.net 的 Unity LifeTime 管理器

标签 c# asp.net-mvc entity-framework unity-container quartz.net

我正在尝试在 asp.NET MVC 应用程序中使用 Quartz.Net。我使用 Unity 作为 DI,使用 PerRequestLifeTimeManager

但是,Quartz.Net 不能与 PerRequestLifeTimeManager 一起工作,因为它没有开始的请求。我尝试用它解决的任何依赖项都会返回 null。

我创建了一个类似于适配器的类,以根据上下文使用两个生命周期管理器,如下所示:

class CustomLifetimeManager : LifetimeManager
{
    private readonly string _key = "CustomLifetimeManagerKey" + Guid.NewGuid();
    private readonly PerResolveLifetimeManager _perResolveLifetimeManager = new PerResolveLifetimeManager();

    private bool IsWebContext => HttpContext.Current != null;

    public override object GetValue()
    {
        return IsWebContext 
            ? HttpContext.Current.Items[_key] 
            : _perResolveLifetimeManager.GetValue();
    }

    public override void SetValue(object newValue)
    {
        if (IsWebContext)
            HttpContext.Current.Items[_key] = newValue;
        else
            _perResolveLifetimeManager.SetValue(newValue);
    }

    public override void RemoveValue()
    {
        if (IsWebContext)
            HttpContext.Current.Items[_key] = null;
        else
            _perResolveLifetimeManager.RemoveValue();
    }
}

我试过 PerThreadLifetimeManager,它第一次执行正常,然后后续执行失败并显示消息

The operation cannot be completed because the DbContext has been disposed.

我已经尝试更改为 PerResolveLifeTimeManager,但失败了

An entity object cannot be referenced by multiple instances of IEntityChangeTracker

我的工作非常简单,类似于以下内容:

[DisallowConcurrentExecution]
class MyJob 
{
    IFooRepository _fooRepository;
    IBarRepository _barRepository;
    public MyJob(IFooRepository fooRepository, IBarRepository barRepository)
    {
        _fooRepository = fooRepository;
        _barRepository = barRepository;
    }

    public void Execute(IJobExecutionContext context)
    {
        var foos = _fooRepository.Where(x => !x.Processed);

        foreach(var foo in foos)
        {
            var bar = _barRepository.Where(x => x.Baz == foo.Baz);
            foo.DoMagic(bar);
            foo.Processed = true;
            _fooRepository.Save(foo);
        }
    }
}

我的工作工厂是

public class UnityJobFactory : IJobFactory
{
    private readonly IUnityContainer _container;

    public UnityJobFactory(IUnityContainer container)
    {
        _container = container;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        return (IJob)_container.Resolve(bundle.JobDetail.JobType);
    }

    public void ReturnJob(IJob job)
    {

    }
}

如何正确管理 Quartz 作业中的依赖生命周期?

最佳答案

我在 CaSTLe.Windsor 和 Quartz.Net 上遇到了同样的问题。我发现唯一适用的方法是 ScopedLifetime,但你必须自己控制范围。如果有新的请求进来,打开一个范围,所有服务都将在这个范围内解析(所谓的 UnitOfWork ;)),当请求结束时,关闭它。

作业处理有点棘手。但是你有两种方法可以解决这个问题。对于这两种方式,您都需要一个可以启 Action 用域的工厂。

  1. 您的作业在构造函数和 Execute(IJobExecutionContext context) 中获得一个工厂工厂启动范围,解析您的服务(存储库...)执行工作所做的任何事情并关闭范围。 using(Factory.BeginScope())非常适合这个。这样做的缺点是,由于使用服务定位器模式,它被认为是不好的做法。

    public class MyJob
    {
        private readonly Factory Factory;
    
        public MyJob(Factory factory)
        {
            Factory = factory;
        }
    
        public void Execute(IJobExecutionContext context)
        {
            using (Factory.BeginScope())
            {
                var repo = Factory.Create<IFooRepository>();
                // Do stuff
    
                Factory.Release(repo);
            }
        }
    }
    
  2. 您的工作得到一个工厂或可以启 Action 用域的东西,您的服务功能如下:Func<IFooRepository> repositoryFunc .然后在你的Execute方法,启动范围(再次使用)并调用您的 repository ,它将返回该范围内的真实存储库,您可以随意使用它。这应该是最好的方法。请注意,这不被视为服务定位器模式,因为你给你的工作赋予了 Func<>对于服务,您只需控制范围。

    public class MyJob
    {
        private readonly Factory Factory;
        private readonly Func<IFooRepository> RepositoryFunc;
    
        public MyJob(Factory factory, Func<IFooRepository> repositoryFunc)
        {
            Factory = factory;
            RepositoryFunc= repositoryFunc;
        }
    
        public void Execute(IJobExecutionContext context)
        {
            using (Factory.BeginScope())
            {
                var repo = RepositoryFunc();
                // Do Stuff
            }
        }
    }
    

问题

  1. PerThreadLifetimeManager

    The operation cannot be completed because the DbContext has been disposed.

    这是因为 Quartz 使用 MainThread 并且默认使用具有 10 个线程的 ThreadPool。所有作业都在 MainThread 中创建,然后在池中的空闲线程中执行。如果您启动一个作业,DBContext 将绑定(bind)到 MainThread。当你开始另一个工作时,那么已经有一个 DBContext 绑定(bind)到这个线程,无论它是被释放还是关闭,LifeTimeManager 都会解析这个已经使用的上下文。 如果你第一次启动你的作业,线程是新的并且你当前的 DBContext 绑定(bind)到这个线程。当您启动下一个作业并在同一个线程中执行时,总会有一个 DBContext 绑定(bind)到该线程。 LifeTimeManager 解析这个已经使用过的上下文,但您不能使用它,因为它已关闭。

  2. PerResolveLifeTimeManager

    An entity object cannot be referenced by multiple instances of IEntityChangeTracker

    这个问题来自 EF。您解析的每个服务都会获得一个新的范围,即使您使用相同的构造函数解析不同的服务也是如此。这导致您使用的每个存储库都有自己的 DBContext。并且 EF 禁止对同一实体使用不同的 DBContext。

关于c# - Quartz.net 的 Unity LifeTime 管理器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40792577/

相关文章:

c# - Facebook OAuth 重定向问题

asp.net-mvc - MVC HttpGet 属性

c# - 使用复杂类型调用存储过程 Entity Framework 动态结果集

c# - 将不可序列化的类转换为字节数组

c# - Response.StatusCode 在 nunit 测试中为 null

c# - 访问多对多表

c# - 增加检索的记录数会成倍增加 linq 查询持续时间

c# - 是否可以让 ASP.NET Core 解决方案包含具有不同目标框架的项目?

c# - #define - 将 C++ 迁移到 C# 或 VB.Net

c# - .Net 观察者模式改变。这些是什么时候发生的,为什么?