.EFCore - 是否可以根据数据库中的列最大大小创建数据注释验证?

.EFCore - Is it possible to create data annotation validation based on the column max size in the database?

我不想查看数据库列定义,而是希望根据数据库中的列大小进行 maxlength 验证,这可能吗?

我问是因为我的一些专栏是 varchar,有些是 nvarchar,这有时会造成混淆,因为对于 varchar 如果 max 是 100 那么您可以插入 100 个字符,但如果它是最大为 100 的 nvarchar,则只能插入 50 个字符。

我想停止这种混淆,这可能吗?

您可以使用 EF 运行 自定义 SQL 查询来读取有关 table 的元信息。 SQL 查询取决于您使用的实际数据库。对于 MySQL,我使用 EXPLAIN table 来获取有关 table 的元信息。此信息将通过您正在使用的 EF 上下文在名为 ExplainInfo 的实体中读取。

对于验证,您可以创建一个新的验证 class/attribute,它从 ValidationAttribute 扩展并覆盖 IsValid(object value, ValidationContext validationContext) 方法。 ValidationContext 参数很重要,因为我们必须将当前打开的上下文注入验证器,并获取有关实体类型和 属性 名称的信息以进行验证。我们用它来发送一个查询,该查询获取有关列的信息并根据它验证当前值。

作为概念验证,解决方案可能如下所示:

(作为余数,EXPLAIN table 的结果如下所示:)

mysql> EXPLAIN Author;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| Id    | int         | NO   | PRI | NULL    | auto_increment |
| Name  | varchar(30) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+

首先我们为 EXPLAIN 查询的结果创建一个实体:

public class ExplainInfo
{
    [Key]
    public string Field {get; set;}
    public string Type {get; set;}
}

要使用它,我们必须像往常一样在上下文中添加一个新的 DbSet 条目:

public DbSet<ExplainInfo> ExplainInfo {get; set;}

然后我们编写新的验证属性:

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;
    }
}

我们将在我们的实体中使用此验证属性:

public class Author
{
    // [...]

    [LimitValidation]
    public string Name {get; set;}
    
    // [...]
}

最后我们将触发验证:

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();
}

这将生成以下验证异常:

The given value for the property Name exceed the limit of 30 characters defined in the database

您必须针对 VARCHARNVARCHAR 类型以及 query to get the table meta info.

调整 IsValid() 方法