簡體   English   中英

使用Autofac解析多個具體的IRepository類 <T> ?

[英]Using Autofac to resolve multiple concrete classes of IRepository<T>?

我是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的參數?

我想過使用“命名服務”來區分具體的存儲庫類和模型基類。 然后我會使用一個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類。 從我在“ 默認注冊 ”的文檔中讀到的內容,它始終是XMLRepository而不是SQLRepository。 然后我嘗試“ 打開通用裝飾器注冊 ”,如:

 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文件,我已經在原始問題中給出了代碼。

這是一個很好的文章在這里 autofac文檔。

使用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>();

一種可能的解決方案是使用密鑰而不是名稱來注冊您的存儲庫:

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<>));

其中key是一些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文檔中找到有關IIndex更多詳細信息。

我不得不承認,當我得到這個答案時,我有點沮喪。 我正在為其他可能遇到同樣問題的人發布這個答案。

由於提供給我的解決方案不能正常工作(直到tdragon更新了他的答案),我去了Googlegroups for 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 }

謝謝大家的幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM