Что такое хороший шаблон Mapper для информации, хранящейся в INI-файле? - PullRequest
2 голосов
/ 04 октября 2011

При проектировании n-уровневого приложения я склонен использовать шаблон, принятый и адаптированный из фреймворка Lhotka CSLA.В двух словах, слой Repository заполняет SqlDataReader и передает считыватель данных, экземпляр для сопоставления и информацию сопоставления слою Mapper, который затем заполняет экземпляр.

Этот шаблон доказал себя снова и снова во многих проектах, над которыми я работал по ряду причин:

  1. Его легко понять и реализовать, разработчики склонны придерживаться егопотому что они понимают, что он делает.
  2. Это позволяет очень легко повторно использовать преобразователи для унаследованных классов, а также составных классов.
  3. Любые изменения в сущности и, следовательно, назначенный ей преобразователь, вызвать ошибки времени компиляции, указывающие непосредственно на сохраненные процессы, которые также должны быть изменены.

Вот пример кода репозитория:

internal static List<CompositeEntities.ContactReportRpa> RetrieveByReportId(int reportId)
{
  CompositeEntities.ContactReportRpa report = null;
  List<CompositeEntities.ContactReportRpa> reports = new List<CompositeEntities.ContactReportRpa>();
  using (SqlConnection conn = DBConnection.GetConnection())
  {
    cmd.Connection = conn;
    cmd.CommandType = System.Data.CommandType.StoredProcedure;
    cmd.CommandText = "ContactReportRpa_SEL_ByIdReport";
    cmd.Parameters.Add("@IdReport", System.Data.SqlDbType.Int).Value = reportId;
    conn.Open();
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
     while (reader.Read())
     {
       report = new CompositeEntities.ContactReportRpa();
       ContactReportRpaMapper.Map("IdReportRpa", "IdReport", "IdRecommendation", "IsDisplayed", "Comments", report.Rpa, reader);
       RpaRecommendationMapper.Map("IdRecommendation", "IdDepartment", "TitleRecommendation", "Description", "DisplayOrderRecommendation", report.Recommendation, reader);
       RpaDepartmentMapper.Map("IdDepartment", "TitleDepartment", "DisplayOrderDepartment", report.Department, reader);
       reports.Add(report);
     }
    }
   }
   return reports;
 }

Вот пример кода Mapper.Это довольно просто: mapper знает, какое свойство класса отображается на каждое поле в считывателе данных.Имя каждого поля передается в преобразователь, поэтому один и тот же преобразователь может использоваться независимо от имен, назначенных каждому полю в sproc.

internal static void Map(string fieldId, string fieldName, string fieldDisplayOrder,   RpaDepartment entity, SqlDataReader reader)
{
  entity.Id = reader.GetInt32(reader.GetOrdinal(fieldId));
  entity.Title = reader.GetString(reader.GetOrdinal(fieldName));
  entity.DisplayOrder = reader.GetInt32(reader.GetOrdinal(fieldDisplayOrder));
}

Поэтому мой вопрос заключается в следующем: как реализовать этот шаблонкогда источником данных является текстовый файл?Я хочу придерживаться этого шаблона, потому что в конечном итоге источник данных будет перенесен в БД.

Есть предложения?

РЕДАКТИРОВАТЬ: INI-файлы уже существуют, и у меня нет возможности изменить их в настоящее время.Так что я застрял с ними на данный момент.

Ответы [ 2 ]

3 голосов
/ 04 октября 2011

Немного сложно создать хорошую иерархию данных в INI-файлах.Это часть того, почему MS, похоже, перешла в основном на XML-файлы.См. Ответ на этот вопрос: Чтение / запись INI-файла

Если вы выберете опцию XML, я пропущу этот процесс отображения и просто сериализую ваши объекты непосредственно после использования XPathнайти соответствующий XML.Тогда вам не нужен маппер.

Вы также можете использовать БД в памяти или на основе файлов, например SqLite .Perf будет великолепен, и у вас будет очень маленький объем развертывания.

Кроме того, я рекомендую избегать попыток абстрагировать этот маппер, так как я не думаю, что он будет хорошо транслироваться между БД и INI-файлом.,Если вы посмотрите на сложность многих библиотек ORM, то увидите, насколько сложным может быть это отображение.Большинство понятий на уровне отображения просто плохо переводятся в INI-файл.Это карты более высокого уровня, которые будут отображаться (репозитории), поэтому я разместил свой оригинальный ответ (см. Ниже).

Но если вы хотите сохранить шаблон, который вы используете, и ваш INI-файлвыглядит примерно так:

[Report.3]
IdReport = 3
IdReportRpas = 7,13

[ReportRpa.7]
IdReportRpa = 7
IdReport = 3
IdRecommendation = 12
IsDisplayed = true
Comments = I'm not sure what an RPA is...

[ReportRpa.13]
IdReportRpa = 13
IdReport = 3
; ... and rest of properties here

[Recommendation.12]
IdRecommendation = 12
IdDepartment = 33
TitleRecommendation = Some Recommendation
Description = Some Recommendation Description
DisplayOrderRecommendation = 0

[Department.33]
IdDepartment = 33
TitleDepartment = Bureau of DBs and ini files
DisplayOrderDepartment = 0

... тогда вы можете просто написать свой репозиторий, чтобы получать данные из разделов ini, и писать свои средства отображения, чтобы смотреть на каждое значение ini так же, как вы смотрите в данный момент.столбцы в наборе результатов.

