使用Autofac解析IRepository的多个具体类<t>?

时间:2017-01-12 06:18:01

标签: c# dependency-injection autofac

我是AutoFac的新手,我遇到了两个需要在使用MVVM的WPF项目中实现的问题。我正在使用接口来实现存储库,但我将为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,他们可以这样做而不必担心重新输入数据。

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

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

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

    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文件,我已经在原始问题中给出了代码。

3 个答案:

答案 0 :(得分: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>();

答案 1 :(得分:0)

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

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也可以使用,但imho enum更清晰,更不容易出错),例如

enum RepositoryType { Sql, Xml, Csv }

然后,您可以注入IRepository<Model>,而不是注入始终为您提供最新注册依赖项的IIndex<RepositoryType, IRepository<Model>>。使用索引运算符可以获得正确的存储库类型。此外,您可以实现某种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的更多详细信息。

答案 2 :(得分:0)

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

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

然而,我已经将tdragon(感谢老兄!)用于提出IIndex方法,这就是为什么我把他的帖子作为答案,但我从其他来源获得了更多关于它的反馈,这就是为什么我&# 39;我发布了我的答案。

我去了并联系了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开始,有一个属性过滤器可以帮助解决这个问题。我需要做的第一件事是添加&#34; .WithAttributeFiltering()&#34;当像这样构建容器时:

 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 }

谢谢大家的帮助!