实体框架验证与使用

13

我知道有AssociationChanged事件,然而这个事件在关联完成后才会触发。并没有AssociationChanging事件。因此,如果我想基于某些验证原因抛出异常,该怎么做并回到我的原始值呢?

另外,我想根据其他实体的信息为我的实体设置默认值,但仅在我知道该实体正在被创建以插入到数据库中时。如何区分这一点和根据现有数据填充对象而实例化对象之间的区别?我应该知道吗?这是否被认为是应该放在我的实体业务逻辑之外的业务逻辑?

如果是这样的话,那我应该设计控制器类来包装所有这些实体吗?我的担忧是,如果我返回一个实体,我希望客户端能够访问属性,但我想保留对它们设置、默认值等方面的严格控制。我看到的每一个例子都涉及上下文,而上下文是在我实体部分类验证之外的,是吗?

顺便说一句,我看了一下EFPocoAdapter,但我始终无法确定如何从我的POCO类内部填充列表……有人知道我如何从EFPoco类获得上下文吗?

5个回答

2
这是我回复留言的内容。希望这能回答你的问题,Shimmy。如果没有回答到你的问题,请随时留言并告诉我,我会进行简化或删除。
要实现类的绑定通知,需要同时实现INotifyPropertyChanging和INotifyPropertyChanged接口(除非它是类似于实体框架对象那样已经内部实现了这些)。
在设置属性值之前,需要使用PropertyChangingEventArgs构造函数中该属性的名称来触发NotifyPropertyChanging.PropertyChanging事件。
在设置完值后,需要再次使用PropertyChangedEventArgs构造函数中该属性的名称来触发NofityPropertyChanged.PropertyChanged事件。
然后,您需要处理PropertyChanging和PropertyChanged事件。在PropertyChanging事件中,您需要缓存该值。在PropertyChanged事件中,则可以对其进行比较并抛出异常。
要从PropertyChanging/PropertyChanged事件参数中获取属性,需要使用反射技术。
// 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,这是一个布尔值,我之前忘记在这些方法之前添加了吗?当你将属性重置回其先前的值时,这些事件将被重新调用。
简而言之,
现在您可以派生一个单一的事件,该事件继承自INotifyPropertyChanged,但使用一个参数,该参数包含表示先前值以及属性名称的对象。这将把触发的事件数量减少到一个,并具有类似的功能,并且与INotifyPropertyChanged向后兼容。
但是,如果您想在属性设置之前处理任何内容(例如,属性执行不可逆更改或者您需要在设置该变量之前设置其他属性,否则会引发异常),则无法这样做。
总体而言,这种方法非常古老。我会采用Poker Villian的答案,并允许输入无效数据。但是禁止保存到数据库。
Entity Framework具有一些出色的代码用于验证。您可以通过属性向属性添加验证。然后它会处理处理这些属性的工作。然后,您可以创建一个名为IsValid的属性,该属性调用特定于Entity Framework的验证。它还区分字段错误(例如输入错误字符或字符串过长)和类错误(例如缺少数据或冲突键)。
然后,您可以将IsValid绑定到控件验证,并在输入无效数据时显示红色气泡。或者您可以自己实现IsValid验证。但是,如果IsValid为false,则SaveChanges事件需要取消保存。
顺便说一下。提供的代码将无法编译,仅为伪代码(混合vb和c#)。但我认为它比仅使用c#更具描述性-显示正在处理的内容。

0
关于您的第一个问题,我会将关联更改实现为业务逻辑。例如,如果您添加了一个具有多个学生的教师类,请不要像这样添加学生:
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;
}

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

关于您问题的第二部分,您可能可以使用OnVarNameChanging事件。如果实体状态为“New”,则可以应用检索实际值的逻辑。

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

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

祝你好运


0

为了回答你的问题或者扩展ADB的答案,你可以使用ObjectStateManager.GetObjectStateEntry来查找实体的状态并编写自定义默认逻辑。

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

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

上下文还有一个ObjectMaterialized事件。

通过这两个事件,您可以将所有验证和创建代码放在一个位置,如果它们包括其他对象的值等复杂内容,则可能是合适的。


0

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

这也让我非常困扰,因此我向Microsoft Connect报告了此问题请在此处投票!

顺便说一下,我认为PropertyChangingEventArgs不继承CancelEventArgs也很愚蠢,因为使用异常取消并不总是优雅的解决方案,而且抛出异常的性能成本比调用OnPropertyChangingEvent然后检查返回的e.Cancel要高,所以它的成本比引发PropertyChangingEvent要低,你无论如何都会调用它们两个。
对于那些坚持走异常路线的人来说,处理程序中仍然可以抛出异常,而不是将e.Cancel标记为true。 在此处投票.


1
PropertyChangingEventArgs与CancelEventArgs没有关系。它们有各自不同的使用目的。强制继承会导致不必要的复杂和挫败感(将它们紧密耦合在一起)。有PropertyChangingEvent和PropertyChangedEvent,我相信它们可以满足您所需的功能,而无需对它们进行更改(更不用说这会破坏.NET 1.1到.NET 4.0的兼容性了)。 - TamusJRoyce
@TamusJRoyce,好的,我同意,它不应该从CancelEventArgs继承,但它应该做的是我的另一个请求,即提供候选值(获取当前方法属性的简单方法是具有相当大的性能成本)。 - Shimmy Weitzhandler

0
创建一个工厂,根据您的需求为您生成实例,例如:
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);
}

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