Imagine I have a hierarchy of objects I want to store in my Entity Framework 4.1 data store. I am creating them using Code First. They look like this:
BasicState
has many -> StatefulEntities
has a -> CreationDate
ReceivedState
has a -> EntityLocation
ApprovedState
has a -> EntityLocation
OrderedState
has a -> Order
DispatchedState
has a -> Order
has a -> DispatchNumber
The BasicState
is the parent for all the other states, everything else shares those two fields/relationships.
My initial thought, and the way I have implemented this, was to use Table Per Hierarchy inheritance because it seemed that I could just inherit from BasicState
and have my other states partake of those common properties.
However that caused a problem because when different states share a field, the underlying data model doesn't, so for example, my database would have EntityLocation_Id
from one of ReceivedState
and ApprovedState
and EntityLocation_Id1
from the other. That makes it hard to create test data as you don't know what fields belong to what models. I asked about this previously.
The solution I went for was along these lines:
public abstract class BaseState
{
public DateTime CreatedDate { get; set; }
public ICollection<StatefulEntity> StatefulEntities{get;set;}
}
public abstract class LocationState : BaseState
{
public Location EntityLocation { get; set; }
}
public class ReceivedState : LocationState
{
}
public class ApprovedState : LocationState
{
}
This worked partially, however although it created a single EntityLocation_Id
it still created two Order_Ids
for OrderedState
and DispatchedState
, even though they behave in the same way and the implementations are indistinguishable.
With that barely half solved, I have now hit another problem which is leading me to ask this question:
Given that I the relationship between BasicState
and StatefulEntities
is many to many in both directions, I need to be able to find the Order details from the OrderedStates
belonging to a StatefulEntity
.
The problem is that although this is quite easy to represent in SQL, the inheritance involved means that Entity Framework doesn't know whether any given BasicState
is an OrderedState
so there appears to be no way to do a single trip to the database to include the OrderedState.Order
.
So what I can't do is something like this:
statefulEntityRepository.Get().Where( x => x.Id == 1 ).Include( x => x.BasicStates ).Include( x.BasicStates.Order );
Obviously, that isn't code that would run but it shows what the problem is- the Order only belongs to certain subtypes of BasicState
and so EF won't preload it, even if I am using some kind of cast.
If I try projection I could get something like this:
statefulEntityRepository.Get().Select( x=> new {
StatefulEntity = x,
EntityLocation = ( from state in x.BaseStates where state is ReceivedState select state.EntityLocation )
}
But having got that I need to find my way back from the anonymous type returned by the projection to my StatefulEntity for the rest of my code to be able to handle it, which feels like a significant longcut.
I have got as far as trying to define my base class and inheritors thus:
public abstract class BaseState
{
public DateTime CreatedDate { get; set; }
public ICollection<StatefulEntity> StatefulEntities{get;set;}
public long EntitytLocationId { get; set; }
}
public abstract class LocationState : BaseState
{
[Column(name="EntityLocationId")]
public Location EntityLocation { get; set; }
}
And so on, but then I seem to be breaking the basic point of having TPH in the first place and I feel as though I might as well be rolling my own object model over a generic data class.
Edit: I think I have a little more understanding of this now- from trying to set the ForeignKey on the far end of that relationship ( i.e. the relationship between Location and LocationState ) it looks as though the inverse collection on the Order or the Location can't talk to a property that isn't on the base object, so I would have to use the version above. This creates a new problem in how I manage cases which have no EntityLocationId or OrderId- if I make those fields nullable it raises an error that reference fields cannot be null during database creation and by default there is no way to set an initial value on a field in Code First.
Can anyone recommend a better way of representing this type of model that will work with Entity Framework?