web-dev-qa-db-ja.com

リポジトリおよびデータマッパーパターン

リポジトリとデータマッパーについて多くのことを読んだ後、テストプロジェクトにこれらのパターンを実装することにしました。これらは初めてなので、簡単なプロジェクトでどのように実装したかについて、あなたの意見を聞きたいと思います。

ジェレミーミラー言う:

設計パターンを自由に試すことができる、自明ではない個人的なコーディングプロジェクトを実行してください。

しかし、私はこのすべてを正しく行ったかどうかは知りません。

これが私のプロジェクト構造です:

enter image description here

ご覧のとおり、以下に詳細に説明するフォルダーが多数あります。

  • ドメイン:プロジェクトドメインエンティティはここに行きますEntityBaseクラスから継承された簡単なPersonnelクラスを持ちます。EntityBaseクラスにはIdという名前の単一のプロパティがあります。

    public int Id { get; set; }
    
  • Infrustructure:これは、2つのクラスを持つ単純なデータアクセスレイヤーです。 SqlDataLayerは、DataLayerという名前の抽象クラスから継承される単純なクラスです。ここでは、次のコードのような機能を提供します。

    public SQLDataLayer() {
        const string connString = "ConnectionString goes here";
        _connection = new SqlConnection(connString);
        _command = _connection.CreateCommand();
    }
    

コマンドパラメータコレクションへのパラメータの追加:

    public override void AddParameter(string key, string value) {
        var parameter = _command.CreateParameter();
        parameter.Value = value;
        parameter.ParameterName = key;

        _command.Parameters.Add(parameter);
    }

dataReaderの実行:

    public override IDataReader ExecuteReader() {
        if (_connection.State == ConnectionState.Closed)
            _connection.Open();

        return _command.ExecuteReader();
    }

等々。

  • リポジトリ:ここでは、リポジトリパターンを実装しようとしました。 IRepositoryは汎用インターフェースです

IRepository.cs:

public interface IRepository<TEntity> where TEntity : EntityBase
{
    DataLayer Context { get; }

    TEntity FindOne(int id);
    ICollection<TEntity> FindAll();

    void Delete(TEntity entity);
    void Insert(TEntity entity);
    void Update(TEntity entity);
}

Repository.cs:

public class Repository<TEntity> : IRepository<TEntity> where TEntity : EntityBase, new() {
    private readonly DataLayer _domainContext;
    private readonly DataMapper<TEntity> _dataMapper;
    public Repository(DataLayer domainContext, DataMapper<TEntity> dataMapper) {
        _domainContext = domainContext;
        _dataMapper = dataMapper;
    }
    public DataLayer Context {
        get { return _domainContext; }
    }
    public TEntity FindOne(int id)
    {
        var commandText = AutoCommand.CommandTextBuilder<TEntity>(CommandType.StoredProcedure, MethodType.FindOne);

        // Initialize parameter and their types
        Context.AddParameter("Id", id.ToString(CultureInfo.InvariantCulture));
        Context.SetCommandType(CommandType.StoredProcedure);
        Context.SetCommandText(commandText);

        var dbReader = Context.ExecuteReader();
        return dbReader.Read() ? _dataMapper.Map(dbReader) : null;
    }

IRepositoryから実装されていないメソッドを公開しませんでした。

ここで、Generic Repositoryクラスでは、コンストラクターの2つのパラメーターが最初にSqlDataLayerクラスへの参照であり、2番目がEntity DataMapperへの参照であることを期待しています。リポジトリクラスから継承した各エンティティリポジトリクラスによって送信されるパラメータ。例えば ​​:

public class PersonnelRepository : Repository<Personnel>, IPersonnelRepository {
    public PersonnelRepository(DataLayer domainContext, PersonnelDataMapper dataMapper)
        : base(domainContext, dataMapper) {

    }
}

こちらのFindOneメソッドでわかるように、CommandTextの作成などの操作を自動化しようとし、DataLayerクラスを利用してコマンドを構成し、最後にコマンドを実行してIDataReaderを取得しました。 IDataReaderをDataMapperクラスに渡して、Entityにマップします。

  • DomainMapper:最後に、IDataReaderの結果をエンティティにマッピングします。以下は、Personnelエンティティをマッピングする方法のサンプルです。

