Click here to Skip to main content
Click here to Skip to main content

Building WPF Applications with Self-Tracking Entity Generator and Visual Studio 2012 - IClientChangeTracking Interface

, 7 Mar 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
This article describes the IClientChangeTracking interface generated by Self-Tracking Entity Generator and Visual Studio 2012.
  • Download source code from here
  • Please visit this project site for the latest releases and source code.

Contents

Introduction

In this article, we will cover the auto-generated IClientChangeTracking interface, and then we will examine how the methods and properties of this interface can be used inside our demo application for client-side change tracking. Please note that this article is based on a previous article on Self-Tracking Entity Generator for Visual Studio 2010 with only minor updates. So, if you have read the previous one, you can safely skip the rest of this article.

IClientChangeTracking Interface

The IClientChangeTracking interface consists of the following members:

  • AcceptChanges() accepts changes for an entity object.
  • AcceptObjectGraphChanges() accepts changes for an entity object and all objects of its object graph.
  • RejectChanges() rejects changes made to an entity object.
  • RejectObjectGraphChanges() rejects changes made to an entity object and all objects of its object graph.
  • ObjectGraphHasChanges() returns whether an entity object along with its object graph has any changes.
  • EstimateObjectGraphSize() returns the estimate size of an entity object along with its object graph.
  • EstimateObjectGraphChangeSize() returns the estimate size of an optimized entity object graph with only objects that have changes.
  • GetObjectGraphChanges() returns an optimized entity object graph with only objects that have changes.
  • HasChanges is read only, and keeps track of whether an entity object has any changes.

The first four methods can be used to accept or rollback any changes made on an entity object. AcceptChanges() accepts changes made to the object only, while AcceptObjectGraphChanges() accepts changes made to the object and all objects of its object graph. The RejectChanges() and RejectObjectGraphChanges() methods work in a similar fashion. The next two are the property HasChanges and the method ObjectGraphHasChanges(), which return whether an entity object has any changes. The difference is that the former only checks the entity object itself, while the latter checks the entire object graph. Finally, the last three methods are related and often used together. The GetObjectGraphChanges() method returns a copy of the calling object graph with only objects that have changes, and the other two are helper methods that return the estimate sizes and help to determine whether it makes sense to call GetObjectGraphChanges() or not.

Before we visit how the IClientChangeTracking interface is used on the client-side, let us take a look at the server-side logic first.

SchoolService Class (Server-side)

Most of the server-side business logic resides in the SchoolService class, and methods inside this class can be roughly divided into data retrieval methods and update methods. Data retrieval methods either return a list of entities or a single entity object, while update methods are called to either add/delete/update a single entity. We will discuss the data retrieval methods next.

Data Retrieval Methods

When implementing data retrieval methods, one area that we should pay special attention is the ones that expand on multiple levels of navigation properties. Take the GetCourses() method as an example, this method returns a list of Course objects and expands on two levels of navigation properties "Enrollments.Student". So, if we implement this method as follows:

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

We would retrieve the list of Course objects as the following diagram shows:

The problem with this list of Course objects is that each Course object does not belong to its own object graph and entities "CS111", "CS112" are connected through Studentobjects. This makes any method that deals with the object graph useless. For example, if we make a change to entity "CS111", a call of ObjectGraphHasChanges() on entity "CS112" will also return true because "CS111" and "CS112" belong to the same object graph.

In order to overcome this problem, the actual implementation of the GetCourses() method is as follows:

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;
    }
}

This modified GetCourses() method will return a list of Course objects as the following diagram shows and we can see that entities "CS111" and "CS112" belong to two disconnected object graphs. This time, if we make a change to entity "CS111", a call of ObjectGraphHasChanges() on "CS111" will return true, while the same call on "CS112" still returns false. Since each Course object belongs to a different object graph, we can detect and save changes to one Course object without affecting others in the list.

Update Methods

The update methods usually handle add/delete/update operations all within a single method for each entity type. Following is the method UpdateCourse() that saves changes for a single Course object no matter if the operation is add, delete, or update.

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;
}

