TopShelf,Ninject和EF代码的主题范围问题首先 [英] Ninject scope problems with TopShelf, Ninject and EF code first

查看:173
本文介绍了TopShelf,Ninject和EF代码的主题范围问题首先的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  static void Main(string [] args)
{
using(IKernel kernel = new StandardKernel(new NinjectDependencyResolver()))
{
设置设置= 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 => 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定时器定时器;

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

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

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

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

设置设置= kernel.Get< Settings>();

if(settings.Service.ServiceType == 1)
{
//接口实现具有IUnitOfWork和IMyRepository的构造函数注入
kernel.GetAll< IExternalReportService> ().Each(x => x.Update());
}

if(settings.Service.ServiceType == 2)
{
//接口实现具有构造函数注入IUnitOfWork和IMyRepository
内核.GetAll< IExternalDataService>()。每个(x => x.GetData());
}

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

这些是Ninject绑定:

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

绑定< IDatabaseFactory>()。到< DatabaseFactory>()
.InThreadScope()
.WithConstructorArgument(connectionString,connectionStringSettings.Name);

绑定< IUnitOfWork>()。到< UnitOfWork>();
绑定< IMyRepository>()。到< MyRepository>();
绑定< IExternalReportService>()。到< ReportService1>();
绑定< IExternalReportService>()。到< ReportService2>();
绑定< IExternalDataService>()。到< DataService1>();
绑定< IExternalDataService>()。到< DataService2>();

绑定< Settings>()。ToConstant(settings);
}

私有设置CreateSettings()
{
//从app.config读取值并返回具有设置的对象
}
}

首先让我说我对这段代码不满意。当应用程序启动时,创建一个内核的实例,将提取设置中的值,并使用TopShelf使用BotService对象创建一个Windows服务。



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



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

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



如果我使用InSingeltonScope,上下文将变得过时,最终问题将会开始上升到上下文无效。如果我使用InThreadScope,我遇到同样的问题,因为线程完成后不会处理对象。因为我已经处理了上下文,所以Run()使用了以前使用的线程和异常。如果我删除了处理上下文的代码行,那么我们遇到与InSingletonScope相同的问题,当我们重新使用线程时,我们最终会出现一个陈旧的上下文。



这导致当前的代码,我保证每个Time Run()被执行,上下文是直到它被完成的地方,因为内核被处置,我确保下一次相同的线程使用我们得到一个新的上下文,因为内核被重新创建(至少我认为这是发生了什么)。



我的Ninject技能不是先进的,非常有限的信息在那里如何处理这个问题。我认为正确的方法是在Main中创建一个内核,然后可以通过构造函数将我需要的内容注入到BotService对象中。但是同时需要为每个Run()创建Context,以避免如果我使用上面提到的一个范围这种方法,那将会发生一个陈旧的上下文。



如何修改上面的例子,这样做是正确的?我目前正在使用Ninject 2.2.1.4。

解决方案

首先,让我尝试并解释你的问题。对我来说听起来像你有一个依赖关系(DatabaseFactory),它需要一个自定义的范围(或者其他人可能会将它们称为)。对我来说听起来像是想要在单次执行Run的过程中返回的DatabaseFactory的相同实例。



如果这是正确的,我想你应该能够通过以下两种方法之一实现:


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

      private StandardKernel _kernel / *传递给构造函数* /; 

    public void Run()
    {
    using(var block = _kernel.BeginBlock())
    {
    var settings = block.Get< Settings> ;();
    if(settings.Service.ServiceType == 1)
    {
    //接口实现具有IUnitOfWork和IMyRepository的构造函数注入
    block.GetAll< IExternalReportService>() (x => x.Update());
    }

    if(settings.Service.ServiceType == 2)
    {
    //接口实现具有IUnitOfWork和IMyRepository
    块的构造函数注入.GetAll< IExternalDataService>()。每个(x => x.GetData());
    }
    }
    }


  2. 如果您只想要具体要为每个执行刷新的实例,您应该可以使用自定义作用域对象来完成此操作(请参阅InScope()方法和这个帖子从Nate )。不幸的是,您可能会遇到多线程问题,因为Timer可能会在另一个线程运行结束之前调用Run。


I am currently using TopShelf with Ninject to create a Windows Service. I have the following code to setup the Windows Service using TopShelf:

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();
    }
}

This is the object behind the Windows Service doing all the work:

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();
    }
}

These are the Ninject bindings:

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

First off let me say that I am not happy with this code. When the application starts an instance of the kernel is created, the values from settings are fetched and I use TopShelf to create a Windows Service using the BotService object.

Everytime the timer event fires the Run() method is executed. Here another instance of the kernel is created, again it reads the settings and depending on the value the kernel fetches all implementations of the interface and executes the corresponding method. Each of these implementations has a constructor where IUnitOfWork and IMyRepository are injected for data access.

When the methods are finished I dispose of the context and dispose of the kernel.

Why did I set it up like this? Originally I only created one kernel in the Main and used a constructor in the BotService to inject the implementations as opposed to creating another instance of the kernel. The problem was that the DatabaseFactory needed a InSingletonScope or InThreadScope to work.

If I used InSingeltonScope the context would become stale and eventually issues would start to creep up where the context is invalid. If I used InThreadScope I run into the same issue because it doesn't dispose the objects once the thread is done. Eventually Run() used a previously used thread and an exception occurrs since I already disposed of the Context. If I removed the line of code where I dispose of the context well then we run into the same issue as InSingletonScope where we end up with a stale context when the thread is re-used.

This lead to the current code where I am guaranteed that each Time Run() is executed the context is around until it is done where it is disposed and since the kernel is disposed as well I ensure that next time the same thread is used we get a new context since the kernel is re-created (at least I think this is what's happening).

My Ninject skills are not that advanced and there is very limited information out there on how to approach this problem. I think the right approach would be to create one kernel in the Main only and then be able to inject what I need into the BotService object via a constructor. But at the same time the Context needs to be created for each Run() in order to avoid a stale context which would happen if I used one of the scopes mentioned above with this approach.

How can I modify the example above so it would be correct? I am currently using Ninject 2.2.1.4.

解决方案

First, let me try and distill your problem down a bit. It sounds to me like you have a dependency (DatabaseFactory) that needs a custom scope (or life-time as others may refer to them as). It sounds to me like you want the same instance of DatabaseFactory returned for the duration of a single execution of Run.

If this is correct, I think you should be able to accomplish this in one of two ways:

  1. If you don't mind all instances being refreshed for each execution of 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. If you only want the specific instances to be refreshed for each execution, you should be able to accomplish this using a custom scope object (have a look at InScope() method and this post from Nate). Unfortunately, you would probably run into a host of multi-threading issues since Timer may call Run before another thread has finished running.

这篇关于TopShelf,Ninject和EF代码的主题范围问题首先的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