diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs index 329ad6834b2f..003508451426 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs @@ -103,6 +103,7 @@ public void RegisterTableDefinition(Table table) /// Constructs a DynamoDBContext object with a default AmazonDynamoDBClient /// client and a default DynamoDBContextConfig object for configuration. /// + [Obsolete("Use the DynamoDBContextBuilder to construct a DynamoDBContext with the recommended configuration.")] public DynamoDBContext() : this(new AmazonDynamoDBClient()) { } @@ -111,6 +112,7 @@ public DynamoDBContext() /// client and a default DynamoDBContextConfig object for configuration. /// /// The region to configure the AmazonDynamoDBClient to use. + [Obsolete("Use the DynamoDBContextBuilder to construct a DynamoDBContext with the recommended configuration.")] public DynamoDBContext(RegionEndpoint region) : this(new AmazonDynamoDBClient(region), true, new DynamoDBContextConfig()) { } @@ -119,6 +121,7 @@ public DynamoDBContext(RegionEndpoint region) /// Uses a default AmazonDynamoDBClient as the client. /// /// + [Obsolete("Use the DynamoDBContextBuilder to construct a DynamoDBContext with the recommended configuration.")] public DynamoDBContext(DynamoDBContextConfig config) : this(new AmazonDynamoDBClient(), config) { } @@ -128,6 +131,7 @@ public DynamoDBContext(DynamoDBContextConfig config) /// /// The region to configure the AmazonDynamoDBClient to use. /// + [Obsolete("Use the DynamoDBContextBuilder to construct a DynamoDBContext with the recommended configuration.")] public DynamoDBContext(RegionEndpoint region, DynamoDBContextConfig config) : this(new AmazonDynamoDBClient(region), true, config) { } #endif @@ -137,6 +141,7 @@ public DynamoDBContext(RegionEndpoint region, DynamoDBContextConfig config) /// Uses default DynamoDBContextConfig object for configuration. /// /// Client to use for making calls + [Obsolete("Use the DynamoDBContextBuilder to construct a DynamoDBContext with the recommended configuration.")] public DynamoDBContext(IAmazonDynamoDB client) : this(client, false, new DynamoDBContextConfig()) { } @@ -146,10 +151,11 @@ public DynamoDBContext(IAmazonDynamoDB client) /// /// Client to use for making calls /// Configuration to use + [Obsolete("Use the DynamoDBContextBuilder to construct a DynamoDBContext with the recommended configuration.")] public DynamoDBContext(IAmazonDynamoDB client, DynamoDBContextConfig config) : this(client, false, config) { } - private DynamoDBContext(IAmazonDynamoDB client, bool ownClient, DynamoDBContextConfig config) + internal DynamoDBContext(IAmazonDynamoDB client, bool ownClient, DynamoDBContextConfig config) { if (client == null) throw new ArgumentNullException("client"); diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextBuilder.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextBuilder.cs new file mode 100644 index 000000000000..0ffe72e69eed --- /dev/null +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextBuilder.cs @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; + +namespace Amazon.DynamoDBv2.DataModel +{ + /// +#if NET8_0_OR_GREATER + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(Amazon.DynamoDBv2.Custom.Internal.InternalConstants.RequiresUnreferencedCodeMessage)] +#endif + public class DynamoDBContextBuilder : IDynamoDBContextBuilder + { + /// + /// The object that is built and then supplied to the returned + /// + private DynamoDBContextConfig _config; + + /// + /// A factory method for creating a client + /// + private Func _clientFactory; + + /// + /// Creates a builder object to construct a + /// + public DynamoDBContextBuilder() + { + _config = new DynamoDBContextConfig(); + _config.DisableFetchingTableMetadata = true; + } + + /// + public IDynamoDBContextBuilder ConfigureContext(Action configure) + { + configure(_config); + + return this; + } + + /// + public IDynamoDBContextBuilder WithDynamoDBClient(Func factory) + { + _clientFactory = factory; + + return this; + } + + /// + public DynamoDBContext Build() + { + IAmazonDynamoDB client; + bool ownClient; + + if (_clientFactory is null) + { + client = new AmazonDynamoDBClient(); + ownClient = true; + } + else + { + client = _clientFactory.Invoke(); + ownClient = false; + } + + return new DynamoDBContext(client, ownClient, _config); + } + } +} diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextInternal.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextInternal.cs index 447c52c65dcd..3d0431f664cc 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextInternal.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextInternal.cs @@ -147,6 +147,9 @@ internal Table GetTargetTable(ItemStorageConfig storageConfig, DynamoDBFlatConfi return table; } +// This is the call we want to avoid with disableFetchingTableMetadata = true, but as long as we still support false, we still need to call the discouraged sync-over-async 'Table.LoadTable(Client, emptyConfig)' +#pragma warning disable CS0618 + // Retrieves Config-less Table from cache or constructs it on cache-miss // This Table should not be used for data operations. // To use for data operations, Copy with a TableConfig first. @@ -203,6 +206,8 @@ internal Table GetUnconfiguredTable(string tableName, bool disableFetchingTableM } } +#pragma warning restore CS0618 + /// /// Stores a table in the cache if there is not an existing entry for the given key /// diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContextBuilder.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContextBuilder.cs new file mode 100644 index 000000000000..a48596d2d717 --- /dev/null +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContextBuilder.cs @@ -0,0 +1,59 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; + +namespace Amazon.DynamoDBv2.DataModel +{ + /// + /// Interface for a builder that constructs a + /// Using to construct a will implicitly set + /// to true which avoids the DescribeTable call + /// and relies entirely on the DynamoDB attributes set on the .NET classes. Alternatively, you can register the + /// table definition using . + /// If needed, you can revert back to the previous behavior by setting + /// to false using as such: + /// + /// var context = new DynamoDBContextBuilder() + /// .ConfigureContext(x => + /// { + /// x.DisableFetchingTableMetadata = false; + /// }) + /// .Build(); + /// + /// + public interface IDynamoDBContextBuilder + { + /// + /// Supplies a factory method for creating a client. + /// If a factory method is not provided, a new client + /// will be created using the environment to search for credentials and region configuration. + /// + /// Factory method for creating a client + IDynamoDBContextBuilder WithDynamoDBClient(Func factory); + + /// + /// Configures the that is being constructed + /// + /// The configuration applied to the constructed + IDynamoDBContextBuilder ConfigureContext(Action configure); + + /// + /// Call at the end to retrieve the new + /// + /// Built + DynamoDBContext Build(); + } +} diff --git a/sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Table.cs b/sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Table.cs index 6a30637db5f6..17c04ce926c9 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Table.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Table.cs @@ -443,6 +443,7 @@ public static void ClearTableCache() /// Client to use to access DynamoDB. /// Configuration to use for the table. /// Table object representing the specified table. + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static Table LoadTable(IAmazonDynamoDB ddbClient, TableConfig config) { Table table = new Table(ddbClient, config); @@ -648,6 +649,7 @@ internal static Table CreateTableFromItemStorageConfig(IAmazonDynamoDB client, T /// Client to use to access DynamoDB. /// Name of the table. /// Table object representing the specified table. + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static Table LoadTable(IAmazonDynamoDB ddbClient, string tableName) { return LoadTable(ddbClient, tableName, DynamoDBEntryConversion.CurrentConversion, false); @@ -663,6 +665,7 @@ public static Table LoadTable(IAmazonDynamoDB ddbClient, string tableName) /// Name of the table. /// Conversion to use for converting .NET values to DynamoDB values. /// Table object representing the specified table. + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static Table LoadTable(IAmazonDynamoDB ddbClient, string tableName, DynamoDBEntryConversion conversion) { return LoadTable(ddbClient, tableName, conversion, false); @@ -678,6 +681,7 @@ public static Table LoadTable(IAmazonDynamoDB ddbClient, string tableName, Dynam /// Name of the table. /// If the property is false, empty string values will be interpreted as null values. /// Table object representing the specified table. + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static Table LoadTable(IAmazonDynamoDB ddbClient, string tableName, bool isEmptyStringValueEnabled) { return LoadTable(ddbClient, tableName, DynamoDBEntryConversion.CurrentConversion, isEmptyStringValueEnabled); @@ -694,6 +698,7 @@ public static Table LoadTable(IAmazonDynamoDB ddbClient, string tableName, bool /// Conversion to use for converting .NET values to DynamoDB values. /// If the property is false, empty string values will be interpreted as null values. /// Table object representing the specified table. + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static Table LoadTable(IAmazonDynamoDB ddbClient, string tableName, DynamoDBEntryConversion conversion, bool isEmptyStringValueEnabled) { var config = new TableConfig(tableName, conversion, DynamoDBConsumer.DocumentModel, storeAsEpoch: null, isEmptyStringValueEnabled: isEmptyStringValueEnabled, metadataCachingMode: MetadataCachingMode.Default); @@ -715,6 +720,7 @@ public static Table LoadTable(IAmazonDynamoDB ddbClient, string tableName, Dynam /// requests. This controls how the cache key is derived, which influences when the SDK will call /// IAmazonDynamoDB.DescribeTable(string) internally to populate the cache. /// Table object representing the specified table. + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static Table LoadTable(IAmazonDynamoDB ddbClient, string tableName, DynamoDBEntryConversion conversion, bool isEmptyStringValueEnabled, MetadataCachingMode metadataCachingMode) { var config = new TableConfig(tableName, conversion, DynamoDBConsumer.DocumentModel, storeAsEpoch: null, isEmptyStringValueEnabled: isEmptyStringValueEnabled, metadataCachingMode: metadataCachingMode); @@ -736,6 +742,7 @@ public static Table LoadTable(IAmazonDynamoDB ddbClient, string tableName, Dynam /// /// True if table was successfully loaded; otherwise false. /// + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static bool TryLoadTable(IAmazonDynamoDB ddbClient, string tableName, out Table table) { return TryLoadTable(ddbClient, tableName, DynamoDBEntryConversion.CurrentConversion, false, out table); @@ -754,6 +761,7 @@ public static bool TryLoadTable(IAmazonDynamoDB ddbClient, string tableName, out /// /// True if table was successfully loaded; otherwise false. /// + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static bool TryLoadTable(IAmazonDynamoDB ddbClient, string tableName, DynamoDBEntryConversion conversion, out Table table) { return TryLoadTable(ddbClient, tableName, conversion, false, out table); @@ -772,6 +780,7 @@ public static bool TryLoadTable(IAmazonDynamoDB ddbClient, string tableName, Dyn /// /// True if table was successfully loaded; otherwise false. /// + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static bool TryLoadTable(IAmazonDynamoDB ddbClient, string tableName, bool isEmptyStringValueEnabled, out Table table) { return TryLoadTable(ddbClient, tableName, DynamoDBEntryConversion.CurrentConversion, isEmptyStringValueEnabled, out table); @@ -791,6 +800,7 @@ public static bool TryLoadTable(IAmazonDynamoDB ddbClient, string tableName, boo /// /// True if table was successfully loaded; otherwise false. /// + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static bool TryLoadTable(IAmazonDynamoDB ddbClient, string tableName, DynamoDBEntryConversion conversion, bool isEmptyStringValueEnabled, out Table table) { return TryLoadTable(ddbClient, tableName, conversion, isEmptyStringValueEnabled, MetadataCachingMode.Default, out table); @@ -813,6 +823,7 @@ public static bool TryLoadTable(IAmazonDynamoDB ddbClient, string tableName, Dyn /// /// True if table was successfully loaded; otherwise false. /// + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static bool TryLoadTable(IAmazonDynamoDB ddbClient, string tableName, DynamoDBEntryConversion conversion, @@ -842,6 +853,7 @@ public static bool TryLoadTable(IAmazonDynamoDB ddbClient, /// /// True if table was successfully loaded; otherwise false. /// + [Obsolete("Use the TableBuilder to construct a Table with the recommended configuration.")] public static bool TryLoadTable(IAmazonDynamoDB ddbClient, TableConfig config, out Table table) { if (config == null) diff --git a/sdk/test/Services/DynamoDBv2/IntegrationTests/Cache.cs b/sdk/test/Services/DynamoDBv2/IntegrationTests/Cache.cs index d146ab3e86a4..6f48710b2e86 100644 --- a/sdk/test/Services/DynamoDBv2/IntegrationTests/Cache.cs +++ b/sdk/test/Services/DynamoDBv2/IntegrationTests/Cache.cs @@ -103,11 +103,15 @@ public void MultipleClientsTest() Table table; using (var nc = new AmazonDynamoDBClient()) { +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors table = Table.LoadTable(nc, tableName); +#pragma warning restore CS0618 // Re-enable the warning } Table.ClearTableCache(); +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors table = Table.LoadTable(client, tableName); +#pragma warning restore CS0618 // Re-enable the warning } [TestMethod] @@ -123,7 +127,9 @@ public void ChangingTableTest() var tableCache = SdkCache.GetCache(client, DynamoDBTests.TableCacheIdentifier, StringComparer.Ordinal); CreateTable(TABLENAME, defaultKeys: true); +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors var table = Table.LoadTable(client, TABLENAME); +#pragma warning restore CS0618 // Re-enable the warning table.PutItem(item); using (var counter = new ServiceResponseCounter(client)) @@ -144,7 +150,9 @@ public void ChangingTableTest() counter.Reset(); Table.ClearTableCache(); +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors table = Table.LoadTable(client, TABLENAME); +#pragma warning restore CS0618 // Re-enable the warning doc = table.GetItem(42, "Yes"); Assert.IsNotNull(doc); Assert.AreNotEqual(0, doc.Count); @@ -153,11 +161,13 @@ public void ChangingTableTest() counter.Reset(); Table.ClearTableCache(); PutItem(tableCache, TABLENAME, oldTableDescription); +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors table = Table.LoadTable(client, TABLENAME); doc = tableCache.UseCache(TABLENAME, () => table.GetItem(42, "Yes"), () => table = Table.LoadTable(client, TABLENAME), shouldRetryForException: null); +#pragma warning restore CS0618 // Re-enable the warning Assert.IsNotNull(doc); Assert.AreNotEqual(0, doc.Count); diff --git a/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs b/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs index 078ea6d7e3a7..aa8da86a6764 100644 --- a/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs +++ b/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs @@ -202,7 +202,9 @@ public void TestContext_RetrieveDateTimeInUtc(bool retrieveDateTimeInUtc) Conversion = DynamoDBEntryConversion.V2, RetrieveDateTimeInUtc = retrieveDateTimeInUtc }; +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors Context = new DynamoDBContext(Client, config); +#pragma warning restore CS0618 // Re-enable the warning var currTime = DateTime.Now; @@ -271,7 +273,9 @@ public void TestContext_CustomDateTimeConverter(bool retrieveDateTimeInUtc) Conversion = DynamoDBEntryConversion.V2, RetrieveDateTimeInUtc = retrieveDateTimeInUtc }; +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors Context = new DynamoDBContext(Client, config); +#pragma warning restore CS0618 // Re-enable the warning // Add a custom DateTime converter Context.ConverterCache.Add(typeof(DateTime), new DateTimeUtcConverter()); @@ -340,7 +344,9 @@ public void TestContext_RetrieveDateTimeInUtc_OperationConfig(bool retrieveDateT CleanupTables(); TableCache.Clear(); +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors Context = new DynamoDBContext(Client, new DynamoDBContextConfig { Conversion = DynamoDBEntryConversion.V2 }); +#pragma warning restore CS0618 // Re-enable the warning var operationConfig = new DynamoDBOperationConfig { RetrieveDateTimeInUtc = retrieveDateTimeInUtc }; var currTime = DateTime.Now; @@ -411,12 +417,14 @@ public void TestWithBuilderTables() // Clear existing SDK-wide cache TableCache.Clear(); +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors // Redeclare Context, which will start with empty caches Context = new DynamoDBContext(Client, new DynamoDBContextConfig { IsEmptyStringValueEnabled = true, Conversion = conversion }); +#pragma warning restore CS0618 // Re-enable the warning Context.RegisterTableDefinition(new TableBuilder(Client, "DotNetTests-HashRangeTable") .AddHashKey("Name", DynamoDBEntryType.String) @@ -455,6 +463,50 @@ public void TestWithBuilderTables() } } + /// + /// Runs the same object-mapper integration tests as , + /// but using a DynamoDBContext created by + /// + [TestMethod] + [TestCategory("DynamoDBv2")] + public void TestWithBuilderContext() + { + foreach (var conversion in new DynamoDBEntryConversion[] { DynamoDBEntryConversion.V1, DynamoDBEntryConversion.V2 }) + { + // Cleanup existing data in the tables + CleanupTables(); + + // Clear existing SDK-wide cache + TableCache.Clear(); + + Context = new DynamoDBContextBuilder() + .ConfigureContext(x => + { + x.IsEmptyStringValueEnabled = true; + x.Conversion = conversion; + }) + .Build(); + + TestEmptyStringsWithFeatureEnabled(); + + TestEnumHashKeyObjects(); + + TestEmptyCollections(conversion); + + TestAnnotatedUnsupportedTypes(); + TestEnums(conversion); + + TestHashObjects(); + TestHashRangeObjects(); + TestOtherContextOperations(); + TestBatchOperations(); + TestAnnotatedTransactionOperations(); + TestAnnotatedMultiTableTransactionOperations(); + + TestStoreAsAnnotatedEpoch(); + } + } + private static void TestEmptyStringsWithFeatureEnabled() { var product = new Product @@ -575,6 +627,37 @@ private static void TestUnsupportedTypes() "Type AWSSDK_DotNet.IntegrationTests.Tests.DynamoDB.DynamoDBTests+EmptyType is unsupported, it has no supported members"); } + private static void TestAnnotatedUnsupportedTypes() + { + // Verify that saving objects with invalid properties result in exceptions + var employee4 = new Employee4 + { + Name = "Alan", + Age = 31, + TimeWithCompany = TimeSpan.FromDays(300) + }; + AssertExtensions.ExpectException(() => Context.Save(employee4), + typeof(InvalidOperationException), + "Type System.TimeSpan is unsupported, it cannot be instantiated"); + var employee5 = new Employee5 + { + Name = "Alan", + Age = 31, + EmptyProperty = new EmptyType() + }; + AssertExtensions.ExpectException(() => Context.Save(employee5), + typeof(InvalidOperationException), + "Type AWSSDK_DotNet.IntegrationTests.Tests.DynamoDB.DynamoDBTests+EmptyType is unsupported, it has no supported members"); + + // Verify that objects that are invalid result in exceptions + AssertExtensions.ExpectException(() => Context.Scan(), + typeof(InvalidOperationException), + "Type System.TimeSpan is unsupported, it cannot be instantiated"); + AssertExtensions.ExpectException(() => Context.Scan(), + typeof(InvalidOperationException), + "Type AWSSDK_DotNet.IntegrationTests.Tests.DynamoDB.DynamoDBTests+EmptyType is unsupported, it has no supported members"); + } + private void TestContextConversions() { var conversionV1 = DynamoDBEntryConversion.V1; @@ -612,6 +695,7 @@ private void TestContextConversions() } { +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors using (var contextV1 = new DynamoDBContext(Client, new DynamoDBContextConfig { Conversion = conversionV1 })) using (var contextV2 = new DynamoDBContext(Client, new DynamoDBContextConfig { Conversion = conversionV2 })) { @@ -619,14 +703,17 @@ private void TestContextConversions() var docV2 = contextV2.ToDocument(product); VerifyConversions(docV1, docV2); } +#pragma warning restore CS0618 // Re-enable the warning } { +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors using (var contextV1 = new DynamoDBContext(Client, new DynamoDBContextConfig { Conversion = conversionV1 })) { contextV1.Save(product); contextV1.Save(product, new SaveConfig { Conversion = conversionV2 }); } +#pragma warning restore CS0618 // Re-enable the warning } // Introduce a circular reference and try to serialize @@ -1534,6 +1621,209 @@ private void TestTransactionOperations() } } + private void TestAnnotatedTransactionOperations() + { + var employee1 = new VersionedAnnotatedEmployee + { + Name = "Mark", + Age = 31, + Score = 120, + ManagerName = "Harmony" + }; + var employee2 = new VersionedAnnotatedEmployee + { + Name = "Helena", + Age = 25, + Score = 140 + }; + var employee3 = new VersionedAnnotatedEmployee + { + Name = "Irving", + Age = 55, + Score = 100 + }; + + { + var transactWrite = Context.CreateTransactWrite(); + transactWrite.AddSaveItems(new List { employee1, employee2 }); + transactWrite.AddSaveItem(employee3); + transactWrite.Execute(); + + Assert.IsNotNull(employee1.Version); + Assert.IsNotNull(employee2.Version); + Assert.IsNotNull(employee3.Version); + } + + { + var transactGet = Context.CreateTransactGet(); + transactGet.AddKeys(new List { employee1, employee2 }); + transactGet.AddKey(employee3.Name, employee3.Age); + transactGet.Execute(); + + Assert.IsNotNull(transactGet.Results); + Assert.AreEqual(3, transactGet.Results.Count); + Assert.AreEqual(employee1.Name, transactGet.Results[0].Name); + Assert.AreEqual(employee1.Version, transactGet.Results[0].Version); + Assert.AreEqual(employee2.Name, transactGet.Results[1].Name); + Assert.AreEqual(employee2.Version, transactGet.Results[1].Version); + Assert.AreEqual(employee3.Name, transactGet.Results[2].Name); + Assert.AreEqual(employee3.Version, transactGet.Results[2].Version); + } + + { + var originalVersion = employee1.Version.Value; + employee1.Version = null; + employee1.Score++; + + var transactWrite = Context.CreateTransactWrite(); + transactWrite.AddSaveItem(employee1); + transactWrite.AddVersionCheckKey(employee2.Name, employee2.Age, employee2.Version); + transactWrite.AddDeleteItem(employee3); + var ex = AssertExtensions.ExpectException(() => transactWrite.Execute()); + + Assert.IsNotNull(ex.CancellationReasons); + Assert.AreEqual(3, ex.CancellationReasons.Count); + Assert.AreEqual("ConditionalCheckFailed", ex.CancellationReasons[0].Code); + Assert.AreEqual("None", ex.CancellationReasons[1].Code); + Assert.AreEqual("None", ex.CancellationReasons[2].Code); + + employee1.Version = originalVersion; + employee1.Score--; + } + + { + var transactGet = Context.CreateTransactGet(); + transactGet.AddKey(employee1); + transactGet.AddKeys(new List { employee2, employee3 }); + transactGet.Execute(); + + Assert.IsNotNull(transactGet.Results); + Assert.AreEqual(3, transactGet.Results.Count); + Assert.AreEqual(employee1.Score, transactGet.Results[0].Score); + Assert.AreEqual(employee1.Version, transactGet.Results[0].Version); + Assert.AreEqual(employee2.Version, transactGet.Results[1].Version); + Assert.AreEqual(employee3.Version, transactGet.Results[2].Version); + } + + { + employee1.Score++; + employee2.Version++; + + var transactWrite = Context.CreateTransactWrite(); + transactWrite.AddSaveItem(employee1); + transactWrite.AddVersionCheckItem(employee2); + transactWrite.AddDeleteItem(employee3); + var ex = AssertExtensions.ExpectException(() => transactWrite.Execute()); + + Assert.IsNotNull(ex.CancellationReasons); + Assert.AreEqual(3, ex.CancellationReasons.Count); + Assert.AreEqual("None", ex.CancellationReasons[0].Code); + Assert.AreEqual("ConditionalCheckFailed", ex.CancellationReasons[1].Code); + Assert.AreEqual("None", ex.CancellationReasons[2].Code); + + employee1.Score--; + employee2.Version--; + } + + { + var transactGet = Context.CreateTransactGet(); + transactGet.AddKeys(new List { employee1, employee2 }); + transactGet.AddKey(employee3.Name, employee3.Age); + transactGet.Execute(); + + Assert.IsNotNull(transactGet.Results); + Assert.AreEqual(3, transactGet.Results.Count); + Assert.AreEqual(employee1.Score, transactGet.Results[0].Score); + Assert.AreEqual(employee1.Version, transactGet.Results[0].Version); + Assert.AreEqual(employee2.Version, transactGet.Results[1].Version); + Assert.AreEqual(employee3.Version, transactGet.Results[2].Version); + } + + { + employee1.Score++; + employee3.Version--; + + var transactWrite = Context.CreateTransactWrite(); + transactWrite.AddSaveItem(employee1); + transactWrite.AddVersionCheckKey(employee2.Name, employee2.Age, employee2.Version); + transactWrite.AddDeleteItem(employee3); + var ex = AssertExtensions.ExpectException(() => transactWrite.Execute()); + + Assert.IsNotNull(ex.CancellationReasons); + Assert.AreEqual(3, ex.CancellationReasons.Count); + Assert.AreEqual("None", ex.CancellationReasons[0].Code); + Assert.AreEqual("None", ex.CancellationReasons[1].Code); + Assert.AreEqual("ConditionalCheckFailed", ex.CancellationReasons[2].Code); + + employee1.Score--; + employee3.Version++; + } + + { + var transactGet = Context.CreateTransactGet(); + transactGet.AddKey(employee1); + transactGet.AddKeys(new List { employee2, employee3 }); + transactGet.Execute(); + + Assert.IsNotNull(transactGet.Results); + Assert.AreEqual(3, transactGet.Results.Count); + Assert.AreEqual(employee1.Score, transactGet.Results[0].Score); + Assert.AreEqual(employee1.Version, transactGet.Results[0].Version); + Assert.AreEqual(employee2.Version, transactGet.Results[1].Version); + Assert.AreEqual(employee3.Version, transactGet.Results[2].Version); + } + + { + var originalVersion1 = employee1.Version.Value; + var originalVersion2 = employee2.Version.Value; + var originalVersion3 = employee3.Version.Value; + employee1.Score++; + employee1.ManagerName = null; + + var transactWrite = Context.CreateTransactWrite(); + transactWrite.AddSaveItem(employee1); + transactWrite.AddVersionCheckItem(employee2); + transactWrite.AddDeleteItem(employee3); + transactWrite.Execute(); + + Assert.AreEqual(originalVersion1 + 1, employee1.Version); + Assert.AreEqual(originalVersion2, employee2.Version); + Assert.AreEqual(originalVersion3, employee3.Version); + } + + { + var transactGet = Context.CreateTransactGet(); + transactGet.AddKeys(new List { employee1, employee2 }); + transactGet.AddKey(employee3.Name, employee3.Age); + transactGet.Execute(); + + Assert.IsNotNull(transactGet.Results); + Assert.AreEqual(2, transactGet.Results.Count); + Assert.AreEqual(employee1.Name, transactGet.Results[0].Name); + Assert.AreEqual(employee1.Score, transactGet.Results[0].Score); + Assert.IsNull(transactGet.Results[0].ManagerName); + Assert.AreEqual(employee1.Version, transactGet.Results[0].Version); + Assert.AreEqual(employee2.Name, transactGet.Results[1].Name); + Assert.AreEqual(employee2.Version, transactGet.Results[1].Version); + } + + { + var transactWrite = Context.CreateTransactWrite(); + transactWrite.AddDeleteItem(employee1); + transactWrite.AddDeleteKey(employee2.Name, employee2.Age); + transactWrite.Execute(); + } + + { + var transactGet = Context.CreateTransactGet(); + transactGet.AddKeys(new List { employee1, employee2, employee3 }); + transactGet.Execute(); + + Assert.IsNotNull(transactGet.Results); + Assert.AreEqual(0, transactGet.Results.Count); + } + } + private void TestMultiTableTransactionOperations() { var employee1 = new VersionedEmployee @@ -1691,6 +1981,163 @@ private void TestMultiTableTransactionOperations() } } + private void TestAnnotatedMultiTableTransactionOperations() + { + var employee1 = new VersionedAnnotatedEmployee + { + Name = "Alan", + Age = 31, + Score = 120, + ManagerName = "Barbara" + }; + var employee2 = new VersionedAnnotatedEmployee + { + Name = "Diane", + Age = 40, + Score = 140 + }; + var product = new VersionedProduct + { + Id = 1001, + Name = "CloudSpotter", + Price = 1200 + }; + + { + var employeeTran = Context.CreateTransactWrite(); + employeeTran.AddSaveItems(new[] { employee1, employee2 }); + var productTran = Context.CreateTransactWrite(); + productTran.AddSaveItem(product); + var tran = Context.CreateMultiTableTransactWrite(employeeTran, productTran); + tran.Execute(); + + Assert.IsNotNull(employee1.Version); + Assert.IsNotNull(employee2.Version); + Assert.IsNotNull(product.Version); + } + + { + var employeeTran = Context.CreateTransactGet(); + employeeTran.AddKeys(new[] { employee1, employee2 }); + var productTran = Context.CreateTransactGet(); + productTran.AddKey(product.Id); + var tran = Context.CreateMultiTableTransactGet(employeeTran, productTran); + tran.Execute(); + + Assert.IsNotNull(employeeTran.Results); + Assert.AreEqual(2, employeeTran.Results.Count); + Assert.AreEqual(employee1.Name, employeeTran.Results[0].Name); + Assert.AreEqual(employee1.Version, employeeTran.Results[0].Version); + Assert.AreEqual(employee2.Name, employeeTran.Results[1].Name); + Assert.AreEqual(employee2.Version, employeeTran.Results[1].Version); + Assert.IsNotNull(productTran.Results); + Assert.AreEqual(1, productTran.Results.Count); + Assert.AreEqual(product.Id, productTran.Results[0].Id); + Assert.AreEqual(product.Version, productTran.Results[0].Version); + } + + { + employee1.Score++; + + var employeeTran = Context.CreateTransactWrite(); + employeeTran.AddSaveItem(employee1); + employeeTran.AddDeleteItem(employee2); + var productTran = Context.CreateTransactWrite(); + productTran.AddVersionCheckKey(product.Id, product.Version.Value + 1); + var tran = Context.CreateMultiTableTransactWrite(employeeTran, productTran); + var ex = AssertExtensions.ExpectException(() => tran.Execute()); + + Assert.IsNotNull(ex.CancellationReasons); + Assert.AreEqual(3, ex.CancellationReasons.Count); + Assert.AreEqual("None", ex.CancellationReasons[0].Code); + Assert.AreEqual("None", ex.CancellationReasons[1].Code); + Assert.AreEqual("ConditionalCheckFailed", ex.CancellationReasons[2].Code); + + employee1.Score--; + } + + { + var employeeTran = Context.CreateTransactGet(); + employeeTran.AddKeys(new[] { employee1, employee2 }); + var productTran = Context.CreateTransactGet(); + productTran.AddKey(product); + var tran = Context.CreateMultiTableTransactGet(employeeTran, productTran); + tran.Execute(); + + Assert.IsNotNull(employeeTran.Results); + Assert.AreEqual(2, employeeTran.Results.Count); + Assert.AreEqual(employee1.Score, employeeTran.Results[0].Score); + Assert.AreEqual(employee1.Version, employeeTran.Results[0].Version); + Assert.AreEqual(employee2.Version, employeeTran.Results[1].Version); + Assert.IsNotNull(productTran.Results); + Assert.AreEqual(1, productTran.Results.Count); + Assert.AreEqual(product.Version, productTran.Results[0].Version); + } + + { + var originalEmployeeVersion1 = employee1.Version.Value; + var originalEmployeeVersion2 = employee2.Version.Value; + var originalProductVersion = product.Version.Value; + employee1.Score++; + employee1.ManagerName = null; + + var employeeTran = Context.CreateTransactWrite(); + employeeTran.AddSaveItem(employee1); + employeeTran.AddDeleteItem(employee2); + var productTran = Context.CreateTransactWrite(); + productTran.AddVersionCheckItem(product); + var tran = Context.CreateMultiTableTransactWrite(employeeTran, productTran); + tran.Execute(); + + Assert.AreEqual(originalEmployeeVersion1 + 1, employee1.Version); + Assert.AreEqual(originalEmployeeVersion2, employee2.Version); + Assert.AreEqual(originalProductVersion, product.Version); + } + + { + var employeeTran = Context.CreateTransactGet(); + employeeTran.AddKeys(new[] { employee1, employee2 }); + var productTran = Context.CreateTransactGet(); + productTran.AddKey(product.Id); + var tran = Context.CreateMultiTableTransactGet(employeeTran, productTran); + tran.Execute(); + + Assert.IsNotNull(employeeTran.Results); + Assert.AreEqual(1, employeeTran.Results.Count); + Assert.AreEqual(employee1.Name, employeeTran.Results[0].Name); + Assert.AreEqual(employee1.Score, employeeTran.Results[0].Score); + Assert.IsNull(employeeTran.Results[0].ManagerName); + Assert.AreEqual(employee1.Version, employeeTran.Results[0].Version); + Assert.IsNotNull(productTran.Results); + Assert.AreEqual(1, productTran.Results.Count); + Assert.AreEqual(product.Id, productTran.Results[0].Id); + Assert.AreEqual(product.Version, productTran.Results[0].Version); + } + + { + var employeeTran = Context.CreateTransactWrite(); + employeeTran.AddDeleteItem(employee1); + var productTran = Context.CreateTransactWrite(); + productTran.AddDeleteKey(product.Id); + var tran = Context.CreateMultiTableTransactWrite(employeeTran, productTran); + tran.Execute(); + } + + { + var employeeTran = Context.CreateTransactGet(); + employeeTran.AddKeys(new[] { employee1, employee2 }); + var productTran = Context.CreateTransactGet(); + productTran.AddKey(product); + var tran = Context.CreateMultiTableTransactGet(employeeTran, productTran); + tran.Execute(); + + Assert.IsNotNull(employeeTran.Results); + Assert.AreEqual(0, employeeTran.Results.Count); + Assert.IsNotNull(productTran.Results); + Assert.AreEqual(0, productTran.Results.Count); + } + } + private void TestOtherContextOperations() { Employee employee1 = new Employee @@ -1976,6 +2423,22 @@ public class Employee3 : Employee public EmptyType EmptyProperty { get; set; } } + /// + /// Annotated class with a property of a type that has no valid constructor + /// + public class Employee4 : AnnotatedEmployee + { + public TimeSpan TimeWithCompany { get; set; } + } + + /// + /// Annotated class with a property of an empty type + /// + public class Employee5 : AnnotatedEmployee + { + public EmptyType EmptyProperty { get; set; } + } + /// /// Empty type /// @@ -1991,6 +2454,16 @@ public class VersionedEmployee : Employee public int? Version { get; set; } } + /// + /// Class representing items in the table [TableNamePrefix]HashTable + /// This class uses optimistic locking via the Version field + /// + public class VersionedAnnotatedEmployee : AnnotatedEmployee + { + [DynamoDBVersion] + public int? Version { get; set; } + } + /// /// Class representing items in the table [TableNamePrefix]HashTable /// Two fields are being stored as epoch seconds, two are not. @@ -2008,6 +2481,31 @@ public class EpochEmployee : Employee public DateTime NonEpochDate2 { get; set; } } + /// + /// Annotated class representing items in the table [TableNamePrefix]HashTable + /// Two fields are being stored as epoch seconds, two are not. + /// + [DynamoDBTable("NumericHashRangeTable")] + public class AnnotatedEpochEmployee + { + [DynamoDBRangeKey] + public string Name { get; set; } + + public int Age { get; set; } + + // Hash key + [DynamoDBHashKey(StoreAsEpoch = true)] + public virtual DateTime CreationTime { get; set; } + + [DynamoDBProperty(StoreAsEpoch = true)] + public DateTime EpochDate2 { get; set; } + + [DynamoDBProperty(StoreAsEpoch = false)] + public DateTime NonEpochDate1 { get; set; } + + public DateTime NonEpochDate2 { get; set; } + } + /// /// Class representing items in the table [TableNamePrefix]NumericHashRangeTable. /// @@ -2017,6 +2515,15 @@ public class NumericEpochEmployee : EpochEmployee } + /// + /// Class representing items in the table [TableNamePrefix]NumericHashRangeTable. + /// + [DynamoDBTable("NumericHashRangeTable")] + public class AnnotatedNumEpochEmployee : AnnotatedEpochEmployee + { + + } + /// /// Same structure as , but the Hash key is annotated /// diff --git a/sdk/test/Services/DynamoDBv2/IntegrationTests/DocumentTests.cs b/sdk/test/Services/DynamoDBv2/IntegrationTests/DocumentTests.cs index 86bad3e5ca89..9631ff8038d4 100644 --- a/sdk/test/Services/DynamoDBv2/IntegrationTests/DocumentTests.cs +++ b/sdk/test/Services/DynamoDBv2/IntegrationTests/DocumentTests.cs @@ -167,7 +167,9 @@ private void TestAsDateTimeUtc(Table numericHashRangeTable) { AttributesToStoreAsEpoch = new List { "CreationTime", "EpochDate2" } }; +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors var numericEpochTable = Table.LoadTable(Client, config); +#pragma warning restore CS0618 // Re-enable the warning // Capture current time var currTime = DateTime.Now; @@ -1816,9 +1818,13 @@ private void LoadTables(DynamoDBEntryConversion conversion, out Table hashTable, { // Load table using TryLoadTable API hashTable = null; +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors Assert.IsFalse(Table.TryLoadTable(Client, "FakeHashTableThatShouldNotExist", conversion, true, out hashTable)); +#pragma warning restore CS0618 // Re-enable the warning Assert.AreEqual(0, counter.ResponseCount); +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors Assert.IsTrue(Table.TryLoadTable(Client, hashTableName, conversion, true, out hashTable)); +#pragma warning restore CS0618 // Re-enable the warning Assert.AreEqual(1, counter.ResponseCount); Assert.IsNotNull(hashTable); @@ -1832,10 +1838,14 @@ private void LoadTables(DynamoDBEntryConversion conversion, out Table hashTable, Assert.AreEqual(0, hashTable.LocalSecondaryIndexes.Count); Assert.AreEqual(0, hashTable.LocalSecondaryIndexNames.Count); +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors // Load table using LoadTable API (may throw an exception) AssertExtensions.ExpectException(() => Table.LoadTable(Client, "FakeHashRangeTableThatShouldNotExist", conversion, true)); +#pragma warning restore CS0618 // Re-enable the warning Assert.AreEqual(1, counter.ResponseCount); +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors hashRangeTable = Table.LoadTable(Client, hashRangeTableName, conversion, true); +#pragma warning restore CS0618 // Re-enable the warning Assert.AreEqual(2, counter.ResponseCount); Assert.IsNotNull(hashRangeTable); Assert.AreEqual(hashRangeTableName, hashRangeTable.TableName); @@ -1849,7 +1859,9 @@ private void LoadTables(DynamoDBEntryConversion conversion, out Table hashTable, Assert.AreEqual(2, hashRangeTable.LocalSecondaryIndexes["LocalIndex"].KeySchema.Count); Assert.AreEqual(1, hashRangeTable.LocalSecondaryIndexNames.Count); +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors numericHashRangeTable = Table.LoadTable(Client, numericHashRangeTableName, conversion, true); +#pragma warning restore CS0618 // Re-enable the warning Assert.AreEqual(1, numericHashRangeTable.HashKeys.Count); Assert.AreEqual(1, numericHashRangeTable.RangeKeys.Count); Assert.AreEqual(2, numericHashRangeTable.Keys.Count); diff --git a/sdk/test/Services/DynamoDBv2/IntegrationTests/DynamoDBTestsBase.cs b/sdk/test/Services/DynamoDBv2/IntegrationTests/DynamoDBTestsBase.cs index 742411330d32..1380e87dfad5 100644 --- a/sdk/test/Services/DynamoDBv2/IntegrationTests/DynamoDBTestsBase.cs +++ b/sdk/test/Services/DynamoDBv2/IntegrationTests/DynamoDBTestsBase.cs @@ -92,14 +92,16 @@ public void CleanupTables() public static void CreateContext(DynamoDBEntryConversion conversion, bool isEmptyStringValueEnabled, bool disableFetchingTableMetadata = false) { - var config = new DynamoDBContextConfig - { - //IgnoreNullValues = true - IsEmptyStringValueEnabled = isEmptyStringValueEnabled, - Conversion = conversion, - DisableFetchingTableMetadata = disableFetchingTableMetadata - }; - Context = new DynamoDBContext(Client, config); + Context = new DynamoDBContextBuilder() + .WithDynamoDBClient(() => Client) + .ConfigureContext(x => + { + //x.IgnoreNullValues = true; + x.IsEmptyStringValueEnabled = isEmptyStringValueEnabled; + x.Conversion = conversion; + x.DisableFetchingTableMetadata = disableFetchingTableMetadata; + }) + .Build(); } public static string hashTableName; @@ -126,7 +128,9 @@ public static void CreateContext(DynamoDBEntryConversion conversion, bool isEmpt public static void ClearTable(string tableName) { +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors var table = Table.LoadTable(Client, tableName, DynamoDBEntryConversion.V1, true); +#pragma warning restore CS0618 // Re-enable the warning var keyNames = table.Keys.Keys.ToList(); // Retrieve all keys diff --git a/sdk/test/Services/DynamoDBv2/IntegrationTests/JSONTests.cs b/sdk/test/Services/DynamoDBv2/IntegrationTests/JSONTests.cs index 70a2f0217757..a4d4b23dec6a 100644 --- a/sdk/test/Services/DynamoDBv2/IntegrationTests/JSONTests.cs +++ b/sdk/test/Services/DynamoDBv2/IntegrationTests/JSONTests.cs @@ -348,8 +348,7 @@ private static void TestFromJsonCanHandleAllDataTypes() } }"; - using (var dbClient = new AmazonDynamoDBClient()) - using (var context = new DynamoDBContext(dbClient)) + using (var context = new DynamoDBContextBuilder().Build()) { var document = Document.FromJson(json); var container = context.FromDocument(document); diff --git a/sdk/test/Services/DynamoDBv2/IntegrationTests/TTLTests.cs b/sdk/test/Services/DynamoDBv2/IntegrationTests/TTLTests.cs index bfbef1f18d43..f92b023140a2 100644 --- a/sdk/test/Services/DynamoDBv2/IntegrationTests/TTLTests.cs +++ b/sdk/test/Services/DynamoDBv2/IntegrationTests/TTLTests.cs @@ -88,6 +88,66 @@ public void TestStoreAsEpoch() Assert.IsNotNull(epochMap["NonEpochDate2"].S); } + public void TestStoreAsAnnotatedEpoch() + { + var numericEmployee = new AnnotatedNumEpochEmployee + { + Name = "Bob", + Age = 45, + CreationTime = EpochDate, + EpochDate2 = EpochDate, + NonEpochDate1 = EpochDate, + NonEpochDate2 = EpochDate + }; + AnnotatedEpochEmployee employee = numericEmployee; + + Context.Save(employee); + var storedEmployee = Context.Load(employee.CreationTime, employee.Name); + Assert.IsNotNull(storedEmployee); + ApproximatelyEqual(EpochDate, storedEmployee.CreationTime); + storedEmployee = Context.Load(employee); + Assert.IsNotNull(storedEmployee); + ApproximatelyEqual(EpochDate, storedEmployee.CreationTime); + + + Context.Save(numericEmployee); + var storedNumericEmployee = Context.Load(employee.CreationTime, employee.Name); + Assert.IsNotNull(storedNumericEmployee); + ApproximatelyEqual(EpochDate, storedNumericEmployee.CreationTime); + storedNumericEmployee = Context.Load(numericEmployee); + Assert.IsNotNull(storedNumericEmployee); + ApproximatelyEqual(EpochDate, storedNumericEmployee.CreationTime); + + + var doc = Context.ToDocument(employee); + ApproximatelyEqual(EpochDate, doc["CreationTime"].AsDateTime()); + ApproximatelyEqual(EpochDate, doc["EpochDate2"].AsDateTime()); + ApproximatelyEqual(EpochDate, doc["NonEpochDate1"].AsDateTime()); + ApproximatelyEqual(EpochDate, doc["NonEpochDate1"].AsDateTime()); + + var docV1 = doc.ForceConversion(DynamoDBEntryConversion.V1); + ApproximatelyEqual(EpochDate, docV1["CreationTime"].AsDateTime()); + ApproximatelyEqual(EpochDate, docV1["EpochDate2"].AsDateTime()); + ApproximatelyEqual(EpochDate, docV1["NonEpochDate1"].AsDateTime()); + ApproximatelyEqual(EpochDate, docV1["NonEpochDate1"].AsDateTime()); + + var docV2 = doc.ForceConversion(DynamoDBEntryConversion.V1); + ApproximatelyEqual(EpochDate, docV2["CreationTime"].AsDateTime()); + ApproximatelyEqual(EpochDate, docV2["EpochDate2"].AsDateTime()); + ApproximatelyEqual(EpochDate, docV2["NonEpochDate1"].AsDateTime()); + ApproximatelyEqual(EpochDate, docV2["NonEpochDate1"].AsDateTime()); + + var epochTable = Context.GetTargetTable(); + var epochAttributes = epochTable.GetStoreAsEpoch().ToList(); + Assert.AreNotEqual(0, epochAttributes.Count); + + var epochMap = epochTable.ToAttributeMap(doc); + Assert.IsNotNull(epochMap["CreationTime"].N); + Assert.IsNotNull(epochMap["EpochDate2"].N); + Assert.IsNotNull(epochMap["NonEpochDate1"].S); + Assert.IsNotNull(epochMap["NonEpochDate2"].S); + } + public void TestStoreAsEpoch(Table hashRangeTable, Table numericHashRangeTable) { // verify conversions @@ -106,14 +166,18 @@ public void TestStoreAsEpoch(Table hashRangeTable, Table numericHashRangeTable) { AttributesToStoreAsEpoch = new List { "CreationTime", "EpochDate2" } }; +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors var epochTable = Table.LoadTable(Client, config); +#pragma warning restore CS0618 // Re-enable the warning CollectionAssert.AreEqual(config.AttributesToStoreAsEpoch, epochTable.GetStoreAsEpoch().ToList()); config = new TableConfig(numericHashRangeTable.TableName) { AttributesToStoreAsEpoch = new List { "CreationTime", "EpochDate2" } }; +#pragma warning disable CS0618 // Disable the warning for the deprecated DynamoDBContext constructors var numericEpochTable = Table.LoadTable(Client, config); +#pragma warning restore CS0618 // Re-enable the warning CollectionAssert.AreEqual(config.AttributesToStoreAsEpoch, epochTable.GetStoreAsEpoch().ToList()); // verify ToAttributeMap calls