MSDN Magazin > Home > Ausgaben > 2008 > September >  Data Services: Erstellen datenzentrischer Weban...
Data Services
Erstellen datenzentrischer Webanwendungen mit Silverlight 2
Shawn Wildermuth
Codedownload verfügbar unter: SLDataServices2008_09a.exe (234 KB)
Code online durchsuchen

Dieser Artikel basiert auf Vorabversionen von Silverlight 2 und ADO.NET Data Services. Änderungen der Informationen in diesem Artikel sind vorbehalten.
Themen in diesem Artikel:
  • Verbinden von Silverlight mit einer Datenquelle
  • Verwenden von ADO.NET Data Services
  • Verwenden von LINQ to SQL
  • Debuggen des Diensts
In diesem Artikel werden folgende Technologien verwendet:
Silverlight 2 Beta 2, LINQ, ADO.NET Data Services
In Silverlight™ können Branchenanwendungen und andere datenzentrische Anwendungen erstellt werden, aber das Arbeiten mit Daten ist in Silverlight nicht immer einfach. Obwohl Silverlight viele der Tools enthält, die Sie zur Nutzung von Daten und zur Unterstützung von Webdiensten und XML benötigen, stellen diese Tools nur die Grundlagen des Datenzugriffs durch eine Firewall dar.
Eine gebräuchliche Datenzugriffsstrategie verwendet eine Mischung aus Webdiensten und clientseitigem LINQ – ein sehr empfohlener Ansatz, wenn Sie vorhandene Webdienstendpunkte einsetzen, um Ihre Silverlight-Anwendungen zu betreiben. Wenn Sie aber neue Webdienste erstellen, die speziell mit Silverlight arbeiten, gibt es einen unnötigen Kostenaufwand.
Für eine typische Webdienstschicht implementieren Sie eine traditionelle Datenzugriffsstrategie auf dem Server (benutzerdefinierte Geschäftsobjekte, LINQ to SQL, Entity Framework, NHibernate usw.) und machen Datenobjekte durch den Webdienst verfügbar. Der Webdienst ist dann bloß ein Gateway zur zugrunde liegenden Datenzugriffsstrategie.
Um aber die vollständige Datenkonnektivität zu ermöglichen, müssen Sie die vier Datenvorgänge (Create, Read, Update und Delete) Webdienstmethoden zuordnen. So würde zum Beinspiel ein einfacher Dienstvertrag zur Unterstützung einer Produktklasse wie in Abbildung 1 aussehen. (Beachten Sie, dass ich im Artikel durchgehend C# verwende. Der Codedownload enthält aber auch Visual Basic®-Code.)
[ServiceContract]
public interface ICustomerService
{
  [OperationContract]
  List<Product> GetAllProducts();

  [OperationContract]
  Product GetProduct(int productID);

  [OperationContract]
  List<Product> GetAllProductsWithCategories();

  [OperationContract]
  Product SaveProduct(Product productToSave);

  [OperationContract]
  void DeleteProduct(Product productToDelete);
}
Es kann ziemlich zeitaufwändig sein, einen Satz von Diensten zu erstellen, der mit dem gesamten Datenmodell einer Anwendung funktioniert. Wie dieses Beispiel zeigt, können featurespezifische Vorgänge Webdienste aufblähen. Anders gesagt, stellt ein Webdienst nach einer gewissen Zeit neue Anforderungen, und es werden Vorgänge hinzugefügt, einschließlich Vorgängen, die nicht wirklich zur zentralen Geschäftsdomäne gehören.
In Abbildung 1 wird der GetAllProductsWithCategories-Vorgang dargestellt, der verwendet wird, um das Produkt abzurufen und die Kategorien standardmäßig einzuschließen. Es wäre keine Überraschung, wenn die Methoden zum Sortieren, Filtern und sogar Auslagern diesem einfachen Beispiel hinzugefügt würden. Es wäre schön, wenn es eine einfache Möglichkeit gäbe, Datenvorgänge (wie Abfragen, Sortieren, Filtern usw.) zu unterstützen, ohne alle diese Methoden ständig manuell erstellen zu müssen. Hier kommt ADO.NET Data Services ins Spiel.

ADO.NET Data Services
Das Ziel von ADO.NET Data Services besteht darin, für ein Datenmodell Endpunkte bereitzustellen, auf die über das Web zugegriffen werden kann. Diese Endpunkte bieten eine Möglichkeit, Daten vom Server zu filtern, zu sortieren, zu formen und auszulagern, ohne dass der Entwickler benutzerdefinierte Funktionalität für diese Vorgänge erstellen muss. Im Grunde ist jeder Endpunkt ein Ausgangspunkt für eine LINQ-Abfrage. Vom Endpunkt aus können Sie die gesuchten Daten abfragen.
Aber halten Sie ADO.NET Data Services nicht für eine gewöhnliche Datenzugriffsstrategie. Durch ADO.NET Data Services wird kein direkter Datenzugriff ermöglicht. Es basiert nämlich auf einer Datenzugriffstechnologie, die Serialisierung, Abfrage und Aktualisierung unterstützt. Der Auftrag zum Abfragen und Aktualisieren der Daten wird der zugrunde liegenden Datenzugriffsschicht überlassen. In Abbildung 2 wird ADO.NET Data Services und seine Position in einer typischen Anwendungsarchitektur gezeigt.
Abbildung 2 Schichten von ADO.NET Data Services (zum Vergrößern auf das Bild klicken)
Da ADO.NET Data Services sich bei der eigentlichen Datenzugriffsarbeit auf eine Datenzugriffsfunktion verlässt, müssen Sie eine Möglichkeit finden anzugeben, wie diese Arbeit aussehen soll. In ADO.NET Data Services muss jeder Dienst von einem LINQ-fähigen Anbieter unterstützt werden. Eigentlich ist jeder Endpunkt einfach nur ein IQueryable-Endpunkt. Folglich wird in ADO.NET Data Services jedes Objekt unterstützt, das IQueryable unterstützt.

Erstellen des Diensts
Wenn Sie Ihrem Projekt ADO.NET Data Services hinzufügen, wird eine neue .svc-Datei erstellt, ebenso wie eine Klasse, die den Dienst repräsentiert. Im Unterschied zu einem Webdienst werden Sie die Dienstvorgänge nicht selbst implementieren, sondern der DataService-Klasse ermöglichen, den größten Teil der Schwerarbeit zu erledigen. Um den Dienst auszuführen, ist eine Reihe von kleinen Aufgaben notwendig. Zunächst erfordert die DataService-Klasse einen Typparameter, das so genannte Kontextobjekt. Das Kontextobjekt ist eine Klasse, die die Daten beschreibt, die als Dienst verfügbar gemacht werden. Wenn Ihr Dienst Daten aus einer relationalen Datenbank verfügbar macht, wird diese Klasse in der Regel vom ObjectContext von EntityFramework oder vom DataContext von LINQ to SQL abgeleitet.
// Use my NorthwindEntities context object 
// as the source of the Service's Data
public class Products : DataService<NorthwindEntities>
Es gibt keine Basisklassenanforderung für das Kontextobjekt. Sie können Ihre eigene Klasse erstellen, solange ihre Eigenschaften die IQueryable-Schnittstelle implementieren. ADO.NET Data Services macht diese Eigenschaften als Endpunkte verfügbar:
public class StateContext
{
  StateList _states = new StateList();
  public IQueryable<State> States 
  {
    get { return _states.AsQueryable<State>(); } 
  }
}
Mit dem InitializeService-Aufruf erhalten Sie ein IDataServiceConfiguration-Objekt, das Sie verwenden können, um festzulegen, welche Arten von Berechtigungen im Dienst zugelassen werden sollen. ADO.NET Data Services verwendet die Abstraktion von Substantiven und Verben zum Festlegen von Berechtigungen (siehe Abbildung 3).
// This method is called only once to initialize service-wide policies.
public static void InitializeService(IDataServiceConfiguration config)
{
  // Only allow us to read or update Products Entities
  // not Delete or Create
  config.SetEntitySetAccessRule("Products", 
                                EntitySetRights.AllRead | 
                                EntitySetRights.WriteUpdate);

  // Only Allow Reading of Category and Supplier Entities
  config.SetEntitySetAccessRule("Categories", EntitySetRights.AllRead);
  config.SetEntitySetAccessRule("Suppliers", EntitySetRights.AllRead);

}
Sobald Sie dies getan haben, können Sie direkt zum Dienst navigieren, und es werden die Atom-Feedinformationen zu jedem Endpunkt angezeigt. Zum Debuggen von ADO.NET Data Services schlage ich vor, dass Sie die RSS-Feedansicht in Internet Explorer® deaktivieren oder einen anderen Browser verwenden, um das XML-Format des Diensts zu sehen.

Abfragen und Aktualisieren von Daten
ADO.NET Data Services macht den Dienst als REST-basierten Dienst (Representational State Transfer) verfügbar, was kein SOAP-basierter Dienst ist. Das bedeutet, dass die Nutzlast der Antwort vom Dienst statt SOAP-Umschlägen nur die Daten enthält, nicht aber die Anforderungsmetadaten. Alle Anforderungen werden unter Verwendung der Kombination des HTTP-Verbs (GET, PUT, POST usw.) und des Anforderungs-URI beschrieben. Nehmen wir an, dass Sie ein Modell haben, das Produkte, Kategorien und Lieferanten beschreibt (siehe Abbildung 4). Der sich daraus ergebende ADO.NET Data Services-Dienst besitzt eventuell drei Endpunkte: einen für jeden Entitätssatz im Modell. Der URI zum Adressieren eines Entitätssatzes im Modell ist einfach die Adresse des Diensts und der Name des Endpunkts: http://localhost/{Dienstname}/{Endpunktname} oder http://localhost/Product.svc/Products.
Abbildung 4 Beispieldatenmodell (zum Vergrößern auf das Bild klicken)
Diese URI-Syntax unterstützt eine Vielzahl verschiedener Features, einschließlich des Abrufens spezifischer Entitäten sowie des Sortierens, Filterns, Auslagerns und Formens der Ergebnisse.
ADO.NET Data Services verwendet diese Abfragen im URL-Stil, um die Daten zum Nutzer des Diensts zurückzugeben. Derzeit werden zwei Serialisierungsformate unterstützt (dies wird wahrscheinlich in zukünftigen Versionen erweitert): JavaScript Object Notation (JSON) und Atom-basiertes XML. JSON ist ein Format, das für clientseitigen Webcode einfach zu nutzen ist, wohingegen Atom ein XML-basiertes Format ist und daher die Unterstützung eines XML-Parsers benötigt.
Statt der Forderung, dass das Serialisierungsformat in der Abfrage angegeben wird, werden in ADO.NET Data Services die Standard-HTTP-Accept-Header verwendet, um zu bestimmen, welches Format zum Client zurückgegeben wird. Wenn Sie eine Anforderung von einem Client (wie z. B. einem Browser) ausgeben, der XML nutzen kann, und wenn Sie keinen bevorzugten Formattyp über einen Accept-Header angeben, ist Atom das Standardformat der zurückgegebenen Daten.
Das Abfragen von Daten ist nur ein Teil der Lösung. Schließlich muss die Lösung sowohl Abfragen als auch Aktualisierungen unterstützen. Um alle diese Anforderungen zu unterstützen, ordnet ADO.NET Data Services die vier grundlegenden Datenzugriffsvorgänge den vier grundlegenden HTTP-Verben zu (siehe Abbildung 5).
Datenzugriffsverb HTTP-Verb
Create POST
Read GET
Update PUT
Delete DELETE
Mithilfe dieser Verben lässt ADO.NET Data Services den Nutzer des Diensts von allen Arten von Datenvorgängen profitieren, ohne dass besondere Endpunkte für die verschiedenen Typen erstellt werden müssen. Die einzige Anforderung dafür, dass die Aktualisierung von Daten innerhalb von ADO.NET Data Services funktioniert, besteht darin, dass die zugrunde liegende Datenzugriffstechnologie die IUpdatable-Schnittstelle unterstützt. Diese Schnittstelle ist der Vertrag, der definiert, wie Aktualisierungen von ADO.NET Data Services zu den Datenquellen weitergeleitet werden.
Entity Framework ist derzeit die einzige Datenzugriffstechnologie, die diese Schnittstelle unterstützt. Für die Zukunft erwarte ich, dass die meisten Schichten ebenfalls Unterstützung hierfür bieten werden (einschließlich LINQ to SQL, LLBGenPro und NHibernate). Da Sie jetzt ausreichende Kenntnisse über ADO.NET Data Services haben, können Sie es nun in Silverlight 2 verwenden.

Die Silverlight 2.0-Clientbibliothek
Wenn Sie ADO.NET Data Services verwenden würden, um Abfragen auszugeben und Daten durch die URI-Syntax zu aktualisieren und das XML direkt zu bearbeiten, würden Sie die gewünschte Funktionalität erhalten, aber Sie würden immer noch viel Grundstruktur erstellen. Hier kommt die ADO.NET Data Services-Clientbibliothek für Silverlight ins Spiel. Diese Bibliothek (in Verbindung mit einem Befehlszeilentool) ermöglicht Ihnen, LINQ-Abfragen direkt in Ihren Silverlight-Anwendungen auszugeben, die wiederum von der Clientbibliothek in HTTP-Abfrage- oder Aktualisierungsanforderungen an den Datendienst übersetzt werden.
Bevor Sie Ihre ersten Schritte unternehmen, müssen Sie etwas Code generieren. Diese Codegenerierung liest die Metadaten des ADO.NET Data Services-Diensts und generiert Nur-Daten-Klassen für die Entitäten des Diensts sowie eine Klasse, um den Dienst selbst zu repräsentieren.
Um diesen Code zu generieren, verwenden Sie das Tool „DataSvcUtil.exe“, das sich im Microsoft® .NET Framework 3.5-Ordner befindet (meist c:\windows\Microsoft.NET\Framework\v3.5\). Sie können dieses Tool ausführen und den URI des Diensts, die Ausgabecodedatei sowie die Sprache für die Codeerstellung angeben, und zwar etwa wie folgt:
DataSvcUtil.exe –uri:http://localhost/Product.svc –out:data.cs  
  –lang:CSharp
Dadurch werden eine neue Datei mit einer Datenvertragsklasse für jeden Endpunkt sowie eine von DataServiceContext abgeleitete Klasse erstellt. Die DataServiceContext-Klasse wird als Diensteinstiegspunkt verwendet (und macht abfragbare Dienstendpunkte verfügbar). Wenn Sie diese Klasse in Ihr Silverlight-Projekt aufnehmen und der System.Data.Services.Client.dll (Teil von Silverlight 2 Beta 2 SDK) einen Verweis hinzufügen, haben Sie allen Code zur Verfügung, um mit ADO.NET Data Services arbeiten zu können.
Der Silverlight-Clientcode ähnelt anderen LINQ-basierten Abfragen, die Sie u. U. in den .NET-Zielcode geschrieben haben. Sie erstellen eine von DataServiceContext abgeleitete Klasse und geben LINQ-Abfragen für sie aus. Dies könnte folgendermaßen aussehen:
// Create the Service class specifying the 
// location of the ADO.NET Data Services 
NorthwindEntities ctx = 
  new NorthwindEntities(new Uri("Products.svc", UriKind.Relative));

// Create a LINQ Query to be issued to the service
var qry = from p in ctx.Products
               orderby p.ProductName
                select p;
Wenn Sie diese Abfrage ausführen, wird eine Webanforderung ausgegeben, um die gewünschten Daten abzurufen. Der Silverlight-Code weicht hier jedoch insofern deutlich von den Standard-LINQ-Abfragen ab, als Silverlight keine synchronen Webanforderungen zulässt. Um die Ausführung asynchron zu machen, müssen Sie deshalb zuerst die Abfrage in ein DataServiceQuery<T>-Objekt umwandeln und dann explizit BeginExecute aufrufen, um die asynchrone Ausführung zu starten, wie hier dargestellt:
// Cast to a DataServiceQuery<Product> 
// (since the query is returning Products)
DataServiceQuery<Product> productQuery =
  (DataServiceQuery<Product>)qry;

  // Execute the Query Asynchronously specifying 
  // a callback method
  productQuery.BeginExecute(new 
    AsyncCallback(OnLoadComplete),
    productQuery);
Sobald die Abfrage ausgeführt wurde (unabhängig davon, ob der Vorgang erfolgreich war oder nicht), wird die in AsyncCallback festgelegte Methode ausgeführt. Dies ist der Zeitpunkt, an dem Sie die LINQ-Abfrage aufzählen können, um die eigentlichen Ergebnisse zu verarbeiten. In der Regel würden Sie die ursprüngliche Abfrage in AsyncCallback einschließen, damit Sie es in der Rückrufmethode abrufen oder als Teil der Klasse speichern können (siehe Abbildung 6).
void OnLoadComplete(IAsyncResult result)
{
  // Get a reference to the Query
  DataServiceQuery<Product> productQuery =
    (DataServiceQuery<Product>)result.AsyncState;

  try
  {
    // Get the results and add them to the collection
    List<Product> products = productQuery.EndExecute(result).ToList();

  }
  catch (Exception ex)
  {
    if (HtmlPage.IsEnabled)
    {
      HtmlPage.Window.Alert("Failed to retrieve data: " + ex.ToString());
    }
  }

}
Wenn Sie noch nie mit LINQ gearbeitet haben, wird Ihnen dieses Muster ziemlich fremd vorkommen. Derzeit gibt es keine guten Muster für asynchrones LINQ, außer für die Ausführung von LINQ in einem asynchronen Paket (wie z. B. ThreadPool und BackgroundWorker). Bei Silverlight müssen alle Anforderungen asynchron sein. Deshalb ist dieses Muster erforderlich, wenn die ADO.NET Data Services-Clientbibliothek verwendet wird.

Laden verwandter Entitäten
Mit ADO.NET Data Services können Sie auch auswählen, wie Sie mit dem Laden verwandter Entitäten umgehen möchten. Im vorherigen Beispiel habe ich Produkte vom Server geladen. Jedes Produkt hat eine Beziehung zum Lieferanten für dieses Produkt sowie die Kategorie für dieses Produkt.
Durch die Verwendung der vorherigen LINQ-Abfrage habe ich nur Produkte abgerufen. Hätte ich Lieferanten- oder Kategorieinformationen anzeigen lassen wollen, wären diese nicht sofort zugänglich gewesen. Ich hätte entweder diese gewünschten Informationen bei Bedarf laden oder explizit während meiner ursprünglichen Abfrage an den Server abrufen müssen. Beide Verfahren haben ihre Vorteile, aber im Allgemeinen ist das explizite Laden viel effizienter, wenn Sie wissen, dass Sie die Informationen für jedes Objekt benötigen. Wenn Sie nur die Daten für einige Entitäten laden müssen, ist die bei Bedarf erfolgende Abfrage vorzuziehen.
Wenn es eine Beziehungseigenschaft gibt (wie z. B. Product.Supplier), ist diese Eigenschaft standardmäßig null, wenn Sie die Eigenschaft nicht explizit laden. Um das bei Bedarf erfolgende Laden zu vereinfachen, besitzt die DataServiceContext-Klasse eine BeginLoadProperty-Methode (die dem gleichen asynchronen Modell folgt), in der Sie die Quellentität, den Namen der Eigenschaft und einen Rückruf angeben:
public void LoadSupplierAsync(Product theProduct)
{
  TheContext.BeginLoadProperty(theProduct, 
                               "Supplier", 
                               new AsyncCallback(SupplierLoadComplete), 
                               null);
  }

  public void SupplierLoadComplete(IAsyncResult result)
  {
    TheContext.EndLoadProperty(result);
  }
Sobald EndLoadProperty aufgerufen wurde, wurde die Eigenschaft mit der zugehörigen Entität ordnungsgemäß geladen. In vielen Fällen sollten Sie sie explizit in der ursprünglichen Abfrage laden. Um dies zu vereinfachen, unterstützt der LINQ-Anbieter die Expand-Erweiterungsmethode. Mit dieser Methode können Sie den Namen der zu ladenden Eigenschaftspfade angeben, wenn die Abfrage ausgeführt wird. Die Expand-Erweiterungsmethode wird in der From-Klausel der LINQ-Abfrage verwendet, um dem Anbieter mitzuteilen, dass er versuchen soll, diese zugehörigen Entitäten zu laden. Wenn Sie zum Beispiel die ursprüngliche Abfrage ändern, sodass die Expand-Methoden sowohl für die Kategorie als auch den Lieferanten verwendet werden, laden unsere Objekte diese zugehörigen Entitäten während der ursprünglichen Abfrageausführung:
var qry = 
  from p in TheContext.Products.Expand("Supplier").Expand("Category")
          orderby p.ProductName
          select p; 
Wenn Sie nur Daten aus ADO.NET Data Services lesen, sind Sie jetzt fertig. Alles, was Sie wissen müssen, ist, wie Sie eine Abfrage erstellen und ausführen und zugehörige Entitäten laden können. Wenn Sie aber Daten bearbeiten müssen, ist ein bisschen mehr Arbeit erforderlich. Binden Sie Ihre neuen Daten an Ihre Silverlight-Steuerelemente, und legen Sie los!

Änderungsverwaltung
Die ADO.NET Data Services-Clientbibliothek unterstützt keine automatische Änderungsüberwachung von Objekten. Das bedeutet, dass es bei einer Änderung von Objekten, Sammlungen und Beziehungen dem Entwickler obliegt, dem DataServiceContext-Objekt diese Änderungen mitzuteilen. Die API für die Benachrichtigung des DataServiceContext-Objekts ist ziemlich einfach und wird in Abbildung 7 gezeigt.
Methode Beschreibung
AddObject Fügt ein neu erstelltes Objekt hinzu.
UpdateObject Markiert ein Objekt als geändert.
DeleteObject Markiert das Objekt für das Löschen.
AddLink Fügt einen Link zwischen zwei Objekten hinzu.
UpdateLink Aktualisiert einen Link zwischen zwei Objekten.
DeleteLink Löscht einen Link zwischen zwei Objekten.
Dies bedeutet, dass Sie Änderungen an den Objekten überwachen und das DataServiceContext-Objekt mit Ihrem eigenen Code benachrichtigen müssen. Auf den ersten Blick ist es enttäuschend, dass es keine automatische Änderungsverwaltung gibt, aber dadurch wird die Bibliothek effizient und klein.
Sie fragen sich vielleicht, wie Änderungen an den Objekten zu überwachen sind. Die Antwort finden Sie im generierten Code. In jeder der generierten Datenvertragsklassen gibt es partielle Methoden, die aufgerufen werden, wenn sich die Daten in der Klasse ändern. (Weitere Informationen zu partiellen Methoden finden Sie unter go.microsoft.com/fwlink/?LinkId=122979.) Wenn diese Methoden nie implementiert werden, verursachen die Klassen keinen Aufwand bei dieser Änderungsbenachrichtigung. Sie können die partiellen Methodenmechanismen für jeden Datenvertrag verwenden, der die Änderung unterstützt, um eine Änderungsbenachrichtigung zu implementieren. Rufen Sie einfach DataServiceContract in partiellen Methoden auf. Eine Kopplung der DataServiceContract-Klasse ist nicht notwendig.
Glücklicherweise unterstützt Silverlight bereits eine Schnittstelle für die Änderungsbenachrichtigung (INotifyPropertyChange). Nutzen Sie diese Schnittstelle in Ihrer Implementierung, um jeden zu benachrichtigen, der an einer Änderung Ihrer Daten interessiert ist. Sie können zum Beispiel INotifyPropertyChange in Ihren Datenvertragsklassen (in unserem Fall die Product-Klasse) implementieren, um ein Ereignis zu definieren, das mit der Änderung des Objekts ausgelöst werden kann. Dies geschieht folgendermaßen:
public partial class Product : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;
}
Diese Implementierung ermöglicht Ihnen, bei der Änderung einer Eigenschaft ein Ereignis auszulösen. Sie können bestimmen, wann dieses Ereignis ausgelöst werden soll, indem Sie die partiellen Methoden implementieren, die von den generierten Datenvertragsklassen aufgerufen werden. Um beispielsweise Abonnenten zu benachrichtigen, wenn sich die ProductName-Eigenschaft ändert, implementieren Sie einfach die partielle OnProductNameChanged-Methode, und lösen Sie dann das PropertyChanged-Ereignis aus. ProductName wird weitergeleitet, um Ereignisabonnenten über die geänderte Eigenschaft zu informieren. Hier ist der Code:
partial void OnProductNameChanged()
{
  if (PropertyChanged != null)
  {
    PropertyChanged(this, new PropertyChangedEventArgs("ProductName"));
  }  
}
Durch Implementieren dieser partiellen Methoden für alle schreibbaren Eigenschaften, haben Sie eine einfache Möglichkeit, Änderungen an Ihrem Objekt zu überwachen. Dann können Sie sich einfach für das PropertyChanged-Ereignis registrieren und das DataServiceContext-Objekt bei einer Änderung des Objekts benachrichtigen:
// In the OnLoadComplete method
// Get the results and add them to the collection
List<Product> products = productQuery.EndExecute(result).ToList();

