/// <summary> Read state data function for this storage provider.</summary> /// <see cref="IGrainStorage.ReadStateAsync(string, GrainReference, IGrainState)"/>. public async Task ReadStateAsync(string grainType, GrainReference grainReference, IGrainState grainState) { //It assumed these parameters are always valid. If not, an exception will be thrown, even if not as clear //as with explicitly checked parameters. var grainId = GrainIdAndExtensionAsString(grainReference); var baseGrainType = ExtractBaseClass(grainType); if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace((int)RelationalStorageProviderCodes.RelationalProviderReading, LogString("Reading grain state", serviceId, this.name, grainState.ETag, baseGrainType, grainId.ToString())); } try { SerializationChoice choice = StorageSerializationPicker.PickDeserializer(serviceId, this.name, baseGrainType, grainReference, grainState, null); if (choice.Deserializer == null) { var errorString = LogString("No deserializer found", serviceId, this.name, grainState.ETag, baseGrainType, grainId.ToString()); logger.Error((int)RelationalStorageProviderCodes.RelationalProviderNoDeserializer, errorString); throw new InvalidOperationException(errorString); } var commandBehavior = choice.PreferStreaming ? CommandBehavior.SequentialAccess : CommandBehavior.Default; var grainIdHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(grainId.GetHashBytes()); var grainTypeHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(Encoding.UTF8.GetBytes(baseGrainType)); var readRecords = (await Storage.ReadAsync(CurrentOperationalQueries.ReadFromStorage, (command => { command.AddParameter("GrainIdHash", grainIdHash); command.AddParameter("GrainIdN0", grainId.N0Key); command.AddParameter("GrainIdN1", grainId.N1Key); command.AddParameter("GrainTypeHash", grainTypeHash); command.AddParameter("GrainTypeString", baseGrainType); command.AddParameter("GrainIdExtensionString", grainId.StringKey); command.AddParameter("ServiceId", serviceId); }), async(selector, resultSetCount, token) => { object storageState = null; int?version; if (choice.PreferStreaming) { //When streaming via ADO.NET, using CommandBehavior.SequentialAccess, the order of //the columns on how they are read needs to be exactly this. const int binaryColumnPositionInSelect = 0; const int xmlColumnPositionInSelect = 1; const int jsonColumnPositionInSelect = 2; var streamSelector = (DbDataReader)selector; if (!(await streamSelector.IsDBNullAsync(binaryColumnPositionInSelect))) { using (var downloadStream = streamSelector.GetStream(binaryColumnPositionInSelect, Storage)) { storageState = choice.Deserializer.Deserialize(downloadStream, grainState.Type); } } if (!(await streamSelector.IsDBNullAsync(xmlColumnPositionInSelect))) { using (var downloadStream = streamSelector.GetTextReader(xmlColumnPositionInSelect)) { storageState = choice.Deserializer.Deserialize(downloadStream, grainState.Type); } } if (!(await streamSelector.IsDBNullAsync(jsonColumnPositionInSelect))) { using (var downloadStream = streamSelector.GetTextReader(jsonColumnPositionInSelect)) { storageState = choice.Deserializer.Deserialize(downloadStream, grainState.Type); } } version = await streamSelector.GetValueAsync <int?>("Version"); } else { //All but one of these should be null. All will be read and an appropriate deserializer picked. //NOTE: When streaming will be implemented, it is worthwhile to optimize this so that the defined //serializer will be picked and then streaming tried according to its tag. object payload; payload = selector.GetValueOrDefault <byte[]>("PayloadBinary"); if (payload == null) { payload = selector.GetValueOrDefault <string>("PayloadXml"); } if (payload == null) { payload = selector.GetValueOrDefault <string>("PayloadJson"); } if (payload != null) { storageState = choice.Deserializer.Deserialize(payload, grainState.Type); } version = selector.GetNullableInt32("Version"); } return(Tuple.Create(storageState, version?.ToString(CultureInfo.InvariantCulture))); }, CancellationToken.None, commandBehavior).ConfigureAwait(false)).SingleOrDefault(); object state = readRecords != null ? readRecords.Item1 : null; string etag = readRecords != null ? readRecords.Item2 : null; if (state == null) { logger.Info((int)RelationalStorageProviderCodes.RelationalProviderNoStateFound, LogString("Null grain state read (default will be instantiated)", serviceId, this.name, grainState.ETag, baseGrainType, grainId.ToString())); state = Activator.CreateInstance(grainState.Type); } grainState.State = state; grainState.ETag = etag; if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace((int)RelationalStorageProviderCodes.RelationalProviderRead, LogString("Read grain state", serviceId, this.name, grainState.ETag, baseGrainType, grainId.ToString())); } } catch (Exception ex) { logger.Error((int)RelationalStorageProviderCodes.RelationalProviderReadError, LogString("Error reading grain state", serviceId, this.name, grainState.ETag, baseGrainType, grainId.ToString(), ex.Message), ex); throw; } }
/// <summary> Write state data function for this storage provider.</summary> /// <see cref="IStorageProvider.WriteStateAsync"/> public async Task WriteStateAsync(string grainType, GrainReference grainReference, IGrainState grainState) { //It assumed these parameters are always valid. If not, an exception will be thrown, even if not as clear //as with explicitly checked parameters. var data = grainState.State; var grainId = GrainIdAndExtensionAsString(grainReference); var baseGrainType = ExtractBaseClass(grainType); if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace((int)RelationalStorageProviderCodes.RelationalProviderWriting, LogString("Writing grain state", ServiceId, Name, grainState.ETag, baseGrainType, grainId.ToString())); } string storageVersion = null; try { var grainIdHash = HashPicker.PickHasher(ServiceId, Name, baseGrainType, grainReference, grainState).Hash(grainId.GetHashBytes()); var grainTypeHash = HashPicker.PickHasher(ServiceId, Name, baseGrainType, grainReference, grainState).Hash(Encoding.UTF8.GetBytes(baseGrainType)); var writeRecord = await Storage.ReadAsync(CurrentOperationalQueries.WriteToStorage, command => { command.AddParameter("GrainIdHash", grainIdHash); command.AddParameter("GrainIdN0", grainId.N0Key); command.AddParameter("GrainIdN1", grainId.N1Key); command.AddParameter("GrainTypeHash", grainTypeHash); command.AddParameter("GrainTypeString", baseGrainType); command.AddParameter("GrainIdExtensionString", grainId.StringKey); command.AddParameter("ServiceId", ServiceId); command.AddParameter("GrainStateVersion", !string.IsNullOrWhiteSpace(grainState.ETag) ? int.Parse(grainState.ETag, CultureInfo.InvariantCulture) : default(int?)); SerializationChoice serializer = StorageSerializationPicker.PickSerializer(ServiceId, Name, baseGrainType, grainReference, grainState); command.AddParameter("PayloadBinary", (byte[])(serializer.Serializer.Tag == UseBinaryFormatPropertyName ? serializer.Serializer.Serialize(data) : null)); command.AddParameter("PayloadJson", (string)(serializer.Serializer.Tag == UseJsonFormatPropertyName ? serializer.Serializer.Serialize(data) : null)); command.AddParameter("PayloadXml", (string)(serializer.Serializer.Tag == UseXmlFormatPropertyName ? serializer.Serializer.Serialize(data) : null)); }, (selector, resultSetCount, token) => { return(Task.FromResult(selector.GetValueOrDefault <int?>("NewGrainStateVersion").ToString())); }, CancellationToken.None).ConfigureAwait(false); storageVersion = writeRecord.SingleOrDefault(); } catch (Exception ex) { logger.Error((int)RelationalStorageProviderCodes.RelationalProviderWriteError, LogString("Error writing grain state", ServiceId, Name, grainState.ETag, baseGrainType, grainId.ToString(), ex.Message), ex); throw; } const string OperationString = "WriteState"; var inconsistentStateException = CheckVersionInconsistency(OperationString, ServiceId, Name, storageVersion, grainState.ETag, baseGrainType, grainId.ToString()); if (inconsistentStateException != null) { throw inconsistentStateException; } //No errors found, the version of the state held by the grain can be updated. grainState.ETag = storageVersion; if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace((int)RelationalStorageProviderCodes.RelationalProviderWrote, LogString("Wrote grain state", ServiceId, Name, grainState.ETag, baseGrainType, grainId.ToString())); } }