リポジトリとデータ マッパーについてたくさん読んだ後、テスト プロジェクトでこれらのパターンを実装することにしました。私はこれらについてまだ詳しくないので、簡単なプロジェクトでどのように実装したかについて、皆さんの意見を聞きたいです。
ジェレミー・ミラーは言う:
デザインパターンを自由に実験できる、何らかの重要な個人的なコーディング プロジェクトを実行します。
しかし、これらすべてを正しく行ったかどうかはわかりません。
私のプロジェクト構造は次のとおりです。
ご覧のとおり、多くのフォルダーがあり、以下で詳しく説明します。
ドメイン: プロジェクト ドメイン エンティティはここに配置します。EntityBase クラスから継承された単純な Personnel クラスがあり、EntityBase クラスには Id という名前の単一のプロパティがあります。
public int Id { get; set; }
インフラストラクチャ: これは 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);
}
リポジトリ.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 つのパラメーターが必要です。1 つ目は SqlDataLayer クラスへの参照、2 つ目は Entity DataMapper への参照です。これらのパラメーターは、Repository クラスから継承された各 Entities Repository クラスによって送信されます。例:
public class PersonnelRepository : Repository<Personnel>, IPersonnelRepository {
public PersonnelRepository(DataLayer domainContext, PersonnelDataMapper dataMapper)
: base(domainContext, dataMapper) {
}
}
ここでわかるように、FindOne メソッドでは、CommandText の作成などの操作を自動化しようとしました。次に、DataLayer クラスを利用してコマンドを構成し、最後にコマンドを実行して IDataReader を取得しました。IDataReader を DataMapper クラスに渡して、エンティティにマップします。
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);
}
私はここで多くの間違いを犯したことを知っています。それが私がここにいる理由です。私が何を間違えたのか、またはこの単純なテスト プロジェクトの優れた点は何かを知るために、あなたのアドバイスが必要です。
前もって感謝します。
ベストアンサー1
いくつかのポイント:
全体的に見て、優れた設計であるように思います。これは、変更されたクラス以外のクラスにほとんど影響を与えずに変更を加えることができるという事実によって部分的に証明されています (低結合)。とはいえ、これは Entity Framework の機能に非常に近いため、個人プロジェクトとしては優れていますが、実稼働プロジェクトに実装する前に、まず EF を使用することを検討します。
リフレクションを使用して、DataMapper クラスをジェネリック (たとえば、) にすることができます
GenericDataMapper<T>
。リフレクションを使用してT型のプロパティを反復処理する、データ行から動的に取得します。Generic DataMapper を作成する場合は、
CreateRepository<T>()
DataLayer にメソッドを作成することを検討できます。これにより、ユーザーはどのタイプの Mapper を選択するかの詳細を気にする必要がなくなります。ちょっとした批判ですが、すべてのエンティティが「Id」という名前の単一の整数 ID を持ち、それによってエンティティを取得するためにストアド プロシージャがセットアップされると想定しています。ここでもジェネリックを使用することで、異なるタイプの主キーを許可することで設計を改善できる可能性があります。
おそらく、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();
- 最後になりましたが、Jeremy と一緒に仕事をしたことがあるので、彼はきっとこれらすべてのクラスに単体テストが必要だと言うでしょう。