From what I can understand, your problem stems from the fact that you have a local explicitly declared object graph made from your DTOs. What I mean is that you have declared public Unit Unit { get; set; }
on your Country
model (not sure why you re declaring them virtual
but that s not directly related to the issue at hand) rather than trying an approach that guarantees simplicity of the object graph into the degenerate case of a single object graph node.
For example, consider defining every "reference" property on your model in the form public UnitID Unit { get; set; }
where UnitID
might actually be int
or Guid
or whatever you use to uniquely identify and distinguish Unit
models from each other. Wherever you have a reference or set of references to another model, replace it with its identifier type instead of its actual type. This strategy lends itself well to a persisted set of models e.g. from/to a database with identity keys for each model. Doing this gets you simplicity of serialization without having to worry about circular references because they are now impossible. Technically, there are no more references (i.e. direct references; they are now indirect references). We re now just adding a layer of indirection in your domain model design. Now let s accommodate for that layer of indirection.
Since you ve claimed that you re fine with the anemic domain model approach then this should fit well with that. You pay the minor (IMHO) cost of indirection in your model design and trade it up for the major (IMHO) benefits of an interface-based approach to data retrieval:
public interface IUnitRepository {
Unit GetUnit(UnitID id);
IEnumerable<Unit> GetUnits(IEnumerable<UnitID> ids);
// etc.
}
In your consumer code (i.e. the code that consumes this interface and your Unit
domain models), it only looks slightly more complex to traverse the implied object graph by performing interface calls to get the underlying models pointed to by the indirect references.
BEFORE:
Country ct = ...; // I assume you have this reference already
Unit ut = ct.Unit;
AFTER:
// Somewhere earlier in your code, i.e. not *every* time this type of code appears
IUnitsRepository repo = new SomeUnitsRepositoryImpl();
Country ct = ...; // I assume you have this reference already
Unit ut = repo.GetUnit(ct.UnitID);
If this bothers you syntactically, you can define a set of extension methods typed on Country
of the form:
public static Unit Unit(this Country c, IUnitsRepository repo) {
return repo.GetUnit(c.UnitID);
}
AFTER EXTENSION METHODS:
IUnitsRepository repo = new SomeUnitsRepositoryImpl();
Country ct = ...; // I assume you have this reference already
Unit ut = ct.Unit(repo);
The interface-based approach gets you the classical set of benefits such as separation of concerns, testability, consumer-producer insulation, and many more. Furthermore, you now get more direct control over object lifetime via the implementation type of your interface. What I mean is that you shouldn t have to assume naivety of implementation of your Unit GetUnit(UnitID id)
method. This method could now perform some local in-memory caching utilizing a Dictionary<UnitID, Unit>
tied with the instance of SomeUnitsRepositoryImpl
.
A bit long-winded, but I hope it helps. As if it weren t obvious from the amount of detail provided, I am currently toying with this design at my place of employment for dealing with our system of record database. :) I am really loving all the flexibility it gives me all at the simple cost of adding one level of indirection in the design of the domain models.