Complex Type cannot have reference to entities<!-- --> | <!-- -->Patrick Desjardins Blog
Patrick Desjardins Blog
Patrick Desjardins picture from a conference

Complex Type cannot have reference to entities

Posted on: March 18, 2014

If you have a complex type, you cannot have within this one any reference to other entities. Let's imagine that I have a money class.

1public class Money { public decimal Value { get; set; } public CurrencyType Currency { get; set; } }

Has you can see, it has a reference to another class, named CurrencyType. This type is abstract and must be set to a Currency class like USD or CND. However, this bring Entity Framework with a ModelValidationException.

System.Data.Entity.ModelConfiguration.ModelValidationException: One or more validation errors were detected during model generation:

DataAccess.Contexts.Implementations.CurrencyType: Name: Each type name in a schema must be unique. Type name 'CurrencyType' is already defined.

It is not possible to have this configuration. It would be very practical because you can have the Money in any of you class and the table would have the Money_Value and Money_CurrencyId.

One option is to remove the currency from the Money or to have both property of the Currency class inside the Money class. However, the strong positive that we had with CurrencyType was that this one was using Value Object pattern. This mean that it was strongly typed in the code and had a reference to a Currency table which had USD and CDN currency.

The question is how to use Entity Framework and still have an association between the amount of the money (value) and the currency without losing the strongly association with the table that has all currencies? This lead us to a second option that is to remove the money from the entity that has the money. Instead of having the money directly into the class, you can have all money into a separated table. This remove the use if complex type on the money table. Of course, this cause some overhead by having more tables and more joins to do to get all the information. It also has the problem of having the ID of the Money inside your Entity.

A possible solution that I have used is to loss the strong Foreign Key but still have the data directly into the entity that use the money. This way, less tables, more speed, and in the code we use the value object pattern. Here is the money class and the value object class for currency.

1public class Money { public decimal Value { get; set; } public int CurrencyTypeId { get { return this.Currency.Id; } set { this.Currency = CurrencyType.GetCurrencyFromId(value); } } /// /// This is ignored by EntityFramework /// public CurrencyType Currency { get; set; } }
2
3public abstract class CurrencyType : ValueObject {
4
5public static readonly CurrencyType Canada = new CanadianCurrency(); public static readonly CurrencyType UnitedStatesOfAmerica = new USACurrency();
6
7public static CurrencyType GetCurrencyFromId(int value) { Type type = typeof(CurrencyType);
8
9var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static); foreach (var field in fields) { var fieldValue = field.GetValue(null) as CurrencyType; if (fieldValue != null) { if (fieldValue.Id == value) { return fieldValue; } } } throw new KeyNotFoundException(string.Concat(value, " cannot be found in any static fields.")); } }

As you can see, the money class does have the value and the CurrencyType property. This property is ignored by Entity Framework so Money can be as Complex Type. However, we have an integer field that can be used but should not since we have a strongly typed currency property. Here is how to make it works with Entity configuration.

1public class MoneyConfiguration : ComplexTypeConfiguration { public MoneyConfiguration() { this.Ignore(d => d.Currency); } }

How to use this money class? The best way is to check the unit tests.

1[TestMethod] public void GivenAnInteger_WhenIntegerIsAKeyOfACurrency_ThenReturnConcreteCurrency() { // Arrange var idOfCanada = CurrencyType.Canada.Id; var expected = CurrencyType.Canada;
2
3// Act var found = CurrencyType.GetCurrencyFromId(idOfCanada);
4
5//Assert Assert.IsInstanceOfType(found,typeof(CanadianCurrency)); Assert.AreEqual(expected.Id,found.Id); }

This is required because when we will load from Entity Framework, we will only get the integer. But, in the business logic the code will use the property. This is why the property of CurrencyTypeId is tightly bound with Currency with the reflection code. In the case that the Id is not legit (does not exist), an exception is thrown. This should never occur but we should still test the case.

1[TestMethod] public void GivenAnInteger_WhenIntegerIsNotAnExistingKeyOfACurrency_ThenThrowException() { // Arrange const int idNonExisting = -1;
2
3// Act & Assert TestExtensions.Thrown(() => CurrencyType.GetCurrencyFromId(idNonExisting)); }

UnitTestPassed 400x38