Extending Entity Framework 4 with ParentValidator

Tags: Entity Framework, Code First, DDD, Validation

From Domain Driven Design:

Aggregate: A collection of objects that are bound together by a root entity, otherwise known as an aggregate root. The aggregate root guarantees the consistency of changes being made within the aggregate by forbidding external objects from holding references to its members.

For example you have an aggregate root Post with child entities Comments. With DDD the post object is responsible for the Comments on that Post and changes to the Comments should be made through the Post object. In Entity Framework 4 this works fine with adding and changing the Comments on a Post. But if you remove a Comment with post.Comments.Remove(comment); and then try to save the EF context you get.

"The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted."

So if you want to delete a Comment from the Post you can't just call Post.Remove(Comment). In EF 4 this will only remove the relation between Post and Comment and will result in an exception stateting that the relation cannot be null. You have to call ObjectContext.DeleteObject(Comment) or DbContext.Set<Comment>.Remove(Comment) to mark the Comment as Deleted.

The big question is where do you call this. There are a few possibilities:

  • You use/inject the EF context in the Post class, creating a dependency on the EF context.
  • You delete comments in the Parent Repository, any function in the Post class that needs to delete a Comment is done in a special Post Service class.
  • You create an extension on the context to remove Comments with no Posts attached.

I think that the third option is the best option, I am using EF Code First so I have to extend the DbContext. This can by overriding the ValidateEntity function or the SaveChanges function, both should work. I currently use the ValidateEntity override because I also use that for the UniqueValidator, but you can argue that overriding SaveChanges is better because it is no real validating.

My solution is a generic solution using a Parent attribute.

The parent attribute, in our example use this on the Post property in the Comment class.

[AttributeUsageAttribute(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public class ParentAttribute : Attribute
{
} 

And the parent validato, this class checks a DbEntityEntry object if the object is changed and if the CurrentValue of the property with the parent attribute is null. If so it deletes the object from the Set.

public class ParentValidator
{
	private static readonly Dictionary<Type, string> _parentAttributes = new Dictionary<Type, string>();

	public static void ValidateEntity(DbContext context, DbEntityEntry entity, Type type)
	{
		if (entity.State == System.Data.EntityState.Modified)
		{
			if (!_parentAttributes.ContainsKey(type))
			{
				var properties = from attributedProperty in type.GetProperties()
								 select new
								 {
									 attributedProperty,
									 attributes = attributedProperty.GetCustomAttributes(true)
										 .Where(attribute => attribute is ParentAttribute)
								 };
				properties = properties.Where(p => p.attributes.Count() > 0);
				_parentAttributes.Add(type,
									  properties.Count() == 1
										  ? properties.First().attributedProperty.Name
										  : string.Empty);
			}

			if (!string.IsNullOrEmpty(_parentAttributes[type]))
			{
				if (entity.Reference(_parentAttributes[type]).CurrentValue == null)
				{
					context.Set(type).Remove(entity.Entity);
				}
			}
		}
	}
}

You can override the ValidateEntity function

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
	Type type = ObjectContext.GetObjectType(entityEntry.Entity.GetType());
	ParentValidator.ValidateEntity(this, entityEntry, type);

	return base.ValidateEntity(entityEntry, items);
}

Or you can override the SaveChanges function

public override int SaveChanges()
{
	var modified = ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Modified);
	foreach (var entity in modified)
	{
		ParentValidator.ValidateEntity(this, entity, ObjectContext.GetObjectType(entity.Entity.GetType()));
	}
	return base.SaveChanges();
}

 With this in place using it is simply adding the ParentAttribute on the Parent properties of classes. Just be sure you always access the child object using the parent so that the Parent property is always filled when saved.

The parentvalidator is now part of the GenericRepository on github.