web-dev-qa-db-ja.com

リポジトリを使用した作業単位パターンでの依存性注入

this と同様の方法でリポジトリをラップする作業単位クラスを作成したいと思います。

私が抱えている問題は、例の汎用リポジトリーをIRepositoryインターフェースに置き換えることにより、依存性注入を実装しようとしていることです。リンクされた記事のuowでは、ゲッターを使用してリポジトリがインスタンス化されているかどうかを確認し、インスタンス化されていない場合はそれをインスタンス化します。

public GenericRepository<Department> DepartmentRepository
{
    get
    {
        if (this.departmentRepository == null)
        {
            this.departmentRepository = new GenericRepository<Department>(context);
        }
        return departmentRepository;
    }
}

これは強く結びついています。

これを回避する方法は2つあります。

  1. コンストラクター注入を使用します。
  2. セッター注入を使用します。

1の問題は、すべてのリポジトリを注入する場合、特定の作業単位インスタンスで使用しなくても、各リポジトリをインスタンス化する必要があることです。したがって、そうすることでオーバーヘッドが発生します。私は、データベース全体の作業単位クラスを1つ使用することを想像していたので、多くの不必要なインスタンス化と巨大なコンストラクターにつながりました。

2の問題は、設定するのを忘れて、null参照例外が発生してしまうことです。

このシナリオには何らかのベストプラクティスがありますか?そして、私が見逃した他のオプションはありますか?

私は依存関係の注入にたどり着き、このトピックで見つけることができるすべての研究を行っていますが、何か重要なことを見逃している可能性があります。

37
rashleighp

これにアプローチする方法は、UnitOfWorkにコンテナの挿入を通じて各Repositoryを作成させるのではなく、各Repositoryに責任を持たせてUnitOfWork _インスタンス化時にその存在を知っています。

これにより、

  • UnitOfWorkは新しいRepositoryごとに変更する必要はありません
  • サービスロケーターを使用していない(多くの人が anti-pattern と見なしている)

これはいくつかのコードで最もよく実証されます-私は SimpleInjector を使用しているので、例はこれに基づいています:

Repository抽象化で始まる:

_public interface IRepository 
{
    void Submit();
}
public interface IRepository<T> :IRepository where T : class { }
public abstract class GenericRepository<T> : IRepository<T> where T : class { }
_

およびUnitOfWork

_public interface IUnitOfWork
{
    void Register(IRepository repository);
    void Commit();
}
_

RepositorymustUnitOfWorkに登録する必要があり、これは抽象親クラスGenericRepositoryを変更することで実行できます。確実に行われるために:

_public abstract class GenericRepository<T> : IRepository<T> where T : class
{
    public GenericRepository(IUnitOfWork unitOfWork)
    {
        unitOfWork.Register(this);
    }
}
_

Repositoryは、GenericRepositoryから継承します。

_public class Department { }
public class Student { }

public class DepartmentRepository : GenericRepository<Department> 
{
    public DepartmentRepository(IUnitOfWork unitOfWork): base(unitOfWork) { }
}

public class StudentRepository : GenericRepository<Student>
{
    public StudentRepository(IUnitOfWork unitOfWork) : base(unitOfWork) { }
}
_

UnitOfWorkの物理的な実装を追加すると、すべて設定できます。

_public class UnitOfWork : IUnitOfWork
{
    private readonly Dictionary<string, IRepository> _repositories;
    public UnitOfWork()
    {
        _repositories = new Dictionary<string, IRepository>();
    }

    public void Register(IRepository repository)
    {
        _repositories.Add(repository.GetType().Name, repository);
    }

    public void Commit()
    {
        _repositories.ToList().ForEach(x => x.Value.Submit());
    }
}
_

コンテナ登録は、IRepositoryのすべての定義済みインスタンスを自動的に選択し、それらをライフタイムスコープに登録して、トランザクションのライフタイム全体が確実に生き残るように設定できます。

_public static class BootStrapper
{
    public static void Configure(Container container)
    {
        var lifetimeScope = new LifetimeScopeLifestyle();

        container.Register<IUnitOfWork, UnitOfWork>(lifetimeScope);

        container.RegisterManyForOpenGeneric(
            typeof(IRepository<>),
            lifetimeScope,
            typeof(IRepository<>).Assembly);
    }
}
_

これらの抽象化とDIを中心に構築されたアーキテクチャにより、サービス呼び出し内でインスタンス化されたすべてのUnitOfWorkを知っているRepositoryがあり、すべてのリポジトリが定義されていることをコンパイル時に検証します。あなたのコードは 拡張のために開いているが、修正のために閉じられている です。

これをすべてテストするには-これらのクラスを追加します

_public class SomeActivity
{
    public SomeActivity(IRepository<Department> departments) { }
}

public class MainActivity
{
    private readonly IUnitOfWork _unitOfWork;
    public MainActivity(IUnitOfWork unitOfWork, SomeActivity activity) 
    {
        _unitOfWork = unitOfWork;
    }

    public void test()
    {
        _unitOfWork.Commit();
    }
}
_

これらの行をBootStrapper.Configure()に追加します

_//register the test classes
container.Register<SomeActivity>();
container.Register<MainActivity>();
_

コード行に対してブレークポイントを設定します。

__repositories.ToList().ForEach(x => x.Value.Submit());
_

最後に、次のコンソールテストコードを実行します。

_class Program
{
    static void Main(string[] args)
    {
        Container container = new Container();
        BootStrapper.Configure(container);
        container.Verify();
        using (container.BeginLifetimeScope())
        {
            MainActivity entryPoint = container.GetInstance<MainActivity>();
            entryPoint.test();
        }
    }
}
_

コードはブレークポイントで停止し、IRepositoryのアクティブなインスタンスが1つあり、データベースへの変更をSubmit()待機しています。

UnitOfWorkをデコレートしてトランザクションなどを処理できます。この時点で強力な.NetJunkieに従うことにし、これら2つの記事 here および here を読むことをお勧めします。

51
qujck

リポジトリインスタンスを注入する代わりに、それらのインスタンスの作成を担当する単一のファクトリオブジェクトを注入します。ゲッターはそのファクトリを使用します。

5
Ladislav Mrnka

私のソリューションは、まだリポジトリの作成を担当するUnitOfWorkですが、それを行うためにUnitOfWorkでGetRepository()ファクトリメソッドを作成しました。

public interface IUnitOfWork : IDisposable
{
    T GetRepository<T>() where T : class;
    void Save();
}

public class UnitOfWork : IUnitOfWork
{
    private Model1 db;

    public UnitOfWork() :  this(new Model1()) { }

    public UnitOfWork(TSRModel1 dbContext)
    {
        db = dbContext;
    }

    public T GetRepository<T>() where T : class
    {          
        var result = (T)Activator.CreateInstance(typeof(T), db);
        if (result != null)
        {
            return result;
        }
        return null;
    }

    public void Save()
    {
        db.SaveChanges();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                db.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

public class TestRepository : GenericRepository<Test>, ITestRepository
{
    public TestRepository(Model1 db)
       : base(db)
    {
    }
}

public class TestManager: ITestManager
{
    private IUnitOfWork unitOfWork;
    private ITestRepository testRepository;
    public TestManager(IUnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork;
        testRepository = unitOfWork.GetRepository<TestRepository>();
    }

}
2
sonmt