Sam
Sam

Reputation: 15761

C# Inheritance Challenge

I am trying to solve a basic problem, and I am a bit stuck.

I have a base class:

public abstract class EntityBase<TEntity> where TEntity : class
{
    public virtual int Id { get; set; }
    public virtual DateTime DateCreated { get; set; }
    public virtual DateTime DateModified { get; set; }
}

That all classes that are persisted to the database (using EF) inherit.

I then have a complex type:

public class Address
{
    public virtual string ContactName { get; set; }
    public virtual string Line1 { get; set; }
    public virtual string Line2 { get; set; }
    public virtual string Line3 { get; set; }
    public virtual string City { get; set; }
    public virtual string State { get; set; }
    public virtual string PostalCode { get; set; }
    public virtual string Country { get; set; }
}

And a CustomerAddress that is the same fields as the Address type, but needs to be persisted to the db.

public class CustomerAddress : EntityBase<CustomerAddress>
{
    public virtual int CustomerId { get; set; }

    public virtual string ContactName { get; set; }
    public virtual string Line1 { get; set; }
    public virtual string Line2 { get; set; }
    public virtual string Line3 { get; set; }
    public virtual string City { get; set; }
    public virtual string State { get; set; }
    public virtual string PostalCode { get; set; }
    public virtual string Country { get; set; }
}

I know you can only inherit one class at a time in c#, but how can I make this work so that basically CustomerAddress inherits both of these classes?

Any ideas?

Upvotes: 2

Views: 245

Answers (3)

Gup3rSuR4c
Gup3rSuR4c

Reputation: 9480

May I make the following suggestion, which is what I'm using in my own EF backed project:

I have an Address class which inherits from another class Int32Entity. Int32Entity inherits from yet another class called Entity and implements two interfaces: ICreatableEntity_TKey; and IIndexableEntity_TKey. And lastly the Entity class implements two other interfaces: ICreatableEntity; and IRemovableEntity. So, as confusing as that is, here's the code in proper order:

ICreatableEntity and ICreatableEntity_TKey interfaces

public interface ICreatableEntity {
    Employee CreatedBy { get; set; }
    DateTime CreatedDateTime { get; set; }
}

public interface ICreatableEntity<TKey> :
    ICreatableEntity {
    TKey CreatedById { get; set; }
}

IIndexableEntity_TKey interface

public interface IIndexableEntity<TKey>
    where TKey : struct {
    TKey Id { get; set; }
}

IRemovableEntity interface

public interface IRemovableEntity {
    bool IsRemoved { get; set; }
    int? RemovedById { get; set; }
    DateTime? RemovedDateTime { get; set; }

    Employee RemovedBy { get; set; }
}

Entity class

public class Entity :
    ICreatableEntity,
    IRemovableEntity {
    #region ICreatableEntity Members
    public DateTime CreatedDateTime { get; set; }

    public virtual Employee CreatedBy { get; set; }
    #endregion

    #region IRemovableEntity Members
    public bool IsRemoved { get; set; }
    public int? RemovedById { get; set; }
    public DateTime? RemovedDateTime { get; set; }

    public virtual Employee RemovedBy { get; set; }
    #endregion

    public Entity() {
        this.CreatedDateTime = DateTime.Now;
    }
}

Int32Entity class

public class Int32Entity :
    Entity,
    ICreatableEntity<int>,
    IIndexableEntity<int> {
    #region ICreatableEntity`TKey Members
    public int CreatedById { get; set; }
    #endregion

    #region IIndexableEntity Members
    public int Id { get; set; }
    #endregion
}

And last, but not least, the Address class (some project specific code omitted)

public class Address : Int32Entity {
    public string Street { get; set; }
    public string City { get; set; }
    public int Zip { get; set; }
    public string Building { get; set; }
    public string Unit { get; set; }
    public UnitType? UnitType { get; set; }
    public string CrossStreets { get; set; }
    public int? GateCode { get; set; }
    public AddressType Type { get; set; }
    public DbGeography Position { get; set; }

    #region Relationship Properties
    public byte StateId { get; set; }