    public class PersonnelDataMapper : DataMapper<Personnel> {
    public override Personnel Map(IDataRecord record) {
        return new Personnel {
            FirstName = record["FirstName"].ToString(),
            LastName = record["LastName"].ToString(),
            Address = record["Address"].ToString(),
            Id = Convert.ToInt32(record["Id"])
        };
    }}
    

使用法:

    using (var context = new SQLDataLayer()) {
        _personnelRepository = new PersonnelRepository(context, new PersonnelDataMapper());
            var personnel  = _personnelRepository.FindOne(1);
    }

私はここで多くの間違いをしたことを知っています、それが私がここにいる理由です。私が間違ったことをしたか、この単純なテストプロジェクトの良い点を知るために、あなたのアドバイスが必要です。

前もって感謝します。

39
saber

いくつかのポイント:

  1. 全体として、あなたには良いデザインがあると思います。これは、変更されたクラス以外のクラスにほとんど影響を与えずに変更を加えることができるという事実によって部分的に証明されています(低結合)。つまり、Entity Frameworkの機能に非常に近いため、個人的なプロジェクトとしては優れていますが、運用プロジェクトで実装する前にまずEFを使用することを検討します。

  2. DataMapperクラスは、リフレクションを使用してジェネリック(たとえば_GenericDataMapper<T>_)にすることができます。 リフレクションを使用してT型のプロパティを反復処理 し、データ行から動的に取得します。

  3. Generic DataMapperを作成すると仮定すると、DataLayerでCreateRepository<T>()メソッドを作成することを検討できます。これにより、ユーザーが選択するMapperのタイプの詳細を心配する必要がなくなります。

  4. 軽微な批判-すべてのエンティティは「Id」という名前の単一の整数IDを持ち、ストアドプロシージャはそれらによって取得されるように設定されると仮定します。ここでも、ジェネリックを使用することで、異なるタイプの主キーを許可することで、設計を改善できる場合があります。

  5. おそらく、ConnectionオブジェクトとCommandオブジェクトを同じ方法で再利用したくないでしょう。これはスレッドセーフではありません。たとえそれがあったとしても、DBトランザクションを巡る驚くべきデバッグしにくい競合状態に陥ります。関数呼び出しごとに新しいConnectionおよびCommandオブジェクトを作成するか(完了後にそれらを必ず破棄する)、データベースにアクセスするメソッドの周りに同期を実装する必要があります。

たとえば、ExecuteReaderのこの代替バージョンをお勧めします。

_public override IDataReader ExecuteReader(Command command) {
    var connection = new SqlConnection(connString);
    command.Connection = connection;
    return command.ExecuteReader();
}
_

古いものはコマンドオブジェクトを再利用したため、マルチスレッドの呼び出し元間の競合状態につながる可能性がありました。古い接続が別の呼び出し元によって開始されたトランザクションに関与している可能性があるため、新しい接続も作成する必要があります。トランザクションを再利用する場合は、接続を作成し、トランザクションを開始し、トランザクションに関連付けたいコマンドをすべて実行するまでそのトランザクションを再利用する必要があります。例として、次のようなExecuteXXXメソッドのオーバーロードを作成できます。

_public override IDataReader ExecuteReader(Command command, ref SqlTransaction transaction) {
    SqlConnection connection = null;
    if (transaction == null) {
        connection = new SqlConnection(connString);
        transaction = connection.BeginTransaction();
    } else {
        connection = transaction.Connection;
    }
    command.Connection = connection;
    return command.ExecuteReader();
}    

// When you call this, you can pass along a transaction by reference.  If it is null, a new one will be created for you, and returned via the ref parameter for re-use in your next call:

SqlTransaction transaction = null;

// This line sets up the transaction and executes the first command
var myFirstReader = mySqlDataLayer.ExecuteReader(someCommandObject, ref transaction);

// This next line gets executed on the same transaction as the previous one.
var myOtherReader = mySqlDataLayer.ExecuteReader(someOtherCommandObject, ref transaction);

// Be sure to commit the transaction afterward!
transaction.Commit();

// Be a good kid and clean up after yourself
transaction.Connection.Dispose();
transaction.Dispose();
_
  1. 最後になりましたが、ジェレミーと一緒に仕事をしたことで、これらのクラスすべてのユニットテストをすべきだと彼は確信しているでしょう!
33
Chris Shain