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:
- It is easy to understand and implement, developers tend to stick to it because they understand what it is doing.
- It enables very easy reuse of the mappers for inherited classes and well as composite classes.
- 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.
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:
… 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.
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.: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
interfacefor 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):
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 butIRepositoryand 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
SqlConnectionandSqlDataReaderwould 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:
Useful reading: