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):
- VS2008 SP1 (Which includes Sql Express 2008)
- Silverlight 3 RTM
- .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.
Comments
Anonymous
July 16, 2009
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.Anonymous
July 16, 2009
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, MartinAnonymous
July 16, 2009
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, ZoranAnonymous
July 17, 2009
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. JohnAnonymous
July 17, 2009
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.Anonymous
July 19, 2009
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? ..bradAnonymous
July 19, 2009
> 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?Anonymous
July 19, 2009
The comment has been removedAnonymous
July 21, 2009
Brad, The SuperEmployeeDTO is still an Entity. How does this really work as a DTO which should be smaller and lightweight?Anonymous
July 21, 2009
In what what is it an entity? you mean on the client or server?Anonymous
July 21, 2009
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?Anonymous
July 21, 2009
Correction. SuperEmployeeDTO on the Server is just an object but on the client it is an Entity type.Anonymous
July 21, 2009
The comment has been removedAnonymous
July 22, 2009
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.Anonymous
July 22, 2009
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?Anonymous
July 24, 2009
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. ..bradAnonymous
July 24, 2009
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.Anonymous
July 28, 2009
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.Anonymous
July 28, 2009
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. ...StefanAnonymous
August 09, 2009
The comment has been removedAnonymous
October 01, 2009
When you insert a new record using POCO, how would you get back the ID, if the ID column in database was autogenerated.