存储库和数据映射器模式
在大量阅读了Repository和Data Mapper之后,我决定在一个测试项目中实现这些模式。 由于我不熟悉这些,所以我希望了解您如何在简单的项目中实现这些功能。
杰里米米勒说:
做一些不重要的个人编码项目,你可以自由地尝试设计模式。
但我不知道我是否做过所有这些事情。
这是我的项目结构:
正如你所看到的,我将在下面详细描述它们。
域:项目域实体去这里我有一个从EntityBase类继承的简单Personnel类,EntityBase类有一个名为Id的单个属性。
public int Id { get; set; }
Infrustructure:这是一个带有两个类的简单数据访问层。 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.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类中,我期望构造函数中的两个参数首先是对SqlDataLayer类的引用,其次是对实体DataMapper的引用。 由Repository类继承的每个实体存储库类发送的参数。 例如 :
public class PersonnelRepository : Repository<Personnel>, IPersonnelRepository {
public PersonnelRepository(DataLayer domainContext, PersonnelDataMapper dataMapper)
: base(domainContext, dataMapper) {
}
}
正如你在FindOne方法中看到的,我试图自动执行一些操作,例如创建CommandText,然后我利用DataLayer类来配置命令,最后执行命令来获取IDataReader。 我将IDataReader传递给我的DataMapper类以映射到实体。
DomainMapper:最后我在这里将IDataReader的结果映射到Entities,bellow是我如何映射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);
}
我知道我在这里犯了很多错误,这就是我来这里的原因。 我需要你的建议来知道我做错了什么,或者在这个简单的测试项目中有什么好处。
提前致谢。
几点:
总的来说,我觉得你有一个好的设计。 部分原因可以从以下事实得到证实:您可以对其中的任何类别(低耦合)以外的类别进行更改,而对其进行更改。 也就是说,它与Entity Framework非常接近,所以虽然它是一个很好的个人项目,但在生产项目中实施它之前,我会考虑首先使用EF。
您的DataMapper类可以使用反射来创建通用的(比如GenericDataMapper<T>
)。 使用反射迭代T类型的属性,并动态地从数据行中获取它们。
假设您制作通用DataMapper,您可以考虑在DataLayer上创建一个CreateRepository<T>()
方法,以便用户无需担心要选择哪种类型的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();