I have a maintenance application that has to turn enterprise data (from various databases/tables) into flat files, each in a specific format, for consumption by a legacy application. I've got data models like
public class StatusCode
{
public String Id { get; set; }
public Char Level { get; set; }
public String Description { get; set; }
}
I will select some subset or all of these records from the data source. And I need to map each entity to one line of the file, which may require adjusting the data (padding, transforming, or handling null
).
public delegate String MapEntity<T>(T entity);
public MapEntity<StatusCode> MapStatusCode = delegate(StatusCode entity)
{
return String.Format("{0},{1},{2}",
entity.Id.PadLeft(3, '0'),
entity.Level == 'S' ? 0 : 1,
entity.Description ?? "-");
}
The question is, how do I write the transformation classes? Do I provide a "DefaultFileCreator" that takes a mapping delegate?
public interface IFileCreator
{
Byte[] Create<T>(MapEntity<T> map, IEnumerable<T> entities);
}
public class DefaultFileCreator : IFileCreator
{
public Byte[] Create<T>(MapEntity<T> map, IEnumerable<T> entities)
{
StringBuilder sb = new StringBuilder();
foreach (T entity in entities)
sb.AppendLine(map(entity));
return Encoding.Default.GetBytes(sb.ToString());
}
}
...
fileCreator.Create(MapStatusCode, repository<StatusCode>.FindAll());
...
With this solution, I'm concerned about where and in what scope I should keep the mapping delegates. And how I'm going to call them without knowing T
(if I ever need to).
Or, do I change the interface and require the mapping in the concrete classes?
public interface IFileCreator<T>
{
Byte[] Create(IEnumerable<T> entities);
}
public abstract class FileCreator : IFileCreator<T>
{
protected abstract String Map(T entity);
public Byte[] Create(IEnumerable<T> entities)
{
StringBuilder sb = new StringBuilder();
foreach (T entity in entities)
sb.AppendLine(Map(entity));
return Encoding.Default.GetBytes(sb.ToString());
}
}
public class StatusCodeFile : FileCreator<StatusCode>
{
public override String Map(T entity)
{
return String.Format("{0},{1},{2}",
entity.Id.PadLeft(3, '0'),
entity.Level == 'S' ? 0 : 1,
entity.Description ?? "-");
}
}
This solution explodes in concrete classes, but they're as thin as the mapping delegates. And I feel more comfortable working with IFileCreator<T>
and a factory. (again, only if necessary).
I'm assuming some base class is useful as the StringBuilder
loop and Byte[]
encoding are straightforward. Should the concrete class set a delegate property in the base class (rather than calling an abstract method)? Should I keep the type parameter on the method (and how would that affect the base/concrete classes)?
I'm up for any solution. My main goal is ease of maintenance. I have 12 models/files right now and this may increase up to 21. I may have a requirement to insert arbitrary header/footer lines in any file (which is why I like the overrideable base class method, Map).