You can use EF to run custom SQL queries to read meta information about a table. The SQL query depends on the actual database you are using. For MySQL I used EXPLAIN table
to get the meta information about a table. This information will be read in an entity called ExplainInfo
via the EF context you are using.
For validation you can create a new validation class/attribute which extends from ValidationAttribute
and override the IsValid(object value, ValidationContext validationContext)
method. The ValidationContext
parameter is important as we have to inject the current open context into the validator as well as get information about the entity type and property name to validate. We use it to send a query which fetches the information about a column and validate the current value against it.
As a proof-of-concept, the solution might look like this:
(as a remainder, the result of EXPLAIN table
looks like this:)
mysql> EXPLAIN Author;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| Id | int | NO | PRI | NULL | auto_increment |
| Name | varchar(30) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
First we create an entity for the result of the EXPLAIN
query:
public class ExplainInfo
{
[Key]
public string Field {get; set;}
public string Type {get; set;}
}
To use it we have to add a new DbSet
entry in the context as usual:
public DbSet<ExplainInfo> ExplainInfo {get; set;}
Then we write our new validation attribute:
public class LimitValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
MyContext context = (MyContext)validationContext.Items["context"];
string entityName = validationContext.ObjectType.Name;
string propertyName = validationContext.MemberName;
IList<ExplainInfo> metaInfo = context.ExplainInfo.FromSqlRaw($"EXPLAIN {entityName}").ToList();
ExplainInfo singleExplainInfo = metaInfo.Single(it => it.Field == propertyName);
string databaseType = singleExplainInfo.Type.ToLowerInvariant();
if (databaseType.StartsWith("varchar("))
{
string regexStr = @"varchar\((\d+)\)";
Regex regex = new Regex(regexStr);
Match match = regex.Match(databaseType);
string limitAsString = match.Groups[1].Value;
int limit = int.Parse(limitAsString);
string valueAsString = (string)value;
if (valueAsString.Length > limit) {
return new ValidationResult("The given value for the property "+propertyName+" exceed the limit of "+limit+" characters defined in the database");
}
}
return ValidationResult.Success;
}
}
We will use this validation attribute in our entity:
public class Author
{
// [...]
[LimitValidation]
public string Name {get; set;}
// [...]
}
And finally we will trigger the validation:
using (MyContext context = new MyContext())
{
Author author = context.Author.First();
ValidationContext validationContext = new ValidationContext(author, new Dictionary<object, object> {
{"context", context}
});
author.Name += "dummy text which will exceed the limit in the database";
Validator.ValidateObject(author, validationContext, true);
context.SaveChanges();
}
This will generate the following validation exception:
The given value for the property Name exceed the limit of 30 characters defined in the database
You have to adjust the IsValid()
method for the VARCHAR
and NVARCHAR
types as well as the query to get the table meta info.