Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 6: Data Transfer Objects (DTOs)

I have gotten some great questions on my series so far…   A couple of readers noted that it was not always a good idea to return DAL types back to the client.  For example, in Part 2, I returned the SuperEmployee type defined by Entity Framework.   

For example, if you change your backed from EF to NHibernate to their might be some effect of that in the client projection.    I can also more easily control exactly how the entity looks to the client.  Both the shape of the data as well as the validation, etc. 

To address these concerns some folks have been using pattern for Data Transfer Objects (DTOs) or some call them client objects.

To show how easily this pattern is used with RIA Services and Silverlight 3 I took the example app from the previous parts of the series and changed it to return DTOs rather than DAL types. 

The demo requires (all 100% free and always free):

  1. VS2008 SP1 (Which includes Sql Express 2008)
  2. Silverlight 3 RTM
  3. .NET RIA Services July '09 Preview

Also, download the full demo files and check out the running application.

First, I defined my DTO type on the server.  I could have made the type of any shape I wanted, ideally whatever is best for the client.  Then I added the validation metadata directly to the type.  This validation will happen on the server AND the client before you DomainService methods are called. 

 public class SuperEmployeeDTO
 {
  
         public SuperEmployeeDTO()
         {
         }
     
  
         [ReadOnly(true)]
         [Key]
         public int EmployeeID {get;set;}
  
  
         [RegularExpression("^(?:m|M|male|Male|f|F|female|Female)$", 
             ErrorMessage = "Gender must be 'Male' or 'Female'")]
         public string Gender {get;set;}
  
         [Range(0, 10000,
             ErrorMessage = "Issues must be between 0 and 1000")]
         public Nullable<int> Issues {get;set;}
  
         public Nullable<DateTime> LastEdit {get;set;}
  
         [Required]
         [StringLength(100)]
         public string Name {get;set;}
  
         public string Origin {get;set;}
  
         public string Publishers {get;set;}
  
         public string Sites {get;set;}
     }

Notice, I do have some validation attributes on this type, so I guess technically it is not purely a DTO… If you need a pure DTO, you can of course put these attributes on on the client.. 

Then my DomainService methods simply map from the DAL types to the DTO when the data goes out and from the DTO to the DAL types when the data comes in. 

 public IQueryable<SuperEmployeeDTO> GetSuperEmployeeDTOs()
 {
     return this.Context.DBSuperEmployeeSet
                .Where(emp=>emp.Issues>100)
                .OrderBy(emp=>emp.EmployeeID)
                .Select (emp=> new SuperEmployeeDTO () {
                     EmployeeID = emp.EmployeeID,
                     Gender = emp.Gender,
                     Issues = emp.Issues,
                     LastEdit = emp.LastEdit,
                     Name = emp.Name,
                     Origin = emp.Origin,
                     Publishers = emp.Publishers,
                     Sites = emp.Sites
                });
 }
 public void UpdateSuperEmployee(SuperEmployeeDTO superEmployee)
 {
  
     var orgEmp = this.ChangeSet.GetOriginal(superEmployee);
  
     var DbEmp = this.Context.DBSuperEmployeeSet
         .Where(e => e.EmployeeID == superEmployee.EmployeeID)
         .FirstOrDefault();
  
     if (orgEmp.Name != superEmployee.Name) DbEmp.Name = superEmployee.Name;
     if (orgEmp.Gender != superEmployee.Gender) DbEmp.Gender = superEmployee.Gender;
     if (orgEmp.Issues != superEmployee.Issues) DbEmp.Issues = superEmployee.Issues;
     if (orgEmp.LastEdit != superEmployee.LastEdit) DbEmp.LastEdit = superEmployee.LastEdit;
     if (orgEmp.Origin != superEmployee.Origin) DbEmp.Origin = superEmployee.Origin;
     if (orgEmp.Publishers != superEmployee.Publishers) DbEmp.Publishers = superEmployee.Publishers;
     if (orgEmp.Sites != superEmployee.Sites) DbEmp.Sites = superEmployee.Sites;                    
 }

Notice here we need to get the original value back.. that is the value that this client last saw… that is important for us to know which item actually changed on the client. Without this, we’d have to assume that ALL item changed and that would create much more data contention.  This way we only update the EF model with the items that changed.

 

Last, I need to update a few names on the client because I changed the name from SuperEmplopyee to SuperEmployeeDTO… but that is very easy to do. 

Run it and the app looks just the way we left it in part 2, but this time all the data is passed via DTOs which gives you more flexibility. 

image