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

标签 windows-services entity-framework-4.1 ef-code-first ninject topshelf

我目前正在使用带有 Ninject 的 TopShelf 来创建 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 绑定(bind):
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() 方法。这里创建了另一个内核实例,它再次读取设置并根据值获取接口(interface)的所有实现并执行相应的方法。这些实现中的每一个都有一个构造函数,其中注入(inject)了 IUnitOfWork 和 IMyRepository 以进行数据访问。

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

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

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

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

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

如何修改上面的示例以使其正确?我目前正在使用 Ninject 2.2.1.4。

最佳答案

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

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

  • 如果您不介意每次执行 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());
            }
        }
    }
    
  • 如果您只想为每次执行刷新特定实例,您应该能够使用自定义范围对象来完成此操作(查看 InScope() 方法和 this post from Nate )。不幸的是,您可能会遇到许多多线程问题,因为 Timer 可能会在另一个线程完成运行之前调用 Run。
  • 关于windows-services - TopShelf、Ninject 和 EF 代码优先的 Ninject 范围问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9216006/

    相关文章:

    c++ - 初始化 COM C++ 服务多线程的正确方法

    c# - 使用安装项目安装时指定 Windows 服务名称

    .net - 有没有办法在不重新编译的情况下覆盖 .NET Windows 服务名称?

    c# - IDbSet.Add 和 DbEntityEntry.State = EntityState.Added 有什么区别?

    entity-framework-4.1 - Unity 和生命周期管理配置 - 始终 transient 生命周期管理器

    ef-code-first - 如何使用 Entity Framework 4 Code-First 定义数据库 View ?

    c# - 如何让Windows服务启动为 "Automatic (Delayed Start)"

    c# - Azure 移动服务 - Entity Framework 代码首先删除并创建数据库不起作用

    asp.net-mvc - MVC4 中 Create() 方法的奇怪行为 - 新对象设置为 ID = 0,另存为其他内容

    c# - Entity Framework 代码的 SQL Server Compact 4.0 连接字符串优先?