foreach (Product product in products)
{
  // Wireup Change Notification
  product.PropertyChanged += 
    new PropertyChangedEventHandler(product_PropertyChanged);
}
Abschließend können Sie die product_PropertyChanged-Methode implementieren, um das DataServiceContext-Objekt zu benachrichtigen:
void product_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
  Product product = (Product)sender;
  TheContext.UpdateObject(product);
}
Ebenso müssen Sie DataServiceContext benachrichtigen, wenn Objekte erstellt oder gelöscht werden (siehe Abbildung 8).
void addNewButton_Click(object sender, RoutedEventArgs e)
{
  Product theProduct = new Product();
  // ...
  TheContext.AddObject(theProduct);
}

void deleteButton_Click(object sender, RoutedEventArgs e)
{
  Product theProduct = (Product)theList.SelectItem;
  TheContext.DeleteObject(theProduct);
  theCollection.Remove(theProduct);
}
Wenn dieser Code implementiert ist, können Sie die Objekte in Ihrer Silverlight-Benutzeroberfläche ändern und dafür sorgen, dass die Datenbindung und Ihr Änderungsbenachrichtigungscode DataServiceContext über jegliche Änderungen informieren. Aber wie führen Sie Aktualisierungen für den Dienst durch?

Aktualisieren durch den Dienst
Da jetzt Ihr DataServiceContext-Objekt über Datenänderungen Bescheid weiß, müssen Sie eine Möglichkeit finden, diese dem Server mitzuteilen. Um dies zu unterstützen, besitzt die DataServiceContext-Klasse eine BeginSaveChanges-Methode, die dem gleichen asynchronen Modell folgt wie die Abfragen, die bereits in diesem Artikel beschrieben wurden. Die BeginSaveChanges-Methode nimmt alle Änderungen in DataServiceContext und sendet sie wie folgt zum Server:
TheContext.BeginSaveChanges(SaveChangesOptions.None, 
                            new AsyncCallback(OnSaveAllComplete), 
                            null);