using(var iniFileReader = new IniFileReader())
{
    string reportSectionName = string.Format("Report.{0}", contactId);
    var reportSection = iniFileReader.GetSection(reportSectionName);

    // Todo: Abstract this sort of procedure/enumeration stuff out.
    // Similar to the existing code's stored procedure call
    int[] idReportRpas = reportSection.GetValue(IdReportRpas)
        .Split(',')
        .Select(s => int.Parse(s);

    foreach(string idReportRpa in idReportRpas)
    {
        report = new CompositeEntities.ContactReportRpa();

        string rpaSectionName = string.Format("ReportRpa.{0}", idReportRpa);
        var rpaSection = iniFileReader.GetSection(rpaSectionName);

        ContactReportRpaMapper.Map("IdReportRpa", "IdReport", "IdRecommendation",
            "IsDisplayed", "Comments", report.Rpa, rpaSection);

        // ...
    }
}

Ваш текущий код сопоставления привязан к вашему типу хранилища, поэтому вам нужно будет придумать более общий интерфейс сопоставления.Или сделайте этот последний параметр считывателя более общим для поддержки обоих типов отображения (в вашем случае reader, в моем случае, каждый экземпляр раздела ini).Например:

public interface IDataValueReader
{
    // Signature is one that might be able to support ini files:
    // string -> string; then cast
    //
    // As well as a DB reader:
    // string -> strongly typed object
    T ReadValue<T>(string valueName);
}

public class DbDataReader : IDataValueReader
{
    private readonly SqlDataReader reader;

    public DbDataReader(SqlDataReader reader)
    {
        this.reader = reader;
    }

    object ReadValue<T>(string fieldId)
    {
        return (T)reader.GetObject(reader.GetOrdinal(fieldId));
    }
}

public class IniDataSectionReader : IDataValueReader
{
    private readonly IniFileSection fileSection;

    public IniDataSectionReader(IniFileSection fileSection)
    {
        this.fileSection = fileSection;
    }

    object ReadValue<T>(string valueName)
    {
        return (T)Convert.ChangeType(fileSection.GetValue(fieldId), typeof(T));
    }
}

Обратите внимание, что это все пользовательский код - нет официального устройства чтения ini-файлов, и я не пробовал его, поэтому я не могу предложить, какую стороннюю библиотеку использовать., Этот вопрос, который я связал сверху , содержит несколько рекомендаций.

Оригинальный ответ

(часть его все еще может быть полезна)

Создайте interface для своего репозитория и убедитесь, что слои более высокого уровня вашего кода общаются только с вашим хранилищем данных через этот интерфейс.

Пример интерфейса (ваш может бытьдругой):

public interface IReportRepository
{
    void Create(Report report);
    Report Read(int id);
    void Update(Report report);
    void Delete(Report report);
}

Вы также можете сделать этот интерфейс универсальным, если хотите.

Чтобы слои более высокого уровня знали только о хранилище, вы можете создать классы для разговора.к файлу / БД в реализации IReportRepository или используйте Внедрение зависимостей для его заполнения.Но что бы вы ни делали, не сообщайте своему высокоуровневому коду о чем-либо, кроме IRepository и ваших отдельных сущностях данных (Report).

Возможно, вы также захотите взглянуть на ЕдиницуРабочий шаблон , и оберните фактический доступ к данным там.Таким образом, вы можете легко поддерживать транзакционную семантику и доступ к буферизированному / отложенному хранилищу (даже с файлом).

В вашем примере реализации SqlConnection и SqlDataReader будут жить вкласс вашей единицы работы, и код отображения и имена конкретных хранимых процедур, вероятно, будут находиться в каждом классе репозитория.

Может быть немного сложно заставить эту структуру работать полностью независимо, но если вы посмотрите накод, который генерирует Microsoft Entity Framework, фактически создает единицу рабочего класса для каждого репозитория, и вы просто получаете к нему доступ как свойство.Примерно так:

public interface IUnitOfWork : IDisposable
{
    void CommitChanges();
    void RollbackChanges();
}

public class MyDataModel : IUnitOfWork
{
    private bool isDisposed;
    private readonly SqlConnection sqlConnection;

    public MyDataModel()
    {
        sqlConnection = DBConnection.GetConnection();
    }

    // Todo: Implement IUnitOfWork here

    public void Dispose()
    {
        sqlConnection.Dispose();
        isDisposed = true;
    }

    public IRepository<Report> Reports
    {
        get
        {
            return new ReportDbRepository(sqlConnection);
        }
    }
}

public class ReportDbRepository : IRepository<Report>
{
    private readonly SqlConnection sqlConnection;

    public ReportDbRepository(SqlConnection sqlConnection)
    {
        this.sqlConnection = sqlConnection;
    }

    // Todo: Implement IRepository<Report> here using sqlConnection
}

Полезное чтение:

0 голосов
/ 04 октября 2011

Вы можете реализовать тот же шаблон для INI-файла, хотя P / Invoke потребует немного усилий для выполнения вызовов.Основная идея состоит в том, чтобы вызвать GetPrivateProfileSectionNames , чтобы получить список имен разделов в файле INI.Затем для каждого имени раздела вызовите GetPrivateProfileSection , чтобы получить список ключей и значений для этого раздела.Оттуда вы можете проанализировать ключи и значения и заполнить ваш список.

См. Ответы на Чтение / запись INI-файла для указателей на код, который будет читать INI-файлы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...