This question stems directly from Validating parameters properties with Code Contracts.
In that question both Ahmed KRAIEM and Stephen J. Anderson state that the checks pertaining to an object's state correctness should be placed within that object if they must always hold.
However I'm encountering an issue implementing it: after implementing the checks as Code Contracts' invariants, I'm no longer able to use object initialization, AutoMapper or EntityFramework.
The problem arise because all of them first create a new object using the default empty constructor and then fill the various properties and this is triggering the Code Contracts' invariants generating exceptions at runtime.
A quick example wrapped up in a Visual Studio unit test (you need to add AutoMapper through NuGet to make it work):
namespace Playground.Sandbox
{
using System.Diagnostics.Contracts;
using AutoMapper;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class ContractTest
{
private const int CatAge = 5;
private const string CatName = "Tama";
private const int DogAge = 10;
private const string DogName = "Poochi";
static ContractTest()
{
Mapper.CreateMap<Dog, Cat>();
}
[TestMethod]
public void EmptyConstructorShouldThrow()
{
var cat = new Cat();
Assert.AreEqual(default(int), cat.Age);
Assert.AreEqual(default(string), cat.Name);
}
[TestMethod]
public void NonEmptyConstructorShouldNotThrow()
{
var cat = new Cat(CatAge, CatName, true);
Assert.AreEqual(CatAge, cat.Age);
Assert.AreEqual(CatName, cat.Name);
}
[TestMethod]
public void ObjectInitializerShouldThrow()
{
var cat = new Cat { Age = CatAge, Name = CatName };
Assert.AreEqual(CatAge, cat.Age);
Assert.AreEqual(CatName, cat.Name);
}
[TestMethod]
public void AutoMapperConversionShouldThrow()
{
var dog = new Dog { Age = DogAge, Name = DogName };
var cat = Mapper.Map<Dog, Cat>(dog);
Assert.AreEqual(DogAge, cat.Age);
Assert.AreEqual(DogName, cat.Name);
}
private class Cat
{
public Cat()
{
}
public Cat(int age, string name, bool doesMeow)
{
this.Age = age;
this.Name = name;
}
public int Age { get; set; }
public string Name { get; set; }
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(this.Age > 0);
Contract.Invariant(!string.IsNullOrWhiteSpace(this.Name));
}
}
private class Dog
{
public int Age { get; set; }
public string Name { get; set; }
}
}
}
If you're wondering why there is the public Cat(int, string, bool)
constructor if it's not used, is to fool AutoMapper. For this example I need a constructor to initialize the whole object, and AutoMapper recognize it and automatically use it to initialize the destination object. However, our data objects do not have such constructors (moreover, they couldn't, as they can have dozens of properties).
The only passing test is NonEmptyConstructorShouldNotThrow
, the others are all (correctly) failing.
The problem arise because all of them first create a new object using the default empty constructor and then fill the various properties and this is triggering the Code Contracts' invariants generating exceptions at runtime.
Am I using Code Contracts incorrectly or there is no way to implement invariants in data objects when using object initialization, AutoMapper or EntityFramework?