Sviluppate applicazioni Silverlight e non usate i WCF RIA Services? beh, non sapete cosa vi state perdendo…
Sebbene la stragrande maggioranza delle demo facciano direttamente uso delle entità generate dall’ORM di turno (Entity Framework/LinqToSql per la maggior parte dei casi) non è assolutamente detto che:
- Usare i WCF RIA Services significhi necessariamente usare Entity Framework o Linq To SQL (anche se questo indubbiamente aiuta)
- Usare i WCF RIA Services richieda la proiezione lato client delle entità generate dall’ORM per lo storage.
Se, giustamente, vogliamo separare il dominio di persistenza dalle entità usate dall’applicazione Silverlight siamo liberissimi di usare i nostri (amati/odiati) DTO, ecco un esempio basato sul database che “non si nega a nessuno” (cit. Andrea) ovvero Northwind.
L’accesso al DB avviene, per comodità con Entity Framework, ma come precedentemente citato, non è assolutamente un vincolo:
Visto che non vogliamo (giustamente) proiettare le classi Category e Product lato client, ci creiamo i relativi DTO:
1: public class CategoryDto
2: {
3: [Key]
4: public int CategoryId { get; set; }
5: public string Name { get; set; }
6: public string Description { get; set; }
7:
8: [Include]
9: [Association("CategoryProducts","CategoryId","CategoryId")]
10: public IEnumerable<ProductDto> Products { get; set; }
11: }
12:
13: public class ProductDto
14: {
15: [Key]
16: public int ProductId { get; set; }
17: public int? CategoryId { get; set; }
18: public string Name { get; set; }
19: public decimal? Price { get; set; }
20: }
Notate gli attributi Include e Association necessari per segnalare all’engine dei WCF RIA Services la presenza di una relazione tra i due tipi.
A questo punto ci creiamo il nostro DomainService ereditando da LinqToEntitiesDomainService:
1: [EnableClientAccess()]
2: public class NorthwindService : LinqToEntitiesDomainService<NorthwindEntities>
3: {
4: public IQueryable<CategoryDto> GetCategories()
5: {
6: var query = from cat in this.ObjectContext.Categories.Include("Products")
7: select new CategoryDto()
8: {
9: CategoryId = cat.CategoryID,
10: Description = cat.Description,
11: Name = cat.CategoryName,
12: Products = from p in cat.Products
13: select new ProductDto {CategoryId=p.CategoryID,
14: Name=p.ProductName,
15: ProductId=p.ProductID,
16: Price=p.UnitPrice }
17: };
18:
19: return query;
20: }
21:
22: public void UpdateProduct(ProductDto currentProduct)
23: {
24: Product dbProduct = this.ObjectContext.Products.Where(p => p.ProductID == currentProduct.ProductId).FirstOrDefault();
25: if (dbProduct != null) this.Rehydrate(dbProduct, currentProduct);
26: }
27:
28: private void Rehydrate(Product dbProduct, ProductDto currentProduct)
29: {
30: if (currentProduct.Name != dbProduct.ProductName) dbProduct.ProductName = currentProduct.Name;
31: if (currentProduct.Price != dbProduct.UnitPrice) dbProduct.UnitPrice = currentProduct.Price;
32: }
33: }
Visto che intendiamo interagire esclusivamente di DTO i metodi del DomainService accettano ed espongono esclusivamente oggetti di tipo CategoryDto e e ProductDto, ovviamente in fase di Update è necessario reidratare l’entità presente nel DB con i nuovi dati passati dal Dto.
Lato client non cambia assolutamente nulla, si tratta di interagire con i DTO anzichè con le entità Category e Product.
C’è un caso particolare da prendere in considerazione: Come si gestisce il caso in cui abbiamo a che fare con proprietà che vengono generate dal DB al momento dell’aggiornamento di una entità? (es: IDENTITY)
E’ evidente che in questo caso si rende necessario re-allineare il DTO dopo le operazioni di Insert e Submit, come?
Immaginiamo il caso di un entità User come quella riportata qui sotto:
Supponendo che il relativo DTO sia:
1: public class UserDto
2: {
3: [Key]
4: public int Id { get; set; }
5: public string FirstName { get; set; }
6: public string LastName { get; set; }
7: }
come allineo la proprietà Id del DTO con quella generata ad ogni aggiornamento?
La risposta sta nel metodo Associate dell’oggetto ChangeSet:
1: public void InsertUser(UserDto user)
2: {
3: User newUser = new User { FirstName = user.FirstName, LastName = user.LastName };
4: this.ChangeSet.Associate(user, newUser, MapCustomerDalUserToDTOUser);
5: this.ObjectContext.AddToUsers(newUser);
6: }
7:
8: private void MapCustomerDalUserToDTOUser(UserDto user, User dalUser)
9: {
10: user.Id = dalUser.Id;
11: }
Se analizziamo il metodo del DataService invocato ogni volta che si ha la necessità di persistere un nuovo utente noterete come il metodo Associate mi permetta di specificare:
- Il DTO con la situazione attuale,
- L’entità del database che quel DTO sta rappresentando
- Un metodo che verrà invocato dopo la Submit grazie alla quale posso allineare il DTO con lo stato più recente dell’entità del DB, campi calcolati inclusi.
Non cosi semplice come quando si usando direttamente le entità generate da EF, ma è bello sapere di poter decidere quale strategia di proiezione usare.
Enjoy WCF RIA Services!