Сначала устраните проблемы с областью действия с помощью кода TopShelf, Ninject и EF - PullRequest
5 голосов
/ 09 февраля 2012

В настоящее время я использую TopShelf с Ninject для создания службы Windows.У меня есть следующий код для настройки службы Windows с помощью 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();
    }
}

Это объект службы 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 для создания службы Windows с использованием объекта BotService.

Каждый раз, когда запускается событие timer, выполняется метод Run (),Здесь создается другой экземпляр ядра, который снова считывает настройки и, в зависимости от значения, ядро ​​выбирает все реализации интерфейса и выполняет соответствующий метод.Каждая из этих реализаций имеет конструктор, в котором IUnitOfWork и IMyRepository внедряются для доступа к данным.

Когда методы заканчиваются, я избавляюсь от контекста и уничтожаю ядро.

Почему я его установилкак это?Первоначально я создал только одно ядро ​​в Main и использовал конструктор в BotService для внедрения реализаций, а не для создания другого экземпляра ядра.Проблема заключалась в том, что DatabaseFactory для работы требовался InSingletonScope или InThreadScope.

Если бы я использовал InSingeltonScope, контекст стал бы устаревшим, и в конечном итоге возникали проблемы, когда контекст недопустим.Если я использовал InThreadScope, я столкнулся с той же проблемой, потому что он не удаляет объекты после завершения потока.В конце концов Run () использовала ранее использовавшийся поток и исключительную ситуацию, так как я уже избавился от контекста.Если я удаляю строку кода, где я удаляю контекст, то мы сталкиваемся с той же проблемой, что и InSingletonScope, где мы получаем устаревший контекст при повторном использовании потока.

Это приводит к текущемукод, в котором я гарантирую, что каждый Time Run () выполняется, контекст существует до тех пор, пока он не будет выполнен там, где он расположен, и поскольку ядро ​​также расположено, я гарантирую, что в следующий раз при использовании того же потока мы получим новый контекст, так какЯдро воссоздано (по крайней мере, я думаю, что это именно то, что происходит).

Мои навыки Ninject не настолько продвинуты, и существует очень ограниченная информация о том, как решить эту проблему.Я думаю, что правильным подходом было бы создать одно ядро ​​только в Main и затем иметь возможность вставлять то, что мне нужно, в объект BotService через конструктор.Но в то же время контекст должен быть создан для каждого Run (), чтобы избежать устаревшего контекста, который случился бы, если бы я использовал одну из областей, упомянутых выше, с этим подходом.

Как я могу изменитьпример выше так будет правильно?В настоящее время я использую Ninject 2.2.1.4.

1 Ответ

4 голосов
/ 10 февраля 2012

Во-первых, позвольте мне попытаться немного разобрать вашу проблему. Для меня это звучит так, как будто у вас есть зависимость (DatabaseFactory), для которой требуется настраиваемая область (или время жизни, на которое другие могут ссылаться). Мне кажется, что вы хотите, чтобы один и тот же экземпляр DatabaseFactory возвращался в течение одного выполнения Run.

Если это правильно, я думаю, что вы должны сделать это одним из двух способов:

  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. Если вы хотите, чтобы только отдельные экземпляры обновлялись для каждого выполнения, вы должны быть в состоянии выполнить это с помощью пользовательского объекта области (взгляните на метод InScope () и этот пост от Нейт ). К сожалению, вы, вероятно, столкнетесь с множеством проблем с многопоточностью, поскольку Timer может вызвать Run до завершения работы другого потока.
...