TopShelf、Ninject 和 EF 代码优先的 Ninject 范围问题

2024-01-27

我目前正在使用 TopShelf 和 Ninject 来创建 Windows 服务。我有以下代码来使用 TopShelf 设置 Windows 服务:

static void Main(string[] args)
{
    using (IKernel kernel = new StandardKernel(new NinjectDependencyResolver()))
    {
        Settings settings = kernel.Get<Settings>();

        var host = HostFactory.New(x =>
        {
            x.Service<BotService>(s =>
            {
                s.ConstructUsing(name => new BotService(settings.Service.TimeInterval));
                s.WhenStarted(ms => ms.Start());
                s.WhenStopped(ms => ms.Stop());
            });

            x.RunAsNetworkService();

            x.SetServiceName(settings.Service.ServiceName);
            x.SetDisplayName(settings.Service.DisplayName);
            x.SetDescription(settings.Service.Description);
        });

        host.Run();
    }
}

这是执行所有工作的 Windows 服务背后的对象:

public class BotService
{
    private readonly Timer timer;

    public BotService(double interval)
    {
        this.timer = new Timer(interval) { AutoReset = true };
        this.timer.Elapsed += (sender, eventArgs) => Run();
    }

    public void Start()
    {
        this.timer.Start();
    }

    public void Stop()
    {
        this.timer.Stop();
    }

    private void Run()
    {
        IKernel kernel = new StandardKernel(new NinjectDependencyResolver());

        Settings settings = kernel.Get<Settings>();

        if (settings.Service.ServiceType == 1)
        {
            // The interface implementation has constructor injection of IUnitOfWork and IMyRepository
            kernel.GetAll<IExternalReportService>().Each(x => x.Update());
        }

        if (settings.Service.ServiceType == 2)
        {
            // The interface implementation has constructor injection of IUnitOfWork and IMyRepository
            kernel.GetAll<IExternalDataService>().Each(x => x.GetData());
        }

        kernel.Get<IUnitOfWork>().Dispose();
        kernel.Dispose();
    }
}

这些是 Ninject 绑定:

public class NinjectDependencyResolver : NinjectModule
{
    public override void Load()
    {
        Settings settings = CreateSettings();
        ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings["DB"];

        Bind<IDatabaseFactory>().To<DatabaseFactory>()
                                .InThreadScope()
                                .WithConstructorArgument("connectionString", connectionStringSettings.Name);

        Bind<IUnitOfWork>().To<UnitOfWork>();
        Bind<IMyRepository>().To<MyRepository>();
        Bind<IExternalReportService>().To<ReportService1>();
        Bind<IExternalReportService>().To<ReportService2>();
        Bind<IExternalDataService>().To<DataService1>();
        Bind<IExternalDataService>().To<DataService2>();

        Bind<Settings>().ToConstant(settings);
    }

    private Settings CreateSettings()
    {
        // Reads values from app.config and returns object with settings
    }
}

首先我要说的是我对这段代码不满意。当应用程序启动时,将创建内核实例,从设置中获取值,然后我使用 TopShelf 通过 BotService 对象创建 Windows 服务。

每次计时器事件触发时,都会执行 Run() 方法。这里创建了内核的另一个实例,它再次读取设置,并根据值,内核获取接口的所有实现并执行相应的方法。这些实现中的每一个都有一个构造函数,其中注入 IUnitOfWork 和 IMyRepository 以进行数据访问。

当这些方法完成后,我会处理上下文并处理内核。

我为什么要这样设置呢?最初,我只在 Main 中创建了一个内核,并在 BotService 中使用构造函数来注入实现,而不是创建内核的另一个实例。问题是 DatabaseFactory 需要 InSingletonScope 或 InThreadScope 才能工作。

如果我使用 InSingeltonScope,上下文就会变得陈旧,最终问题会在上下文无效的地方开始蔓延。如果我使用 InThreadScope 我会遇到同样的问题,因为线程完成后它不会释放对象。最终 Run() 使用了以前使用过的线程并且发生了异常,因为我已经处理了 Context。如果我删除了很好地处理上下文的代码行,那么我们会遇到与 InSingletonScope 相同的问题,当重新使用线程时,我们最终会得到陈旧的上下文。

这导致了当前的代码,我保证每次执行 Run() 时,上下文都在周围,直到它在其被处置的地方完成,并且由于内核也被处置,所以我确保下次使用相同的线程时,我们得到重新创建内核后的新上下文(至少我认为这是正在发生的事情)。

我的 Ninject 技能并不是那么先进,并且关于如何解决这个问题的信息非常有限。我认为正确的方法是仅在 Main 中创建一个内核,然后能够通过构造函数将我需要的内容注入到 BotService 对象中。但同时需要为每个 Run() 创建上下文,以避免如果我使用上述方法中的范围之一,就会出现陈旧的上下文。

我怎样才能修改上面的例子,使它是正确的?我目前使用的是 Ninject 2.2.1.4。


首先,让我尝试稍微提炼一下您的问题。在我看来,您有一个依赖项(DatabaseFactory)需要自定义范围(或其他人可能将其称为生命周期)。在我看来,您希望在单次执行 Run 期间返回相同的 DatabaseFactory 实例。

如果这是正确的,我认为您应该能够通过以下两种方式之一来完成此任务:

  1. 如果您不介意每次执行 Run 时刷新所有实例:

    private StandardKernel _kernel /* passed into constructor */;
    
    public void Run()
    {
        using (var block = _kernel.BeginBlock())
        {
            var settings = block.Get<Settings>();
            if (settings.Service.ServiceType == 1)
            {
                // The interface implementation has constructor injection of IUnitOfWork and IMyRepository
                block.GetAll<IExternalReportService>().Each(x => x.Update());
            }
    
            if (settings.Service.ServiceType == 2)
            {
                // The interface implementation has constructor injection of IUnitOfWork and IMyRepository
                block.GetAll<IExternalDataService>().Each(x => x.GetData());
            }
        }
    }
    
  2. 如果您只想为每次执行刷新特定实例,您可以should能够使用自定义作用域对象来完成此操作(查看 InScope() 方法和内特的这篇文章 http://kohari.org/2009/03/06/cache-and-collect-lifecycle-management-in-ninject-20/)。不幸的是,您可能会遇到许多多线程问题,因为 Timer 可能会在另一个线程完成运行之前调用 Run。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TopShelf、Ninject 和 EF 代码优先的 Ninject 范围问题 的相关文章

随机推荐