Saturday, April 24, 2010

Deep Dive into Custom RIA Service and Transactions

Silverlight Data and Controls Series

Introduction

In one of my previous blogs, I gave a quick introduction on MVVM, RIA Services and building a Custom Domain Service. Then in another blog I gave an introduction to the life-cycle of domain service methods. In this blog I am going to build upon them and have a deeper discussion on how to build up a custom RIA Domain Service with Data Transactions Objects (DTOs) that need to can connect to an existing data service layer which may not be something like Entity.

I had not intended for this blog to be as long as it is but it turned into a deeper dive than I expected. I found out some rather interesting things in the process. What I will discuss in depth is how to create associations between DTOs and how the ChangeSet is utilized with RIA Services.

DTOs

Here is the data model I created for this working example. In this example I am going to have an EmployeeDTO and a ProjectDTO. Every employee can have zero to many projects. As well a project can have many employees, so we will want to put an association table between the two called EmployeeProjectDTO. A typical many-to-many relationship.

Here are my three DTOs needed. As you can see the only relationships I have are the foreign key references however we can do better.

using System;
using System.ComponentModel.DataAnnotations;

namespace MVVM.Web.Model
{
public class EmployeeDTO
{
[Key]
public int ID { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public DateTime HireDate { get; set; }

public int Title { get; set; }
}
}

using System;
using System.ComponentModel.DataAnnotations;

namespace MVVM.Web.Model
{
public class ProjectDTO
{
[Key]
public int ID { get; set; }

public string Name { get; set; }

public DateTime StartDate { get; set; }

public int RegionalDirectorID { get; set; }
}
}

using System.ComponentModel.DataAnnotations;

namespace MVVM.Web.Model
{
public class EmployeeProjectDTO
{
[Key]
public int ID { get; set; }

public int EmployeeID { get; set; }

public int ProjectID { get; set; }

public bool IsProjectManager { get; set; }

public bool IsProgramDirector { get; set; }
}
}

Associating DTOs

Now that we have three DTOs, it is time to add some associations. What I am going to do is:

