Patrón de Repositorio y Mapeador de datos


Después de leer mucho sobre el Repositorio y el Mapeador de datos, decidí implementar esos patrones en un proyecto de prueba. Ya que soy nuevo en estos, me gustaría obtener sus puntos de vista sobre cómo implementarlos en un proyecto simple.

Jeremy Miller dice:

Haga algún tipo de proyecto de codificación personal no trivial donde pueda experimentar libremente con patrones de diseño.

Pero no se que hice todas estas cosas bien o no.

Aquí está la estructura de mi proyecto :

introduzca la descripción de la imagen aquí

Como puede ver, hay muchas carpetas que voy a describir en detalle a continuación.

  • Domain : Project Domain Entities ir aquí Tengo una clase Personnel simple que se hereda de la clase EntityBase, la clase EntityBase tiene una sola propiedad llamada Id.

    public int Id { get; set; }
    
  • Infrustructure : Aquí hay una capa de acceso a datos simple con dos clases. SqlDataLayer es una clase simple que se hereda de una clase abstracta llamada dataLayer. Aquí proporciono algunas funcionalidades como el siguiente código:

    public SQLDataLayer() {
        const string connString = "ConnectionString goes here";
        _connection = new SqlConnection(connString);
        _command = _connection.CreateCommand();
    }
    

Agregar parámetro a la colección de parámetros de comandos:

    public override void AddParameter(string key, string value) {
        var parameter = _command.CreateParameter();
        parameter.Value = value;
        parameter.ParameterName = key;

        _command.Parameters.Add(parameter);
    }

Ejecutando DataReader:

    public override IDataReader ExecuteReader() {
        if (_connection.State == ConnectionState.Closed)
            _connection.Open();

        return _command.ExecuteReader();
    }

Y así sucesivamente.

  • Repositorio : Aquí intenté implementar el patrón de repositorio. IRepository es una interfaz genérica

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

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

No expuse los métodos no implementados de IRepository.

Aquí en la clase Generic Repository espero que dos parámetros en constructor primero es una referencia a mi clase SqlDataLayer y segundo es una referencia a Entity DataMapper. Los parámetros enviados por cada clase de Repositorio de Entidades que heredó de la clase de repositorio. por ejemplo :

public class PersonnelRepository : Repository<Personnel>, IPersonnelRepository {
    public PersonnelRepository(DataLayer domainContext, PersonnelDataMapper dataMapper)
        : base(domainContext, dataMapper) {

    }
}

Como se puede ver aquí en el método findOne traté de automatizar algunas operaciones como la creación de CommandText, luego tomé la ventaja de mi clase dataLayer para configurar comando y finalmente ejecute el comando para obtener IDataReader. Paso IDataReader a mi clase DataMapper para asignar a la Entidad.

  • DomainMapper: Finalmente aquí mapeo el resultado de IDataReader a Entidades, a continuación se muestra una muestra de cómo mapeo la entidad Personal :

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

Uso :

    using (var context = new SQLDataLayer()) {
        _personnelRepository = new PersonnelRepository(context, new PersonnelDataMapper());
            var personnel  = _personnelRepository.FindOne(1);
    }

Sé que cometí muchos errores aquí, por eso estoy aquí. Necesito tu consejo para saber qué hice mal o cuáles son los puntos buenos en este simple proyecto de prueba.

Gracias previamente.

Author: saber, 2012-01-13

1 answers

Algunos puntos:

  1. Me parece que en general, tienes un buen diseño. Eso se evidencia, en parte, por el hecho de que puede hacer cambios en él con poco impacto en cualquier clase fuera de las que se cambian (acoplamiento bajo). Dicho esto, está muy cerca de lo que Entity Framework hace, así que si bien es un buen proyecto personal, consideraría usar EF primero antes de implementarlo en un proyecto de producción.

  2. Su clase DataMapper podría hacerse genérica (por ejemplo, GenericDataMapper<T>) usando reflexión. Itere sobre las propiedades del tipo T usando reflexión, y obtenerlas de la fila de datos dinámicamente.

  3. Asumiendo que haces un DataMapper Genérico, podrías considerar hacer un método CreateRepository<T>() en dataLayer, para que los usuarios no tengan que preocuparse por los detalles de qué tipo de Mapper elegir.

  4. Una crítica menor-se supone que todas las entidades tendrán un único ID entero llamado "Id", y que se configurará un procedimiento almacenado para recuperarlos por tal. Usted puede ser capaz de mejorar su diseño aquí permitiendo claves primarias de diferentes tipos, de nuevo tal vez mediante el uso de genéricos.

  5. Probablemente no desee reutilizar los objetos de conexión y Comando de la manera en que lo hace. Eso no es seguro para subprocesos, e incluso si lo fuera, terminarías con algunas condiciones de carrera sorprendentes y difíciles de depurar en torno a las transacciones de base de datos. Debe crear nuevos objetos de conexión y Comando para cada llamada a la función (asegurándose de disponer de después de que haya terminado), o implementar alguna sincronización alrededor de los métodos que acceden a la base de datos.

Por ejemplo, sugeriría esta versión alternativa de ExecuteReader:

public override IDataReader ExecuteReader(Command command) {
    var connection = new SqlConnection(connString);
    command.Connection = connection;
    return command.ExecuteReader();
}

El anterior reutilizó el objeto command, lo que podría conducir a condiciones de carrera entre llamadores multihilo. También desea crear una nueva conexión, ya que la conexión anterior podría estar involucrada en una transacción iniciada por un llamante diferente. Si desea reutilizar transacciones, usted debe crear una conexión, iniciar una transacción y reutilizar esa transacción hasta que haya ejecutado todos los comandos que desea asociar con la transacción. Como ejemplo, puede crear sobrecargas de sus métodos ExecuteXXX como este:

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. Por último, pero no menos importante, después de haber trabajado con Jeremy Estoy seguro de que diría que usted debe tener pruebas unitarias para todas estas clases!
 31
Author: Chris Shain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:55:11