Skip to content

Commit 478b9df

Browse files
fix: support deserializing timespan properties (Azure#49967)
* fix: support deserializing timespan properties * Update sdk/tables/Azure.Data.Tables/tests/TableClientLiveTests.cs Co-authored-by: Copilot <[email protected]> * pr feedback * minor cleanup * update changelog --------- Co-authored-by: Copilot <[email protected]>
1 parent 0929374 commit 478b9df

File tree

6 files changed

+83
-9
lines changed

6 files changed

+83
-9
lines changed

sdk/tables/Azure.Data.Tables/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
### Breaking Changes
88

99
### Bugs Fixed
10+
- Fixed an issue where `TimeSpan` properties in strongly typed table entities were not being deserialized.
11+
- Fixed an issue when deserializing strongly typed table entities with enum properties. Enum values that aren't defined in the enum type are now skipped during deserialization of the table entity.
1012

1113
### Other Changes
1214

@@ -313,4 +315,4 @@ Thank you to our developer community members who helped to make Azure Tables bet
313315
This is the first beta of the `Azure.Data.Tables` client library. The Azure Tables client library can seamlessly target either Azure Table storage or Azure Cosmos DB table service endpoints with no code changes.
314316

315317
This package's [documentation](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/tables/Azure.Data.Tables/README.md)
316-
and [samples](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/tables/Azure.Data.Tables/samples) demonstrate the new API.
318+
and [samples](https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/tables/Azure.Data.Tables/samples) demonstrate the new API.

sdk/tables/Azure.Data.Tables/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "net",
44
"TagPrefix": "net/tables/Azure.Data.Tables",
5-
"Tag": "net/tables/Azure.Data.Tables_9bdee2bad4"
5+
"Tag": "net/tables/Azure.Data.Tables_f74f623264"
66
}

sdk/tables/Azure.Data.Tables/src/Extensions/TablesTypeBinder.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,32 +139,46 @@ protected override bool TryGet<T>(BoundMemberInfo memberInfo, IDictionary<string
139139
{
140140
value = (T)(object) DateTime.Parse(propertyValue as string, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
141141
}
142+
else if (typeof(T) == typeof(TimeSpan))
143+
{
144+
value = (T)(object)TypeFormatters.ParseTimeSpan(propertyValue as string, "P");
145+
}
146+
else if (typeof(T) == typeof(TimeSpan?))
147+
{
148+
value = (T)(object)TypeFormatters.ParseTimeSpan(propertyValue as string, "P");
149+
}
142150
else if (typeof(T) == typeof(string))
143151
{
144-
value = (T)(object) (propertyValue as string);
152+
value = (T)(object)(propertyValue as string);
145153
}
146154
else if (typeof(T) == typeof(int))
147155
{
148-
value = (T)(object) (int)propertyValue;
156+
value = (T)(object)(int)propertyValue;
149157
}
150158
else if (typeof(T) == typeof(int?))
151159
{
152-
value = (T)(object) (int?)propertyValue;
160+
value = (T)(object)(int?)propertyValue;
153161
}
154162
else if (typeof(T).IsEnum)
155163
{
156-
value = (T)Enum.Parse(memberInfo.Type, propertyValue as string );
164+
if (!Enum.IsDefined(memberInfo.Type, propertyValue as string))
165+
return false;
166+
167+
value = (T)Enum.Parse(memberInfo.Type, propertyValue as string);
157168
}
158169
else if (typeof(T).IsGenericType &&
159170
typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>) &&
160171
typeof(T).GetGenericArguments() is { Length: 1 } arguments &&
161172
arguments[0].IsEnum)
162173
{
163-
value = (T)Enum.Parse(arguments[0], propertyValue as string );
174+
if (!Enum.IsDefined(arguments[0], propertyValue as string))
175+
return false;
176+
177+
value = (T)Enum.Parse(arguments[0], propertyValue as string);
164178
}
165179
else if (typeof(T) == typeof(ETag))
166180
{
167-
value = (T)(object) new ETag(propertyValue as string);
181+
value = (T)(object)new ETag(propertyValue as string);
168182
}
169183

170184
return true;

sdk/tables/Azure.Data.Tables/tests/TableClientLiveTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,29 @@ public async Task CustomEntityCanBeUpserted()
937937
Assert.That(updatedEntity[propertyName], Is.EqualTo(updatedValue), $"The property value should be {updatedValue}");
938938
}
939939