    public virtual State State { get; set; }
    #endregion
}

There's also byte and short and their nullable equivalents to the Int32Entity class. Although it may seem like an awful lot of inheritance, it works, and it works well. The interfaces basically guarantee that the inheriting objects will have the properties that describe when they were created or removed and by whom. The multiple inhertied classes are also necessary to break things apart because I do have some entities that don't behave like the others such as ones that have composite keys.

Also, the interfaces can then be used as generic constraints else where in your project hierarchy if needed as I have done in my DbContext implementation here: Entity Framework 6.1 - SaveChanges fails with Optional Principal relationship (the answer).

Lastly, a couple of notes of criticism:

  • You're using too many strings for your properties. The State and Country properties should be broken out to individual State and Country objects that are then related back to your Address object. See further below for an example.
  • The Line1, Line2, and Line3 properties have absolutely no meaning to anyone other than you. Although it's commonly used in forms throughout the Interworld, I personally think its sloppy. Notice how in my Address class I try to get really detailed so I know what is what and I can then turn around and format the data for display as I want.
  • There's no need to create an IAddress interface for one object. Interfaces are meant to be used across multiple objects, and you're not doing that. If you continue on this path you could end up with Email : IEmail, Phone : IPhone, so on and so forth, which is all pointless.
  • You're using the virtual keyword too much. virtual is used to lazy load related entities, not the properties of the object. You're never going to lazy load the City, State, PostalCode or Country properties independent of each other on an address because then it's no longer a complete and useful address.

Ignore this line, trying to cheat the text formatter on SO...

public class State {
    // If you need more than 256 State entities, just bump it to a short
    // and you should be good to for every possible State (or Province) in the world
    public byte Id { get; set; }
    public string Abbreviation { get; set; }
    public string Name { get; set; }
}

public class Country {
    public byte Id { get; set; }
    public string Abberviation { get; set; }
    public string Name { get; set; }
}

public class Address {
    // Blah, blah, blah...

    // Nullable because not all countries have states
    public byte? StateId { get; set; }
    public byte CountryId { get; set; }

    public virtual State { get; set; }
    public virtual Country { get; set; }
}

I hope this helps you.

Upvotes: 1

Sam
Sam

Reputation: 15761

So what I ended up doing here was choosing composition over inheritance.

Address Interface

public interface IAddress
{
    string ContactName { get; set; }
    string Line1 { get; set; }
    string Line2 { get; set; }
    string Line3 { get; set; }
    string City { get; set; }
    string State { get; set; }
    string PostalCode { get; set; }
    string Country { get; set; }
}

Address Complex Type

public class Address : IAddress
{
    public virtual string ContactName { get; set; }
    public virtual string Line1 { get; set; }
    public virtual string Line2 { get; set; }
    public virtual string Line3 { get; set; }
    public virtual string City { get; set; }
    public virtual string State { get; set; }
    public virtual string PostalCode { get; set; }
    public virtual string Country { get; set; }
}

CustomerAddress Type

public class CustomerAddress : EntityBase<CustomerAddress>, IAddress
{
    public virtual int CustomerId { get; set; }
    public virtual AddressType AddressType { get; set; }
    public virtual bool IsDefault { get; set; }
    public virtual string Nickname { get; set; }

    public virtual string ContactName { get; set; }
    public virtual string Line1 { get; set; }
    public virtual string Line2 { get; set; }
    public virtual string Line3 { get; set; }
    public virtual string City { get; set; }
    public virtual string State { get; set; }
    public virtual string PostalCode { get; set; }
    public virtual string Country { get; set; }
}

I think it solves the problem and cleanly states the intentions.

Any objections?

Upvotes: 0

Alexandre
Alexandre

Reputation: 31

What if you use Address as a parameter of your entity class CustomerAddress?

public class CustomerAddress : EntityBase<CustomerAddress>
{
    public int CustomerId { get; set; }

    [ForeignKey("CustomerId")]
    public virtual Customer Customer { get; set; }

    public Address BaseAddress { get; set; }
}

Upvotes: 3

Related Questions