Chi usa i WCF Data Service (vedi ADO.NET Data Service), potrebbe incontrare qualche difficoltà nell’utilizzo di Entity Framework 4.1 utilizzando l’approccio Code First, “a causa” del DbContext, dato che un DataService<T> si aspetta un T derivato da ObjectContext . “Under the hood” il DbContext utilizza ObjectContext e di conseguenza è facile immaginare una possibile soluzione al problema: eseguire l’ovveride del metodo CreateDataSource del DataService<T> ed utilizzare l’ObjectContext corrente. E’ sufficiente qualche ricerca tramite Bing o Google per trovare del codice di esempio. Per chi ha voglia di sperimentare, può a scaricare la versione WCF Data Service CTP 2 di Marzo 2011, per utilizzare il supporto “nativo” al DbContext. Il pacchetto d’installazione può essere scaricato qui: http://www.microsoft.com/downloads/en/details.aspx?FamilyID=60fb0117-8cea-4359-b392-6b04cdc821be dove tra l’altro è presente un overview delle modifiche apportare rispetto alla versione precedente (altre info si trovano qui: http://blogs.msdn.com/b/writingdata_services/archive/2011/03/09/march-2011-ctp-of-wcf-data-services-for-odata-v3-is-live.aspx).
Per eseguire qualche test, partiamo da un’applicazione ASP.NET vuota, alla quale aggiungiamo una classe C# con la definizione del modello dati, una biblioteca, composta dalle entità “Book” ed “Author”, ogni “Author” ha una relazione one-to-many con “Book”. Tradotto in codice:
public class Book
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength (80)]
public string Title { get; set; }
[Required]
public string Summary { get; set; }
}
public class Author
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
Generalmente preferisco utilizzare le Fluent API rispetto alle DateAnnotations, ma essendo un modello veramente semplice…va bene lo stesso
. Definiamo il
DbContext in questo modo:
public class LibraryContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
}
A questo punto aggiungiamo un WCF Data Service al progetto Web:
Rinominiamolo in LibraryService.svc. Per utilizzare la versione CTP 2 è necessario eliminare i riferimenti alle librerie System.Data.Services e System.Data.Services.Client ed aggiungere i riferimenti alle librerie Microsoft.Data.Services e Microsoft.Data.Services.Client presenti nella directory Bin presente nel percorso di installazione della versione di WCF Data Service appena installata. Dovremmo ottenere qualcosa del genere:
Oltre alle altre References. Modifichiamo il codice della classe LibraryService in questo modo:
public class LibraryService : DataService<LibraryContext>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Books", EntitySetRights.All );
config.SetEntitySetAccessRule("Authors", EntitySetRights.All);
config.SetServiceOperationAccessRule("FindBooksByTitle", ServiceOperationRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}
[WebGet]
public IQueryable<Book> FindBooksByTitle(string title)
{
return (from b in CurrentDataSource.Books where b.Title.Contains(title) select b);
}
}
Dove utilizziamo direttamente la nostra classe LibraryContext senza strane alchimie (override). Tramite l’InitializeService configuriamo il servizio per l’accesso alle entità “Books” ed “Authors”, configuriamo la versione del protocollo da utilizzare (DataServiceProtocolVersion.V3) e l’accesso a FindBooksByTitle che permette di trovare tutti i libri che contengono un determinato titolo. Se utilizziamo del codice simile al seguente per creare e popolare la base dati sottostante:
Book book1 = new Book()
{
Title = "Programming .Net 4 in C#",
Summary = "Progamming .Net Framework 4 Book (C#)"
};
Book book2 = new Book()
{
Title = "Programming .Net 4 in VB.NET",
Summary = "Progamming .Net Framework 4 Book (VB.NET)"
};
Author author = new Author()
{
Name="Tizio Caio"
};
author.Books = new List<Book>();
author.Books.Add(book1);
author.Books.Add(book2);
cnt.Books.Add(book1);
cnt.Authors.Add(author);
cnt.SaveChanges();
Possiamo avviare il sito tramite il Server Web locale ed utilizzare direttamente il browser per interagire con il servizio. Ad esempio possiamo recuperare la lista dei “Book” presenti digitando: http://localhost:5104/LibraryService.svc/Books o visualizzare i metadata esposti digitando http://localhost:5104/LibraryService.svc/$metadata. Passiamo a qualcosa di più concreto aggiungendo un’applicazione console alla nostra soluzione, creando un riferimento al servizio tramite la voce di menu “Add Service Reference”. Visual Studio crea per noi tutte le classi proxy necessarie ed un file Service.edmx con la definizione del modello. Testiamo il tutto aggiungendo del codice nel Main della classe Program:
LibraryServiceReference.LibraryContext ctx = new LibraryServiceReference.LibraryContext(new Uri("http://localhost:5104/LibraryService.svc/"));
#region Add Book and Author
Book book = new Book { Title = "Programming C# 4", Summary = "aaa" };
Author author = new Author { Name = "Tizio Caio" };
author.Books.Add(book);
ctx.AddToBooks(book);
ctx.AddToAuthors(author);
ctx.AddLink(author, "Books", book);
ctx.SaveChanges();
Console.WriteLine(ctx.Books.Count());
#endregion
#region Update Book Title
book.Title = "Programming .NET 4 in C#";
ctx.UpdateObject(book);
ctx.SaveChanges();
Book storedBook = ctx.Books.Where(p => p.Id == book.Id).Select(p => p).FirstOrDefault();
Console.WriteLine("Book Title={0}", book.Title);
#endregion
#region Delete Book-Author link
Author storedAuthor = (from a in ctx.Authors.Expand("Books")
where a.Name.Equals("Tizio Caio")
select a)
.FirstOrDefault();
Console.WriteLine("Author={0}, Book Title={1}",storedAuthor.Name , storedAuthor.Books[0].Title);
////Delete Link. Set null value in author_id column (Books Table).
ctx.DeleteLink(storedAuthor, "Books", storedAuthor.Books[0]);
ctx.SaveChanges();
#endregion
Il codice interagisce con un’istanza della LibraryContext sottostante per Aggiungere\Modificare\Eliminare i dati presenti nella base dati. Se volessimo interagire tramite HTTPWebRequest, potremmo scrivere del codice simile al seguente:
#region HttpWebRequest
////Get all Books
HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(new Uri("http://localhost:5104/LibraryService.svc/Books"));
webRequest.Method = "GET";
webRequest.Accept = "application/json";
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
System.IO.StreamReader streamReader = new System.IO.StreamReader(webResponse.GetResponseStream());
string jSONResponse = streamReader.ReadToEnd();
Console.WriteLine(jSONResponse);
////Find Books by title.
webRequest = (HttpWebRequest)HttpWebRequest.Create(new Uri("http://localhost:5104/LibraryService.svc/FindBooksByTitle?title='Programming'"));
webRequest.Method = "GET";
webRequest.Accept = "application/json";
webResponse = (HttpWebResponse)webRequest.GetResponse();
streamReader = new System.IO.StreamReader(webResponse.GetResponseStream());
jSONResponse = streamReader.ReadToEnd();
Console.WriteLine(jSONResponse);
#endregion
#region Delete all objects
////Delete Objects.
ctx.DeleteObject(author);
ctx.DeleteObject(book);
ctx.SaveChanges();
#endregion
In entrambi i casi i dati vengono restituiti in formato JSON, quindi facilmente
parsabili. Possiamo verificare come le nostre query LINQ siano tradotte in chiamate HTTP gestendo l’evento
SendingRequest del servizio:
static void ctx_SendingRequest(object sender, System.Data.Services.Client.SendingRequestEventArgs e)
{
Console.WriteLine(e.Request.RequestUri.ToString());
}