39 votes

Modèle de référentiel et de mappeur de données

Après beaucoup de lecture à propos de Dépôt et de Mapper des Données, j'ai décidé de mettre en œuvre ces modèles dans un projet de test. Depuis que je suis nouveau à cela, je voudrais avoir votre avis sur comment j'ai pu mettre en œuvre ces dans un projet simple.

Jeremy Miller dit :

Faire une sorte de non triviale, personnels de codage projet où vous pouvez expérimenter avec des modèles de conception.

Mais je ne sais pas j'ai fait toutes ces choses bien ou pas.

Voici mon projet de structure :

enter image description here

Comme vous pouvez le voir, il ya de nombreux dossiers dont je vais décrire dans le détail ci-dessous.

  • Domaine : Projet Domaine Entités aller ici, j'ai une simple Personnel de classe qui est héritée de EntityBase classe, EntityBase classe possède une seule propriété nommée Id.

    public int Id { get; set; }
    
  • Infrustructure : Ici est une simple Couche d'Accès aux Données avec deux classes. SqlDataLayer est une simple classe qui hérite d'une classe abstraite nommée DataLayer. Ici je fournir certaines fonctionnalités comme le code suivant :

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

l'ajout de paramètres aux commandes de paramètre collection :

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

        _command.Parameters.Add(parameter);
    }

l'exécution de DataReader :

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

        return _command.ExecuteReader();
    }

et ainsi de suite.

  • Référentiel : Ici, j'ai essayé de mettre en œuvre un modèle de référentiel. IRepository est une interface générique

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

Référentiel.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;
    }

Je n'ai pas l'exposer pas mis en œuvre des méthodes de IRepository.

Ici au Générique de la classe Repository je m'attends à deux paramètres dans le constructeur de la première est une référence à mon SqlDataLayer classe et le second est une référence à l'Entité DataMapper. Ces paramètres envoyés par chacune des Entités du Référentiel de la classe qui a hérité du Dépôt de la classe. par exemple :

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

    }
}

Comme vous pouvez le voir ici dans le FindOne méthode que j'ai essayé d'automatiser certaines opérations telles que la création de CommandText, puis j'ai pris le parti de mon DataLayer classe pour configurer la commande et enfin exécuter la commande pour obtenir IDataReader. Je passe IDataReader à mon DataMapper classe à la carte à l'Entité.

  • DomainMapper : Enfin là j'ai la carte résultat de IDataReader à des Entités, le soufflet est un exemple de la façon dont j'ai la carte du Personnel de l'entité :

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

Utilisation :

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

Je sais que j'ai fait beaucoup d'erreur ici, c'est pourquoi je suis ici. J'ai besoin de vos conseils pour savoir ce que j'ai fait de mal ou ce sont les bons points à ce test simple du projet.

Merci à l'avance.

33voto

Chris Shain Points 33569

Quelques points:

  1. Il me semble que, dans l'ensemble, vous avez une bonne conception. C'est démontré, en partie, par le fait que vous pouvez y apporter des modifications avec peu d'impact sur toutes les classes en dehors de celles qui sont modifiées (couplage faible). Cela dit, il est très proche de ce que Entity Framework ne, alors que c'est un bon projet personnel, je voudrais examiner à l'aide de EF en premier avant de le mettre en œuvre dans un projet de production.

  2. Votre DataMapper classe pourrait être faite générique (par exemple, GenericDataMapper<T>) à l'aide de la réflexion. Itérer sur les propriétés de type T à l'aide de la réflexion, et de les obtenir à partir de la ligne de données de façon dynamique.

  3. En supposant que vous faites un Générique DataMapper, vous pourriez envisager de faire un CreateRepository<T>() méthode sur DataLayer, de sorte que les utilisateurs n'ont pas besoin de vous soucier des détails du type de correspondance pour choisir.

  4. Un mineur critique - vous supposer que toutes les entités disposent d'un nombre entier unique ID nommé "Id", et que des procédures stockées seront mis en place pour les récupérer, par exemple. Vous pouvez être en mesure d'améliorer votre conception ici en permettant pour les clés primaires de types différents, encore, peut-être par l'utilisation de génériques.

  5. Vous ne voulez probablement pas à la ré-utilisation de la Connexion et de Commande des objets de la façon dont vous le faites. Ce n'est pas thread-safe, et même si cela était, vous seriez à la fin avec de surprenantes et difficiles à déboguer des conditions de course autour de Transactions DB. Vous devez créer une nouvelle Connexion et de Commande des objets pour chaque appel de fonction (assurez-vous de disposer de la fin des travaux), ou de mettre en œuvre une synchronisation autour des méthodes d'accès à la base de données.

Par exemple, je vous suggère cette version alternative de ExecuteReader:

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

Votre ancien ré-utilisé la commande de l'objet, ce qui pourrait conduire à des conditions de course entre multithread appelants. Vous aussi vous souhaitez créer une nouvelle connexion, parce que l'ancienne connexion peut être engagé dans une opération a commencé par une autre appelant. Si vous voulez ré-utiliser les transactions, vous devez créer une connexion, d'entamer une transaction, et la ré-utilisation de la transaction jusqu'à ce que vous avez exécuté toutes les commandes que vous souhaitez associer à l'opération. Comme un exemple, vous pourriez créer des surcharges de votre ExecuteXXX méthodes comme ceci:

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. Dernière mais pas moins, pour avoir travaillé avec Jeremy je suis sûr qu'il dirais que vous devriez avoir des tests unitaires pour l'ensemble de ces classes!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X