  • Add a list of EmployeeProjectDTO objects to EmployeeDTO to contain the list of projects an employee.
  • Add a list of EmployeeProjectDTO objects to ProjectDTO to contain a list of all the employees working on a project.
  • Add an EmployeeDTO property to ProjectDTO for the regional director.

Here are my DTOs again. Please take note of the new attributes that I have added to my properties that expose lists or complex types. Setting these up will ensure that objects will be returned and that when the data is submitted, insert, updates and deletes in the ChangeSet will be called in the correct order.

Please also take note that the foreign keys on all of the tables still remain. For example look at EmployeeDTO’s association to EmployeeProjectDTO. Notice that references “EmployeeID” and not “Employee.ID”. If you tried to use “Employee.ID” you will get an error saying “invalid: OtherKey property named 'Employee.ID' cannot be found on entity type”.

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.ServiceModel.DomainServices.Server;

namespace MVVM.Web.Model
{
public class EmployeeDTO
{
private List<EmployeeProjectDTO> _employeeProjects = new List<EmployeeProjectDTO>();

[Key]
public int ID { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public DateTime HireDate { get; set; }

public int Title { get; set; }

[Include]
[Composition]
[Association("EmployeeDTO_EmployeeProjectDTO", "ID", "EmployeeID")]
public List<EmployeeProjectDTO> EmployeeProjects {
get {
return _employeeProjects;
}
}
}
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.ServiceModel.DomainServices.Server;

namespace MVVM.Web.Model
{
public class ProjectDTO
{
private List<EmployeeProjectDTO> _employeeProjects = new List<EmployeeProjectDTO>();
private EmployeeDTO _projectRegionalDirector;

[Key]
public int ID { get; set; }

public string Name { get; set; }

public DateTime StartDate { get; set; }

public int RegionalDirectorID { get; set; }

[Include]
[Association("ProjectDTO_RegionalDirectorEmployeeDTO", "RegionalDirectorID", "ID", IsForeignKey = true)]
public EmployeeDTO RegionalDirector {
get {
return _projectRegionalDirector;
}
set {
_projectRegionalDirector = value;
RegionalDirectorID = _projectRegionalDirector.ID;
}
}

[Include]
[Composition]
[Association("ProjectDTO_EmployeeProjectDTO", "ID", "ProjectID")]
public List<EmployeeProjectDTO> EmployeeProjects
{
get
{
return _employeeProjects;
}
}
}
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.ServiceModel.DomainServices.Server;

namespace MVVM.Web.Model
{
public class EmployeeProjectDTO
{
private EmployeeDTO _employee;
private ProjectDTO _project;

[Key]
public int ID { get; set; }

public int EmployeeID { get; set; }

public int ProjectID { get; set; }

public bool IsProjectManager { get; set; }

public bool IsProgramDirector { get; set; }

[Include]
[Association("EmployeeProjectDTO_EmployeeDTO", "EmployeeID", "ID", IsForeignKey = true)]
public EmployeeDTO Employee
{
get {
return _employee;
}
set {
_employee = value;
if (_employee != null)
{
EmployeeID = _employee.ID;
}
else
{
EmployeeID = 0;
}
}
}

[Include]
[Association("EmployeeProjectDTO_ProjectDTO", "ProjectID", "ID", IsForeignKey = true)]
public ProjectDTO Project {
get
{
return _project;
}
set
{
_project = value;
if (_project != null)
{
ProjectID = _project.ID;
}
else
{
ProjectID = 0;
}
}
}
}
}

Getting Data

The following is a RIA Service with a method that will get all of the employees and fill it up with data. Please read through the data I set up because you will have to understand it for the following sections.

namespace MVVM.Web.Service
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using System.Security.Principal;

using Model;

[EnableClientAccess()]
[RequiresAuthentication()]
public class EmployeeDomainService : DomainService
{

public List<EmployeeDTO> GetEmployees()
{
List<EmployeeDTO> employees = new List<EmployeeDTO>();

//Create the employees
EmployeeDTO developer = new EmployeeDTO();
developer.ID = 1;
developer.FirstName = "Jason";
developer.LastName = "Apergis";
developer.Title = 1;
developer.HireDate = new DateTime(1990, 4, 15);
employees.Add(developer);

EmployeeDTO projectManager = new EmployeeDTO();
projectManager.ID = 2;
projectManager.FirstName = "Ethan";
projectManager.LastName = "Apergis";
projectManager.Title = 2;
projectManager.HireDate = new DateTime(2000, 6, 3);
employees.Add(projectManager);

EmployeeDTO programDirector = new EmployeeDTO();
programDirector.ID = 3;
programDirector.FirstName = "Caroline";
programDirector.LastName = "Apergis";
programDirector.Title = 3;
programDirector.HireDate = new DateTime(2005, 12, 20);
employees.Add(programDirector);

EmployeeDTO regionalDirector = new EmployeeDTO();
regionalDirector.ID = 4;
regionalDirector.FirstName = "Catherine";
regionalDirector.LastName = "Apergis";
regionalDirector.Title = 3;
regionalDirector.HireDate = new DateTime(2005, 12, 20);
employees.Add(regionalDirector);

//Create a project
ProjectDTO project = new ProjectDTO();
project.ID = 1;
project.Name = "Project ABC";
project.StartDate = new DateTime(2006, 6, 17);
project.RegionalDirector = regionalDirector;

//Create the employee to project associations
EmployeeProjectDTO employeeProject1 = new EmployeeProjectDTO();
employeeProject1.ID = 1;
employeeProject1.Project = project;
employeeProject1.Employee = developer;

EmployeeProjectDTO employeeProject2 = new EmployeeProjectDTO();
employeeProject2.ID = 2;
employeeProject2.Project = project;
employeeProject2.Employee = projectManager;
employeeProject2.IsProjectManager = true;

EmployeeProjectDTO employeeProject3 = new EmployeeProjectDTO();
employeeProject3.ID = 3;
employeeProject3.Project = project;
employeeProject3.Employee = programDirector;
employeeProject3.IsProgramDirector = true;

//Add the associations to the employee objects
developer.EmployeeProjects.Add(employeeProject1);
projectManager.EmployeeProjects.Add(employeeProject2);
programDirector.EmployeeProjects.Add(employeeProject3);

//Add the associations to the project object
project.EmployeeProjects.Add(employeeProject1);
project.EmployeeProjects.Add(employeeProject2);
project.EmployeeProjects.Add(employeeProject3);

return employees;
}

}
}

Persisting Data

To persist the data, I need to have the following methods on my custom domain service I just created. I have no code in here right now to actually wire up to the custom enterprise services I will be utilizing.

private static int count = 10;

public void InsertEmployeeDTO(EmployeeDTO employee) {
employee.ID = count++;
System.Diagnostics.Debug.WriteLine("Insert EmployeeDTO");
}

public void UpdateEmployeeDTO(EmployeeDTO employee)
{
System.Diagnostics.Debug.WriteLine("Update EmployeeDTO");
}

public void DeleteEmployeeDTO(EmployeeDTO employee)
{
System.Diagnostics.Debug.WriteLine("Delete EmployeeDTO");
}

public void InsertProjectDTO(ProjectDTO project)
{
project.ID = count++;
System.Diagnostics.Debug.WriteLine("Insert ProjectDTO");
}

public void UpdateProjectDTO(ProjectDTO project)
{
System.Diagnostics.Debug.WriteLine("Update ProjectDTO");
}

public void DeleteProjectDTO(ProjectDTO project)
{
System.Diagnostics.Debug.WriteLine("Delete ProjectDTO");
}

public void InsertEmployeeProjectDTO(EmployeeProjectDTO employeeProject)
{
employeeProject.ID = count++;
System.Diagnostics.Debug.WriteLine("Insert EmployeeProjectDTO");
}

public void UpdateEmployeeProjectDTO(EmployeeProjectDTO employeeProject)
{
System.Diagnostics.Debug.WriteLine("Update EmployeeProjectDTO");
}

public void DeleteEmployeeProjectDTO(EmployeeProjectDTO employeeProject)
{
System.Diagnostics.Debug.WriteLine("Delete EmployeeProjectDTO");
}

Scenario 1

Now I am ready to start using my RIA Services. I am not going to bind these objects to a Silverlight UI as I want to focus on the transactions. I am now going to give a couple scenarios and discuss which methods will be called in the RIA Services.

In the following code, demonstrates how I would promote the developer to a project manager of a specific project.

_context.Load<EmployeeDTO>(_context.GetEmployeesQuery());

//Update the developers EmployeeProjectDTO to IsProjectManager
EmployeeDTO developer = _context.EmployeeDTOs.
Where<EmployeeDTO>(e => e.ID == 1).FirstOrDefault();
EmployeeProjectDTO developerEmployeeProject = developer.EmployeeProjects.
Where<EmployeeProjectDTO>(ep => ep.ID == 1).FirstOrDefault();
developerEmployeeProject.IsProjectManager = true;

//Remove the previous Project Manager's association to the project
EmployeeDTO projectManager = _context.EmployeeDTOs.
Where<EmployeeDTO>(e => e.ID == 2).FirstOrDefault();
EmployeeProjectDTO projectManagerEmployeeProject = projectManager.EmployeeProjects.
Where<EmployeeProjectDTO>(ep => ep.Project.ID == developerEmployeeProject.Project.ID).FirstOrDefault();
projectManager.EmployeeProjects.Remove(projectManagerEmployeeProject);

//Save the changes
if (_context.HasChanges)
{
_context.SubmitChanges();
}

Based on the code above the following service methods will be called:

