库和数据映射模式 [英] Repository and Data Mapper pattern

查看:137
本文介绍了库和数据映射模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一个许多关于库和数据映射读取后,我决定来实施测试项目的模式。由于我是新来的,这些我想获得你对我怎么落实这些在一个简单的项目的意见。

杰里米·米勒说:


  

做一些平凡的,个人编码项目,您可以自由使用设计模式的实验。


但我不知道我所做的这一切事情的正确与否。

下面是我的项目结构:

正如你可以看到有哪些我将详细介绍这些内容在下面许多文件夹。


  • 域:工程领域的实体去在这里,我是从EntityBase类继承的简单的人事类,EntityBase类有一个名为ID的一个属性。

     公众诠释标识{搞定;组; }


  • Infrustructure:这里是两个类简单的数据访问层。 SqlDataLayer是由一个名为数据层的抽象类继承的简单的类。在这里,我喜欢提供下列code一些功能:

     公共SQLDataLayer(){
        常量字符串CONNSTRING =ConnectionString的到这里;
        _connection =新的SqlConnection(CONNSTRING);
        _command = _connection.CreateCommand();
    }


添加参数命令参数采集:

 公共覆盖无效AddParameter(字符串键,字符串值){
        变量参数= _command.CreateParameter();
        parameter.Value =价值;
        parameter.ParameterName =键;        _command.Parameters.Add(参数);
    }

执行DataReader的:

 公众覆盖的IDataReader的ExecuteReader(){
        如果(_connection.State == ConnectionState.Closed)
            _connection.Open();        返回_command.ExecuteReader();
    }

等。


  • 存储库:在这里,我试图执行存储库的模式。 IRepository是一个通用的接口

IRepository.cs:

 公共接口IRepository< TEntity>其中,TEntity:EntityBase
{
    数据层上下文{搞定; }    TEntity FindOne(中间体ID);
    ICollection的< TEntity>的FindAll();    无效删除(TEntity实体);
    无效插入(TEntity实体);
    无效更新(TEntity实体);
}