Bei der BeginSaveChanges-Ausgabe gibt es eine gekennzeichnete Enumeration namens „SaveChangesOptions“. Bei dieser Enumeration können Sie zwei Optionen angeben: ob die Batchverarbeitung verwendet werden soll und ob fortgefahren werden soll, selbst wenn einige Objekte keine Speicherung durchführen. Im Allgemeinen empfehle ich, Batchverarbeitung zu verwenden. Die Batchverarbeitung ist für einige Beziehungsarten zwischen übergeordneten und untergeordneten Elementen für eine korrekte Aktualisierung erforderlich, wenn für den Server besondere referentielle Integritätseinschränkungen gelten.
Nachdem die Speicherung abgeschlossen ist, wird der Rückruf ausgeführt. Es gibt zwei Mechanismen, die für Sie Fehlerinformationen weiterleiten. Wenn während der Ausführung der Speicherung eine Ausnahme ausgelöst wird, wird diese Ausnahme ausgegeben, wenn Sie im Rückruf „EndSaveChanges“ aufrufen. Aus diesem Grund sollten Sie einen try/catch-Block verwenden, um schwerwiegende Fehler abzufangen. Außerdem ist der Rückgabetyp von EndSaveChanges ein DataServiceResponse-Objekt. DataServiceResponse besitzt eine HasErrors-Eigenschaft (siehe Abbildung 9), aber diese ist in der Silverlight 2 Beta 2-Version der Bibliothek nicht zuverlässig.
void OnSaveAllComplete(IAsyncResult result)
{
  bool succeeded = true;
  try
  {
    DataServiceResponse response = 
      (DataServiceResponse)TheContext.EndSaveChanges(result);

    foreach (OperationResponse opResponse in response)
    {
      if (opResponse.HasErrors)
      {
        succeeded = false;
      }
    }

  }
  catch (Exception ex)
  {
    succeeded = false;
  }

  // Alert the User
}
Statt einer Abhängigkeit können Sie die eigentlichen OperationResponse-Objekte durchlaufen (DataServiceResponse ist eine Sammlung von OperationResponse-Objekten), um Fehler bei den Antworten zu erkennen, die vom Server zurückgegeben werden. In höheren Versionen sollten Sie sich auf die HasErrors-Eigenschaft der DataServiceResponse-Klasse verlassen können.

