diff --git a/src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorage.cs b/src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorage.cs
index 19b5d57856..03fcdf2b8e 100644
--- a/src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorage.cs
+++ b/src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorage.cs
@@ -40,6 +40,7 @@ public class AzureTableGrainStorage : IGrainStorage, IRestExceptionDecoder, ILif
private const string BINARY_DATA_PROPERTY_NAME = "Data";
private const string STRING_DATA_PROPERTY_NAME = "StringData";
+
private readonly string name;
/// Default constructor
@@ -202,23 +203,29 @@ private static async Task DoOptimisticUpdate(Func updateOperation, string
///
internal void ConvertToStorageFormat(T grainState, TableEntity entity)
{
- int dataSize;
- IEnumerable> properties;
- string basePropertyName;
+ var binaryData = storageSerializer.Serialize(grainState);
- // Convert to binary format
- var data = this.storageSerializer.Serialize(grainState);
- basePropertyName = BINARY_DATA_PROPERTY_NAME;
+ CheckMaxDataSize(binaryData.ToMemory().Length, MAX_DATA_CHUNK_SIZE * MAX_DATA_CHUNKS_COUNT);
- dataSize = data.ToMemory().Length;
- properties = SplitBinaryData(data);
+ if (options.UseStringFormat)
+ {
+ var properties = SplitStringData(binaryData.ToString().AsMemory());
- CheckMaxDataSize(dataSize, MAX_DATA_CHUNK_SIZE * MAX_DATA_CHUNKS_COUNT);
+ foreach (var keyValuePair in properties.Zip(GetPropertyNames(STRING_DATA_PROPERTY_NAME),
+ (property, name) => new KeyValuePair(name, property.ToString())))
+ {
+ entity[keyValuePair.Key] = keyValuePair.Value;
+ }
+ }
+ else
+ {
+ var properties = SplitBinaryData(binaryData);
- foreach (var keyValuePair in properties.Zip(GetPropertyNames(basePropertyName),
+ foreach (var keyValuePair in properties.Zip(GetPropertyNames(BINARY_DATA_PROPERTY_NAME),
(property, name) => new KeyValuePair(name, property.ToArray())))
- {
- entity[keyValuePair.Key] = keyValuePair.Value;
+ {
+ entity[keyValuePair.Key] = keyValuePair.Value;
+ }
}
}
diff --git a/src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorageOptions.cs b/src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorageOptions.cs
index a854848ecd..ba5f7a800e 100644
--- a/src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorageOptions.cs
+++ b/src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorageOptions.cs
@@ -19,6 +19,11 @@ public class AzureTableStorageOptions : AzureStorageOperationOptions, IStoragePr
///
public bool DeleteStateOnClear { get; set; } = false;
+ ///
+ /// Indicates if grain data should be stored in string or in binary format.
+ ///
+ public bool UseStringFormat { get; set; }
+
///
/// Stage of silo lifecycle where storage should be initialized. Storage must be initialized prior to use.
///
diff --git a/test/Extensions/TesterAzureUtils/Persistence/PersistenceProviderTests.cs b/test/Extensions/TesterAzureUtils/Persistence/PersistenceProviderTests.cs
index d33c5bff2e..5fdb8e1105 100644
--- a/test/Extensions/TesterAzureUtils/Persistence/PersistenceProviderTests.cs
+++ b/test/Extensions/TesterAzureUtils/Persistence/PersistenceProviderTests.cs
@@ -166,6 +166,40 @@ public async Task PersistenceProvider_Azure_ChangeWriteFormat(int? stringLength,
await Test_PersistenceProvider_WriteRead(testName, store, grainState, grainId);
}
+ [SkippableTheory, TestCategory("Functional"), TestCategory("AzureStorage")]
+ [InlineData(null, true, false)]
+ [InlineData(null, false, true)]
+ [InlineData(15 * 32 * 1024 - 256, true, false)]
+ [InlineData(15 * 32 * 1024 - 256, false, true)]
+ public async Task PersistenceProvider_Azure_ChangeStorageDataFormat_WhenJsonSerializerIsUsed(int? stringLength, bool useStringFormatForFirstWrite, bool useStringFormatForSecondWrite)
+ {
+ // always use JsonSerializer over OrleansSerializer since specifying 'useStringFormat = true'
+ // writes to 'StringData', the OrleansSerializer can not read from the 'StringData' column as its not a format which it expects.
+ const bool useJson = true;
+
+ var testName = string.Format("{0}({1}={2},{3}={4},{5}={6})",
+ nameof(PersistenceProvider_Azure_ChangeStorageDataFormat_WhenJsonSerializerIsUsed),
+ nameof(stringLength), stringLength == null ? "default" : stringLength.ToString(),
+ "strFormat1stW", useStringFormatForFirstWrite,
+ "strFormat2ndW", useStringFormatForSecondWrite);
+
+ var grainState = TestStoreGrainState.NewRandomState(stringLength);
+ EnsureEnvironmentSupportsState(grainState);
+
+ var grainId = LegacyGrainId.NewId();
+
+ var store = await InitAzureTableGrainStorage(useJson: useJson, useStringFormat: useStringFormatForFirstWrite);
+
+ await Test_PersistenceProvider_WriteRead(testName, store, grainState, grainId);
+
+ grainState = TestStoreGrainState.NewRandomState(stringLength);
+ grainState.ETag = "*";
+
+ store = await InitAzureTableGrainStorage(useJson: useJson, useStringFormat: useStringFormatForSecondWrite);
+
+ await Test_PersistenceProvider_WriteRead(testName, store, grainState, grainId);
+ }
+
[SkippableTheory, TestCategory("Functional"), TestCategory("AzureStorage")]
[InlineData(null, false)]
[InlineData(null, true)]
@@ -266,8 +300,13 @@ private async Task InitAzureTableGrainStorage(AzureTable
return store;
}
- private async Task InitAzureTableGrainStorage(bool useJson = false, TypeNameHandling? typeNameHandling = null)
+ private async Task InitAzureTableGrainStorage(bool useJson = false, bool useStringFormat = false, TypeNameHandling? typeNameHandling = null)
{
+ if (useStringFormat && !useJson)
+ {
+ throw new InvalidOperationException($"Using {nameof(OrleansGrainStorageSerializer)} in conjuction with string data format makes no sense, there for stopping attempt.");
+ }
+
var options = new AzureTableStorageOptions();
var jsonOptions = this.providerRuntime.ServiceProvider.GetService>();
if (typeNameHandling != null)
@@ -276,6 +315,7 @@ private async Task InitAzureTableGrainStorage(bool useJs
}
options.ConfigureTestDefaults();
+ options.UseStringFormat = useStringFormat;
// TODO change test to include more serializer?
var binarySerializer = new OrleansGrainStorageSerializer(this.providerRuntime.ServiceProvider.GetRequiredService());