  • UpdateEmployeeDTO (Developer instance)
  • InsertEmployeeProjectDTO (Developer instance)
  • UpdateEmployeeDTO (Project Manager instance)
  • DeleteEmployeeProjectDTO (Project Manager instance)

One of the first things you may say is, well I did not modify anything on the EmployeeDTO objects, all I did was modify the EmployeeProjectDTOs. Well the great thing about RIA Services and the ChangeSet is that it recognizes a concept typically referred to as shallow and deep dirty. Even though the Employee DTOs were not directly modified, objects they were associated with are direct making them dirty (i.e. deep dirty). It is great that this is done because this allows you to potentially perform an operation on the EmployeeDTO if you need, like maybe update a time stamp or lock a record.

Scenario 2

In this scenario, let’s remove all of the employee projects from all the employees (i.e. the project has ended and the employees no longer need to be associated to the project however we do not want to delete the project itself).

_context.Load<EmployeeDTO>(_context.GetEmployeesQuery());

//Delete the project associations
foreach (EmployeeDTO employee in _context.EmployeeDTOs)
{
foreach (EmployeeProjectDTO employeeProject in employee.EmployeeProjects)
{
employee.EmployeeProjects.Remove(employeeProject);
}
}

//Save the changes
if (_context.HasChanges)
{
_context.SubmitChanges();
}

The following domain services methods will be called in the following order:

