物理モデルに1:1で対応するドメインモデルがあるとします。また:
- 物理モデルのすべてのテーブルには、主キーとして列名「Id」があります
- 多くのテーブルには「LastChanged」タイムスタンプ列があります
- 一部のテーブルにはローカライズされたデータが含まれています
目的:ドメインモデルクラス(POCO)と適切なリポジトリを作成します。ツール-VS2010、EF4.1
最も明白なアプローチは、DBからEFモデルを生成し、その上でT4POCOGeneratorを実行することです。出力はPOCOクラスのセットです。
//POCO classes
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime LastModified { get; set; }
...
}
public class User
{
public int Id { get; set; }
public string FirtsName { get; set; }
public DateTime LastModified { get; set; }
...
}
これまでのところ良いですが、リポジトリを実装するときにいくつかのコードの重複に遭遇します。インターフェイス定義レベルの両方:
public interface ICountryRepository
{
IEnumerable<Country> FindAll();
Country FindById(int id);
void Add(Country country);
void Delete(Country country);
}
//Here the ONLY difference is the type of the entity
public interface IUserRepository
{
IEnumerable<User> FindAll();
User FindById(int id);
void Add(User user);
void Delete(User user);
}
そして、実装レベルでは:
class CountryRepository : ICountryRepository
{
IEnumerable<Country> FindAll()
{
//implementation
}
Country FindById(int id)
{
//find by id logic
}
void Add(Country country)
{
//logic
}
void Delete(Country country)
{
//logic
}
}
class UserRepository : IUserRepository
{
IEnumerable<User> FindAll()
{
//the only difference in the implementation
//is the type of returned entity
}
User FindById(int id)
{
//the only difference in the implementation
//is the type of returned entity
}
void Add(User user)
{
//the only difference in the implementation
//is the type of returned entity
}
void Delete(User user)
{
//the only difference in the implementation
//is the type of returned entity
}
}
上記のコードのほとんどはより一般的に記述できることを考慮に入れると、次のアプローチを取ることもできます。
POCOオブジェクト階層を作成します。
public class EntityBase
{
public int Id { get; set; }
}
public class TrackableEntity : EntityBase
{
public DateTime LastChanged { get; set; }
}
public class LocalizedEntity : TrackableEntity
{
public int ResourceId { get; set; }
}
public class Country : LocalizedEntity
{
}
そして、基本実装のリポジトリ階層:
public interface IRepository<TEntity> where TEntity : EntityBase
{
IEnumerable<TEntity> FindAll();
TEntity FindById(int id);
void Add(TEntity entity);
void Delete(TEntity entity);
}
public interface ILocalizedRepository<TEntity> : IRepository<TEntity> where TEntity : LocalizedEntity
{
IEnumerable<TEntity> FindByCultureIso2(string cultureIso2);
}
public interface ICountryRepository : ILocalizedRepository<Country>
{
}
internal class RepositoryBase<TEntity> : IRepository<TEntity> where TEntity : EntityBase
{
private readonly IObjectSet<TEntity> _objectSet;
public RepositoryBase(ObjectContext database)
{
_objectSet = database.CreateObjectSet<TEntity>();
}
protected virtual IQueryable<TEntity> All()
{
return _objectSet;
}
public virtual IEnumerable<TEntity> FindAll()
{
return All().ToList();
}
public virtual TEntity FindById(int id)
{
return All().Where(entity => entity.Id == id).SingleOrDefault();
}
public virtual void Add(TEntity entity)
{
_objectSet.AddObject(entity);
}
public virtual void Delete(TEntity entity)
{
_objectSet.DeleteObject(entity);
}
}
internal class LocalizedRepositoryBase<TEntity> : RepositoryBase<TEntity>, ILocalizedRepository<TEntity> where TEntity : LocalizedEntity
{
public LocalizedRepositoryBase(ObjectContext database) : base(database)
{
}
protected override IQueryable<TEntity> All()
{
return (base.All() as ObjectSet<TEntity>).Include("Resource.LocalizedResources.Culture");
}
public IEnumerable<TEntity> FindByCultureIso2(string cultureIso2)
{
IEnumerable<TEntity> entities = All().Where(...);
return entities.ToList();
}
}
internal class CountryRepository : LocalizedRepositoryBase<Country>, ICountryRepository
{
public CountryRepository(ObjectContext database) : base(database)
{
}
}
後者のアプローチの魅力的な利点は、コードがはるかに構造化されているため、コードの重複を回避できることです。
しかし、このシナリオは、大量の手作業の表面を開くT4コード生成にはほとんど適していません。
次のことを考えていることを教えていただければ幸いです。
- コードの美しさは、手動で実装する手間をかけるだけの価値があると思いますか?
- コードの重複を取り除く他の方法はありますか?