65.9K
CodeProject 正在变化。 阅读更多。
Home

使用自跟踪实体生成器和 Visual Studio 2012 构建 WPF 应用程序 - IClientChangeTracking 接口

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2012 年 8 月 16 日

CPOL

10分钟阅读

viewsIcon

26669

本文介绍由自跟踪实体生成器和 Visual Studio 2012 生成的 IClientChangeTracking 接口。

  • 这里 下载源代码
  • 请访问此 项目站点 以获取最新版本和源代码。

目录

引言

在本文中,我们将介绍自动生成的 IClientChangeTracking 接口,然后探讨如何在我们的演示应用程序中使用此接口的方法和属性进行客户端更改跟踪。请注意,本文基于一篇关于 Visual Studio 2010 自跟踪实体生成器的先前文章,仅进行了少量更新。因此,如果您已阅读过之前的文章,则可以安全地跳过本文的其余部分。

IClientChangeTracking 接口

IClientChangeTracking 接口包含以下成员:

  • AcceptChanges() 接受实体对象的更改。
  • AcceptObjectGraphChanges() 接受实体对象及其对象图的所有对象的更改。
  • RejectChanges() 拒绝对实体对象所做的更改。
  • RejectObjectGraphChanges() 拒绝对实体对象及其对象图的所有对象所做的更改。
  • ObjectGraphHasChanges() 返回实体对象及其对象图是否具有任何更改。
  • EstimateObjectGraphSize() 返回实体对象及其对象图的估计大小。
  • EstimateObjectGraphChangeSize() 返回优化后的实体对象图(仅包含已更改对象)的估计大小。
  • GetObjectGraphChanges() 返回优化后的实体对象图(仅包含已更改对象)。
  • HasChanges 是只读的,用于跟踪实体对象是否具有任何更改。

前四个方法可用于接受或回滚实体对象上的任何更改。AcceptChanges() 仅接受对对象所做的更改,而 AcceptObjectGraphChanges() 则接受对对象及其对象图上的所有对象所做的更改。RejectChanges()RejectObjectGraphChanges() 方法的作用方式类似。接下来是属性 HasChanges 和方法 ObjectGraphHasChanges(),它们返回实体对象是否具有任何更改。区别在于前者仅检查实体对象本身,而后者则检查整个对象图。最后,最后三个方法是相关的,并且经常一起使用。GetObjectGraphChanges() 方法返回调用对象图的副本,其中仅包含已更改的对象,而其他两个方法是帮助方法,用于返回估计大小,并帮助确定调用 GetObjectGraphChanges() 是否有意义。

在介绍 IClientChangeTracking 接口如何在客户端使用之前,我们先来看一下服务器端的逻辑。

SchoolService 类(服务器端)

大多数服务器端业务逻辑都位于 SchoolService 类中,该类中的方法大致可以分为数据检索方法和更新方法。数据检索方法返回实体列表或单个实体对象,而更新方法用于添加/删除/更新单个实体。接下来我们将讨论数据检索方法。

数据检索方法

在实现数据检索方法时,一个需要特别注意的领域是那些会扩展到多个导航属性级别的。以 GetCourses() 方法为例,该方法返回 Course 对象列表,并扩展了两个级别的导航属性“Enrollments.Student”。因此,如果我们按如下方式实现此方法:

public List<Course> GetCourses()
{
    using (var context = new SchoolEntities())
    {
        return context.Courses
            .Include("Enrollments.Student")
            .ToList();
    }
}

我们将检索到如下图所示的 Course 对象列表:

Course 对象列表的问题在于,每个 Course 对象不属于其自己的对象图,并且实体“CS111”和“CS112”是通过 Student 对象连接的。这使得任何处理对象图的方法都无法使用。例如,如果我们更改实体“CS111”,则对实体“CS112”调用 ObjectGraphHasChanges() 也会返回 true,因为“CS111”和“CS112”属于同一个对象图。

为了克服这个问题,GetCourses() 方法的实际实现如下:

public List<Course> GetCourses(ClientQuery clientQuery)
{
    using (var context = new SchoolEntities())
    {
        if (clientQuery.IncludeList.Count == 0)
        {
            return context.Courses.ApplyClientQuery(clientQuery).ToList();
        }
        var courseList = new List<Course>();
        foreach (var course in context.Courses.ApplyClientQuery(clientQuery).ToList())
        {
            var currentCourse = course;
            using (var innerContext = new SchoolEntities())
            {
                courseList.Add(
                    innerContext.Courses
                    .ApplyIncludePath(clientQuery)
                    .Single(n => n.CourseId == currentCourse.CourseId));
            }
        }
        return courseList;
    }
}

修改后的 GetCourses() 方法将返回如下图所示的 Course 对象列表,我们可以看到实体“CS111”和“CS112”属于两个不相关的对象图。这次,如果我们更改实体“CS111”,则对“CS111”调用 ObjectGraphHasChanges() 将返回 true,而对“CS112”的相同调用仍将返回 false。由于每个 Course 对象都属于不同的对象图,因此我们可以检测并保存对一个 Course 对象所做的更改,而不会影响列表中的其他对象。

更新方法

更新方法通常在一个方法内处理每种实体类型的添加/删除/更新操作。以下是 UpdateCourse() 方法,它保存单个 Course 对象的所有更改,无论操作是添加、删除还是更新。

public List<object> UpdateCourse(Course item)
{
    var returnList = new List<object>();
    if (item == null)
    {
        returnList.Add("Course cannot be null.");
        returnList.Add(0);
        return returnList;
    }
    try
    {
        using (var context = new SchoolEntities())
        {
            switch (item.ChangeTracker.State)
            {
                case ObjectState.Added:
                    // server side validation
                    item.ValidateObjectGraph();
                    // verify whether the instructor for this course exists
                    bool instructorExists = context.People.OfType<Instructor>()
                            .Any(n => n.PersonId == item.InstructorId);
                    if (!instructorExists)
                    {
                        returnList.Add("Error_CannotAddCourseNoInstructor");
                        returnList.Add(item.CourseId);
                        return returnList;
                    }
                    // verify all enrollments for this course have valid students
                    bool enrollmentsValid = item.Enrollments
                        .Aggregate(true, (current, enrollment) =>
                            current && context.People.OfType<Student>()
                                .Any(n => n.PersonId == enrollment.StudentId));
                    if (!enrollmentsValid)
                    {
                        returnList.Add("Error_CannotAddCourseNoStudent");
                        returnList.Add(item.CourseId);
                        return returnList;
                    }
                    // save changes
                    context.Courses.ApplyChanges(item);
                    context.SaveChanges();
                    break;
                case ObjectState.Deleted:
                    // save changes
                    context.Courses.ApplyChanges(item);
                    context.SaveChanges();
                    break;
                default:
                    // server side validation
                    item.ValidateObjectGraph();
                    // verify whether the instructor for this course exists
                    instructorExists =
                    context.People.OfType<Instructor>()
                        .Any(n => n.PersonId == item.InstructorId);
                    if (!instructorExists)
                    {
                        returnList.Add("Error_CannotUpdateCourseNoInstructor");
                        returnList.Add(item.CourseId);
                        return returnList;
                    }
                    // verify all enrollments for this course have valid students
                    enrollmentsValid = item.Enrollments
                        .Aggregate(true, (current, enrollment) =>
                            current && context.People.OfType<Student>()
                                .Any(n => n.PersonId == enrollment.StudentId));
                    if (!enrollmentsValid)
                    {
                        returnList.Add("Error_CannotUpdateCourseNoStudent");
                        returnList.Add(item.CourseId);
                        return returnList;
                    }
                    // save changes
                    context.Courses.ApplyChanges(item);
                    context.SaveChanges();
                    break;
            }
        }
        returnList.Add(string.Empty);
        returnList.Add(item.CourseId);
    }
    catch (OptimisticConcurrencyException)
    {
        returnList.Add("Error_CourseModifiedByAnotherUser");
        returnList.Add(item.CourseId);
    }
    catch (Exception ex)
    {
        Exception exception = ex;
        while (exception.InnerException != null)
        {
            exception = exception.InnerException;
        }
        var errorMessage = exception.Message;
        returnList.Add(errorMessage);
        returnList.Add(item.CourseId);
    }
    return returnList;
}

Course 实体本身会跟踪所有已进行的更改,并将其对象状态存储在 ChangeTracker.State 属性中。如果 State 是 Added,我们将添加一个新课程。如果 State 是 Deleted,我们将从数据库中删除该课程。如果 State 是 Unchanged 或 Modified,我们将执行更新操作。此外,对于所有三种情况,我们都通过简单地调用 context.Courses.ApplyChanges(item) 然后调用 context.SaveChanges() 来保存更改。

至此,我们已经完成了关于服务器端逻辑的讨论。现在,我们准备探讨 IClientChangeTracking 接口如何在客户端使用。

SchoolModel 类(客户端)

以“Student”屏幕为例,我们来检查实现此屏幕的基本要求。首先,应该有一个列表来存储从服务器端检索到的所有 Student 实体。其次,应该有一个变量指向正在编辑的当前 Student 对象。然后,应该有一些布尔属性来跟踪是否进行了任何更改。最后,应该有一组方法来检索、更新和回滚学生信息。所有这些都已在 SchoolModel 类中实现,并可总结如下:

  • StudentsList 存储从服务器端检索到的所有 Student 实体。
  • CurrentStudent 跟踪当前正在编辑的对象。
  • 只读属性 StudentsListHasChanges 跟踪 StudentsList 是否有更改。
  • 只读属性 CurrentStudentHasChanges 跟踪 CurrentStudent 是否有更改。
  • GetStudentsAsync() 从服务器端检索 Student 实体列表。
  • SaveStudentChangesAsync(bool allItems = true)allItems 为 true 时,保存 StudentsList 中所有已更改的实体,并在 allItems 设置为 false 时,保存 CurrentStudent 的更改。
  • RejectStudentChanges(bool allItems = true)allItems 为 true 时,回滚 StudentsList 的所有更改,并在 allItems 设置为 false 时,回滚 CurrentStudent 的更改。

StudentsListHasChanges 和 CurrentStudentHasChanges

布尔属性 StudentsListHasChangesCurrentStudentHasChanges 分别存储 StudentsListCurrentStudent 是否存在更改。要更新这两个属性,我们需要调用下面的 private 方法 ReCalculateStudentsListHasChanges()ReCalculateCurrentStudentHasChanges(),这两个方法都依赖于 IClientChangeTracking 接口的 ObjectGraphHasChanges(),该方法返回实体对象及其对象图是否具有任何更改。

public bool StudentsListHasChanges
{
    get { return _studentsListHasChanges; }
    private set
    {
        if (_studentsListHasChanges != value)
        {
            _studentsListHasChanges = value;
            OnPropertyChanged("StudentsListHasChanges");
        }
    }
}

private bool _studentsListHasChanges;

public bool CurrentStudentHasChanges
{
    get { return _currentStudentHasChanges; }
    private set
    {
        if (_currentStudentHasChanges != value)
        {
            _currentStudentHasChanges = value;
            OnPropertyChanged("CurrentStudentHasChanges");
        }
    }
}

private bool _currentStudentHasChanges;

private void ReCalculateStudentsListHasChanges()
{
    // re-calculate StudentsListHasChanges
    StudentsListHasChanges = StudentsList != null
        && StudentsList.Any(n => n.ObjectGraphHasChanges());
}

private void ReCalculateCurrentStudentHasChanges()
{
    // re-calculate CurrentStudentHasChanges
    CurrentStudentHasChanges = CurrentStudent != null
        && CurrentStudent.ObjectGraphHasChanges();
}

ReCalculateStudentsListHasChanges()ReCalculateCurrentStudentHasChanges() 都需要在可能发生对 StudentsListCurrentStudent 更改的任何地方被调用,这些将在接下来的内容中介绍。

StudentsList 和 CurrentStudent

StudentsList 订阅 CollectionChanged 事件,列表中的每个 Student 对象也订阅 PropertyChanged 事件。每当 StudentsListCollectionChanged 事件触发时,ReCalculateStudentsListHasChanges() 将重新计算 StudentsList 是否有更改。其次,每当 StudentsList 中的任何 Student 对象的 PropertyChanged 事件触发,并且更改的属性等于 HasChanges 时,也会调用 ReCalculateStudentsListHasChanges() 来重新计算 StudentsList 是否有更改。最后,如果 StudentsList 本身被设置为指向另一个列表,则再次使用 ReCalculateStudentsListHasChanges() 方法来重置 StudentsListHasChanges 属性。

public ObservableCollection<Student> StudentsList
{
    get { return _studentsList; }
    set
    {
        if (!ReferenceEquals(_studentsList, value))
        {
            if (_studentsList != null)
            {
                _studentsList.CollectionChanged -= _studentsList_CollectionChanged;
                foreach (var student in _studentsList)
                {
                    ((INotifyPropertyChanged)student).PropertyChanged -= 
						EntityModel_PropertyChanged;
                }
            }
            _studentsList = value;
            if (_studentsList != null)
            {
                _studentsList.CollectionChanged += _studentsList_CollectionChanged;
                foreach (var student in _studentsList)
                {
                    ((INotifyPropertyChanged)student).PropertyChanged += 
						EntityModel_PropertyChanged;
                }
            }
            ReCalculateStudentsListHasChanges();
        }
    }

private ObservableCollection<Student> _studentsList;

private void _studentsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.NewItems != null)
    {
        foreach (Student newItem in e.NewItems)
            ((INotifyPropertyChanged)newItem).PropertyChanged += 
					EntityModel_PropertyChanged;
    }
    if (e.OldItems != null)
    {
        foreach (Student oldItem in e.OldItems)
            ((INotifyPropertyChanged)oldItem).PropertyChanged -= 
					EntityModel_PropertyChanged;
    }
    ReCalculateStudentsListHasChanges();
}

private void EntityModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName.Equals("HasChanges"))
    {
        if (sender is Student)
        {
            ReCalculateStudentsListHasChanges();
            ReCalculateCurrentStudentHasChanges();
        }
        else if (sender is Instructor)
        {
            ReCalculateInstructorsListHasChanges();
            ReCalculateCurrentInstructorHasChanges();
        }
        else if (sender is Course || sender is Enrollment)
        {
            ReCalculateCoursesListHasChanges();
            ReCalculateCurrentCourseHasChanges();
        }
        else
        {
            throw new NotImplementedException();
        }
    }
}

CurrentStudent 遵循类似的模式。区别在于它仅订阅 PropertyChanged 事件。每当 PropertyChanged 事件触发并且更改的属性是 HasChanges 时,都会调用 ReCalculateCurrentStudentHasChanges() 方法。同样,当 CurrentStudent 被赋值给另一个 Student 对象时,ReCalculateCurrentStudentHasChanges() 也会更新 CurrentStudentHasChanges

public Student CurrentStudent
{
    get { return _currentStudent; }
    set
    {
        if (!ReferenceEquals(_currentStudent, value))
        {
            if (_currentStudent != null)
            {
                ((INotifyPropertyChanged)_currentStudent).PropertyChanged -= 
						EntityModel_PropertyChanged;
            }
            _currentStudent = value;
            if (_currentStudent != null)
            {
                ((INotifyPropertyChanged)_currentStudent).PropertyChanged += 
						EntityModel_PropertyChanged;
            }
            ReCalculateCurrentStudentHasChanges();
        }
    }
}

private Student _currentStudent;

到目前为止,我们已经展示了如何定义属性 StudentsListCurrentStudent 以及两个附属属性 StudentsListHasChangesCurrentStudentHasChanges。这四个属性使得 Student 屏幕能够显示从数据库获取的学生信息。此外,根据 StudentsListHasChangesCurrentStudentHasChanges 的值,我们可以轻松地确定“保存”、“全部保存”、“取消”和“全部取消”按钮是否应该启用或禁用。然而,此设计有一个小缺点:如果 Student 实体类型有许多导航属性,并且每个导航属性都扩展了多个级别,那么 StudentsList 的属性 setter 可能会变得有点复杂。因为我们需要跟踪多个导航属性的更改,所有这些都必须订阅 PropertyChanged 事件。

接下来,我们将继续讨论用于填充 StudentsListCurrentStudent 属性的客户端数据检索方法。

数据检索方法

SchoolModel 类的数据检索方法是异步方法,使用 IAsyncResult 设计模式。下面显示的 GetStudentsAsync() 方法就是其中之一。它通过 WCF 服务调用 BeginGetStudents() 开始检索学生信息,其第二个参数是一个指向 BeginGetStudentsCompleteAsyncCallback 委托。当此 WCF 服务调用完成后,AsyncCallback 委托将在单独的线程中处理检索操作的结果。由于我们需要在 UI 线程上触发 GetStudentsCompleted 事件,因此必须将其包含在 ThreadHelper.BeginInvokeOnUIThread() 中,如下所示:

public void GetStudentsAsync(string includeOption, string screenName)
{
    _proxy.BeginGetStudents(includeOption, BeginGetStudentsComplete, screenName);
    _proxy.IncrementCallCount();
}

/// <summary>
/// AsyncCallback for BeginGetStudents
/// </summary>
/// <param name="result"></param>
private void BeginGetStudentsComplete(IAsyncResult result)
{
    ThreadHelper.BeginInvokeOnUIThread(
        delegate
        {
            _proxy.DecrementCallCount();
            try
            {
                // get the return values
                var students = _proxy.EndGetStudents(result);
                if (GetStudentsCompleted != null)
                {
                    GetStudentsCompleted(this, new ResultsArgs<Student>
				(students, null, false, result.AsyncState));
                }
            }
            catch (Exception ex)
            {

                if (GetStudentsCompleted != null && 
			(_lastError == null || AllowMultipleErrors))
                {
                    GetStudentsCompleted(this, new ResultsArgs<Student>
				(null, ex, true, result.AsyncState));
                }
                _lastError = ex;
            }
        });
}

更新方法

同样,更新方法也是异步方法。我们这里的示例是 SaveStudentChangesAsync() 方法。此调用接受一个布尔参数 allItems。如果 allItems 为 true,它将遍历 StudentsList 中所有已更改的项目并调用 BeginUpdateStudent()。否则,它只检查 CurrentStudent 是否有更改,如果确实有更改,则该方法仅为 CurrentStudent 调用 BeginUpdateStudent()

SaveStudentChangesAsync() 使用了 IClientChangeTracking 接口的几个方法。首先,我们使用 ObjectGraphHasChanges() 来确定 Student 对象是否具有要保存的更改。接下来,我们使用两个辅助方法 EstimateObjectGraphSize()EstimateObjectGraphChangeSize() 来确定对象图更改的大小是否小于总大小的 70%。如果为 true,我们将调用 GetObjectGraphChanges() 来获取一个优化后的实体对象图,其中仅包含已更改的对象。

GetObjectGraphChanges() 方法在减少从客户端发送到服务器端的总数据量方面非常有用。例如,如果我们有一个订单屏幕,该屏幕检索一个订单以及数百个订单明细行作为其导航集合,而我们只更改了订单的实际发货日期而未更改任何订单明细行,那么在保存此订单之前调用 GetObjectGraphChanges() 将确保我们仅发送订单对象而没有订单明细行。这样,就克服了使用自跟踪实体的一个主要缺点。

/// <summary>
/// If allItems is true, all items from the StudentsList have
/// their changes saved; otherwise, only CurrentStudent from
/// the StudentsList has its changes saved.
/// </summary>
/// <param name="allItems"></param>
public void SaveStudentChangesAsync(bool allItems = true)
{
    if (allItems)
    {
        if (StudentsList != null && StudentsListHasChanges)
        {
            // save changes for all items from the StudentsList
            foreach (var student in StudentsList.Where(n => n.ObjectGraphHasChanges()))
            {
                var totalSize = student.EstimateObjectGraphSize();
                var changeSize = student.EstimateObjectGraphChangeSize();
                // if the optimized entity object graph is less than 70%
                // of the original, call GetObjectGraphChanges()
                var currentStudent = changeSize < (totalSize*0.7)
                                        ? (Student) student.GetObjectGraphChanges()
                                        : student;

                _actionQueue.Add(
                    n => _proxy.BeginUpdateStudent(
                        currentStudent,
                        BeginUpdateStudentComplete,
                        currentStudent.PersonId));
            }
            // start save changes for the first student
            if (_actionQueue.BeginOneAction()) _proxy.IncrementCallCount();
        }
    }
    else
    {
        if (CurrentStudent != null && StudentsList != null && CurrentStudentHasChanges)
        {
            // save changes for only CurrentStudent from the StudentsList
            var currentStudent = StudentsList
                .FirstOrDefault(n => n.PersonId == CurrentStudent.PersonId);
            if (currentStudent != null)
            {
                var totalSize = currentStudent.EstimateObjectGraphSize();
                var changeSize = currentStudent.EstimateObjectGraphChangeSize();
                // if the optimized entity object graph is less than 70%
                // of the original, call GetObjectGraphChanges()
                currentStudent = changeSize < (totalSize*0.7)
                                    ? (Student) currentStudent.GetObjectGraphChanges()
                                    : currentStudent;

                _actionQueue.Add(
                    n => _proxy.BeginUpdateStudent(
                        currentStudent,
                        BeginUpdateStudentComplete,
                        currentStudent.PersonId));
                // start save changes for the current student
                if (_actionQueue.BeginOneAction()) _proxy.IncrementCallCount();
            }
        }
    }
}

BeginUpdateStudentComplete() 方法是上面描述的 BeginUpdateStudent()AsyncCallback,它处理异步更新操作的结果。如果更新成功且服务器端没有警告消息,我们将调用 AcceptObjectGraphChanges()IClientChangeTracking 接口中定义的另一个方法),它将接受对 Student 对象及其对象图中所有对象的更改。之后,Student 对象的 HasChanges 属性将被设置回 false。

/// <summary>
/// AsyncCallback for BeginUpdateStudent
/// </summary>
/// <param name="result"></param>
private void BeginUpdateStudentComplete(IAsyncResult result)
{
    ThreadHelper.BeginInvokeOnUIThread(
        delegate
        {
            try
            {
                // get the return values
                var returnList = _proxy.EndUpdateStudent(result);
                // returnList[0] could be a resource key or warning message
                var resourceKey = returnList[0] as string;
                // returnList[1] is the updated student Id
                var updatedStudentId = Convert.ToInt32(returnList[1]);
                // retrieve the actual warning message
                var warningMessage = GetWarningMessageFromResource(resourceKey, updatedStudentId);
                // get the studentId for the student that finished saving changes
                var studentId = Convert.ToInt32(result.AsyncState);
                var student = StudentsList.Single(n => n.PersonId == studentId);
                // update the student Id if the student State is Added
                if (student.ChangeTracker.State == ObjectState.Added)
                    student.PersonId = updatedStudentId;

                if (string.IsNullOrEmpty(warningMessage))
                {
                    var isDeleted = student.ChangeTracker.State == ObjectState.Deleted;
                    // if there is no error or warning message, 
		  // call AcceptObjectGraphChanges() first
                    student.AcceptObjectGraphChanges();
                    // if State is Deleted, remove the student from the StudentsList
                    if (isDeleted) StudentsList.Remove(student);
                    // then, continue to save changes for the next student in queue
                    if (_actionQueue.BeginOneAction() == false)
                    {
                        // all changes are saved, we need to send notification
                        _proxy.DecrementCallCount();
                        if (SaveStudentChangesCompleted != null &&
                            _lastError == null && string.IsNullOrEmpty(warningMessage))
                        {
                            SaveStudentChangesCompleted(this,
                                new ResultArgs<string>(string.Empty, null, false, null));
                        }
                    }
                }
                else
                {
                    // if there is a warning message, 
		  // we need to stop and send notification
                    // on first occurrence, in other words, if _lastError is still null
                    _actionQueue.Clear();
                    _proxy.DecrementCallCount();
                    if (SaveStudentChangesCompleted != null &&
                        (_lastError == null || AllowMultipleErrors))
                    {
                        SaveStudentChangesCompleted(this,
                            new ResultArgs<string>(warningMessage, null, true, null));
                    }
                }
            }
            catch (Exception ex)
            {
                // if there is an error, we need to stop and send notification
                // on first occurrence, in other words, if _lastError is still null
                _actionQueue.Clear();
                _proxy.DecrementCallCount();
                if (SaveStudentChangesCompleted != null &&
                    (_lastError == null || AllowMultipleErrors))
                {
                    SaveStudentChangesCompleted(this,
                        new ResultArgs<string>(string.Empty, ex, true, null));
                }

                _lastError = ex;
            }
        });
}