  • UpdateEmployeeDTO (Developer)
  • DeleteEmployeeProjectDTO (Developer)
  • UpdateEmployeeDTO (Project Manager)
  • DeleteEmployeeProjectDTO (Project Manager)
  • UpdateEmployeeDTO (Program Director)
  • DeleteEmployeeProjectDTO (Program Director)

Now you may be looking at the code above and say, if you want to remove all of the associations from a project what was just done is pretty error prone given I have no criteria to select which project has objects deleted. Plus, it would be more efficient to just remove the associations directly off the ProjectDTO. I agree. To do this, the first thing you will try to do is go _context.ProjectDTOs and quickly find out that the property does not exist. This is because there is no query methods yet on the domain service that returns a collection of ProjectDTOs. Even though in the GetEmployees() method I have initiated the objects to do what is need, it is not possible. All you need to do is add an empty method like the following to the domain service:

public List<ProjectDTO> GetProjects()
{
return new List<ProjectDTO>();
}

Now you will be able to write the following code:
_context.Load<EmployeeDTO>(_context.GetEmployeesQuery());

foreach (ProjectDTO project in _context.ProjectDTOs) {
foreach (EmployeeProjectDTO employeeProject in project.EmployeeProjects)
{
project.EmployeeProjects.Remove(employeeProject);
}
}

//Save the changes
if (_context.HasChanges)
{
_context.SubmitChanges();
}

The following domain services methods will be called in the following order:

  • UpdateProjectDTO (Project ABC)
  • DeleteEmployeeProjectDTO (Developer)
  • DeleteEmployeeProjectDTO (Project Manager)
  • DeleteEmployeeProjectDTO (Program Director)

This is still not the greatest example because I still called GetEmployees() to delete the projects. It would be better to call GetProjects() however I wanted to demonstrate a point that I have to a method on the RIA Services to return a collection of ProjectDTOs.

Scenario 3

Now in this scenario, let’s take the previous code and add to it code to create a brand new project and associate existing employees to that project.

_context.Load<EmployeeDTO>(_context.GetEmployeesQuery());

//Delete the project associations
foreach (ProjectDTO project in _context.ProjectDTOs) {
foreach (EmployeeProjectDTO employeeProject in project.EmployeeProjects)
{
project.EmployeeProjects.Remove(employeeProject);
}
}

//Get the employees
EmployeeDTO developer = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 1).FirstOrDefault();
EmployeeDTO projectManager = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 2).FirstOrDefault();
EmployeeDTO programDirector = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 3).FirstOrDefault();
EmployeeDTO regionalDirector = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 4).FirstOrDefault();

//Now let's add a new project
ProjectDTO newProject = new ProjectDTO();
newProject.Name = "New Project";
newProject.RegionalDirector = regionalDirector;

//Add the project to the context
_context.ProjectDTOs.Add(newProject);

//Create the EmployeeProjectDTO
EmployeeProjectDTO employeeProject1 = new EmployeeProjectDTO();
employeeProject1.Employee = developer;
employeeProject1.Project = newProject;

EmployeeProjectDTO employeeProject2 = new EmployeeProjectDTO();
employeeProject2.Employee = projectManager;
employeeProject2.Project = newProject;
employeeProject2.IsProjectManager = true;

EmployeeProjectDTO employeeProject3 = new EmployeeProjectDTO();
employeeProject3.Employee = programDirector;
employeeProject3.Project = newProject;
employeeProject3.IsProgramDirector = true;

//Create project associations
newProject.EmployeeProjects.Add(employeeProject1);
newProject.EmployeeProjects.Add(employeeProject2);
newProject.EmployeeProjects.Add(employeeProject3);

