存储库和数据映射器模式

在大量阅读了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是一个通用接口
  • 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();
    
  • 最后但并非最不重要的是,与Jeremy合作我相信他会说你应该为所有这些类进行单元测试!
  • 链接地址: http://www.djcxy.com/p/56241.html

    上一篇: Repository and Data Mapper pattern

    下一篇: Using data mappers in Zend Framework