940+
[RecordedTest]
941+
public async Task TimespanPropertyInCustomEntityCanBeUpsertedAndRead()
942+
{
943+
const string rowKeyValue = "1";
944+
DateTime date1 = new DateTime(2010, 1, 1, 8, 0, 15);
945+
DateTime date2 = new DateTime(2010, 8, 18, 13, 30, 30);
946+
TimeSpan interval = date2 - date1;
947+
var entity = new TimeSpanTestEntity { PartitionKey = PartitionKeyValue, RowKey = rowKeyValue, TimespanProperty = interval, };
948+
949+
// Create the new entity.
950+
await client.UpsertEntityAsync(entity, TableUpdateMode.Replace).ConfigureAwait(false);
951+
952+
// Fetch the created entity from the service.
953+
var retrievedEntity = (await client.QueryAsync<TimeSpanTestEntity>($"PartitionKey eq '{PartitionKeyValue}' and RowKey eq '{rowKeyValue}'")
954+
.ToEnumerableAsync()
955+
.ConfigureAwait(false)).Single();
956+
957+
Assert.IsNotNull(retrievedEntity, "The entity should not be null");
958+
Assert.IsTrue(TimeSpan.Compare(
959+
retrievedEntity!.TimespanProperty.Value, interval) == 0,
960+
"The property value should be equal to the original value");
961+
}
962+
940963
/// <summary>
941964
/// Validates the functionality of the TableClient.
942965
/// </summary>

sdk/tables/Azure.Data.Tables/tests/TableClientTests.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Diagnostics.Tracing;
77
using System.Linq;
88
using System.Net;
9-
using System.Reflection.Metadata;
109
using System.Threading.Tasks;
1110
using Azure.Core;
1211
using Azure.Core.Diagnostics;
@@ -370,6 +369,33 @@ public void EnumPropertiesAreDeSerializedProperly()
370369
Assert.That(dictEntity.TryGetValue(TableConstants.PropertyNames.Timestamp, out var _), Is.False, "Only PK, RK, and user properties should be sent");
371370
}
372371

372+
// This test validates that a table entity with an enum property that has an unknown enum value is not deserialized.
373+
[Test]
374+
public void EnumPropertiesWithUnknownValuesAreNotDeserialized()
375+
{
376+
Foo foo = (Foo)3;
377+
NullableFoo? nullableFoo = (NullableFoo)3;
378+
var entity = new EnumEntity
379+
{
380+
PartitionKey = "partitionKey",
381+
RowKey = "01",
382+
Timestamp = DateTime.Now,
383+
MyFoo = foo,
384+
MyNullableFoo = nullableFoo,
385+
ETag = ETag.All
386+
};
387+
388+
// Create the new entities.
389+
var dictEntity = entity.ToOdataAnnotatedDictionary();
390+
var deserializedEntity = dictEntity.ToTableEntity<EnumEntity>();
391+
Assert.That(deserializedEntity.PartitionKey, Is.EqualTo(entity.PartitionKey), "The entities should be equivalent");
392+
Assert.That(deserializedEntity.RowKey, Is.EqualTo(entity.RowKey), "The entities should be equivalent");
393+
Assert.That(deserializedEntity.MyFoo.ToString(), Is.EqualTo(default(Foo).ToString()), "The non-existing enum value should not be deserialized.");
394+
Assert.IsNull(deserializedEntity.MyNullableFoo, "The non-existing nullable enum value should not be deserialized.");
395+
Assert.IsNull(deserializedEntity.MyNullableFoo2, "The entities should be equivalent.");
396+
Assert.That(dictEntity.TryGetValue(TableConstants.PropertyNames.Timestamp, out var _), Is.False, "Only PK, RK, and user properties should be sent");
397+
}
398+
373399
[Test]
374400
public void RoundTripContinuationTokenWithPartitionKeyAndRowKey()
375401
{

sdk/tables/Azure.Data.Tables/tests/TableServiceLiveTestsBase.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,15 @@ public class SimpleTestEntity : ITableEntity
421421
public ETag ETag { get; set; }
422422
}
423423

424+
public class TimeSpanTestEntity : ITableEntity
425+
{
426+
public string PartitionKey { get; set; }
427+
public string RowKey { get; set; }
428+
public DateTimeOffset? Timestamp { get; set; }
429+
public ETag ETag { get; set; }
430+
public TimeSpan? TimespanProperty { get; set; }
431+
}
432+
424433
public class ComplexEntity : ITableEntity
425434
{
426435
public const int NumberOfNonNullProperties = 28;

0 commit comments

Comments
 (0)