【问题标题】:Using Autofac to resolve multiple concrete classes of IRepository<T>?使用 Autofac 解析多个 IRepository<T> 的具体类?
【发布时间】:2017-01-12 06:18:01
【问题描述】:

我是 AutoFac 的新手,我遇到了两个需要在我的 WPF 项目中使用 MVVM 实现的问题。我正在使用一个接口来实现一个存储库,但我将为 SQL、XML 和 CSV 实现多个存储库。所以我的界面是这样的:

public interface IRepository<T> : IReadOnlyRepository<T>, IWriteOnlyRepository<T>
{
}

// covariance interface
public interface IReadOnlyRepository<out T> : IDisposable
{
    T FindById(int id);
    IEnumerable<T> GetAllRecords();
}

// contravariance interface
public interface IWriteOnlyRepository<in T> : IDisposable
{
    void Add(T item);
    void Delete(T item);    
    int Save(); 
}

public class SQLRepository<T> : IRepository<T>
{
    // implements the interface using Entity Framework
}

public class XMLRepository<T> : IRepository<T>
{
    // implements the interface using XML Serializer/Deserializer
}

public class CSVRepository<T> : IRepository<T>
{
    // Implements the interface for TextReader/TextWriter for CSV Files (Excel)
}

所以问题来了:老板告诉我,客户需要在运行程序的同时更改存储库。所以我需要在运行时动态更改存储库。默认为 SQL Server,但客户端可能希望更改为 XML...而不丢失存储库中已有的数据。其背后的原因是,如果他们从 SQL 加载配置但他们想将其保存到 XML 文件并将其发送给他们的客户端,他们可以这样做

-- 或者--

他们从其中一个客户端获取一个 XML 文件,并且他们希望将配置保存到 SQL,他们可以这样做而不必担心重新输入数据。

我通过使用泛型解决了一个问题,因为我将使用相同的 POCO 数据模型类,因此它保留了数据,但是:

  1. 如何实现 3 个不同的具体存储库类?
  2. 如何传入T的参数?

我考虑过使用“命名服务”来区分具体的存储库类和模型基类。然后我会使用引导程序看起来像这样:

public class BootStrapper
{

    public IContainer BootStrap()
    {
        var builder = new ContainerBuilder();

        builder.RegisterType<MainWindow>.AsSelf();

        builder.RegisterType<MainViewModel>.As<IMainViewModel>();

        //?? How do I resolve T of IRepository<T>?
        builder.RegisterType<SQLRepository>.Named<IRepository>("SQL") 
        builder.RegisterType<XMLRepository>.Named<IRepository>("XML")
        builder.RegisterType<CSVRepository>.Named<IRepository>("CSV")

        return builder.Build();
    }
}

public partial class App : Application
{
    protected override void OnStartUp(StartUpEventArgs e)
    {
        base.OnStartUp(e);
        var bootsrapper = new BootStrapper();
        var container = bootstrapper.BootStrap();

        // ?? How do I set the SQLRepository as default?
        var mainWindow = container.Resolve<MainWindow>(); 
        mainWindow.Show();
    }
 }

有什么建议吗?

编辑:我忘记在其中添加我在我的 ViewModel 上使用依赖注入,因此,在我的 MainViewModel 中:

 public class MainViewModel
 {
      private IRepository<Model> _repository;

      public MainViewModel(IRepository<Model> repo)
      {
            _repository = _repo;
      }
 }

