【问题标题】:Entity Framework Validation & usage实体框架验证和使用
【发布时间】:2010-09-23 02:34:19
【问题描述】:

我知道有一个 AssociationChanged 事件,但是,在建立关联后会触发此事件。没有 AssociationChanging 事件。那么,如果我出于某种验证原因想抛出异常,我该如何做到这一点并恢复到我的原始值?

另外,我想根据来自其他实体的信息为我的实体设置默认值但只有在我知道实体已实例化以插入数据库时​​才这样做。我如何区分它与实例化的对象之间的区别,因为它将基于现有数据进行填充?我应该知道吗?是否应该在我的实体业务逻辑之外进行考虑的业务逻辑?

如果是这样,那么我应该设计控制器类来包装所有这些实体吗?我担心的是,如果我返回一个实体,我希望客户端能够访问属性,但我希望对它们的设置、默认等验证保持严格控制。我看到的每个示例都引用上下文,这超出了我的实体部分类验证,对吗?

顺便说一句,我查看了 EFPocoAdapter 并且在我的一生中无法确定如何从我的 POCO 类中填充列表...有人知道我如何从 EFPoco 类中获取上下文吗?

【问题讨论】:

    标签: entity-framework validation


    【解决方案1】:

    关于您的第一个问题,我将简单地将关联更改实现为业务逻辑。例如,如果您添加一个有多个学生的教师类,请不要添加学生喜欢

    aTeacher.Students.Add(new Student)
    

    相反,创建一个 AddStudent 方法

    public Student AddNewStudent(string name, string studentID)
    {
    
        Student s = new Student( name, studentID);
        s.Teacher = this; // changes the association
        return s;
    }
    

    这样您就可以完全控制何时更改关联。当然,是什么阻止了其他程序员直接添加学生?在学生端,您可以将教师设置器设置为私有(并将构造函数更改为接受教师或类似的)。在教师方面,如何使学生集合不可插入?我不确定...也许将其转换为不接受插入的自定义集合。

    关于问题的第二部分,您可能会使用 OnVarNameChanging 事件。如果 EntityState 是“新”,那么您可以应用获取实际值的逻辑。

    还有一个在您保存更改时触发的事件 (OnSavingChanges?),您可以使用它来确定哪些对象是新的并设置一些值。

    但也许最简单的解决方案是始终在构造函数中设置默认值,如果从数据库加载数据,它们将被覆盖。

    祝你好运

    【讨论】:

      【解决方案2】:

      根据您的需要创建一个为您生成实例的工厂,例如:

      getStudent(String studentName, long studentId, Teacher teacher) {
          return new Student(studentName, studentId);
      }
      
      getStudentForDBInseration(String studentName, long studentId, Teacher teacher) {
          Student student = getStudent(studentName, studentId);
          student = teacher;
          //some entity frameworks need the student to be in the teachers student list
          //so you might need to add the student to the teachers student list
          teacher.addStudent(student);
      }
      

      【讨论】:

        【解决方案3】:

        没有 AssociationChanging(继承自 CancelEventArgs)事件是一个严重的缺陷。

        我也很困扰,因此我向 Microsoft Connect 报告了这个问题Please vote here!

        顺便说一句,我也认为 PropertyChangingEventArgs 不继承 CancelEventArgs 也是愚蠢的,因为用异常取消并不总是优雅的解决方案,此外,抛出异常比调用 OnPropertyChangingEvent 然后检查返回的性能要高e.Cancel,因此它的成本是否低于引发 PropertyChangingEvent 的成本,无论如何您都称它们为两者。
        对于那些坚持采用异常方式的人来说,无论如何都可以在处理程序中引发异常,而不是将 e.Cancel 标记为 true。 Vote Here

        【讨论】:

        • PropertyChangingEventArgs 与 CancelEventArgs 无关。他们有时需要专门用于不同的目的。强制继承会导致不必要的复杂性和挫败感(将它们紧密耦合在一起)。有 PropertyChangingEvent 和 PropertyChangedEvent,我相信它们可以满足您想要的功能,而无需对其进行更改(更不用说,这会破坏 .NET 1.1 与 .NET 4.0 的兼容性)。
        • @TamusJRoyce,好的,我同意,它不应该从 CancelEventArgs 继承,但它应该做的是我的另一个请求,即提供候选值(获得它的简单方法是获取具有大量性能成本的当前方法属性)。
        【解决方案4】:

        要回答您的部分问题或解释 ADB 的回答,您可以使用 ObjectStateManager.GetObjectStateEntry 来查找实体的状态并编写您的自定义默认逻辑。

        SaveChanges 是上下文中您可以使用的方法,或者 SavingChanges 是调用 SaveChanges 之前发生的事件。

        如果您不想中止更改,您可以覆盖 SaveChanges 并仅调用 base.SaveChanges

        还有一个针对上下文的 ObjectMaterialized 事件。

        在两者之间,您可以将所有验证和创建代码粘贴在一个位置,如果它们很复杂并且包含其他对象的值等,这可能是合适的。

        【讨论】:

          【解决方案5】:

          这是对我留下的评论的回复。希望这能回答你的问题,Shimmy。只需评论,如果它不能回答您的问题,我会缩短或删除它。

          您将需要在您的类上实现 INotifyPropertyChanging 和 INotifyPropertyChanged 接口(除非它类似于实体框架对象,我相信它会在内部实现这些)。

          在为该属性设置值之前,您需要使用 PropertyChangingEventArgs 构造函数中的属性名称引发 NotifyPropertyChanging.PropertyChanging 事件。

          设置此值后,您需要引发 NofityPropertyChanged.PropertyChanged 事件,再次使用在 PropertyChangedEventArgs 构造函数中引发的属性名称。

          然后您必须处理 PropertyChanging 和 PropertyChanged 事件。在 PropertyChanging 事件中,您需要缓存该值。在 PropertyChanged 事件中,可以比较并抛出异常。

          要从 PropertyChanging/PropertyChanged 事件参数中获取属性,需要使用 relfection。

          // PropertyName is the key, and the PropertyValue is the value.
          Dictionary <string, object> propertyDict = new Dictionary<object, object>();
          
              // Convert this function prototype to C# from VBNet.  I like how Handles is descriptive.
              Public Sub PropertyChanging(sender As object, e As PropertyChangingEventArgs) Handles Foo.PropertyChanging
              {
                if (sender == null || preventRecursion)
                {
                  return;
                } // End if
          
                Type senderType = sender.GetType();
                PropertyInfo info = senderType.GetProperty(e.PropertyName);
                object propertyValue = info.GetValue(sender, null);
          
                // Change this so it checks if e.PropertyName already exists.
                propertyDict.Add(e.PropertyName, propertyValue);
              } // End PropertyChanging() Event
          
               // Convert this function prototype to C# from VBNet.  I like how Handles is descriptive.
              Public Sub PropertyChanged(sender As object, e As PropertyChangedEventArgs) Handles Foo.PropertyChanged
              {
                if (sender == null || preventRecursion)
                {
                  return;
                } // End if
          
                Type senderType = sender.GetType();
                PropertyInfo info = senderType.GetProperty(e.PropertyName);
                object propertyValue = info.GetValue(sender, null);
          
                // Change this so it makes sure e.PropertyName exists.
                object oldValue = propertyDict(e.PropertyName);
                object newValue = propertyValue;
          
                // No longer needed.
                propertyDict.Remove(e.PropertyName);
          
                if (/* some condition */)
                {
                  try {
                    preventRecursion = true;
                    info.SetValue(oldValue, null);
                    Throw New Exception();
                  } finally {
                    preventRecursion = false;
                  } // End try
                } // End if
              } // End PropertyChanging() Event
          

          注意我是如何使用PreventRecursion的,这是一个我忘记在这些方法之上添加的布尔值?当您将属性重置为之前的值时,将调用这些事件。

          tl;博士

          现在您可以派生一个从 INotifyPropertyChanged 继承的单个事件,但使用一个包含表示先前值的对象以及属性名称的参数。这会将被触发的事件数量减少到一个,具有类似的功能,并与 INotifyPropertyChanged 向后兼容。

          但是如果你想在设置属性之前处理任何事情(比如属性做了不可逆的更改,或者你需要在设置该变量之前设置其他属性,否则会抛出异常)你将无法做到那个。

          总的来说,这种方法是一种非常古老的做事方式。我会接受 Poker Villian 的回答,并且可以输入无效数据。但不允许保存到数据库。

          Entity Framework 有一些优秀的验证代码。您可以通过属性向您的属性添加验证。然后它负责处理这些属性的工作。然后您可以创建一个名为 IsValid 的属性,该属性调用实体框架特定的验证。它还区分了字段错误(例如输入错误的字符或字符串太长)和类错误(例如缺少数据或键冲突)。

          然后你可以将 IsValid 绑定到控件验证上,当输入无效数据时它们会显示一个红色气泡。或者您可以自己实现 IsValid 验证。但如果 IsValid 为 false,SaveChanges 事件将需要取消保存。

          顺便说一句。提供的代码不会编译并且只是伪代码(混合 vb 和 c#)。但我相信它比单独的 c# 更具描述性——准确地显示正在处理的内容。

          【讨论】:

            猜你喜欢
            • 2010-09-16
            • 1970-01-01
            • 2012-10-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多