How to trick the EDM model builder to allow recursive ComplexTypes

Posted by Carl-Hugo Marcotte on September 6, 2016
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.

  1. The problem
    1. The model
    2. The configuration
    3. The failure
  2. Some added realism
    1. The new application
    2. The new and updated classes
  3. The solution
    1. The models
    2. Yeah, it worked!
  4. Conclusion

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: recursive-complex-type-error

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: OData-ComplexType-SmartArt3

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 a view model, but instead of being sent to a view 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. DTOs 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: OData-ComplexType-SmartArt4

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: OData-json-1

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: OData-ComplexType-SmartArt2

I hope you enjoyed, happy coding!





Comments