我正在尝试分解一些重复的代码,但它现在开始闻起来很时髦。 假设我从这开始不太正确,但你抓住了我的漂移:
public virtual OrganisationEntity Get(int id)
{
SqlCommand command = new SqlCommand();
command.CommandText = @"SELECT t.Id, t.Description FROM Organisation t Where t.Id = @Id";
command.Parameters.Add("@id", SqlDbType.Int).Value = id;
List<OrganisationEntity> entities = new List<OrganisationEntity>();
SqlDataReader reader = Database.ExecuteQuery(command, ConnectionName.Dev);
while (reader.Read())
{
OrganisationEntityMapper mapper = Mapper;
entities = mapper.MapAll(reader);
}
return entities.First<OrganisationEntity>();
}
很明显,除了查询相同的形式之外,每个其他Get(int id)方法都有,所以我的下一步是创建一个基类,RepositoryBase看起来像:
public abstract class RepositoryBase<T> where T : new()
{
/// <summary>
///
/// </summary>
public abstract EntityMapperBase<T> Mapper { get; }
public virtual T Get(int id)
{
List<T> entities = new List<T>();
SqlDataReader reader = Database.ExecuteQuery(Command, ConnectionName);
while (reader.Read())
{
EntityMapperBase<T> mapper = Mapper;
entities = mapper.MapAll(reader);
}
return entities.First<T>();
}
}
添加一些通用的时髦,但这也是它变得难看的地方。 首先,Database.ExecuteQuery需要一个SqlCommand和一个枚举,所以我可以,然后我会添加2个属性,我将用一些东西启动它。 我已经意识到我不再需要int id参数了,因为我在子类中构造查询,所以我不妨将命令和connectionName作为参数传递,我希望connectionName无论如何都要依赖于OrganisationRepository(其他需要另一个字符串):
public class OrganisationRepository : RepositoryBase<OrganisationEntity>
{
protected override EntityMapperBase<OrganisationEntity> Mapper
{
get
{
return new OrganisationMapper();
}
}
public override OrganisationEntity Get(int id)
{
SqlCommand command = new SqlCommand();
command.CommandText = @"SELECT t.Id, t.Description FROM Organisation t Where t.Id = @Id";
command.Parameters.Add("@id", SqlDbType.Int).Value = id;
return base.Get(command, ConnectionName.Dev);
}
}
但是,oops,当然,现在方法签名不再同步了......哎呀! 所以,基本上我在想。它只是感觉很讨厌,但不知道究竟为什么。 一方面,我想尽可能地分解重复代码,但现在它让我留下了这个!
如何将此重构为(更多)适当的OO?我应该忘记分解查询字符串并编写大量重复内容吗?
答案 0 :(得分:1)
你的“下一步”与我的不一样。
我的下一步是找到你试图重构的这个“常用代码”的另一个例子。也许是一个“CustomerEntity.Get(int id)'”方法。
现在,让我们假设CustomerEntity和OrganisationEntity版本之间的唯一区别是查询字符串以及用“Customer”替换术语“Organization”。我的下一步是尝试使这两种方法越来越相同。假设此方法是OrganisationEntityRepository类的一部分,我会将其重构为EntityRepository1类,并将CustomerEntityRepository重构为EntityRepository2。
步骤1是为实体类型引入通用参数。您必须对OrganisationEntityMapper和CustomerEntityMapper类执行相同的操作。
接下来,回过头来看看还有什么不同。我看到他们使用不同的映射器类,所以让我们使映射器类型通用。为了做到这一点并仍然引用MapAll方法,我将介绍一个带有MapAll方法的IMapper接口,并让我的两个具体映射器类实现它。
现在,下一个重大区别是查询。我将它放入虚拟的“CommandText”属性中。
现在我觉得我已经准备好了一个基类,也许是EntityRepositoryBase<TEntity,TMapper>
。通过适当的假设,我结束了以下几点:
public abstract class EntityRepositoryBase<TEntity, TMapper>
where TMapper : IMapper<TEntity>
{
public virtual TEntity Get(int id)
{
List<TEntity> entities;
using (var command = new SqlCommand {CommandText = CommandText})
{
command.Parameters.Add("@id", SqlDbType.Int).Value = id;
entities = new List<TEntity>();
using (var reader = Database.ExecuteQuery(command, ConnectionName.Dev))
{
while (reader.Read())
{
var mapper = Mapper;
entities = mapper.MapAll(reader);
}
}
}
return entities.First();
}
protected abstract string CommandText { get; }
protected abstract TMapper Mapper { get; }
}
public class OrganisationEntityRepository :
EntityRepositoryBase<OrganisationEntity, OrganisationEntityMapper<OrganisationEntity>>
{
protected override string CommandText
{
get { return @"SELECT t.Id, t.Description FROM Organisation t Where t.Id = @Id"; }
}
protected override OrganisationEntityMapper<OrganisationEntity> Mapper
{
get { throw new NotImplementedException(); }
}
}
public class CustomerEntityRepository : EntityRepositoryBase<CustomerEntity, CustomerEntityMapper<CustomerEntity>>
{
protected override string CommandText
{
get { return @"SELECT t.Id, t.Description FROM Customer t Where t.Id = @Id"; }
}
protected override CustomerEntityMapper<CustomerEntity> Mapper
{
get { throw new NotImplementedException(); }
}
}
而且,不用说,不管怎样我会说出来:支持JetBrains ReSharper 5.1做所有动作的事情,所以我没有必要。
答案 1 :(得分:0)
我相信像Entity Framework或nHibernate这样的ORM会更适合这种情况。他们可以照顾你自己想要建造的所有管道。
答案 2 :(得分:0)
如果您可以在项目中使用ORM框架,我认为最好不要手工编写所有这些内容。
但是,你可以做的一种重构方法是在RepositoryBase中有一个具有这个签名的抽象方法:
public abstract T Get(int id);
以及带有此签名的受保护方法:
protected T Get(SqlCommand command, SqlConnection connection)
与您之前在RepositoryBase中显示的代码相同。
这样,在派生类中,您只需要实现构造命令的那个,并从执行实际数据库调用的基类中调用该命令。
答案 3 :(得分:0)
我将从以下开始。在单独的接口中定义数据库和映射功能并将它们注入存储库,这样存储库将更容易测试。使用此方法,您应该能够扩展存储库以包含其他CRUD操作。
这种方法的一个问题是映射器和SqlCommand的创建之间的分离,通过select语句返回哪些列可能不是很明显。
// The concrete implementation of this interface will handle connections to the
// database
public interface IDatabase
{
SqlDataReader ExecuteQuery(SqlCommand command);
}
public interface IEntityMapper<T>
{
T MapAll(SqlDataReader reader);
}
public abstract class EntityRepository<T>
{
private readonly IDatabase _database;
private readonly IEntityMapper<T> _mapper;
protected EntityRepository(IEntityMapper<T> mapper, IDatabase database)
{
_mapper = mapper;
_database = database;
}
public T Get(int id)
{
return this.Get(_mapper, _database, id);
}
protected virtual T Get(IEntityMapper<T> mapper, IDatabase database, int id)
{
// Create a command can be used to fetch the entity, remember to dispose when complete
using (var cmd = this.CreateGetCommand(id))
{
using (var reader = database.ExecuteQuery(cmd))
{
// No need to read all the rows, just the first...
return reader.Read() ? mapper.MapAll(reader) : default(T);
}
}
}
protected abstract SqlCommand CreateGetCommand(int id);
}
并实施以下
public class OrganisationEntityRepository : EntityRepository<OrganisationEntity>
{
public OrganisationEntityRepository(IEntityMapper<OrganisationEntity> mapper, IDatabase database) : base(mapper, database)
{
}
protected override SqlCommand CreateGetCommand(int id)
{
var command = new SqlCommand(@"SELECT t.Id, t.Description FROM Organisation t Where t.Id = @Id");
command.Parameters.Add("@id", SqlDbType.Int).Value = id;
return command;
}
}
public class OrganisationEntityMapper : IEntityMapper<OrganisationEntity>
{
public OrganisationEntity MapAll(SqlDataReader reader)
{
return new OrganisationEntity(); // Populate using the reader...
}
}
答案 4 :(得分:0)
以下是我将如何重构它:
public class Repository<T> : IRepository<T> where T : class, new()
{
private IEntityMapper<T> _mapper;
public Repository(IEntityMapper<T> mapper)
{
_mapper = mapper;
}
public virtual T Find(string value)
{
SqlCommand command = new SqlCommand();
command.CommandText = @"SELECT t.Id, t.Description FROM Organisation t Where t.Description LIKE @value";
command.Parameters.Add("@value").Value = value + "%";
SqlDataReader reader = Database.ExecuteQuery(command, ConnectionName.Dev);
return FillCollection(reader);
}
public void T Get(int id)
{
SqlCommand command = new SqlCommand();
command.CommandText = @"SELECT t.Id, t.Description FROM Organisation t Where t.id = @value";
command.Parameters.Add("@value").Value = id;
SqlDataReader reader = Database.ExecuteQuery(command, ConnectionName.Dev);
if (!reader.Read())
return null;
T entity = new T();
_mapper.Map(entity, reader);
return entity;
}
protected IList<T> FillCollection(IDataReader reader)
{
List<T> items = new List<T>();
while (reader.Read())
{
T entity = new T();
_mapper.Map(entity, reader);
_items.Add(entity);
}
return items;
}
}
public interface IEntityMapper<T>
{
//row is the most generic part of a DataReader
void Map(T entity, IDataRow row);
}
关键点:
IDataRecord
代替DbDataReader