Debuggen des Diensts
Beim Debuggen des Diensts gibt es drei wichtige Aufgaben, die Sie vielleicht durchführen möchten: das Anzeigen des Datenstatus im DataServiceContext-Objekt, das Anzeigen der Anforderungen, die von ADO.NET Data Services gestellt werden, und zu guter Letzt das Abfangen von Serverfehlern.
Sehen wir uns zuerst den Zustand der Entitäten im DataServiceContext-Objekt an. Die DataServiceContext-Klasse macht zwei nützliche Sammlungen verfügbar: Entities und Links. Diese Sammlungen sind schreibgeschützte Sammlungen der Entitäten sowie Verknüpfungen zwischen Entitäten, die von DataServiceContext nachverfolgt werden. Beim Debuggen – unabhängig davon, ob Sie Objekte als geändert markieren oder nicht – ist das Anzeigen dieser Sammlungen im Debugger äußerst wichtig, um festzustellen, ob Ihr Code zur Nachverfolgung von Änderungen richtig funktioniert.
Beachten Sie, dass es auch wichtig ist, die Serveranforderungen anzuzeigen, die von Ihrer Silverlight 2-Anwendung ausgegeben werden. Die beste Möglichkeit hierfür ist eine Art von Netzwerkproxy. Ich verwende Fiddler2 (fiddler2.com). Wenn Sie mit Fiddler2 nicht vertraut sind: Es ist im Grunde ein Tool zum Anzeigen von Webdatenverkehr, damit Sie sehen können, was bei der Übertragung passiert.
Bei ADO.NET Data Services möchten Sie die Kommunikation bei der Übertragung sehen, um zu erkennen, was zur und von der Silverlight-Anwendung gesendet wird. Weitere Informationen dazu finden Sie in meinem Blog (wildermuth.com/2008/06/07/Debugging_ADO_NET_Data_Services_with_Fiddler2).
Schließlich werden beim aktuellen .NET Framework 3.5 SP1 serverseitige Fehler nicht gut zum Client weitergeleitet. Die meisten Fehler beim Server werden nämlich vom Server verschluckt. Die beste Taktik zum Debuggen von Serverfehlern besteht darin, die Exception-Option im Menü „Debuggen“ („Debuggen“ -> „Ausnahmen…“) zu verwenden und den Debugger so zu konfigurieren, dass alle .NET-Ausnahmen gestoppt werden. Sobald Sie diese Option ausgewählt haben, werden die vom Dienst ausgeworfenen Ausnahmen angezeigt. (Sie müssen jedoch mit „Weiter“ durch die ersten Ausnahmen blättern.)

Der Stand der Dinge
Mein Ziel war es, Ihnen zu zeigen, inwiefern ADO.NET Data Services die Brücke zwischen Silverlight 2 und serverbasierten Modellen darstellt. Sie wissen jetzt, wie Sie mit ADO.NET Data Services Daten vom Server lesen und schreiben können, ohne auf selbst erstellte Webdienste zurückgreifen zu müssen. Wie Sie erfahren haben, können Sie mit der Kombination aus Silverlight, ADO.NET Data Services und LINQ leistungsfähige, datengesteuerte Webanwendungen mit allen Vorteilen der Web 2.0-Technologien erstellen. Weitere Informationen zu diesen Technologien finden Sie in der Randleiste „Weiterführende Literatur“.
Shawn Wildermuth ist ein Microsoft MVP (C#) und der Gründer von Wildermuth Consulting Services. Er ist Autor mehrerer Bücher und zahlreicher Artikel. Darüber hinaus leitet Shawn Wildermuth derzeit die Silverlight-Tour, um in den USA Silverlight 2 zu lehren. Sie können ihn unter shawn@wildermuthconsulting.com erreichen.

Page view tracker