Reputation: 15761
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
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:
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.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.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.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
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
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