T-Sql money verhonderdvoudigt kosten in Azure

Seeing anything you like? Google translate does not work out? Drop me a note and I will translate this post.

Met Sql Server 2012 Management Studio is het nu eenvoudig om bacpac-bestanden te maken om zo Sql Server databases te exporteren en te importeren. Dit is dé manier om Sql-Azure databases initieel mee te vullen.

Een bacpac bestaat uit een combinatie van: de scripts om de database aan te maken en json bestanden om de te exporteren data in op te slaan. En deze is in zijn geheel gezipt om de overdracht te verspoedigen. (een bacpac stelt ook eisen aan de meta data overigens, de database moet eerst door een validatie heen.)

Wij gebruiken op ons project bacpac-bestanden om onze lokale database dus naar de cloud te brengen.

Gisteren kreeg ik de melding van onze tester dat de prijzen van artikelen, opgehaald uit Azure, honderd maal zo groot waren vergeleken met een lokale database. Ik kon het inderdaad naspelen en kwam er op een gegeven moment achter dat het gewoon aan de vulling van de database lag. De prijzen waren in de Sql-Azure databasetabel gewoon honderd maal hoger.

Het veld was als money in TSql opgenomen. Dit zal historisch ooit in de database geslopen zijn. Persoonlijk vind ik een decimal voldoende. Mocht ik het type valuta (anders dan euro’s) willen opslaan dan zou ik de valuta isocode daarvan in een andere kolom opnemen. En kan een money veld ook met vier cijfers achter de komma omgaan, iets wat veel banken doen om (zichtbare) afrondingsfouten te voorkomen?

Ik heb toen gekeken naar waar dit aan zou kunnen liggen en ik kwam bij de bacpac uit. We hadden nog de originele versie en deze kan je simpel openen door de extensie met .zip te verlengen. Dan kom je ook bij de json bestanden uit en daar stond het: de prijzen waren als decimaal afgebeeld in de json waarden voor de bedragen maar er zat een komma in!

Nu zijn wij Nederlanders en mijn collega had zijn lokale instellingen op Dutch gezet. Op zijn machine is de bacpac aangemaakt. Zou het daar aan liggen, zou de locale van zijn machine een rol spelen? We hebben dus een testje gedaan.

We maakten een tabel met de volgende velden:

[Id] [bigint] IDENTITY(1,1) NOT NULL,[Name] [nvarchar](50) NULL,

[Age] [int] NULL,

[Decimal] [decimal](5, 3) NULL,

[Float] [float] NULL,

[Money] [money] NULL,

[Numeric] [numeric](10, 5) NULL,

[Datetime] [datetime] NULL,

[Datetimeoffset] [datetimeoffset](7) NULL,

Als je deze exporteert met een Engelse locale krijg je:

{“0″:[1,”test   name”,23,”3.566″,”3.566″,”3.55″,”3.56600″,”2012-12-23T21:45:59.0000000″,”2012-12-23T23:45:31.0000000+01:00″]}

Als je deze exporteert met een Nederlandse locale krijg je:

{“0″:[1,”test   name”,23,”3.566″,”3.566″,”3,55″,”3.56600″,”2012-12-23T21:45:59.0000000″,”2012-12-23T23:45:31.0000000+01:00″]}

Het money veld volgt dus inderdaad de locale. En ja, onze databaseprijzen waren dus ingesteld met dat money veld.

Blijkbaar beschouwt Azure, of de conversie er naar toe, 3,55 euro als 355 euro. De decimale komma gaat op in een ‘formattering’ correctie.

Conclusie: klanten van Microsoft die niet met de Engelse locale werken kunnen beter geen money veld toepassen in Sql-Azure. De decimal is een waardig alternatief tot het moment waarop Microsoft besluit deze ‘feature’ te verhelpen.

Advertentie

Hoe soap headers in WCF toe te passen

In need of an English translation? Please drop me a note.

Een http bericht kan uit twee delen bestaan, een header en een soap envelop. In deze http header staat bv. dat  de aanvrager het antwoord gecomprimeer terug wilt hebben (Accept-encoding: gzip, deflate).

De soap is de daadwerkelijke payload van het bericht. WCF plaatst in de body van de envelop direct alle informatie.

Maar zoals de titel al doet vermoeden, is het ook mogelijk om in de soap envelop een header op te nemen. Dit is een interessante optie om aan het bericht een context mee te geven. Er kan bijvoorbeeld in de header informatie over de verzender opgenomen worden. Ook kan bv. een volgnummer opgenomen worden zodat vraag en antwoord beter getraceerd kunnen worden.

Er zijn dus twee uiteindelijk twee soorten headers: die op het HTTP bericht en die op het soap bericht. Meer informatie over soap is ook te vinden op http://www.syncfusion.com/resources/techportal/ebooks/http .

In deze blog richten we ons op het meesturen van een header van client naar de WCF service. Als voorbeeld gebruik ik het standaard WCF contract zoals Microsoft die aanbiedt als een WCF service net nieuw aangemaakt is.

We bekijken eens een standaard aanvraag:

var client =
  new ServiceReference1.
             Service1Client();

string data = client.GetData(42);

Deze geeft als Request:

POST http://localhost:52568/Service1.svc   HTTP/1.1Content-Type: text/xml;   charset=utf-8VsDebuggerCausalityData:   uIDPo/nwpA5i4l1IifBRBjolEk0AAAAAsmDH0cCjakSxrkG/OfV+SitX6PLpY7pFoZKEmtJtaY8ACQAASOAPAction:   “http://tempuri.org/IService1/GetData”Host: localhost:52568Content-Length: 158Expect: 100-continueAccept-Encoding: gzip, deflateConnection: Keep-Alive<s:Envelope   xmlns:s=”http://schemas.xmlsoap.org/soap/envelope/”><s:Body><GetData   xmlns=”http://tempuri.org/”><value>42</value></GetData></s:Body></s:Envelope&gt;

