C# 存储库和数据映射器模式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8844105/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Repository and Data Mapper pattern
提问by saber
After a lots of read about Repository and Data Mapper I decided to implement those patterns in a test project. Since I'm new to these I'd like to get your views about how did I implement those in a simple project.
在阅读了大量关于 Repository 和 Data Mapper 的文章后,我决定在一个测试项目中实现这些模式。由于我是这些新手,我想了解您对我如何在一个简单项目中实现这些的看法。
Jeremy Miller says :
杰里米·米勒 说:
Do some sort of nontrivial, personal coding project where you can freely experiment with design patterns.
做一些不平凡的个人编码项目,在那里你可以自由地试验设计模式。
But I don't know I did all this things right or not.
但我不知道我是否做对了所有这些事情。
Here is my project structure :
这是我的项目结构:


As you can see there are many folders which I'm going to describe them in detail in below.
如您所见,有许多文件夹,我将在下面详细介绍它们。
Domain : Project Domain Entities go here I've a simple Personnel class which is inherited from EntityBase class, EntityBase class has a single property named Id.
public int Id { get; set; }Infrustructure : Here is a simple Data Access Layer with two classes. SqlDataLayer is a simple class which is inherited from an abstract class named DataLayer. Here I provide some functionality like following code :
public SQLDataLayer() { const string connString = "ConnectionString goes here"; _connection = new SqlConnection(connString); _command = _connection.CreateCommand(); }
Domain : Project Domain Entities go here 我有一个简单的 Personnel 类,它继承自 EntityBase 类,EntityBase 类有一个名为 Id 的属性。
public int Id { get; set; }基础结构:这是一个带有两个类的简单数据访问层。SqlDataLayer 是一个简单的类,它继承自一个名为 DataLayer 的抽象类。在这里,我提供了一些功能,如以下代码:
public SQLDataLayer() { const string connString = "ConnectionString goes here"; _connection = new SqlConnection(connString); _command = _connection.CreateCommand(); }
adding parameter to commands parameter collection :
向命令参数集合添加参数:
public override void AddParameter(string key, string value) {
var parameter = _command.CreateParameter();
parameter.Value = value;
parameter.ParameterName = key;
_command.Parameters.Add(parameter);
}
executing DataReader :
执行 DataReader :
public override IDataReader ExecuteReader() {
if (_connection.State == ConnectionState.Closed)
_connection.Open();
return _command.ExecuteReader();
}
and so on.
等等。
- Repository : Here I tried to implement repository pattern. IRepository is a generic interface
- 存储库:在这里我尝试实现存储库模式。IRepository 是一个通用接口
IRepository.cs :
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 :
存储库.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;
}
I didn't expose not implemented methods from IRepository.
我没有从 IRepository 公开未实现的方法。
Here in Generic Repository class I expect two parameters in constructor first is a reference to my SqlDataLayer class and second is a reference to Entity DataMapper. Those parameters sent by each Entities Repository class which inherited from Repository class. for example :
在 Generic Repository 类中,我希望构造函数中有两个参数,第一个是对我的 SqlDataLayer 类的引用,第二个是对实体 DataMapper 的引用。由从 Repository 类继承的每个 Entities Repository 类发送的那些参数。例如 :
public class PersonnelRepository : Repository<Personnel>, IPersonnelRepository {
public PersonnelRepository(DataLayer domainContext, PersonnelDataMapper dataMapper)
: base(domainContext, dataMapper) {
}
}
As you can see here in the FindOne method I tried to automate some operation such as creating CommandText, then I took the advantage of my DataLayer class to configure command and finally execute command to get IDataReader. I pass IDataReader to my DataMapper class to map to the Entity.
正如您在 FindOne 方法中看到的,我尝试自动化一些操作,例如创建 CommandText,然后我利用我的 DataLayer 类来配置命令,最后执行命令以获取 IDataReader。我将 IDataReader 传递给我的 DataMapper 类以映射到实体。
DomainMapper : Finally here I map result of IDataReader to Entities, bellow is a sample of how I map Personnel entity :
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"]) }; }}
DomainMapper :最后,我将 IDataReader 的结果映射到实体,下面是我如何映射 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"]) }; }}
Usage :
用法 :
using (var context = new SQLDataLayer()) {
_personnelRepository = new PersonnelRepository(context, new PersonnelDataMapper());
var personnel = _personnelRepository.FindOne(1);
}
I know I did many mistake here, that's why I'm here. I need your advice to know what I did wrong or what are the good points in this simple test project.
我知道我在这里犯了很多错误,这就是我来这里的原因。我需要你的建议才能知道我做错了什么,或者这个简单的测试项目有什么好处。
Thanks in advance.
提前致谢。
采纳答案by Chris Shain
A few points:
几点:
It strikes me that overall, you have a good design. That's evidenced, in part, by the fact that you can make changes in it with little impact on any classes outside of the ones that are changed (low coupling). That said, it's very close to what Entity Framework does, so while it's a good personal project, I'd consider using EF first before implementing it in a production project.
Your DataMapper class could be made generic (say,
GenericDataMapper<T>) using reflection. Iterate over the properties of type T using reflection, and get them from the data row dynamically.Assuming you do make a Generic DataMapper, you could consider making a
CreateRepository<T>()method on DataLayer, so that users don't need to worry about the details of which type of Mapper to pick.A minor critique- you assume that all entities will have a single integer ID named "Id", and that a stored procedures will be set up to retrieve them by such. You may be able to improve your design here by allowing for primary keys of differing types, again maybe by using generics.
You probably don't want to re-use Connection and Command objects the way you do. That isn't thread safe, and even if it was, you'd end up with some surprising and hard-to-debug race conditions around DB Transactions. You either should create new Connection and Command objects for each function call (making sure to dispose of them after you are done), or implement some synchronization around the methods that access the database.
我觉得总的来说,你有一个很好的设计。部分证明了这一点,因为您可以对其进行更改,而对更改的类之外的任何类几乎没有影响(低耦合)。也就是说,它与 Entity Framework 的功能非常接近,因此虽然它是一个很好的个人项目,但我会考虑在生产项目中实施之前先使用 EF。
可以
GenericDataMapper<T>使用反射使您的 DataMapper 类变得通用(例如)。 使用反射迭代 T 类型的属性,并从数据行动态获取它们。假设你做了一个Generic DataMapper,你可以考虑
CreateRepository<T>()在DataLayer上做一个方法,这样用户就不用担心选择哪种类型的Mapper的细节了。一个小批评 - 您假设所有实体都有一个名为“Id”的整数 ID,并且将设置一个存储过程来检索它们。您可以通过允许不同类型的主键来改进您的设计,也可以使用泛型。
您可能不想像您那样重复使用 Connection 和 Command 对象。这不是线程安全的,即使是,您最终也会遇到一些令人惊讶且难以调试的围绕 DB 事务的竞争条件。您应该为每个函数调用创建新的 Connection 和 Command 对象(确保在完成后处理它们),或者围绕访问数据库的方法实现一些同步。
For instance, I'd suggest this alternate version of ExecuteReader:
例如,我建议使用这个替代版本的 ExecuteReader:
public override IDataReader ExecuteReader(Command command) {
var connection = new SqlConnection(connString);
command.Connection = connection;
return command.ExecuteReader();
}
Your old one re-used the command object, which could lead to race conditions between multithreaded callers. You also want to create a new connection, because the old connection might be engaged in a transaction started by a different caller. If you want to re-use transactions, you should create a connection, begin a transaction, and re-use that transaction until you have executed all of the commands that you want to associate with the transaction. As an example, you could create overloads of your ExecuteXXX methods like this:
您的旧对象重新使用了命令对象,这可能会导致多线程调用者之间出现竞争条件。您还想创建一个新连接,因为旧连接可能参与由不同调用者启动的事务。如果要重用事务,则应创建连接、开始事务并重用该事务,直到执行了要与该事务关联的所有命令。例如,您可以像这样创建 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();
- Last but not least, having worked with Jeremy I'm sure he'd say that you should have unit tests for all of these classes!
- 最后但并非最不重要的一点是,与 Jeremy 一起工作后,我相信他会说您应该对所有这些类进行单元测试!

