Ich habe gerade eine interessante Erkenntnis im Umgang mit Azure Table Storage gehabt!
Ich verwende eine Table um Email-Nachrichten abzuspeichern.
1. Datenklasse:
Dazu habe ich folgende Datenklasse MailMessage gebaut:
1: [DataServiceKey("PartitionKey", "RowKey")]
2: public class MailMessage
3: {
4: public MailMessage()
5: {
6: }
7:
8: public MailMessage(string mailAdress, string fromAdress, DateTime date)
9: {
10: this.ToAdress = mailAdress;
11: this.FromAdress = fromAdress;
12: this.ReceivedDate = date;
13:
14: this.PartitionKey = this.ToAdress;
15: this.RowKey = date.ToString();
16:
17: this.Id = Guid.NewGuid();
18: }
19:
20: public string PartitionKey { get; set; }
21: public string RowKey { get; set; }
22:
23: public string ToAdress { get; set; }
24: public DateTime ReceivedDate { get; set; }
25: public Guid Id { get; set; }
26: ...
27: }
Die Datenklasse muss entweder mit dem [DataServiceKey] Attribut versehen sein, oder von TableServiceEntity ableiten. Um die Klasse als Model universell verwenden zu können habe ich mich für die erstere Variante entschieden.
2. Zugriff auf den Table Storage:
Um auf den Table Storage zuzugreifen muss zuerst die Configuration initialisiert werden. Danach wird über ein ConfigurationSettings (“Storage”) die Verbindung hergestellt. Das erfolgt eigentlich mit Hilfe der ADO.NET Data Services.
1: // Initialize Configuration
2: CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSettingPublisher) =>
3: {
4: var connectionString = RoleEnvironment.GetConfigurationSettingValue(configName);
5: configSettingPublisher(connectionString);
6: });
7:
8: // Get account
9: var account = CloudStorageAccount.FromConfigurationSetting("Storage");
10: // Create Table Client
11: var tableClient = account.CreateCloudTableClient();
12: // Create table if not existing
13: tableClient.CreateTableIfNotExist("Messages");
14:
15: // Access table storage data via ADO.NET DataService Context
16: var dataService = tableClient.GetDataServiceContext();
17:
18: var msg = new MailMessage(...){ ... };
19:
20: dataService.AddObject("Messages", msg);
21: dataService.SaveChanges();
Abschließend werden die Änderungen in den Table Storage gespeichert.
Damit das Beispiel funktioniert, muss in der Rollen Konfiguration noch die Verbindung zu Azure Storage konfiguriert werden:

Der Fehler: “One of the request inputs is out of range”
Nachdem ich den Code wie oben angeführt in der lokalen DevFabric erfolgreich getestet hatte, wollte ich das Ganze real in Azure ausprobieren.
Leider ohne Erfolg! So trat dort immer eine
Microsoft.WindowsAzure.StorageClient.StorageClientException: One of the request inputs is out of range. auf.
(herausgefunden habe ich das durch den Einsatz von IntelliTrace).
Der Fehler weist darauf hin, dass Spalten einen inkorrekten Wert haben. Das kann zwei Ursachen haben:
- Falscher Datentyp (erlaubt sind Byte[], bool, DateTime, double, Guid, Int32, Int64, String)
- Illegale Zeichen in RowKey oder PartitionKey (nicht erlaubt sind / \ # ?)
- Table Name ist Case-Insensitive, Spalten Name Case-Sensitive.
Details zu den Beschränkungen in der MSDN Library.
Alle drei sind meiner Meinung nach hier nicht der Fall. Doch warum tritt dieser Fehler auch lokal überhaupt nicht auf?
Die Lösung
Der Unterschied zwischen Azure DevFabric und Real-Azure sind hier die Regional Settings. Außerdem die unsaubere Verwendung des Datums als RowKey (s.o.):
this.RowKey = date.ToString();
Dadurch wird der RowKey bei der DevFabric auf einen deutschen Datumswert gesetzt. Bei Real-Azure sind die Regional Settings aber auf Englisch, sodass der Datumswert Slashes enthält – die im RowKey und PartitionKey NICHT verwendet werden dürfen!
Die Erkenntnis: Datumswerte sollten bei Azure Table Storage nicht als RowKey oder PartitionKey verwendet werden. Wenn JA, dann sollten diese (wie eigentlich normal auch immer) explizit auf eine Kultur – in dem Fall auf KEINE, die SLASHES enthält formatiert werden.
Die Lösung ist in dem Fall also z.B. die obere Zeile auf folgende zu ändern:
t.ToString(new CultureInfo("de-at"))
knom