现在我确实按照建议将代码更改为:

 builder.RegisterGeneric(typeof(SQLRepository<>).As(typeof(IRepository<>));
 builder.RegisterGeneric(typeof(XMLRepository<>).As(typeof(IRepository<>));

然后我通过单步调试代码,当我点击 MainViewModel 构造函数时,它给了我 XMLRepository 类。从我在“default registrations”的文档中读到的内容,它将始终是 XMLRepository 而不是 SQLRepository。然后我尝试“open generic decorator registration”喜欢:

 builder.RegisterGeneric(typeof(SQLRepository<>).Named("SQL", typeof(IRepository<>));
 builder.RegisterGeneric(typeof(XMLRepository<>).Named("XML", typeof(IRepository<>));


 builder.RegisterGenericDecorator(typeof(SQLRepository<>), typeof(IRepository<>), fromKey: "SQL");
 builder.RegisterGenericDecorator(typeof(XMLRepository<>), typeof(IRepository<>), fromKey: "XML");     

但是当我尝试使用 MainWindow 时如何解决它?

更新编辑#2

好的,tdragon 向我提出了一个合理的问题,询问我希望如何解决这个问题。 MainWindow.xaml.cs 文件如下所示:

public partial class MainWindow : Window
{
     private MainViewModel _viewModel;

     public MainWindow(MainViewModel viewModel)
     {
          InitializeComponent();

          _viewModel = viewModel;
          DataContext = _viewModel;
     }
}

但真正的问题在于 App.xaml.cs 文件,我已经在原始问题中给出了代码。

【问题讨论】:

    标签: c# dependency-injection autofac


    【解决方案1】:

    autofac 文档中有一篇好文章here

    使用 RegisterGeneric() 构建器方法注册通用组件,如下所示。

      var builder = new ContainerBuilder();
      builder.RegisterGeneric(typeof(SQLRepository<>));
      builder.RegisterGeneric(typeof(XMLRepository<>));
      builder.RegisterGeneric(typeof(CSVRepository<>));
      builder.RegisterGeneric(typeof(SQLRepository<>))
                   .As(typeof(IRepository<>))
                   .InstancePerLifetimeScope();
      builder.RegisterGeneric(typeof(XMLRepository<>))
                   .As(typeof(IRepository<>))
                   .InstancePerLifetimeScope();
      builder.RegisterGeneric(typeof(CSVRepository<>))
                   .As(typeof(IRepository<>))
                   .InstancePerLifetimeScope();
      builder.Register(c => new Myclass()).OnActivating(
                    e =>
                    {
                        e.Instance.SqlTaskRepo = e.Context.Resolve<SQLRepository<Task>>();
    
                    }
                    );
    

    更新

    您可以通过扫描程序集来解决 T 问题,这将是更好的解决方法,请查看下面的代码,希望对您有所帮助

        builder.RegisterGeneric(typeof(SQLRepository<>));
        builder.RegisterGeneric(typeof(XMLRepository<>));
        builder.RegisterGeneric(typeof(CSVRepository<>));
        var dataAccess = Assembly.GetExecutingAssembly();
        builder.RegisterAssemblyTypes(dataAccess)
             .Where(t => typeof(SQLRepository<>).IsAssignableFrom(t));
        builder.RegisterAssemblyTypes(dataAccess)
                 .Where(t => typeof(XMLRepository<>).IsAssignableFrom(t));
        builder.RegisterAssemblyTypes(dataAccess)
                 .Where(t => typeof(CSVRepository<>).IsAssignableFrom(t));
        builder.RegisterType<MainViewModel>();
    

    【讨论】:

    • 感谢 Curiousdev 的回复,但没有成功。我稍微修改了我的问题,但您的示例的底线将始终是 SQLRepository。阅读我的编辑以获得进一步的解释。
    • @SteveBrother 看看我更新的答案,希望对您有所帮助
    • 好的,我可以看到,但是让我感到困惑的是如何使用 container.Resolve 并像模型的 POCO 类一样传递 T?请记住,我要做的是解决整个项目中的所有依赖项,因此我需要解决 MainWindow 然后通过执行 MainWindow.Show() 启动窗口
    【解决方案2】:

    一种可能的解决方案是使用密钥而不是名称来注册您的存储库:

    var builder = new ContainerBuilder();
    
    builder.RegisterGeneric(typeof(SqlRepository<>)).Keyed(RepositoryType.Sql, typeof(IRepository<>));
    builder.RegisterGeneric(typeof(XmlRepository<>)).Keyed(RepositoryType.Xml, typeof(IRepository<>));
    builder.RegisterGeneric(typeof(CsvRepository<>)).Keyed(RepositoryType.Csv, typeof(IRepository<>));
    

    其中键将是一些 enum 值(string 也可以使用,但恕我直言 enum 更简洁且不易出错),例如

    enum RepositoryType { Sql, Xml, Csv }
    

    然后,您可以注入IIndex&lt;RepositoryType, IRepository&lt;Model&gt;&gt;,而不是注入总是为您提供最新注册依赖项的IRepository&lt;Model&gt;。使用索引运算符,您可以获得正确的存储库类型。此外,您可以实现某种ConfigurationProvider,您可以在其中存储当前选择的存储库类型,例如:

    public interface IConfigurationProvider
    {
        RepositoryType SelectedRepositoryType { get; set; }
    }
    
    public class ConfigurationProvider : IConfigurationProvider
    {
        public RepositoryType SelectedRepositoryType
        {
            get { /* read the value from some configuration file */ }
            set { /* store the new value */ }
        }
    }
    

    当然,它也应该在容器中注册。您可以将此值存储在任何您想要的位置(app.config、任何其他自定义文件)。

    那么,MainViewModel 的构造函数将如下所示:

    public MainViewModel(
        IIndex<RepositoryType, IRepository<Model>> repoIndex, 
        IConfigurationProvider configurationProvider)
    {
        var repository = repoIndex[configurationProvider.SelectedRepositoryType];  // would return the repository of currently selected type
    }
    

    您可以在Autofac documentation 中找到有关IIndex 的更多详细信息。

    【讨论】:

    • 嗨 tdragon,我喜欢你的替代方案,但后来我遇到了与 Curiousdev 评论相同的问题。如何解析 MainWindow 并将类 EF POCO 类 Model 传递给容器?
    • 你能提供MainWindow类的构造函数吗?你没有在你的问题中提到它......
    • 我不确定我是否正确理解你想要达到的目标......我更新了我的答案,请检查这是否是你想要的
    • 感谢天龙!那行得通!我只需要更多地了解 appsetting.json 并对其进行一些调整以使其正常工作!谢谢老兄!
    【解决方案3】:

    我不得不承认,当我得到这个答案时,我有点不知所措。我正在为可能遇到与我遇到的相同问题的其他人发布此答案。

    由于提供给我的解决方案无法正常工作(直到 tdragon 更新了他的答案),我去了 Googlegroups 获取 Autofac,其他人想出了答案。

    但是,我感谢 tdragon(感谢老兄!)提出了 IIndex 方法,这就是我将他的帖子作为答案的原因,但我从其他来源获得了更多关于它的反馈,这就是为什么我我发布我的答案。

    我联系了 Thomas Claudius Huber,他是两门关于 WPF 和 MVVM 的优秀 Pluralsight 课程的作者。一个是在做 ModelWrappers,另一个是用 ViewModels 做单元测试。我强烈建议那些试图完善 WPF 和 MVVM 技能的新手学习这些课程。正是他的课程让我开启了 Autofac,它帮了我很大的忙。 Thomas 和 tdragon 使用 IIndexing 的解决方案确实帮助解决了这个问题。

    但是有一个有趣的替代方案是 Alex Meyer-Gleaves 在Autofac Googlegroup 上的某个人。他的第一个替代方案是使用 Lambda 表达式:

     builder.Register(c => new MainViewModel(c.ResolveNamed<IRepository<Stock>>("XMLrepository"), c.ResolveNamed<IRepository<Vendor>>("SQLRepository"))).AsSelf();
    

    但他也提到,从 Autofac 4.3.0 开始,有一个属性过滤器可以帮助解决这个问题。我需要做的第一件事是在构建这样的容器时添加“.WithAttributeFiltering()”:

     public IContainer BootStrap()
     {
          builder.RegisterType<MainViewModel>().AsSelf().WithAttributeFiltering();
          builder.RegisterType<MainView>().AsSelf();
          builder.RegisterGeneric(typeof(XMLRepository<>)).Keyed("XMLRepository", typeof(IRepository<>));
          builder.RegisterGeneric(typeof(SQLRepository<>)).Keyed("SQLRepository", typeof(IRepository<>));
    
          return builder.Build();
     }
    

    然后在构造函数中,你可以这样做:

       public MainViewModel([KeyFilter("XMLRepository")]IRepository<Stock> stockRepo, 
                            [KeyFilter("XMLRepository")]IRepository<Vendor> vendorRepo)
      { ... // code here }
    

    感谢大家的帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多