使用DI / StructureMap切换数据库

时间:2015-04-20 14:05:56

标签: c# dependency-injection inversion-of-control structuremap easynetq

我有一个应用程序,它侦听队列中的消息并处理所述消息。

到目前为止,我已经为每个客户安装了一个单独的应用程序实例,每个客户在配置文件中都有自己的数据库连接字符串。在更新应用程序时,这变得很痛苦。

现在我试图重构这个,这样我就可以安装一个具有所有必要连接字符串的实例,并且会根据一些运行时参数在数据库之间切换 - 例如message.Client 。

如果我手工连接所有东西,这一切都很好......

var data = new MyData(message.Client);
var processor = new MessageProcessor(new Foo(data), new Bar(data));
processor.Process(message);

...但我正在努力解决如何使用DI容器开始实现这一目标。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using StructureMap;

namespace M.Test
{
    public interface IData
    {
        IEnumerable<string> Names { get; }
    }

    public class MyData : IData
    {
        private readonly string _client;

        public MyData(string client)
        {
            _client = client;
        }

        public IEnumerable<string> Names 
        {
            get { return _client == "Client A" ? new[] {"One", "Two", "Three"} : new[] {"Uno", "Dos", "Tres"}; }
        }
    }

    public interface IFoo
    {
        void FooDo();
    }

    public class Foo : IFoo
    {
        private readonly IData _dataContext;

        public Foo(IData dataContext)
        {
            _dataContext = dataContext;
        }

        public void FooDo()
        {
            Debug.WriteLine(_dataContext.Names.First());
        }
    }

    public interface IBar
    {
        void BarDo();
    }

    public class Bar : IBar
    {
        private readonly IData _data;

        public Bar(IData data)
        {
            _data = data;
        }

        public void BarDo()
        {
            Debug.WriteLine(_data.Names.Last());
        }
    }

    public interface IMessageProcessor
    {
        void Process(Message message);
    }

    public class MessageProcessor : IMessageProcessor
    {
        private readonly IFoo _foo; 
        private readonly IBar _bar;

        public MessageProcessor(IFoo foo, IBar bar)
        {
            _foo = foo;
            _bar = bar;
        }

        public void Process(Message message)
        {
            _foo.FooDo();
            _bar.BarDo();
        }
    }

    public class Message
    {
        public string Client { get; set; }
    }

    class Program
    {
        private static Container _container;

        static void Main(string[] args)
        {
            _container = new Container();
            _container.Configure(x =>
            {
                x.For<IData>().Use<MyData>(); 
                x.For<IMessageProcessor>().Use<MessageProcessor>();
                x.For<IBar>().Use<Bar>();
                x.For<IFoo>().Use<Foo>();
            });

            MessageReceived(new Message {Client = "Client A"});
            MessageReceived(new Message {Client = "Client B"});
            MessageReceived(new Message {Client = "Client A"});

            Console.ReadKey();
        }

        private static void MessageReceived(Message message)
        {
            // Fine if I do this...
            var data = new MyData(message.Client);
            var processor = new MessageProcessor(new Foo(data), new Bar(data));
            processor.Process(message);

            // But would like to do this...
            var diProcessor = _container.TryGetInstance<IMessageProcessor>();
            diProcessor.Process(message);
        }
    }
}

根据我的阅读,我可以看到构造函数参数可以与StructureMap一起使用。例如:

x.For<IData>().Use<MyData>().Ctor<string>("client").Is(someValueAtRunTime);

...但是无法弄清楚应该设置someValueAtRunTime的时间/方式,无论我尝试什么,我都会质疑线程的安全性。

任何帮助表示感谢。

1 个答案:

答案 0 :(得分:1)

实现此类运行时配置的常见模式是使用abstract factory pattern。 (见类似问题here

抽象工厂本质上是一个普通的工厂对象,它负责根据您的运行时配置返回正确的数据库实现 - 但是,返回一个具体的类型,您的工厂方法返回一个抽象形式的抽象接口或抽象类)。

您使用StructureMap注册此工厂类,因此它也可以通过StructureMap管理其依赖项,如下所示:

public class StorageRegistry : Registry
{
    public StorageRegistry()
    {
        ...
        this.For<IDataStoreInstance>().Use(ctx => ctx.GetInstance<DataStoreAbstractFactory>().ConfigureStorage());
        ...
    }
}

工厂类看起来像这样(这个例子来自于aboven引用的答案,但你可以清楚地看到它将如何在你的环境中组合。

public class DataStoreAbstractFactory
{
    public DataStoreAbstractFactory()
    {
        // Dependencies to figure out data storage method can be injected here.
    }

    public IDataStoreInstance ConfigureStorage()
    {
        // This method can be used to return type of storage based on your configuration (ie: online or maintenance)
    }
}

public interface IDataStoreInstance
{
    void Save();
}

public class DatabaseStorage : IDataStoreInstance
{
    public void Save()
    {
        // Implementation details of persisting data in a database
    }
}

public class FileStorage : IDataStoreInstance
{
    public void Save()
    {
        // Implementation details of persisting data in a file system
    }
}

我希望这会有所帮助。