//Save the changes
if (_context.HasChanges)
{
_context.SubmitChanges();
}

The following domain services methods will be called in the following order:

  • InsertProjectDTO (New Project)
  • InsertEmployeeProjectDTO (Developer)
  • InsertEmployeeProjectDTO (Project Manager)
  • InsertEmployeeProjectDTO (Program Director)
  • UpdateProjectDTO (ABC Project)
  • DeleteEmployeeProjectDTO (Developer)
  • DeleteEmployeeProjectDTO (Project Manager)
  • DeleteEmployeeProjectDTO (Program Director)

One thing that you will notice when running this code is that some of the references to objects will be null. For instance in the InsertEmployeeProjectDTO calls, the EmployeeProjectDTO.Employee will be null but EmployeeProjectDTO.EmployeeID will not. This goes back to my earlier discussion on why we had to keep the primitive types for associations. Please note that even though EmployeeProjectDTO.Employee is null on the RIA Service Method call to InsertEmployeeProjectDTO, it is NOT null back on the code in the Silverlight project.

To test some things, I wanted to see if I could achieve the same thing but change some lines of code to instead create the new project from the employee perspective. You will notice I removed the line to add the ProjectDTO to the context and added the EmployeeProjectDTOs directly to the EmployeeDTOs.

_context.Load<EmployeeDTO>(_context.GetEmployeesQuery());

//Delete the project associations
foreach (ProjectDTO project in _context.ProjectDTOs) {
foreach (EmployeeProjectDTO employeeProject in project.EmployeeProjects)
{
project.EmployeeProjects.Remove(employeeProject);
}
}

//Get the employees
EmployeeDTO developer = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 1).FirstOrDefault();
EmployeeDTO projectManager = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 2).FirstOrDefault();
EmployeeDTO programDirector = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 3).FirstOrDefault();
EmployeeDTO regionalDirector = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 4).FirstOrDefault();

//Now let's add a new project
ProjectDTO newProject = new ProjectDTO();
newProject.Name = "New Project";
newProject.RegionalDirector = regionalDirector;

//Create the EmployeeProjectDTO
EmployeeProjectDTO employeeProject1 = new EmployeeProjectDTO();
employeeProject1.Employee = developer;
employeeProject1.Project = newProject;

EmployeeProjectDTO employeeProject2 = new EmployeeProjectDTO();
employeeProject2.Employee = projectManager;
employeeProject2.Project = newProject;
employeeProject2.IsProjectManager = true;

EmployeeProjectDTO employeeProject3 = new EmployeeProjectDTO();
employeeProject3.Employee = programDirector;
employeeProject3.Project = newProject;
employeeProject3.IsProgramDirector = true;

//Associate to the employee and manager
developer.EmployeeProjects.Add(employeeProject1);
projectManager.EmployeeProjects.Add(employeeProject2);
programDirector.EmployeeProjects.Add(employeeProject3);

//Save the changes
if (_context.HasChanges)
{
_context.SubmitChanges();
}

The following domain services methods will be called in the following order:

  • InsertProjectDTO (New Project)
  • UpdateEmployeeProjectDTO (Developer)
  • InsertEmployeeProjectDTO (Developer)
  • UpdateEmployeeProjectDTO (Project Manager)
  • InsertEmployeeProjectDTO (Project Manager)
  • UpdateEmployeeProjectDTO (Program Director)
  • InsertEmployeeProjectDTO (Program Director)
  • UpdateProjectDTO (ABC Project)
  • DeleteEmployeeProjectDTO (Developer)
  • DeleteEmployeeProjectDTO (Project Manager)
  • DeleteEmployeeProjectDTO (Program Director)

As you can see the exact same result was achieved.

Now if you have been paying attention you may be asking yourself, how come when I add the EmployeeProjectDTOs I do not add them to both the EmployeeDTOs and ProjectDTO like the following:

//Associate to the employee and manager
developer.EmployeeProjects.Add(employeeProject1);
projectManager.EmployeeProjects.Add(employeeProject2);
programDirector.EmployeeProjects.Add(employeeProject3);

//Create project associations
newProject.EmployeeProjects.Add(employeeProject1);
newProject.EmployeeProjects.Add(employeeProject2);
newProject.EmployeeProjects.Add(employeeProject3);

