C# 实体框架自定义约束

4

我目前正在尝试使用C# Entity Framework创建自定义约束。具体而言:

我有一个数字字段,它只能具有某些特定值(例如1、2和3)。在Code First环境中如何实现此约束?


2
所有这些实体框架标签是什么意思? - Salah Akbari
抱歉,已删除重复内容。 - Simon Schiller
2个回答

3
Entity Framework会通过ValidationAttribute自动验证您在模型中添加的任何验证。RequiredAttributeRangeAttribute是此属性的两个内置子类的示例。
如果您想要一些自定义验证,最方便的方法是利用这个机制并创建自己的ValidationAttribute子类。
如果您想验证不连续范围的值,则无法使用RangeAttribute,但可以创建自己的属性,例如:
public class AllowedValuesAttribute : ValidationAttribute
{
    private readonly ICollection<object> _validValues;
    private readonly Type _type;

    public AllowedValuesAttribute(Type type, object[] validValues)
    {
        _type = type;
        _validValues = validValues.Where(v => v != null && v.GetType() == type).ToList();
    }

    public override bool IsValid(object value)
    {
        return value.GetType() == _type && _validValues.Contains(value);
    }

    public override string FormatErrorMessage(string name)
    {
        return string.Format("Value for '{0}' is not an allowed value", name);
    }
}

使用方法:

[AllowedValues(typeof(int), new object[] { 1, 2, 4, 8, 16 })]
public int Base { get; set; }

请注意,我们必须在这里使用固定值,因为属性的内容必须在编译时知道。此外,我们必须使用object,因为(目前)C#不支持泛型属性。除此之外,还有许多选项。例如,属性也可以具有在运行时查找允许值的方法,可能来自命名源,因此您可以在其构造函数中提供此名称。
我认为在实体类中使用验证属性没有任何问题。实体模型不是领域模型,它是数据访问层的一部分。它的主要目的是(并应该是)促进应用程序的数据访问。如果实体模型也恰好支持业务逻辑,那只是额外的奖励。

经过思考,我仍然不知道为什么有人会使用这个。对此使用属性是错误的方式。你的例子说明了这一点:如果值是1、3、7、20等,则应该是一个“Flag”-“Enum”或简单的“Enum”。如果数据具有特定的含义,您将创建一个表并使用PK作为FK。如果您真的必须使用int(因为不知道为什么),您将在控制器中处理它或在viewmodel上使用注释,因为其他控制器可能会输入其他int - (如果没有,枚举或FK它)...抱歉,但我仍然看不到在实体类上使用它的任何理由。 - Matthias Burger
@MatthiasBurger 在应用程序中,您希望有一个单一的验证点。我永远不会使用抛出异常的属性设置器。这会引入许多潜在的代码位置,必须处理异常。当EF的验证启动时,该属性不会进行验证。属性的优点是它在正确的时间和正确的方式下进行验证,即像其他EF验证一样。任何违规行为都将在其他验证错误中很好地列出。您可以免费获得很多东西。 - Gert Arnold
@MatthiasBurger 当然我不会用它来进行FK检查,是的,我建议使用枚举。在相对简单的情况下,属性可能已经足够了。 - Gert Arnold
验证问题是正确的,我同意这一点。但我认为实体类对于这种验证来说太晚了。既然它是视图验证,我认为它应该在视图模型中得到验证(除非你不使用视图模型并将实体类带入视图)。视图模型也可以成为视图模型的基类,这样你就有了一个单一的验证点。例如,AccountViewModelLoginViewModelRegisterViewModel 的基类。我会在 AccountViewModel 中验证邮箱地址,并将有效数据发送到实体类 ApplicationUser。或者我错了吗? - Matthias Burger
@MatthiasBurger 我完全同意 :) 我不喜欢在网络上传输完整的实体对象。但视图模型也可以有属性。这完全取决于个人偏好。例如,在ASP.Net MVC中,使用验证属性(在任何模型类上)非常方便。尽管如此,在实体类中使用验证属性(或实现IValidatableObject - 我的偏好)可以提供一个很好的最终保护,防止垃圾进入数据库。 - Gert Arnold
长时间的讨论,但输入字符太少了 :D - 最终,我同意你的观点,但我认为这很大程度上取决于个人偏好和编码风格 :) - 但是听到/阅读其他程序员的想法总是很好的。它只会让我的代码变得更好 :) - Matthias Burger

0

在领域模型中添加数据注释是非常糟糕的做法,就像D.Mac所写的那样。 那么有没有更好的方法呢?

public MyClass
{    
    private int myNumberField;
    public int MyNumberField 
    { 
        get { return myNumberField; } 
        set
        { 
            if (value >= 1 && value <=3)
                myNumberField = value;
            else
                // throw exception?
                // set default-value (maybe 1)?
                // do nothing?
        }
    }

}

你可以在属性的setter中做任何想做的事情

仅在前端限制它并不是最好的解决方案,因为你总是可以修改javascript/html - 但你应该向用户显示,他只能插入值1、2或3。同时,在视图模型中使用数据注释进行限制。

或者:

你也可以重写EntityFrameworks的SaveChanges并添加你的业务逻辑:

public override int SaveChanges(SaveOptions options)
{

    foreach (ObjectStateEntry entry in
        ObjectStateManager.GetObjectStateEntries(
        EntityState.Added | EntityState.Modified))
    {
        // Validate the objects in the Added and Modified state
        // if the validation fails, e.g. throw an exeption.
    }
    return base.SaveChanges(options);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接