Improving Model Validation for Entity Framework Core 2.0

If you’ve done .NET programming against a SQL data store for any length of time, you’ve likely run into the following error:

String or binary data would be truncated. The statement has been terminated.

This is an unfortunately common scenario for me and it feels like there is no easy way (at least that I’m aware of) to identify which column/property is the culprit.  I also recently just started working with Entity Framework (I had always preferred not using an ORM prior to this) and figured this would be handled by the framework.   After a bit of digging, I found this in the Ef Core documentation:

Entity Framework does not do any validation of maximum length before passing data to the provider. It is up to the provider or data store to validate if appropriate. For example, when targeting SQL Server, exceeding the maximum length will result in an exception as the data type of the underlying column will not allow excess data to be stored.

Even if I use the [MaxLength(#)] or [Required] attributes, Entity Framework does not look at these prior to submitting changes to the database.  So it looks like it’s up to the developer to solve for this given EF Core is a cross-platform framework capable of working with multiple data stores, each with their own rules.  As a result, I created the extension method below to perform my own validation:

public static async Task<int> SaveChangesWithValidationAsync(this DbContext context)
{
	var recordsToValidate = context.ChangeTracker.Entries();
	foreach(var recordToValidate in recordsToValidate)
	{
		var entity = recordToValidate.Entity;
		var validationContext = new ValidationContext(entity);
		var results = new List<ValidationResult>();
		if (!Validator.TryValidateObject(entity, validationContext, results, true)) // Need to set all properties, otherwise it just checks required.
		{
			var messages = results.Select(r => r.ErrorMessage).ToList().Aggregate((message, nextMessage) => message + ", " + nextMessage);
			throw new ApplicationException($"Unable to save changes for {entity.GetType().FullName} due to error(s): {messages}");
		}
	}
	return await context.SaveChangesAsync();
}

Note:  We could also implement IValidatableObject on our model classes and perform more unique validation inside the Validate method and have it covered by this extension method as well.

This method can then be called from within your code as follows:

public class Person 
{
  [MaxLength(50)]
  public string Name { get; set; }
  [MaxLength(50)]
  public string Title { get; set; }
}
    
var person = new Person() { Title = "This title is longer than 50 characters and will throw an exception" };
context.PersonSet.Add(person);
await context.SaveChangesWithValidationAsync();
 

Kyle Ballard