The Course entity itself keeps track of all the changes made, and also stores the object's state inside the property ChangeTracker.State. If the State is Added, we are going to add a new course. If the State is Deleted, we will delete that course from the database. And if the State is either Unchanged or Modified, we will do an update operation. Also, for all three cases, we save changes by simply calling context.Courses.ApplyChanges(item) followed by context.SaveChanges().

This concludes our discussion about the server-side logic. Now we are ready to examine how the IClientChangeTracking interface can be used on the client side.

SchoolModel Class (Client-side)

Let us take the "Student" screen as an example, and check the basic requirements to implement this screen. First, there should be a list that stores all Student entities retrieved from the server-side. Second, there should be one variable that points to the current Student object in edit. Then, there should be Boolean properties that keep track of whether there are any changes made. And finally, there should be a set of methods to retrieve, update, and rollback student information. All of these are implemented in the class SchoolModel and can be summarized as follows:

  • StudentsList keeps all Student entities retrieved from the server-side.
  • CurrentStudent keeps track of what is currently in edit.
  • Read only property StudentsListHasChanges keeps track of whether StudentsList has changes.
  • Read only property CurrentStudentHasChanges keeps track of whether CurrentStudent has changes.
  • GetStudentsAsync() retrieves a list of Student entities from the server-side.
  • SaveStudentChangesAsync(bool allItems = true) saves all changed entities from StudentsList when allItems is true, and saves changes from CurrentStudent when allItems is set to false.
  • RejectStudentChanges(bool allItems = true) rolls back all changes from StudentsList when allItems is true, and rolls back changes from CurrentStudent when allItems is set to false.

StudentsListHasChanges and CurrentStudentHasChanges

Boolean properties StudentsListHasChanges and CurrentStudentHasChanges store whether there are changes to StudentsList and CurrentStudent, respectively. To update these two properties, we need to call the private methods ReCalculateStudentsListHasChanges() and ReCalculateCurrentStudentHasChanges() shown below, and both methods rely on ObjectGraphHasChanges() from the IClientChangeTracking interface, which returns whether an entity object along with its object graph has any changes.

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();
}

Both ReCalculateStudentsListHasChanges() and ReCalculateCurrentStudentHasChanges() need to be called from any place where a change to StudentsList and CurrentStudent could take place, which will be covered next.

StudentsList and CurrentStudent

StudentsList subscribes to the CollectionChanged event and each Student object inside the list also subscribes to the PropertyChanged event. Whenever the CollectionChanged event fires for the StudentsList, ReCalculateStudentsListHasChanges() will recalculate whether StudentsList has changes or not. Secondly, whenever the PropertyChanged event fires for any Student object inside the StudentsList and the changed property equals HasChanges, ReCalculateStudentsListHasChanges() also gets called to recalculate whether StudentsList has changes or not. Lastly, if StudentsList itself is set to point to a different list, the ReCalculateStudentsListHasChanges() method is used again to reset the property 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 follows a similar pattern. The difference is that it only subscribes to the PropertyChanged event. The method ReCalculateCurrentStudentHasChanges() is called whenever the PropertyChanged event fires and the changed property is HasChanges. Likewise, when CurrentStudent is assigned to a different Student object, ReCalculateCurrentStudentHasChanges() will also update 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;

So far, we have shown how to define properties StudentsList and CurrentStudent along with two accompanying properties StudentsListHasChanges and CurrentStudentHasChanges. These four properties make it possible for the Student screen to display the student information fetched from the database. Also, based on the values of StudentsListHasChanges and CurrentStudentHasChanges, we can easily determine whether the "Save", "Save All", "Cancel", and "Cancel All" buttons should be enabled or disabled. There is, however, one small drawback with this design: the property setter for StudentsList could get a bit complicated if the Student entity type has many navigation properties and each navigation property expands on multiple levels. Because we need to keep track of changes on multiple navigation properties, all of them have to be subscribed to the PropertyChanged event.

Next, let us move on to discuss the client-side data retrieval methods for populating the StudentsList and CurrentStudent properties.

Data Retrieval Methods