回滚方法

最后一个方法是 RejectStudentChanges()。与 SaveStudentChangesAsync() 类似,RejectStudentChanges() 也接受一个布尔参数 allItems。如果 allItems 为 true,该方法将遍历 StudentsList 中所有已更改的项目并调用 RejectObjectGraphChanges()IClientChangeTracking 接口的另一个方法)。否则,该方法只检查 CurrentStudent 是否有更改,如果有,则该方法仅为 CurrentStudent 调用 RejectObjectGraphChanges()

/// <summary>
/// If allItems is true, all items from the StudentsList have
/// their changes rejected; otherwise, only CurrentStudent from
/// the StudentsList has its changes rejected.
/// </summary>
/// <param name="allItems"></param>
public void RejectStudentChanges(bool allItems = true)
{
    if (allItems)
    {
        if (StudentsList != null && StudentsListHasChanges)
        {
            // reject changes for all items from the StudentsList
            foreach (var student in StudentsList.Where
			(n => n.ObjectGraphHasChanges()).ToList())
            {
                var isAdded = student.ChangeTracker.State == ObjectState.Added;
                student.RejectObjectGraphChanges();
                // if the State is Added, simply remove it from the StudentsList
                if (isAdded) StudentsList.Remove(student);
            }
        }
    }
    else
    {
        if (CurrentStudent != null && StudentsList != null && CurrentStudentHasChanges)
        {
            // reject changes for only CurrentStudent from the StudentsList
            var currentStudent = StudentsList
                .FirstOrDefault(n => n.PersonId == CurrentStudent.PersonId);
            if (currentStudent != null)
            {
                var isAdded = currentStudent.ChangeTracker.State == ObjectState.Added;
                currentStudent.RejectObjectGraphChanges();
                // if the State is Added, simply remove it from the StudentsList
                if (isAdded) StudentsList.Remove(currentStudent);
            }
        }
    }
}

总结

我们已经完成了关于如何使用 IClientChangeTracking 接口的方法和属性的讨论。总结一下,ObjectGraphHasChanges() 方法在多个地方用于检查实体是否具有任何更改。其次,AcceptObjectGraphChanges() 方法仅在更新操作成功完成时使用,而 RejectObjectGraphChanges() 方法在回滚操作中调用,用于撤销所有已做的更改。最后,GetObjectGraphChanges() 方法在节省通过网络发送的总数据量方面非常有用。

希望您觉得本文有用,请在下方评分和/或留下反馈。谢谢!

历史

  • 2012 年 8 月 - 初始发布。
  • 2013 年 3 月 - 更新至 2.1.3 版本。
© . All rights reserved.