Well if I did this I would get the following error:

Submit operation failed. Invalid change-set : Entity for operation '0' has multiple parents.

The best I can figure is the ChangeSet just cannot handle this scenario because it does not want to potentially add the EmployeeProject objects more than once.

Alternate Approach

In some other posting I was reading when researching this topic I saw some examples where the following as presented as a solution. On the RIA Services instead of having the individual insert, update and delete methods for each object type, instead have an update method for the root object and then have methods like the following:

namespace MVVM.Web.Service
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using System.Security.Principal;

using Model;

[EnableClientAccess()]
[RequiresAuthentication()]
public class EmployeeDomainService : DomainService
{

public void UpdateEmployeeDTO(EmployeeDTO employee)
{
System.Diagnostics.Debug.WriteLine("Update EmployeeDTO");
SaveDTO(employee);
}

public void UpdateProjectDTO(ProjectDTO project)
{
System.Diagnostics.Debug.WriteLine("Update ProjectDTO");
SaveDTO(project);
}

private void SaveDTO(EmployeeDTO employee)
{
ChangeOperation employeeCO = this.ChangeSet.GetChangeOperation(employee);

switch (employeeCO)
{
case ChangeOperation.Insert:
//Add code to insert the EmployeeDTO
employee.ID = count++;
break;

case ChangeOperation.Update:
//Add code to update the EmployeeDTO
break;

case ChangeOperation.Delete:
//Add code to delete the EmployeeDTO
break;

case ChangeOperation.None:
break;
}

foreach (EmployeeProjectDTO employeeProject in this.ChangeSet.GetAssociatedChanges(employee, e => e.EmployeeProjects))
{
SaveDTO(employeeProject);
}
}

private void SaveDTO(ProjectDTO project)
{
ChangeOperation projectCO = this.ChangeSet.GetChangeOperation(project);

switch (projectCO)
{
case ChangeOperation.Insert:
//Add code to insert the EmployeeDTO
project.ID = count++;
break;

case ChangeOperation.Update:
//Add code to update the EmployeeDTO
break;

case ChangeOperation.Delete:
//Add code to delete the EmployeeDTO
break;

case ChangeOperation.None:
break;
}

foreach (EmployeeProjectDTO employeeProject in this.ChangeSet.GetAssociatedChanges(project, p => p.EmployeeProjects))
{
SaveDTO(employeeProject);
}
}

private void SaveDTO(EmployeeProjectDTO employeeProject)
{
ChangeOperation employeeProjectCO = this.ChangeSet.GetChangeOperation(employeeProject);
switch (employeeProjectCO)
{
case ChangeOperation.Insert:
//Add code to insert the EmployeeProjectDTO
employeeProject.ID = count++;
break;

case ChangeOperation.Update:
//Add code to update the EmployeeProjectDTO
break;

case ChangeOperation.Delete:
//Add code to delete the EmployeeProjectDTO
break;

case ChangeOperation.None:
break;
}
}

}
}

I have tested this and it would be a viable approach and support all of the examples that I presented earlier. I personally prefer the previous approach. Only drawback of this approach is you have to make sure you are always working with root objects and do not do this for non-root objects because it is possible that you can run a transaction twice and cause an error.

2 comments:

Michael Teper said...

Jason,

I came across this blog post searching for the "entity has multiple parents" error. I am wondering what version of RIA Services you tested your code with. The reason I ask, is that using the RTM bits and a configuration nearly identical to yours, I can't seem to escape the error. The client classes generated by RIA Services ensure that when the association class properties are assigned parent entities (in your case employee and project), the association object is added to the collection classes on those entities. The explicit .Add operations you have in your code are redundant and have no effect. Removing the association object from either of the collections also automatically sets the corresponding association object property to null. Have you experienced this as well? Is there something in your code I am missing?

Thank you!
-Michael

Jason Apergis said...

Michael,

If you are getting an error sayinig "entity has multiple parents" it sounds like something is messed up with the way you have created your associations.

Can you explain to me what you are doing in your code that raises this expection? Is it raised when you try to add an object into the context or when you try to commit data in the context?


Jason