|
Comelio-Blog > MS SQL Server > Nachrichtenstruktur von Webservices Nachrichtenstruktur
Das
automatisch erstellte WSDL liefert zwar die Information, dass überhaupt XML
zurückgeliefert wird, wenn nicht nur ein einfacher Rückgabewert einer
Funktion die Antwort bildet, aber in XML Schema wird nicht etwa die SELECT…FOR
XML-Anfrage ausgelesen und so ausgewertet, dass die Antwortstruktur auch in diesem
Bereich automatisch generiert wird.
|
 | Kontakt
|
Webservices mit komplexer Nachrichtenstruktur
Wie zuvor schon gesehen, liefert das automatisch erstellte WSDL zwar die Information,
dass überhaupt XML zurückgeliefert wird, wenn nicht nur ein einfacher
Rückgabewert einer Funktion die Antwort bildet, aber in XML Schema wird
nicht etwa die SELECT…FOR XML-Anfrage ausgelesen und so ausgewertet, dass
die Antwortstruktur auch in diesem Bereich automatisch generiert wird. Während
man diese Aufgabe dem WSDL-Generator vielleicht noch zutrauen würde, ist
es unmöglich, sich vorzustellen, dass die eingehende Frage-Struktur auch
automatisch aus dem Algorithmus von Prozedur/Funktion ausgelesen wird. Für
beide Nachrichtentypen muss der xml-Datentyp in seiner typisierten Form auftreten,
sodass aus dem im MS SQL Server angemeldeten XML auch unmittelbar die Strukturen
ausgelesen werden. Dies soll im folgenden Beispiel erläutert und vorgeführt
werden.
Strukturen und Prozedur erstellen und veröffentlichen
Die Grundidee, die Nachrichtenstruktur von Anfrage und Antwort vorzugeben,
ist sehr einfach und baut auf den bereits im XML Schema-Kapitel beschriebenen
Techniken auf. Sofern man als Webservice nur Operationen anbietet, die über
eine Reihe von auch zu Überladung führenden Parametern gestartet werden
können, dann stellt sich das diesem Abschnitt zu Grunde liegende Problem
überhaupt nicht. Wenn allerdings komplexe XML-Nachrichten erwartet und
verschickt werden sollen, dann muss dem Webservice-Entwickler auf der Klientenseite
diese Struktur ebenfalls mitgeteilt werden. Es liegt nur nahe, diese auch im
WSDL-Dokument unterzubringen, wo ohnehin schon alle Nachrichtenstrukturen in
XML Schema angegeben werden. Dazu ist lediglich notwendig, typisiertes XML für
die verschiedenen Parameter einzusetzen.

Als Beispiel soll eine sehr einfache Produktbestellung verwendet werden, bei
der lediglich die Nachrichtenstrukturen betrachtet werden und ansonsten alle
über die Zerlegung und Erstellung der Nachricht hinausgehenden denkbaren
T-SQL-Anweisungen ausgelassen werden. In einem ersten Schritt muss das XML Schema
für die Produktbestellung (Anfrage) unter einem Namen im MS SQL Server
angemeldet werden. Es enthält innerhalb eines ProductOrder-Elements die
drei Kinder: die Produktnummer ProductNumber als xs:string, die Menge Quantity
als xs:int und die Kontonummer AccountNumber als xs:string.
IF ((SELECT COUNT(*)
FROM sys.xml_schema_collections
WHERE [name] = 'ProductOrder') > 0) BEGIN
DROP XML SCHEMA COLLECTION Sales.ProductOrder
END
GO
CREATE XML SCHEMA COLLECTION Sales.ProductOrder AS
N'<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
attributeFormDefault="unqualified" xmlns:c="http://www.contoso.com/ProductOrder"
targetNamespace="http://www.contoso.com/ProductOrder">
<xs:element name="ProductOrder">
<xs:complexType>
<xs:sequence>
<xs:element name="ProductNumber" type="xs:string"/>
<xs:element name="Quantity" type="xs:int"/>
<xs:element name="AccountNumber" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>'
GO
Die Antwortnachricht kann bei längerem Überlegen überaus komplex
ausfallen, denn eine wie auch immer einfach gehaltene Bestellung kann sich auf
Produkte auswirken, die auf Lager sind und sofort verschickt werden sollen,
oder auf solche, die noch extra bestellt und daher zu einem unbekannten Termin
ausgeliefert werden können. Dazu könnten Produkte bestellt werden,
die an bestimmte Kunden nicht geliefert werden können, die nur in bestimmten
Mengen denkbar sind usw. Um wenigstens eine Fallunterscheidung zu verwenden,
liefert die Antwortnachricht entweder eine Bestätigung oder eine Meldung
zurück, dass das Produkt nicht lieferbar sei.
Im ersten Fall schickt es die eingegangenen Nachrichtenelemente als Bestätigung
zurück und fügt ein Auslieferungsdatum ein. Die Meldung über
die Nicht-Lieferbarkeit wird in einem Cancellation-Element untergebracht.
CREATE XML SCHEMA COLLECTION Sales.ProductOrderConfirmation AS N'<?xml version="1.0"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified" xmlns="http://www.contoso.com/ProductOrderConfirmation" targetNamespace="http://www.contoso.com/ ProductOrderConfirmation"> <xs:element name="ProductOrderConfirmation"> <xs:complexType> <xs:choice> <xs:element name="Confirmation"> <xs:complexType> <xs:sequence> <xs:element name="ProductNumber" type="xs:string"/> <xs:element name="Quantity" type="xs:int"/> <xs:element name="ShipDate" type="xs:string"/> <xs:element name="AccountNumber" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="Cancellation"> <xs:complexType> <xs:sequence> <xs:element name="Comment" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema>' GO
Um im nachfolgenden Quelltext nur die absolut notwendigen Bestandteile zu zeigen,
wurden die Anforderungen hinsichtlich der Realitätstreue erheblich reduziert
und auch eine Funktion für die Ermittlung der Menge von Produkten auf dem
Lager namens int Sales.usfGetQuantity(@productNumber nvarchar(25)) erstellt.
Sie ist ebenfalls in der Datei 521_01.sql ganz am Anfang vorhanden und muss
vor einem Test des gesamten Beispiels zunächst erstellt werden. Sie ist
nicht abgedruckt.
Nachdem die beiden XML Schema-Dateien übertragen sind, erstellt man in
einem zweiten Schritt erst die Funktion, welche den Webservice bilden soll.
Dabei verwendet man jeweils typisiertes XML für den einzigen Übergabeparameter
und den Rückgabewert. Dies führt dazu, dass in der automatisch erstellten
WSDL-Datei ebenfalls die zuvor übertragenen XML Schema-Angaben zu finden
sind und sich ein Entwickler, der den Webservice nutzt, sich über die benötigten
Strukturen informieren kann.
In einem dritten Schritt muss bei der Erstellung von Funktion/Prozedur, welche
den Webservice bilden soll, das übergebene XML zerlegt werden, was mit
den unterschiedlichen T-SQL-Mitteln durchgeführt werden kann. Die ausgelesenen
Daten können dann beliebig verarbeitet werden. Im aktuellen Beispiel kann
man sich vorstellen, dass wesentlich mehr Aktionen notwendig wären, als
einfach nur die Verfügbarkeit des Produkts auf Lager zu testen.
In einem vierten und letzten Schritt setzt man schließlich eine Antwort-Nachricht
über Zeichenketten-Verarbeitung oder eine Abfrage zusammen, welche an den
Klienten wieder zurückgeliefert wird.
CREATE FUNCTION Production.ProductOrder ( @order xml(Sales.ProductOrder) ) RETURNS xml(Sales.ProductOrderConfirmation) AS BEGIN -- XML-Spaltenvariablen, Hilfsvariablen DECLARE @productNumber varchar(50), @quantity int, @accountNumber varchar(25), @availableProducts int, @confirmation xml(Sales.ProductOrderConfirmation) -- Zerlegung SET @productNumber = @order.value( 'declare namespace c="http://www.contoso.com/ProductOrder"; (//c:ProductNumber)[1]', 'varchar(25)') SET @quantity = @order.value( 'declare namespace c="http://www.contoso.com/ProductOrder"; (//c:Quantity)[1]', 'int') SET @accountNumber = @order.value( 'declare namespace c="http://www.contoso.com/ProductOrder"; (//c:AccountNumber)[1]', 'varchar(25)') -- Erhältlichkeit prüfen? SELECT @availableProducts = Sales.usfGetQuantity(@productNumber) -- Produkt erhältlich IF @availableProducts > 0 BEGIN -- Bestellung auslösen und Prozesse anstoßen... SET @confirmation = N'<c:ProductOrderConfirmation xmlns:c="http://www.contoso.com/ProductOrderConfirmation"> <c:Confirmation> <c:ProductNumber>BA-8327</c:ProductNumber> <c:Quantity>3</c:Quantity> <c:ShipDate></c:ShipDate> <c:AccountNumber>AW00019908</c:AccountNumber> </c:Confirmation> </c:ProductOrderConfirmation>' END -- Produkt nicht erhältlich ELSE SET @confirmation = N'<c:ProductOrderConfirmation xmlns:c="http://www.contoso.com/ProductOrderConfirmation"> <c:Cancellation> <c:Comment>Produkt nicht verfügbar.</c:Comment> </c:Cancellation> </c:ProductOrderConfirmation>' RETURN @confirmation END
Wie man unschwer erkennen kann, nimmt die Erstellung eines solchen Webdienstes
deutlich mehr Zeit in Anspruch, als man es vielleicht bei den ersten Gehversuchen
mit einfachen Funktionen/Prozeduren vermuten konnte. Weil man zunächst
auch erst XML Schema-Strukturen anmelden muss, auf die dann nachher verwiesen
wird, erhält man zudem ein sehr umfangreiches Geflecht an Strukturen in
der Datenbank, das am besten nach Erstellung der Funktion/Prozedur, welche den
Webservice später abbilden soll, getestet wird. Da man sich innerhalb von
T-SQL bewegt, ist es auch nicht notwendig, unbedingt den gesamten Webservice
anzumelden und in .NET einen Test-Klienten zu schreiben. Vielmehr kann man auch
einfach nur in T-SQL die eigentliche Funktion/Prozedur überprüfen,
ob bei Übergabe der erwarteten XML-Anfrage auch die gewünschte XML-Antwort
zurückkommt.
SOAP-Nachrichten
Die beiden Dateien 521_01.wsdl und 521_01simple.wsdl enthalten die beiden WSDL-Dateien
für diesen Dienst. Der WSDL-Generator fügt den zuvor angemeldeten
XML Schema-Strukturen noch automatisch Namensraum-Informationen hinzu, verzichtet
allerdings ansonsten bei der Standard-WSDL-Datei auf die Verkürzung, welche
durch das xs:any-Element bewirkt wird. Hier erscheinen also in der Webservice-Beschreibung
ausdrücklich die benötigten und zurückgelieferten XML-Strukturen.
Lediglich die vereinfachte Variante beschreibt die Nachrichten nur mit <xsd:any
minOccurs="0" maxOccurs="unbounded" processContents="skip"
/>.
Im allerersten Beispiel hatte man schon gesehen, wie eine Antwort-Nachricht
eine komplexe XML-Nachricht enthält. Dies ist bei dem gerade erstellten
Webservice nun auch bei der Anfrage der Fall. Hier verschickt man eine ähnliche
Datei wie zuvor für den Test und fragt, ob die erstellte Funktion die übergebene
XML-Datei überhaupt akzeptiert, verarbeiten und eine sinnvolle Antwort
zurückliefern kann.
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Body> <t:ProductOrder xmlns:t="http://tempUri.org/"> <t:order> <c:ProductOrder xmlns:c="http://www.contoso.com/ProductOrder"> <c:ProductNumber>BA-8327</c:ProductNumber> <c:Quantity>3</c:Quantity> <c:AccountNumber>AW00019908</c:AccountNumber> </c:ProductOrder> </t:order> </t:ProductOrder> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Sofern das Produkt tatsächlich verfügbar ist, erhält man innerhalb
der beiden Element ProductOrderResponse, das die Operation beschreibt, und ProductOrderResult,
welches das gesamte Ergebnis umschließt, das Element Confirmation mit
der im XML Schema angegebenen Kind-Struktur. Weil das Lieferdatum bei der Erstellung
nicht weiter beachtet worden ist, ist es nun leer.
... <c:ProductOrderConfirmation xmlns:c="http:// www.contoso.com/ ProductOrderConfirmation"> <c:Confirmation> <c:ProductNumber>BA-8327</c:ProductNumber> <c:Quantity>3</c:Quantity> <c:ShipDate/> <c:AccountNumber>AW00019908</c:AccountNumber> </c:Confirmation> </c:ProductOrderConfirmation> ...
Im gegenteiligen Fall erhält man die Antwort im Cancellation-Element mit
der zuvor angegebenen Meldung, dass das Produkt nicht verfügbar sei. Ob
dies nun eine Fehlermeldung oder doch eher eine tatsächliche Antwort zu
einer Bestellung ist, die nach einem nicht lieferbaren Produkt fragte, kann
man diskutieren.
... <c:ProductOrderConfirmation xmlns:c=" http://www.contoso.com/ProductOrderConfirmation"> <c:Cancellation> <c:Comment>Produkt nicht verfügbar.</c:Comment> </c:Cancellation> </c:ProductOrderConfirmation> </method:ProductOrderResult> </method:ProductOrderResponse> ...
In der Datei soap-anfrage-fehler.xml befindet sich dagegen ein tatsächlicher
XML-Fehler; nämlich das leere Element <c:ProductOrder xmlns:c="http://
www.contoso.com/ProductOrder"/>. In der nachfolgenden Datei fehlen einige
Elemente und alle Namensräume, um sie möglichst kurz zu halten.
<SOAP-ENV:Envelope> <SOAP-ENV:Body> <SOAP-ENV:Fault> <faultcode>SOAP-ENV:Client</faultcode> <faultstring>There was an error in the incoming SOAP request packet: Client, InvalidXml</faultstring> <detail> <SOAP-1_2-ENV:Detail> <sqlresultstream:SqlMessage> <sqlmessage:Class>16</sqlmessage:Class> <sqlmessage:LineNumber>1</sqlmessage:LineNumber> <sqlmessage:Message>XML-Überprüfung: Ungültiger Inhalt. Erwartete Elemente: http://www.contoso.com/ ProductOrder:ProductNumber. Ort: /*:ProductOrder[1]. </sqlmessage:Message> </sqlresultstream:SqlMessage> </SOAP-1_2-ENV:Detail> </detail> </SOAP-ENV:Fault> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Bei der Erstellung des Klienten erscheint dieses Mal in der Vorschau des ausgewählten
Webservices der Eintrag ProductOrder() AS ProductOrderType unter METHODEN, da
nun die XML-Nachricht als Klasse abgebildet wird. Sie wird vom Visual Studio
automatisch erstellt.
Ein Klient in .NET
Bei einer Übergabe von XML aus einer .NET-Klientanwendung an den MS SQL
Server wird der Vorteil, dass der xml-Datentyp der Datenbank auch fragmentiertes
XML, d.h. XML ohne Wurzelelement, speichern kann, zu einem Nachteil für
den Klienten. Die Instanzen des Datentyps xml werden im Visual Studio einem
Array von System.Xml.XmlNode zugeordnet und können nicht als System.Xml.XmlDocument
direkt abgerufen werden. Die letztere Möglichkeit wäre natürlich
viel einfacher als das, was im nächsten Beispiel-.NET-Programm präsentiert
wird.
Um den Quelltext abzukürzen, wird der XML-Quelltext, der zum Webservice
geschickt wird, nicht als DOM-Dokument erstellt oder aus einer Datei abgerufen,
sondern direkt als (gekürzte) Zeichenkette fest gespeichert. Man kann sich
allerdings sicherlich gut vorstellen, aus wie vielen unterschiedlichen Quellen
das XML direkt in der korrekten Form abgerufen, aus einer anderen Form mit XSLT/DOM
umgewandelt oder aus einem Formular in XML zusammengesetzt werden könnte.
Ebenso kann man sich sicherlich leicht vorstellen, wie das vom Webservice übermittelte
XML-Antwort-Dokument mit XSLT in XML/HTML umgewandelt oder mit dem DOM für
ein Formular in einzelne Variablen zerlegt wird.
Da der xml-Datentyp fragmentiertes XML unterstützt, muss in .NET ebenfalls
ein XmlDocumentFragment erstellt werden. Es besitzt ebenfalls eine Outer-/InnerXml-Eigenschaft,
die man zum Schreiben und Lesen von XML-Daten verwenden kann. Dabei erstellt
man zunächst ein Array von XmlNode-Objekten, das nur aus einem einzigen
Knoten besteht, in dem man seine abgerufenen XML-Daten speichern und schließlich
dem Parameterobjekt für die XML-Nachricht zuweisen kann. Die Verwendung
des Webservices selbst entspricht in diesem Beispiel dem vorherigen .NET-Klienten.
Hier kommen nur ganz andere Datentypen zum Einsatz.
Die beiden Eigenschaften OuterXml und InnerXml besitzen unterschiedliche Daten,
die insbesondere für die Antwortnachricht bedeutsam sind. InnerXml enthält
ausschließlich die über XML Schema modellierte Antwortnachricht,
d.h. das Elternelement Confirmation. Die Eigenschaft OuterXml enthält dagegen
auch noch das umschließende Element für den Operationsnamen ProductOrderConfirmation.
Sofern hier also eine XML Schema-Validierung für die Antwort durchgeführt
werden soll, ist die InnerXml-Eigenschaft die bessere Wahl, da hier nicht noch
zusätzlich das Eltern-Element überspringen werden muss.
static void Main(string[] args) {
// XML-Bestellung als Zeichenkette (kürzer als DOM)
String orderText = "<c:ProductOrder ...</c:ProductOrder>";
// Bestellung in automatisch erstelltem Typ anlegen
ProductOrderTypeorder order = new ProductOrderTypeorder();
// Zeichenktte in XML umwandeln
XmlDocument xmldoc = new XmlDocument();
XmlDocumentFragment fragIn = xmldoc.
CreateDocumentFragment();
fragIn.InnerXml = orderText;
XmlNode[] xmlnodes = new XmlNode[1];
xmlnodes[0] = (XmlNode)fragIn;
// Bestellung XML zuweisen
order.Any = xmlnodes;
// Webservice-Proxy anlegen
epProduct proxy = new epProduct();
proxy.Credentials = System.Net.CredentialCache.
DefaultCredentials;
// Webservice aufrufen und XML-Bestellung verschicken
try {
// Bestätigung in automatisch erstelltem Typ anlegen
ProductOrderType confirmation = proxy.
ProductOrder(order);
// XML als Zeichenkette auslesen und ausgeben
XmlNode[] nodeArr = confirmation.Any;
String confirmationXml = nodeArr[0].InnerXml;
String soapXml = nodeArr[0].OuterXml;
Console.WriteLine(confirmationXml);
Console.WriteLine("-----------------");
Console.WriteLine(soapXml);
}
...
Als Ergebnis des Programms erhält man in der Kommandozeile die übermittelte
Antwort, wobei beide möglichen XML-Fassungen angezeigt werden.

OOP Consulting MS SQL Server: SOAP-Nachrichtenstruktur - T-SQL XML Webservices Programmierung Bücher Anleitung Tutorial Skulschus Wiederstein Kozik XML Server Bücher Services Microsoft Analysis Webservices Reporting .NET Intelligence T-SQL MS SQL Business Bochum Bremen Münster Duisburg Bonn Bottrop München Mettmann Berlin Leipzig Stuttgart Wiesbaden Velbert Neuss Frankfurt Essen Gelsenkirchen Hattingen Ratingen Köln Wuppertal Herne Hamburg Dortmund DüsseldorfOOP Consulting MS SQL Server: SOAP-Nachrichtenstruktur - T-SQL XML Webservices Programmierung Bücher Anleitung Tutorial Skulschus Wiederstein Kozik XML Server Bücher Services Microsoft Analysis Webservices Reporting .NET Intelligence T-SQL MS SQL Business Bochum Bremen Münster Duisburg Bonn Bottrop München Mettmann Berlin Leipzig Stuttgart Wiesbaden Velbert Neuss Frankfurt Essen Gelsenkirchen Hattingen Ratingen Köln Wuppertal Herne Hamburg Dortmund DüsseldorfOOP Consulting MS SQL Server: SOAP-Nachrichtenstruktur - T-SQL XML Webservices Programmierung Bücher Anleitung Tutorial Skulschus Wiederstein Kozik XML Server Bücher Services Microsoft Analysis Webservices Reporting .NET Intelligence T-SQL MS SQL Business Bochum Bremen Münster Duisburg Bonn Bottrop München Mettmann Berlin Leipzig Stuttgart Wiesbaden Velbert Neuss Frankfurt Essen Gelsenkirchen Hattingen Ratingen Köln Wuppertal Herne Hamburg Dortmund DüsseldorfOOP Consulting MS SQL Server: SOAP-Nachrichtenstruktur - T-SQL XML Webservices Programmierung Bücher Anleitung Tutorial Skulschus Wiederstein Kozik XML Server Bücher Services Microsoft Analysis Webservices Reporting .NET Intelligence T-SQL MS SQL Business Bochum Bremen Münster Duisburg Bonn Bottrop München Mettmann Berlin Leipzig Stuttgart Wiesbaden Velbert Neuss Frankfurt Essen Gelsenkirchen Hattingen Ratingen Köln Wuppertal Herne Hamburg Dortmund DüsseldorfOOP Consulting MS SQL Server: SOAP-Nachrichtenstruktur - T-SQL XML Webservices Programmierung Bücher Anleitung Tutorial Skulschus Wiederstein Kozik XML Server Bücher Services Microsoft Analysis Webservices Reporting .NET Intelligence T-SQL MS SQL Business Bochum Bremen Münster Duisburg Bonn Bottrop München Mettmann Berlin Leipzig Stuttgart Wiesbaden Velbert Neuss Frankfurt Essen Gelsenkirchen Hattingen Ratingen Köln Wuppertal Herne Hamburg Dortmund DüsseldorfOOP Consulting MS SQL Server: SOAP-Nachrichtenstruktur - T-SQL XML Webservices Programmierung Bücher Anleitung Tutorial Skulschus Wiederstein Kozik XML Server Bücher Services Microsoft Analysis Webservices Reporting .NET Intelligence T-SQL MS SQL Business Bochum Bremen Münster Duisburg Bonn Bottrop München Mettmann Berlin Leipzig Stuttgart Wiesbaden Velbert Neuss Frankfurt Essen Gelsenkirchen Hattingen Ratingen Köln Wuppertal Herne Hamburg Dortmund DüsseldorfOOP Consulting MS SQL Server: SOAP-Nachrichtenstruktur - T-SQL XML Webservices Programmierung Bücher Anleitung Tutorial Skulschus Wiederstein Kozik XML Server Bücher Services Microsoft Analysis Webservices Reporting .NET Intelligence T-SQL MS SQL Business Bochum Bremen Münster Duisburg Bonn Bottrop München Mettmann Berlin Leipzig Stuttgart Wiesbaden Velbert Neuss Frankfurt Essen Gelsenkirchen Hattingen Ratingen Köln Wuppertal Herne Hamburg Dortmund DüsseldorfOOP Consulting MS SQL Server: SOAP-Nachrichtenstruktur - T-SQL XML Webservices Programmierung Bücher Anleitung Tutorial Skulschus Wiederstein Kozik XML Server Bücher Services Microsoft Analysis Webservices Reporting .NET Intelligence T-SQL MS SQL Business Bochum Bremen Münster Duisburg Bonn Bottrop München Mettmann Berlin Leipzig Stuttgart Wiesbaden Velbert Neuss Frankfurt Essen Gelsenkirchen Hattingen Ratingen Köln Wuppertal Herne Hamburg Dortmund DüsseldorfOOP Consulting MS SQL Server: SOAP-Nachrichtenstruktur - T-SQL XML Webservices Programmierung Bücher Anleitung Tutorial Skulschus Wiederstein Kozik XML Server Bücher Services Microsoft Analysis Webservices Reporting .NET Intelligence T-SQL MS SQL Business Bochum Bremen Münster Duisburg Bonn Bottrop München Mettmann Berlin Leipzig Stuttgart Wiesbaden Velbert Neuss Frankfurt Essen Gelsenkirchen Hattingen Ratingen Köln Wuppertal Herne Hamburg Dortmund Düsseldorf |