Data retrieval methods of the SchoolModel class are asynchronous methods that use the IAsyncResult design pattern. The GetStudentsAsync() method shown below is one of them. It starts retrieving student information through a WCF Service call of BeginGetStudents() with its second parameter as an AsyncCallback delegate pointing to BeginGetStudentsComplete. When this WCF Service call completes, the AsyncCallback delegate will process the results of the retrieval operation in a separate thread. Since we need to trigger the event GetStudentsCompleted on the UI thread, we have to enclose them inside ThreadHelper.BeginInvokeOnUIThread() as listed below:

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;
            }
        });
}

Update Methods

Similarly, update methods are also asynchronous methods. Our example here is the SaveStudentChangesAsync() method. This call accepts one Boolean parameter allItems. If allItems is true, it goes through all changed items of StudentsList and calls BeginUpdateStudent(). Otherwise, it only checks whether CurrentStudent has changes, and if that is true, the method calls BeginUpdateStudent() for CurrentStudentonly.

SaveStudentChangesAsync() uses several methods of the IClientChangeTracking interface. First, we use ObjectGraphHasChanges() to find out whether a Student object has changes to save or not. Next, we use two helper methods, EstimateObjectGraphSize() and EstimateObjectGraphChangeSize(), to determine if the object graph change size is less than 70% of the total size. If this is true, we call GetObjectGraphChanges() to get an optimized entity object graph with only objects that have changes.

The GetObjectGraphChanges() method can be quite useful in reducing the total amount of data sent from client to server side. For example, if we have an order screen that retrieves an order along with hundreds of order detail lines as its navigation collection, and if we only change the order's actual ship date without changing any order detail lines, calling GetObjectGraphChanges() before saving this order will make sure that we only send the order object without any order detail lines. Thus, overcoming a major shortcoming of using self-tracking entities.

/// <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();
            }
        }
    }
}

The BeginUpdateStudentComplete() method is the AsyncCallback of BeginUpdateStudent() described above, and it processes the results of the asynchronous update operation. If the update is successful and there is no warning message from the server-side, we call AcceptObjectGraphChanges(), another method defined inside the IClientChangeTracking interface, which accepts changes for the Student object and all objects of its object graph. After that, the Student object's HasChanges property is set back to 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;
            }
        });
}

Rollback Methods

The last method is RejectStudentChanges(). Just like SaveStudentChangesAsync(), RejectStudentChanges() accepts one Boolean parameter allItems. If allItems is true, the method goes through all changed items of StudentsList and calls RejectObjectGraphChanges()(another method of the IClientChangeTracking interface). Otherwise, the method only checks whether CurrentStudent has changes, and if it has, the method calls RejectObjectGraphChanges() for CurrentStudent only.

/// <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);
            }
        }
    }
}

Wrapping Up

We have finished discussing how to use the methods and properties of the IClientChangeTracking interface. To summarize, the ObjectGraphHasChanges() method is used in multiple places to check whether an entity has any changes or not. Secondly, the AcceptObjectGraphChanges() method is only used when an update operation completes successfully, while the RejectObjectGraphChanges() method is called inside a rollback operation to revoke any changes made. Lastly, the GetObjectGraphChanges() method can be quite useful in saving the total amount of data sent over the wire.

I hope you find this article useful, and please rate and/or leave feedback below. Thank you!

History

  • August, 2012 - Initial release.
  • March, 2013 - Update for version 2.1.3.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Weidong Shen
Software Developer (Senior)
United States United States
Weidong has been an information system professional since 1990. He has a Master's degree in Computer Science, and is currently a MCSD .NET

Comments and Discussions

 
QuestionHelp me [modified] PinmemberSaif8415-May-13 5:12 
AnswerRe: Help me PinmemberWeidong Shen15-May-13 17:24 
GeneralRe: Thanks PinmemberSaif8415-May-13 22:33 
QuestionRe: My vote of 5 PinmemberSureshChandran13-Mar-13 22:06 
AnswerRe: My vote of 5 PinmemberWeidong Shen14-Mar-13 14:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 7 Mar 2013
Article Copyright 2012 by Weidong Shen
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid