存储库和数据映射器模式
经过大量的关于Repository和Data Mapper的阅读之后,我决定在一个testing项目中实现这些模式。 由于我是新手,所以我想就如何在一个简单的项目中实现这些观点获得您的观点。
杰里米·米勒说:
做一些不重要的个人编码项目,你可以自由地尝试devise模式。
但是我不知道我做的所有事情是否正确。
这是我的项目结构:
正如你可以看到有很多文件夹,我将在下面详细描述它们。
-
域:项目域实体去这里我有一个简单的Personnel类inheritance自EntityBase类,EntityBase类有一个名为Id的单一属性。
public int Id { get; set; }
-
Infrustructure:这是一个简单的数据访问层,有两个类。 SqlDataLayer是一个从名为DataLayer的抽象类inheritance的简单类。 在这里我提供了一些如下代码的function:
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); }
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类inheritance的每个Entities Repository类发送的参数。 例如 :
public class PersonnelRepository : Repository<Personnel>, IPersonnelRepository { public PersonnelRepository(DataLayer domainContext, PersonnelDataMapper dataMapper) : base(domainContext, dataMapper) { } }
正如你在FindOne方法中所看到的,我试图自动化一些操作,例如创buildCommandText,然后我利用DataLayer类来configuration命令,最后执行命令来获得IDataReader。 我将IDataReader传递给我的DataMapper类映射到实体。
-
DomainMapper:最后在这里我将IDataReader的结果映射到实体,下面是我如何映射人员实体的示例:
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); }
我知道我在这里犯了很多错误,所以我就在这里。 我需要你的build议来知道我做错了什么,或者在这个简单的testing项目中有什么好处。
提前致谢。
几点:
-
总的来说,我觉得这是一个很好的devise。 事实certificate,部分原因在于你可以对其中的任何类别(低耦合)以外的类别进行更改,而对其进行更改。 也就是说,它与Entity Framework非常接近,所以虽然这是一个很好的个人项目,但是在生产项目中实施之前,我会先考虑使用EF。
-
你的DataMapper类可以使用reflection通用(例如,
GenericDataMapper<T>
)。 使用reflection迭代Ttypes的属性 ,并dynamic地从数据行中获取它们。 -
假设你做了一个通用的DataMapper,你可以考虑在DataLayer上创build一个
CreateRepository<T>()
方法,以便用户不必担心要select哪种types的Mapper的细节。 -
一个小的批评 – 你假定所有的实体都有一个名为“Id”的整数ID,并且将设置一个存储过程来检索它们。 您可以通过允许不同types的主键来改进您的devise,也可以使用generics。
-
您可能不希望按照您的方式重新使用Connection和Command对象。 这不是线程安全的,即使是这样,你最终会得到一些惊人的,难以debugging的DB事务条件。 您应该为每个函数调用创build新的Connection和Command对象(确保在完成后处理它们),或围绕访问数据库的方法实现一些同步。
例如,我build议这个替代版本的ExecuteReader:
public override IDataReader ExecuteReader(Command command) { var connection = new SqlConnection(connString); command.Connection = connection; return command.ExecuteReader(); }
您的旧的重新使用的命令对象,这可能会导致multithreading调用之间的竞争条件。 您还需要创build一个新的连接,因为旧的连接可能参与由其他调用者启动的事务。 如果要重新使用事务,则应创build一个连接,开始一个事务,并重新使用该事务,直到执行完所有要与该事务关联的命令。 作为一个例子,你可以像这样创buildExecuteXXX方法的重载:
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合作,我相信他会说你应该为所有这些类进行unit testing!