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

Comments (21)

  1. eKyNoX23 says:

    Hello,

    i am very interested to make a webiste where silverlight plugin take all the browser size but if my website start to have many pictures, files … in it, is there any way to do not have to load all the website at startup included inside one .xap file ?

    Thanks for answer.

  2. Martin says:

    Hi Brad,

    Excellent stuff – thanks.

    Can you confirm whether DomainServices work with Table-per-Type "inheritance" classes?

    For example, say you have an EntityFramework model with a Person entity (PersonId, FirstName, LastName, DateOfBirth) and a derived Contact entity (PersonId, Email, PhoneNumber).

    Should I expect a ContactDomainService class to "just work"? If not, what is the best practice with the current version of RIA Services/Entity Framework?

    Thanks,

    Martin

  3. Zoran says:

    These examples are really excellent way of getting into RIA. Thank you Brad.

    I know this is not "Make a wish" type of blog but 🙂

    What would be nice to see is more complex scenario when your app is vertically segmented into several DomainServices. (I.e. "Contact Management", "Product Management", … but all toghether is wired into singe application). I am personally having problems to understand how conceptually RIA is supposed to be used in such situations. Key thing are I guess transactions which needs to span across more than one DomainService.

    Thanks,

    Zoran

  4. John Schroedl says:

    I think it would be enlightening if you could post about the approach you would take if those ErrorMessage values needed to be localized according to the client’s locale.

    John

  5. Dave says:

    Hi Brad,

    Thanks so much for this latest set of articles.  They have been extremely helpful.

    In regards to your DTO approach, we were actually going this way, but found that there are some issues with this approach when you try to shape or filter the data on the client side.  For example, if you try and run a query operation from Silverlight to only return SuperEmployees that have a name starting with the letter A, you would do something similar to:

    var query = context.GetSumperEmployeeQuery().Where(emp=>emp.Name.StartsWith("A");

    context.Load(query);

    This operation will throw a LINQ To Entities exception, but maybe I am just doing something wrong on my side, so I would be interested to know if you tried this.  We also ran into the same problem when trying to load up these DTO objects with their associations.

    Anyways, just thought I would point that out.

    Thanks again Brad.

  6. BradA says:

    Dave – hmm… Not sure why you are hitting this, but client side queries like that should work… this is exactly how the filtering works in this example.. can you post a repro on the forums so we can take a look at it?

    ..brad

  7. BradA says:

    > Can you confirm whether DomainServices work with Table-per-Type "inheritance" classes?

    Nope — inhertance is on the feature list, but it is not currently implemented..    Do you think tihs is a critical scenario?

  8. Martin says:

    > Can you confirm whether DomainServices work with Table-per-Type "inheritance" classes?

    > Nope — inhertance is on the feature list, but it is not currently implemented..    Do you think tihs is a critical scenario?

    Well, I need to deal with Table-per-type "inheritance" classes in my current project. If it is not currently implemented, can you describe:

    a) What I should expect to happen if I try it (e.g. what goes "wrong")

    b) How I can best deal with the scenario, e.g. can I get some of the benefits of RIA Services if I override some of the "out-of-the-box" behaviour (DomainService.Submit, for example)?

    Thanks,

    Martin

  9. Robert Kozak says:

    Brad,

    The SuperEmployeeDTO is still an Entity. How does this really work as a DTO which should be smaller and lightweight?

  10. BradA says:

    In what what is it an entity?  you mean on the client or server?  

  11. Robert Kozak says:

    Both. SuperEmployeeDTO on both the Server and the generaqted code on the Client still derive from Entity.

    I was hoping for a better separation between Models that the application needs to use and the model that RIA Services needs for the database.

    Basically, continuing with your example, I would love to have an ISuperEmployeeDTO and a POCO implementation of ISuperEmployeeDTO. Is this possible?

  12. Robert Kozak says:

    Correction. SuperEmployeeDTO on the Server is just an object but on the client it is an Entity type.

  13. BradA says:

    Robert — that is right, the server is POCO, the client does derive from a base class that has helpers to deal with data binding and data validation, etc..    My thinking was that this was boilerplate enough to not be a problem..  If you want PURE POCO on the client you can use use the Astoria REST head on DomainService, but you’d lose some of the validation and binding benefits.

  14. Robert Kozak says:

    Thanks, my devs are complaining that it seems like more work and maintenance to create a DTO (especially since we renegineered the backend database to match more closely the classes used in the application) if they look like the original DALs. If RIA services could surface interfaces it would be an easier "sell" to develop DTO classes.

  15. Andy Schroeder says:

    I may be totally wrong about how the Query Methods are working here, but would using the DTO object like this have an effect on the way linq queries get translated to sql statements?

    When you use the LinqToEntitiesDomainService, the client passes linq queries down to the service, including "where clauses" added by your DomainDataSource.FilterDescriptors.  These linq queries get combined with any on the service and ultimatly generate sql such that all the filtering is happening on the database. (i.e. in your example the "Origin StarsWith" filter from the UI would ultimatly result in a where clause in the database)

    However, once you stop returning EF entities from the Domain service, then only the linq over EF context gets translated to a sql statement, and the linq passed from the client is applied to the result as an in-memory filter. (still filtered on the web server, but not at the database level)

    Am I understanding correctly?

  16. BradA says:

    Andy Schroeder  – Even in this case, the query executes on the DB directly… basically the Linq provider here is smart enough to do the right things with projections.  Let me know if you are seeing something different happen.

    ..brad

  17. Andy Schroeder says:

    Wow, I’m supprised the provider is able to handle that.  The projection is the key that i missed here.

    I planned the transition from linq object to DTO outside the linq query syntax (like a helper mapping function to translate to a DTO, instead of projecting it in the linq query)  then that would have prevented the provider from combining the linq queries.

    I guess my thought process is that if you inject DTO’s in there you need to be careful not to make a change that prevents the provider from helping you out and moving the filtering all the way to the db.

  18. pianomanjh says:

    Please include inheritance in the next release.  I definitely believe this is critical.  Supporting the concept of super/subtypes in a database encourages good design.  I currently have a project that would use RIA Services, save for this key deal-breaking limitation.

  19. Stefan Olson says:

    Brad,

    I absolutely think that inheritance is CRITICAL.  In the project I’m currently working on I have managed to avoid inheritance up till now, but today I have a particular scenario where I need inheritance.

    .net ria services as it currently stands will try and flatten my inherited classes. The problem with this is that it requires a large amount of code replication.  For example I’ve got a base Favourite class and derived FavouriteItem and FavouriteUser etc…. If I had inheritance I would only have to set up the shared object properties which sit in the base class once (in the constructor or whatever).

    Given that I have 4 derived classes I now have to have the same code duplicated 4 times!

    In this particular situation I may be able to work around that by doing the object setup on the server. However, my next .net ria services project which uses a fairly complex multilevel inherited entity framework database structure will not have the same flexibility.  It is absolutely crucial that I can address objects polymorphicly on the client – otherwise the project won’t be able to work.

    …Stefan

  20. Rob Harwood says:

    I second inheritance being critial.. Inheritance (especially table-per-type) seems to have been heavily pushed with the introduction of EF but then got sort of forgotten about, for example it is _very_ easy to have EF spew some god awful SQL when querying base types, ADO.NET Data Services doesn’t support it properly (no OfType operator) and now RIA services, which by the way is a bloody cool technology, doesn’t support it at all and the flimsy flattening support doesn’t generate buildable code from my entity model which has locked me out entirely. =(

    Flattening is problematic because it means rewriting validation/logic for base properties for every type of entity which is surfaced with RIA Services, plus its nice to be able to write UI which can take a base type as a model and present the shared properties, which would not be possible with the clientside classes currently generated by RIA services.

    The whole point of having EF Support inheritance was to allow developers to surface models that are more natural to work with, which are abstracted from the data source. If RIA services just reflattens the structure it is directly negating one of the core principals of using EF.

    I really hope that VS2010 and it’s related technologies will begin to sort the inheritance support nightmare out!

  21. Yanal says:

    When you insert a new record using POCO, how would you get back the ID, if the ID column in database was autogenerated.