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.

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”.

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.

//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.

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.

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).

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(){returnnew List<ProjectDTO>();}

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

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.

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.

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:

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:

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?

LinkedIn

About Me

I currently work for Office 365 as a Principle PM on the Office 365 Gov cloud team. I spent my first 7 years at Microsoft as an Office 365 technical specialist leading sales efforts with US Federal. Prior to Microsoft I worked for a Microsoft Gold partner called RDA as a Project Manager. Prior to that, I was a software engineer working with Big 4 management consulting firms. Over the past years I have done a lot of work with Office 365, SharePoint, Exchange, Skype, Silverlight, K2, BizTalk, InfoPath, ASP.net, etc. I have written a Wrox book about SharePoint BPA technologies and one of the solutions was a finalist at Microsoft Worldwide Partner of the Year conference. I am a Virginia Tech alumnus completing both my undergrad and masters in Information Technology. I play ice hockey and lacrosse on a regular basis and I am huge Washington Capitals fan.