De soap envelop bevat dus alleen een body.

We sturen nu een simpele string in de soap header mee met de volgende aanroep vanuit de cliënt:

using (var client = new ServiceReference1.Service1Client())
{
  using (new System.ServiceModel.
               OperationContextScope(client.InnerChannel))
  {
    var header = MessageHeader.CreateHeader
                      ("Key",
                       "http://somenamespace.com/",
"Value");

    OperationContext.Current.
                          OutgoingMessageHeaders.Add(header);

    // Do the call within the using
    string data = client.GetData(42);

    Console.WriteLine("Data: " + data);
  }
}

De request wordt dan:

POST http://localhost:52568/Service1.svc   HTTP/1.1Content-Type: text/xml;   charset=utf-8VsDebuggerCausalityData:   uIDPo2Vd4ZtREjVIsNbfCTIikcIAAAAAsmDH0cCjakSxrkG/OfV+SitX6PLpY7pFoZKEmtJtaY8ACQAASOAPAction:   “http://tempuri.org/IService1/GetData”Host: localhost:52568Content-Length: 229Expect: 100-continueAccept-Encoding: gzip, deflateConnection: Keep-Alive<s:Envelope   xmlns:s=”http://schemas.xmlsoap.org/soap/envelope/”><s:Header><Key   xmlns=”http://somenamespace.com/”>Value</Key></s:Header><s:Body><GetData   xmlns=”http://tempuri.org/”><value>42</value></GetData></s:Body></s:Envelope&gt;

We zien nu dus de soap envelop uitgebreid worden met een header met daarin een ”key” element.

Nu maken we het wat interessanter. Er kan ook een object doorgegeven worden dus we definiëren een extra class die zowel op de cliënt als op de server bekent is:

public class SoapHeader
{
  public int ValueOne { get; set; }
  public string ValueTwo { get; set; }
}

De aanroep wordt dan als volgt:

using (new System.ServiceModel.
         OperationContextScope(client.InnerChannel))
{
  var soapHeader = new SoapHeader
  {
    ValueOne = 123,
    ValueTwo = "Two"
  };

  var header = MessageHeader.CreateHeader
              ("Key",
               "http://somenamespace.com/",
               soapHeader);

  OperationContext.Current.OutgoingMessageHeaders.Add(header);

  // Do the call within the using
  string data = client.GetData(42);

  Console.WriteLine("Data: " + data);
}

De Request wordt dan uiteindelijk:

POST http://localhost:52568/Service1.svc   HTTP/1.1Content-Type: text/xml;   charset=utf-8VsDebuggerCausalityData:   uIDPoxZdul7FztlGiQ39MXH6mwoAAAAAsmDH0cCjakSxrkG/OfV+SitX6PLpY7pFoZKEmtJtaY8ACQAASOAPAction:   “http://tempuri.org/IService1/GetData”Host: localhost:52568Content-Length: 448Expect: 100-continueAccept-Encoding: gzip, deflateConnection: Keep-Alive<s:Envelope   xmlns:s=”http://schemas.xmlsoap.org/soap/envelope/”><s:Header><Key   xmlns=”http://somenamespace.com/&#8221;   xmlns:i=”http://www.w3.org/2001/XMLSchema-instance”><ValueOne   xmlns=”http://schemas.datacontract.org/2004/07/ClassLibrary1″>123</ValueOne><ValueTwo   xmlns=”http://schemas.datacontract.org/2004/07/ClassLibrary1″>Two</ValueTwo></Key></s:Header><s:Body><GetData   xmlns=”http://tempuri.org/”><value>42</value></GetData></s:Body></s:Envelope&gt;

In de soap header zien we ons samengestelde object als “key” terugkeren met de twee waarden. Maar hoe kan de doorgegeven informative weer uitgelezen worden? De OutgoingMessageHeaders heeft een tegenganger IncomingMessageHeaders:

var soapHeader = OperationContext.Current.
    IncomingMessageHeaders.
      GetHeader<SoapHeader>("Key",
                            "http://somenamespace.com/");

Dit geeft de correcte waarden door aan de Wcf Service:

Zo is het dus wel heel eenvoudig geworden om aan een Soap envelop extra contextinformatie mee te geven.

Om vanuit de server weer informatie richting de client te sturen kan de volgende code gebruikt worden:

var header = MessageHeader.
  CreateHeader("Key",
               "http://somenamespace.com/",
               soapHeader);
OperationContext.Current.
  OutgoingMessageHeaders.Add(header);

Op de client lees je deze waarden weer terug met:

var soapHeaderReturned =
  OperationContext.Current.
    IncomingMessageHeaders.GetHeader<SoapHeader>
    (
      "Returned",
      "http://servicenamespace.com/"
);

Extra: In bovenstaande code heb ik IISExpress in combinatie met Fiddler toegepast om de soapberichten te kunnen afvangen. Hiervoor heb ik in de  app.config als adres voor de webservice gebruikt: http://localhost.fiddler:52568/Service1.svc

Hiermee forceer ik dat Fiddler de berichten kan afluisteren. Helaas verlies ik hierdoor wel de mogelijkheid om vanuit een test applicatie direct in een breakpoint op de service over te stappen.

Update: toevoeging van code om een header terug te sturen naar de client.