【问题标题】:Where should I validate adding to my entity's collection property?我应该在哪里验证添加到我的实体的集合属性?
【发布时间】:2013-01-03 16:20:08
【问题描述】:

我正要开始一个新的宠物项目,我一直想知道在将实体添加到父级的一对多集合时应该如何进行验证。我将使用两个示例类来总结关于StudentTeacher 的内容。这里的限制是,在任何给定时间,Student 只能由一个(并且只有一个)Teacher 教授,而后者又可以教授一个或多个 Students)。

public class Student
{
    public bool IsEnrolled { get; set; }

    public virtual Teacher IsCurrentlyBeingTaughtBy { get; set; }
}

public class Teacher
{
    public virtual ICollection<Student> IsCurrentlyTeaching { get; set; }
}

当学生上课时,我需要将他们分配到TeacherIsCurrentlyTeaching 集合,但我首先需要确保他们已注册。我的问题是在哪里最好地验证这个基本规则?目前我脑海中的选项是:

1.使用存储库模式

由于我将应用单元测试,我倾向于使用这种方法,因为我可以将我的数据访问逻辑包装到一个可模拟的对象中,并且这里只有一个责任,所以我只需要在我的存储库一次。但是 - 验证是存储库的责任,还是我应该只处理存储库中实体的 CRUD?

2。在控制器操作中验证这一点

我应该在这里提一下,我建议这是一个 MVC3 项目,因此在将 Student 添加到存储库之前(以及随后的 Teacher's他们目前正在教的学生名单)。但是 - 我是不是走上了一条我真的不应该走的fat controller 路径?

3.对 Teacher 实体执行此验证

我是否应该通过Teacher POCO 上的方法(例如AddStudent(Student student))添加Student 并在尝试添加尚未添加的学生时抛出自定义异常,从而切断中间人(即存储库)被录取了吗?

可能还有更多可用的选项,但这是我目前正在尝试在这三个之间进行选择的选项,我从考虑这个问题中获得了一些狭隘的视野。显然,以上所有内容都可以进行适当的单元测试,但从长远考虑(并适应增长)我应该走哪条路?

【问题讨论】:

    标签: c# asp.net-mvc-3 entity-framework repository-pattern


    【解决方案1】:

    您可以为此创建自己的自定义验证器。这将让您捎带 MVC 已经提供的验证。我从未尝试过,但我想这样的事情会起作用:

    public class EnsureEnrollment : ValidationAttribute
    {
        public EnsureEnrollment () {    }
    
        public override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var studentList = value as IEnumerable<Student>;
            if (studentList == null)
            {
                return ValidationResult.Success;
            }
    
            foreach(Student s in studentList )
            {
                if(!s.IsEnrolled)
                {
                    //Insert whatever error message you want here.  
                    return new ValidationResult("Student \"" + s.Name + "\" is not enrolled.");
                }
            }
    
            return ValidationResult.Success;
        }
    }
    

    然后在您的财产上添加您的注释:

    [EnsureEnrollment()]
    public virtual ICollection<Student> IsCurrentlyTeaching { get; set; }
    

    【讨论】:

    • 有趣,我从没想过使用自定义属性。我想我在这里遇到的主要问题是我的约束是在业务层还是数据层。这当然是业务层约束的一个选项。
    【解决方案2】:

    就我个人而言,我喜欢将验证作为实体上静态 CRUDL 方法的一部分。诚然,您必须将上下文传递给它们中的每一个,但它可以使控制器更加简洁,并使所有这些功能都可用于将来可能使用您的实体的任何其他项目。

    之前我创建了一个基类,我从该基类派生的所有实体都必须覆盖 Validate。几乎所有的 CRUDL 方法和其他工作方法都调用了 Validate 方法,以确保实体在对其进行操作之前是正确的。这些验证规则中的大多数都比较复杂,可以使用 DataAnnotations 属性轻松表达。

    或者您可以将特定验证点集成到具有更特定目的的方法中。举个例子:

        public static bool AddToTeacher(SchoolContext db, Student student, Teacher teacher)
        {
            if (student.IsEnrolled)
            {
                teacher.IsCurrentlyTeaching(student);
                return db.SaveChanges() > 0;
            }
            return false;
        }
    

    AddToTeacher 方法仅确保满足特定要求。如果我想确保学生形成正确的形式并且符合合格的课程轨道等等,我可能会编写一个简短的方法(或几个都由“容器”方法调用)来验证这些特定点。

    简而言之,我尽我所能在实体上保留实体特定代码的每一点,以便控制器几乎不知道实体是如何工作的。

    至于放在哪个实体上,就看你怎么想了。在我看来,Student.AddToTeacher 与 Teacher.AddStudent 一样可行。我个人会使用前者,因为这是我的大多数实体目前的样子,“子”实体将自己添加到“父母”,而不是相反。

    【讨论】:

    • 传递上下文并不是一个真正的问题,因为我可以将其卸载到 DI(尽管 - 我可能有点错过了这一点 - 我看不到上下文的用途)例子?)。然而,用实体封装这个逻辑感觉更……正确。
    • 实际上在我的示例中我没有使用它(我忘了)。在大多数情况下,我会立即保留我的更改,因此在 AddToTeacher 方法中调用 SaveChanges。
    • 我决定采用存储库模式并将我的逻辑封装在有界上下文中,我很高兴。任何修改实体状态的东西,我都作为一种方法隐藏在实体内部——任何影响数据层的东西(即,一旦实体修改了自己/另一个,就将更改保存回数据库)我委托给存储库。这感觉不错,将其标记为答案,因为它最接近我想要的,谢谢!
    猜你喜欢
    • 1970-01-01
    • 2014-05-25
    • 2021-07-19
    • 1970-01-01
    • 2020-12-29
    • 1970-01-01
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    相关资源
    最近更新 更多