使用自跟踪实体生成器和 Visual Studio 2012 构建 WPF 应用程序 - IClientChangeTracking 接口
本文介绍由自跟踪实体生成器和 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
布尔属性 StudentsListHasChanges
和 CurrentStudentHasChanges
分别存储 StudentsList
和 CurrentStudent
是否存在更改。要更新这两个属性,我们需要调用下面的 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()
都需要在可能发生对 StudentsList
和 CurrentStudent
更改的任何地方被调用,这些将在接下来的内容中介绍。
StudentsList 和 CurrentStudent
StudentsList
订阅 CollectionChanged
事件,列表中的每个 Student
对象也订阅 PropertyChanged
事件。每当 StudentsList
的 CollectionChanged
事件触发时,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;
到目前为止,我们已经展示了如何定义属性 StudentsList
和 CurrentStudent
以及两个附属属性 StudentsListHasChanges
和 CurrentStudentHasChanges
。这四个属性使得 Student
屏幕能够显示从数据库获取的学生信息。此外,根据 StudentsListHasChanges
和 CurrentStudentHasChanges
的值,我们可以轻松地确定“保存”、“全部保存”、“取消”和“全部取消”按钮是否应该启用或禁用。然而,此设计有一个小缺点:如果 Student
实体类型有许多导航属性,并且每个导航属性都扩展了多个级别,那么 StudentsList
的属性 setter 可能会变得有点复杂。因为我们需要跟踪多个导航属性的更改,所有这些都必须订阅 PropertyChanged
事件。
接下来,我们将继续讨论用于填充 StudentsList
和 CurrentStudent
属性的客户端数据检索方法。
数据检索方法
SchoolModel
类的数据检索方法是异步方法,使用 IAsyncResult
设计模式。下面显示的 GetStudentsAsync()
方法就是其中之一。它通过 WCF 服务调用 BeginGetStudents()
开始检索学生信息,其第二个参数是一个指向 BeginGetStudentsComplete
的 AsyncCallback
委托。当此 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 版本。