Bob Tway
Bob Tway

Reputation: 9613

Avoiding repetition when writing different objects with identical data structures

In my current system I've got two EF objects with near-identical sets of properties but which can't for various reasons, be unified or joined to share some of the same structure. It's effectively personal details, so it looks a bit like this:

public partial class Person
{
    public int PersonID { get; set; }
    public string Title { get; set; }
    public string Surname { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    // .. etc
}

public partial class Member
{
    public int MemberID { get; set; }
    public string Title { get; set; }
    public string Surname { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    // .. etc
}

Eventually a mixture of these two object types have to be written to the same file, like this:

foreach (Person p in People)
{
    StringBuilder lineBuilder = new StringBuilder();
    lineBuilder.Append("\"");
    lineBuilder.Append(p.PersonID.ToString());
    lineBuilder.Append("\",\"");
    lineBuilder.Append(p.Title);
    lineBuilder.Append("\",\"");
    lineBuilder.Append(p.Forename);
    lineBuilder.Append("\",\"");
    // etc...
}

foreach (Member m in Members)
{
    StringBuilder lineBuilder = new StringBuilder();
    lineBuilder.Append("\"");
    lineBuilder.Append(m.MemberID.ToString());
    lineBuilder.Append("\",\"");
    lineBuilder.Append(m.Title);
    lineBuilder.Append("\",\"");
    lineBuilder.Append(m.Forename);
    lineBuilder.Append("\",\"");
    // etc...
}

This is obviously A Bad Thing - not only does it repeat code, but it's reliant on both loops maintaining the same number of columns in the output which might easily be overlooked if the code is changed.

Short of creating an intermediary object and mapping the fields on to it - which is really just shifting the problem elsewhere - is there a way to approach this that allows me to get this down to a single loop?

Upvotes: 0

Views: 166

Answers (5)

Servy
Servy

Reputation: 203827

You're doing two things here. You're wrapping fields in quotes, and you're joining a series of strings together with commas. Simply write a method that does that for any number of arbitrary strings:

public static string BuildLine(IEnumerable<string> fields)
{
    return string.Join(",", fields.Select(WrapInQuotes));

}
private static string WrapInQuotes(string rawData)
{
    return string.Format("\"{0}\"", rawData);
}

Once you have this you can now have each of your classes simply provide a collection of their fields, without being responsible for formatting them for output.

Note that it looks like you're writing out a CSV file here. My first advice is don't. Just use an existing CSV writer that will be able to handle all of the character escaping and so on for you so that you don't have to. There are some things that you haven't accounted for so far including fields that have line breaks or quotes in them, or the fact that you really shouldn't be quote escaping every single field, but rather only those that contain characters requiring quote escaping (namely line breaks, if you want to support multi-line fields, or commas).

Upvotes: 1

Eric Scherrer
Eric Scherrer

Reputation: 3398

Assuming you are stuck with those two entities since you are using DB first EF, three suggestions I can think of:

  1. Use AutoMapper to map both these objects to a third class. In the third class overwrite ToString(). If you include the AutoMapper unit test to validate mappings you never have to worry about changes - AutoMapper will pick it up and fail the test.

  2. Refactor the DB to a polymorphic Person table with a Person type field and then you have just one Person entity with a person type field.

  3. Use code first EF and have a base person class, or interface.

Upvotes: 1

Valentyn Vynogradskiy
Valentyn Vynogradskiy

Reputation: 653

you can use interface,

public interface ISocialHuman{
    public int PersonID { get; set; }
    public string Title { get; set; }
    public string Surname { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
}

public static void writeHumanSomewhere(ISocialHuman){
    StringBuilder lineBuilder = new StringBuilder();
    lineBuilder.Append("\"");
    lineBuilder.Append(p.PersonID);
    lineBuilder.Append("\",\"");
    lineBuilder.Append(p.Title);
    lineBuilder.Append("\",\"");
    lineBuilder.Append(p.Forename);
    lineBuilder.Append("\",\"");
}

But you should change the ID name.

Upvotes: 0

Dave Zych
Dave Zych

Reputation: 21887

Create an interface (or abstract class) for it.

public interface IDetails
{
    string Title;
    string Surname;
    string AddressLine1;
    string AddressLine2;
}

public partial class Member : IDetails
{
    //etc
}

public partial class Person : IDetails
{
    //etc
}

Upvotes: 0

TGH
TGH

Reputation: 39268

Put an interface on the two entities and let your logic work against the interface instead of the concrete classes

Upvotes: 4

Related Questions