使用 DI/StructureMap 切换数据库

Switching Databases using DI/StructureMap

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

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

现在我正在尝试重构它,以便我能够安装一个具有所有必要连接字符串的实例,并将根据某些 运行 时间参数在数据库之间切换 - 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);

...但无法弄清楚 when/how 应该设置 someValueAtRunTime,无论我尝试什么,我都会质疑它的线程安全性。

感谢任何帮助。

实现这样的 运行 时间配置的常见模式是使用 abstract factory pattern. (See a similar question )

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

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

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

工厂 class 看起来像这样(这个例子取自上面引用的答案,但你可以清楚地看到它在你的情况下是如何组合在一起的。

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

希望对您有所帮助。