How to trick the EDM model builder to allow recursive ComplexTypes

Before starting, this article is built on top of How to create an OData reporting service in ASP.NET 4.5 but you can easily adapt it to any of your project. The code is in the same git repository as the previous article, in the branch
recursive-complex-type, on GitHub.
The problem
We are trying to solve the following use case (at this stage, code will fail at runtime).
The commit title is Use case: the problem., if you want to try the failing code.
The model
The failing model look like this:
The Children
Have you noticed the property public ICollection<MyComplexType> Children { get; set; }
in the class MyComplexType
?
Well this, the EDM model builder does not like, and since it is not uncommon to have such a data structure, let’s trick that evil model builder into allowing it ;)
The configuration
The configuration look like this (in WebApiConfig.cs
) - nothing fancy here:
builder.ComplexType<MyComplexType>();
The failure
This is what happen when you run the code:
Some added realism
Before going further, i added a layer of realism to the projet. I avoided to include external libraries like a DI container or AutoMapper to focus on the demo code instead of one specific technology.
The new application
The execution now flow like this:
When you call the controller, it use a service that use a repository. The repository return the database-model to the service that convert it to its transferable-types to finally return it to the controller.
The simulation
Basically, i simulated a more real-life-like application where we have database entities and DTO
’s (data transfer objects).
You can compare a
DTO
to aview model
, but instead of being sent to aview
and displayed as HTML (in case of the Web), it is serialized in JSON1 and sent to the client that way.1 Note that the DTO’s serialization does not have to be in JSON, it could be XML or any other way you’d like.
DTO
s are a concept and they are not bound to any implementation or technologies, beside the object-oriented programming paradigm.
The new and updated classes
Lets take a look at the new stuff and let the code talk by itself :)
If you are not interested in the project infrastructure, you can jump right to: The solution.
MyDatabaseEntityRepository
MyODataModelService
MyODataModelMapper
MyODataModelController
The solution
If we run the code, we still have a problem… It’s a more real-life-like problem, but it’s still a problem…
Now that we know what the problem is, lets trick the EDM model builder to believe that there are no such thing as a recursive loop of complex types in our model. To do that we will create two different ComplexType class that are identical. The only difference is that class1
will be composed of a class2
collection and class2
will be composed of a class1
collection.
This will break the cycle:
The models
Lets take a look at more code (this one you might not want to skip tho)…
The data model
The data model MyDatabaseComplexType.Children
property reference itself. No big deal here, the static repository has no problem storing that kind of data.
In a real database scenario we could add an id
property to the MyDatabaseComplexType
class. That said, for some reasons, we might not want to transfer that id
to the client - that’s basically the problem that inspired this article.
The problematic transfer model
The transfer model contain the same recursive loop of complex types since it is a copy of the data model (for now).
The updated transfer model
By using the little technique displayed in the previous graph, we will create what I call a “two-level-circular-dependency” (i don’t know if that term exists but it sounds good).
By updating the transfer model to the following, the EDM model builder will never notice and the circular dependency will be “broken” (look for MyComplexType1
and MyComplexType2
):
I also updated MyODataModelMapper
to handle the changes to the transfer model as follows (this is basic mapping code, it is not that important):
Yeah, it worked!
In a browser, a GET request to http://localhost:1079/odata/MyODataModel
will result in:
Conclusion
With a bit of imagination and some programming experience, it was easy to trick the EDM model builder.
To break the cycle we converted the following “circular dependency” to a “two-level-circular-dependency”, as follows:
I hope you enjoyed, happy coding!