MartinFowlerの「PatternsofEnterpriseApplication Architecture」では、エンティティのマッパーのセットのようにDALを編成するためのアプローチについて説明しています。それぞれに、特定のエンティティを格納する独自のIdentityMapがあります。
たとえば、私のASP.NETWebアプリケーションでは次のようになります。
//AbstractMapper - superclass for all mappers in DAL
public abstract class AbstractMapper
{
private readonly string _connectionString;
protected string ConnectionString
{
get { return _connectionString; }
}
private readonly DbProviderFactory _dbFactory;
protected DbProviderFactory DBFactory
{
get { return _dbFactory; }
}
#region LoadedObjects (IdentityMap)
protected Hashtable LoadedObjects = new Hashtable();
public void RegisterObject(long id, DomainObject obj)
{
LoadedObjects[id] = obj;
}
public void UnregisterObject(long id)
{
LoadedObjects.Remove(id);
}
#endregion
public AbstractMapper(string connectionString, DbProviderFactory dbFactory)
{
_connectionString = connectionString;
_dbFactory = dbFactory;
}
protected virtual string DBTable
{
get
{
throw new NotImplementedException("database table is not defined in class " + this.GetType());
}
}
protected virtual T Find<T>(long id, IDbTransaction tr = null) where T : DomainObject
{
if (id == 0)
return null;
T result = (T)LoadedObjects[id];
if (result != null)
return result;
IDbConnection cn = GetConnection(tr);
IDbCommand cmd = CreateCommand(GetFindStatement(id), cn, tr);
IDataReader rs = null;
try
{
OpenConnection(cn, tr);
rs = cmd.ExecuteReader(CommandBehavior.SingleRow);
result = (rs.Read()) ? Load<T>(rs) : null;
}
catch (DbException ex)
{
throw new DALException("Error while loading an object by id in class " + this.GetType(), ex);
}
finally
{
CleanUpDBResources(cmd, cn, tr, rs);
}
return result;
}
protected virtual T Load<T>(IDataReader rs) where T : DomainObject
{
long id = GetReaderLong(rs["ID"]);
T result = (T)LoadedObjects[id];
if (result != null)
return result;
result = (T)DoLoad(id, rs);
RegisterObject(id, result);
return result;
}
// another CRUD here ...
}
// Specific Mapper for entity Account
public class AccountMapper : AbstractMapper
{
internal override string DBTable
{
get { return "Account"; }
}
public AccountMapper(string connectionString, DbProviderFactory dbFactory) : base(connectionString, dbFactory) { }
public Account Find(long id)
{
return Find<Account>(id);
}
public override DomainObject DoLoad(long id, IDataReader rs)
{
Account account = new Account(id);
account.Name = GetReaderString(rs["Name"]);
account.Value = GetReaderDecimal(rs["Value"]);
account.CurrencyID = GetReaderLong(rs["CurrencyID"]);
return account;
}
// ...
}
問題は、これらのマッパーをどこに保存するかということです。システムサービス(エンティティ)はどのようにマッパーを呼び出す必要がありますか?
すべてのマッパーを含むMapperRegistryを作成することにしました。したがって、サービスは次のようなマッパーを呼び出すことができます。
public class AccountService : DomainService
{
public static Account FindAccount(long accountID)
{
if (accountID > 0)
return MapperRegistry.AccountMapper.Find(accountID);
return null;
}
...
}
しかし、MapperRegistryインスタンスはどこに保存できますか?次の亜種が表示されますが、どれも好きではありません。
MapperRegistryはアプリケーションに対してグローバルです(シングルトン)
- マルチスレッドASP.NETアプリケーションでの同期が必要なため、適用されません(少なくとも、Martinは、このバリアントを選択できるのは狂牛病だけだと言っています)
セッションごとのMapperRegistry
- あまり良くないようです。すべてのORM(NHibernate、LINQ to SQL、EntityFramework)マスターは、リクエストごとにDataContext(NHibernateSession、ObjectContext)を使用し、Sessionにコンテキストを格納しないようにアドバイスします。
- また、私のWebAppでは、ほとんどすべてのリクエストは、JSONを返すEntityController.asmx(属性ScriptServiceを使用)へのAJAXリクエストです。そして、セッションは許可されていません。
リクエストごとのMapperRegistry
- 個別のAJAX呼び出しがたくさんあります。この場合、MapperRegistryのライフサイクルは小さすぎます。そのため、ほとんどの場合、データはデータベースから取得され、その結果、パフォーマンスが低下します。
親愛なる専門家、建築ソリューションを手伝ってください。