Arhn - архитектура программирования

Репозиторий и шаблон Data Mapper

Прочитав много о Repository и Data Mapper, я решил реализовать эти шаблоны в тестовом проекте. Поскольку я новичок в этом, я хотел бы узнать ваше мнение о том, как я реализовал их в простом проекте.

Джереми Миллер говорит:

Сделайте какой-нибудь нетривиальный личный проект кодирования, в котором вы можете свободно экспериментировать с шаблонами проектирования.

Но я не знаю, правильно я все сделал или нет.

Вот моя структура проекта:

введите описание изображения здесь

Как видите, есть много папок, которые я подробно опишу ниже.

  • Домен: здесь находятся объекты домена проекта. У меня есть простой класс 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();
    }
    

добавление параметра в коллекцию параметров команд:

    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, а второй - ссылка на Entity DataMapper. Эти параметры отправляются каждым классом Entities Repository, унаследованным от класса Repository. Например :

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

    }
}

Как вы можете видеть здесь, в методе FindOne я попытался автоматизировать некоторые операции, такие как создание CommandText, затем я воспользовался своим классом DataLayer, чтобы настроить команду и, наконец, выполнить команду для получения IDataReader. Я передаю IDataReader своему классу DataMapper для сопоставления с сущностью.

  • DomainMapper: Наконец, здесь я сопоставляю результат IDataReader с сущностями, ниже приведен пример того, как я сопоставляю сущность персонала:

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

Я знаю, что сделал здесь много ошибок, поэтому я здесь. Мне нужен ваш совет, чтобы узнать, что я сделал не так или какие положительные моменты в этом простом тестовом проекте.

Заранее спасибо.


  • Не могли бы вы рассказать нам немного больше об абстрактном классе DataLayer (возможно, разместив код)? 17.01.2012
  • У вас есть ссылка на статью Джереми Миллера, на которую вы ссылались? 20.09.2013
  • @Curt, Нашел 11.09.2014
  • @saber Привет! У вас есть этот образец на github или в другом репо? Было бы очень полезно! 12.07.2016

Ответы:


1

Несколько моментов:

  1. Мне кажется, что в целом у вас хороший дизайн. Об этом отчасти свидетельствует тот факт, что вы можете вносить в него изменения с небольшим влиянием на любые классы, кроме тех, которые были изменены (низкая связь). Тем не менее, это очень близко к тому, что делает Entity Framework, поэтому, хотя это хороший личный проект, я бы сначала подумал об использовании EF, прежде чем реализовывать его в производственном проекте.

  2. Ваш класс DataMapper можно сделать универсальным (скажем, GenericDataMapper<T>) с помощью отражения. Итерация по свойствам типа T с использованием отражения, и получить их из строки данных динамически.

  3. Предполагая, что вы действительно создаете Generic DataMapper, вы можете подумать о создании CreateRepository<T>() метода на DataLayer, чтобы пользователям не приходилось беспокоиться о деталях того, какой тип Mapper выбрать.

  4. Небольшая критика - вы предполагаете, что все сущности будут иметь один целочисленный идентификатор с именем «Id», и что хранимые процедуры будут настроены для их получения таким образом. Здесь вы можете улучшить свой дизайн, допустив первичные ключи разных типов, опять же, возможно, используя дженерики.

  5. Вероятно, вы не захотите повторно использовать объекты Connection и Command так, как вы это делаете. Это небезопасно для потоков, и даже если бы это было так, вы бы столкнулись с некоторыми неожиданными и трудными для отладки условиями гонки вокруг транзакций БД. Вы должны либо создать новые объекты 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();
  1. И последнее, но не менее важное: поработав с Джереми, я уверен, что он сказал бы, что у вас должны быть модульные тесты для всех этих классов!
15.01.2012
  • +1 Отличные советы Крис, но мне любопытно насчет 5-го. Пожалуйста, объясните это поподробнее. Вы говорите мне создавать соединение для каждого вызова функции, это нормально. Но как мне поступить с DbTransaction? 15.01.2012
  • Добавлен базовый пример обработки транзакции 15.01.2012
  • +1, но похоже, что вы игнорируете утилизацию предметов. Например, SqlConnection - это IDisposable. 15.01.2012
  • Арванд: Если вы имеете в виду контейнер решения DataMapper, я готов поспорить, что это просто артефакт того, как был запущен проект (возможно, Sabre изначально был ориентирован на DataMapper). +1 к Сэйбер и Крису! 27.07.2012
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..