Repository.cs:

 公共类资源库< TEntity> :IRepository< TEntity>其中,TEntity:EntityBase,新的(){
    私人只读数据层_domainContext;
    私人只读的DataMapper< TEntity> _dataMapper;
    公共仓库(数据层domainContext,DataMapper的< TEntity>的DataMapper){
        _domainContext = domainContext;
        _dataMapper = DataMapper的;
    }
    公共数据层上下文{
        {返回_domainContext; }
    }
    公共TEntity FindOne(INT ID)
    {
        VAR的CommandText = AutoCommand.CommandTextBuilder< TEntity>(CommandType.StoredProcedure,MethodType.FindOne);        //初始化参数和它们的类型
        Context.AddParameter(ID,id.ToString(CultureInfo.InvariantCulture));
        Context.SetCommandType(CommandType.StoredProcedure);
        Context.SetCommandText(CommandText中);        变种dbReader = Context.ExecuteReader();
        返回dbReader.Read()? _dataMapper.Map(dbReader):空;
    }

我没有从IRepository公开不落实的方法。

在这里,在通用库类我想到两个参数的构造函数中的第一个是我的SqlDataLayer类的引用,第二个是实体DataMapper的一个参考。
由从仓库类继承的每个实体仓储类发送这些参数。例如:

 公共类PersonnelRepository:库<人员>中IPersonnelRepository {
    公共PersonnelRepository(数据层domainContext,PersonnelDataMapper的DataMapper)
        :基地(domainContext,DataMapper的){    }
}

正如你可以看到在FindOne方法我试过自动执行某些操作,如创建的CommandText这里,然后我把我的数据层类的优势,配置命令,最后执行命令来获取IDataReader的。我通过IDataReader的我的DataMapper类映射到实体。


  • DomainMapper:最后我在这里映射到实体的IDataReader的结果,波纹管是我如何映射人事实体的示例:

     公共类PersonnelDataMapper:DataMapper的<人事及GT; {
    公众覆盖人事地图(IDataRecord记录){
        返回新的人事{
            名字=记录[名字]。的ToString()
            姓氏=记录[姓氏]。的ToString()
            地址=记录[地址]。的ToString()
            ID = Convert.ToInt32(记录[ID])
        };
    }}


用法:

 使用(VAR上下文=新SQLDataLayer()){
        _personnelRepository =新PersonnelRepository(上下文,新PersonnelDataMapper());
            变种人员= _personnelRepository.FindOne(1);
    }

我知道我做了很多的错误在这里,这就是为什么我在这里。我需要你的意见,知道我做错了什么还是什么都在这个简单的测试项目中的好点。

先谢谢了。


解决方案

的几点:


  1. 这让我觉得,总体而言,你有一个很好的设计。这证明,部分的事实,你可以用在任何类的人之外几乎没有影响被改变(低耦合),它的变化。这就是说,它非常接近到什么实体框架做,因此,尽管这是一个很好的个人项目,我会考虑首先使用EF在生产项目实施之前。


  2. 您的DataMapper类可以进行通用的(比如, GenericDataMapper&LT; T&GT; )使用反射。 <一href=\"http://stackoverflow.com/questions/531384/how-to-loop-through-all-the-properties-of-a-class\">Iterate在T型的使用反射,并从动态数据行让他们的属性。


  3. 假设你做一个通用DataMapper的,你可以考虑制定 CreateRepository&LT; T&GT;()对数据层方法,使用户不需要担心细节哪种类型映射的回暖。


  4. A小调critique-你认为所有的实体将有一个名为身份证一个整数ID以及一个存储过程将成立由这样对它们进行检索。您可以通过允许不同类型的主键,又可能是通过使用泛型在这里提高你的设计。


  5. 您可能不想再使用的连接和Command对象你的方式。这不是线程安全的,即使它是,你最终周围DB交易一些令人惊讶和难以调试的竞争条件。要么你应该创建新的连接和Command对象的每个函数调用(确保在完成后处理掉),或围绕实现访问数据库的方法的一些同步。


举例来说,我建议的ExecuteReader这个备用版本:

 公众覆盖的IDataReader的ExecuteReader(Command命令){
    VAR连接=新的SqlConnection(CONNSTRING);
    command.Connection =连接;
    返回Command.ExecuteReader却();
}

您老再使用的命令对象,这可能导致多线程调用者之间的竞争条件。您还希望创建一个新的连接,因为旧的连接可能会通过不同的来电者启动的事务从事。如果你想重新使用事务,应该创建一个连接,开始一个事务,直到你执行所有要与交易相关联的命令重新使用该交易。举个例子,你可以创建你的ExecuteXXX方法重载是这样的:

 公众覆盖的IDataReader的ExecuteReader(Command命令,楼盘的SqlTransaction事务){
    SqlConnection的连接= NULL;
    如果(交易== NULL){
        连接=新的SqlConnection(CONNSTRING);
        交易= connection.BeginTransaction();
    }其他{
        连接= transaction.Connection;
    }
    command.Connection =连接;
    返回Command.ExecuteReader却();
}//当你调用这个,你可以沿着交易按引用传递。如果为null,一个新的人会为你创建,并通过在您下次呼叫再使用ref参数返回:交易的SqlTransaction = NULL;//此行设置了交易和执行的第一个命令
VAR myFirstReader = mySqlDataLayer.ExecuteReader(someCommandObject,楼盘成交);//这下一行大干快上的相同的事务previous 1执行。
VAR myOtherReader = mySqlDataLayer.ExecuteReader(someOtherCommandObject,楼盘成交);//请务必之后提交事务!
器transaction.commit();//做个好孩子后自己清理
transaction.Connection.Dispose();
transaction.Dispose();


  1. 最后但并非最不重要的,有与杰里米工作,我敢肯定,他会说,你应该有单元测试这些类的!

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.

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();
    }
    

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 :

    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.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;
    }

I didn't expose not implemented methods from 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 :

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.

  • 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"])
        };
    }}
    

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.

解决方案

A few points:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

For instance, I'd suggest this alternate version of 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:

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();

  1. 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!

这篇关于库和数据映射模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