2

When designing an n-tier application, I tend to use a pattern adopted and adapted from Lhotka's CSLA framwork. In a nutshell, the Repository layer populates a SqlDataReader and passes the data reader, the instance to be mapped, and the mapping information to the Mapper layer which then populates the instance.

This pattern has proved itself over and again in many projects I have worked on for a number of reasons:

  1. It is easy to understand and implement, developers tend to stick to it because they understand what it is doing.
  2. It enables very easy reuse of the mappers for inherited classes and well as composite classes.
  3. Any changes in the entity, and therefore the mapper assigned to it, cause compile time errors pointing directly to the stored procs that also need to be changed.

Here is some sample repository code:

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

Here is some sample Mapper Code. It is pretty straightforward: The mapper knows which class property gets mapped to each field in the data reader. The name of each field is passed to the mapper so the same mapper can be used regardless of the names assigned to each field in the 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));
}

So my question is this: How should I implement this pattern when the data source is a text file? I want to stick with this pattern because eventually the data source will be migrated to a db.

Any suggestions?

EDIT: The ini files already exist and I do not have the go ahead to change them at this time. So I am stuck with them for now.

Shai Cohen
  • 6,074
  • 4
  • 31
  • 54
  • Can you edit the question to explain how the mapper pattern works a little more? Also, it would be useful to see how your entities/relations and ini files will be structured, since ini files don't have the same sorts of hierarchy as DB tables/result sets. (and you might have better luck with XML files - it is easier to build a data hierarchy in those, and you can use the .Net built-in XML serializer to populate your objects) – Merlyn Morgan-Graham Oct 03 '11 at 21:54
  • @MerlynMorgan-Graham: I just added the mapper function for the RpaDepartment class. Regarding the hierarchy, it's easy. There is none in this case :) – Shai Cohen Oct 03 '11 at 22:12
  • Wouldn't the implementation of the pattern depend on the "shape" of the original data source? In that case, the format of the text file would be relevant. You'll also need to make an abstraction over `SqlDataReader` to a generic source of named fields that are convertible to basic types, and implement that for the given text file format. – millimoose Oct 03 '11 at 22:23
  • (Personally I'd just make the config file be JSON that deserializes to your entities instead of making a mess of your architecture just to *temporarily* support a different data source.) – millimoose Oct 03 '11 at 22:24
  • @Shai: I don't know about the case of your real DB instead of your example code, but there may be more hierarchy than you think. Even lists are a form of hierarchy. This will become apparent when looking at an ini file format that captures the same data and supports the same search features your stored procedure is using. Take a look at the example ini data I put in my answer. If your real objects are stand-alone and have no relationships, then any format will work equally as well (json, xml, ini). – Merlyn Morgan-Graham Oct 03 '11 at 23:01

2 Answers2

3

It is a bit difficult to create a good data hierarchy in ini files. This is part of why MS seems to have migrated mostly to XML files. See the answer to this question: Reading/writing an INI file

If you go with the XML option, I'd skip this mapping stuff and simply serialize your objects directly in after using XPath to find the appropriate XML. Then you don't need a mapper.

You could also go with an in-memory or file-based DB, like SqLite. Perf will be great, and you will have a very small deployment footprint.

Also, I recommend avoiding trying to abstract this mapper stuff, since I don't think it will translate well between a DB and an ini file. If you look at the complexity of the many ORM libraries out there, you will see how difficult this mapping really can be. Most of the concepts at the mapping level simply don't translate well to an ini file. It is the higher level concepts that will map (repositories), which is why I posted my original answer (see below).

But if you want to keep with the pattern you're using, and your ini file looks something like this:

[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

... then you could simply write your repository to grab data out of ini sections, and write your mappers to look at each ini value the same way you are currently looking at columns in your result set.

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

        // ...
    }
}

Your current mapper code is bound to your storage type, so you'll need to come up with a more generic mapper interface. Or make that last reader parameter more generic to support both mapping types (in your case, reader, in my case, each ini section instance). E.g.:

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

Note that this is all custom code - there is no official ini file reader, and I haven't tried any out so I can't make a suggestion on which third party library to use. That question I linked at the top has some recommendations.

Original answer

(part of it may still be useful)

Make an interface for your repository, and make sure the higher level layers of your code only talk to your data store through this interface.

An example interface (yours might be different):

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

You could also make this interface generic, if you wanted.

To make sure the higher level layers only know about the repository, you could construct the classes for talking to the file/DB in the implementation of IReportRepository, or use Dependency Injection to populate it. But whatever you do, don't let your higher level code know about anything but IRepository and your individual data entities (Report).

You might also want to look into the Unit of Work pattern, and wrap the actual data access there. That way you can easily support transactional semantics, and buffered/lazy storage access (even with a file).

For your example implementation, the SqlConnection and SqlDataReader would live in your unit of work class, and the mapping code and specific stored procedure names would probably live in each repository class.

It might be a bit tricky to get this structure to work completely independently, but if you look at the code the Microsoft Entity Framework generates, they actually have their unit of work class instantiate each repository, and you just access it like a property. Something roughly like:

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
}

Useful reading:

Community
  • 1
  • 1
Merlyn Morgan-Graham
  • 58,163
  • 16
  • 128
  • 183
  • It's good advice to keep in mind, but @KirkWoll is correct is doesn't exactly address my problem. – Shai Cohen Oct 03 '11 at 21:36
  • @Shai: Oh, I got lost in the body of the question, and came out thinking you meant "how should you write your program to isolate yourself from your storage" :) Maybe you can edit the question to explain how the mapper pattern works a little more so myself or someone better equipped can figure out how to map that pattern to ini files. Showing how your entities and ini files are structured could be useful too, since ini files aren't structured the same as DB tables. – Merlyn Morgan-Graham Oct 03 '11 at 21:48
  • @Shai, Kirk: Okay tried to directly address the mapper part. Big edits :) – Merlyn Morgan-Graham Oct 03 '11 at 23:04
  • Thanks for the extra effort. I think you hit the nail on the head when you said "Also, I recommend avoiding trying to abstract this mapper stuff, since I don't think it will translate well between a DB and an ini file." Thanks again. – Shai Cohen Oct 04 '11 at 14:36
0

You can implement the same pattern for an INI file, although it will take a little mucking about with P/Invoke to make the calls. The basic idea is to call GetPrivateProfileSectionNames to get a list of the section names in the INI file. Then for each section name call GetPrivateProfileSection to get a list of the keys and values for that section. From there you can parse the keys and values and populate your list.

See the responses to Reading/writing an INI file for pointers to code that will read INI files.

Community
  • 1
  • 1
Jim Mischel
  • 131,090
  • 20
  • 188
  • 351