0

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?

Bob Tway
  • 9,301
  • 17
  • 80
  • 162

5 Answers5

4

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

TGH
  • 38,769
  • 12
  • 102
  • 135
  • D'oh! I'm used to thinking of Interfaces exclusively in terms of patterns. I should have realised they were just as useful in much simpler scenarios. Thanks ... for making me feel an idiot ;) – Bob Tway Jul 08 '14 at 14:17
  • @aevitas Firstly, they're auto-generated EF objects so it's not so easy. Second, I've simplified this data for demonstration purposes - in the real-world scenario there are situations where Person and Member need to contain duplicate data but still exist as separate entities. – Bob Tway Jul 08 '14 at 14:21
  • Hmm. Not so easy. You can't put an interface on the user-generated half of the partial class auto-generated by EF. – Bob Tway Jul 08 '14 at 14:25
  • @MattThrower Since it's a partial, you can just create your own file and put the interface on it. Partial classes make it easy to spread the definition over multiple files to support generated code etc. – TGH Jul 08 '14 at 14:27
  • So ... you can re-declare the Interfaced fields in both bits of the partial class? – Bob Tway Jul 08 '14 at 14:28
  • You only need it in your file. Here's an example http://stackoverflow.com/questions/20508279/adding-an-interface-to-a-partial-class – TGH Jul 08 '14 at 14:34
1

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.

Eric Scherrer
  • 3,328
  • 1
  • 19
  • 34
1

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

Servy
  • 202,030
  • 26
  • 332
  • 449
0

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
}
Dave Zych
  • 21,581
  • 7
  • 51
  • 66
0

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.