/* * 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; using System.Collections.Generic; using System.Threading; #if AWS_ASYNC_API using System.Threading.Tasks; #endif using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.DocumentModel; namespace Amazon.DynamoDBv2.DataModel { /// /// Context object for using the DataModel mode of DynamoDB. /// Used to interact with the service, save/load objects, etc. /// public partial class DynamoDBContext : IDynamoDBContext { #region Private members private bool disposed; private bool ownClient; internal IAmazonDynamoDB Client { get; private set; } private Dictionary tablesMap; private readonly ReaderWriterLockSlim _readerWriterLockSlim = new ReaderWriterLockSlim(); internal DynamoDBContextConfig Config { get; private set; } internal ItemStorageConfigCache StorageConfigCache { get; private set; } #endregion #region Public properties /// /// This cache is a way to store Converters for objects which provides a way to expand Context /// public Dictionary ConverterCache { get; private set; } #endregion #region Constructors #if !NETSTANDARD /// /// Constructs a DynamoDBContext object with a default AmazonDynamoDBClient /// client and a default DynamoDBContextConfig object for configuration. /// public DynamoDBContext() : this(new AmazonDynamoDBClient()) { } /// /// Constructs a DynamoDBContext object with a default AmazonDynamoDBClient /// client and a default DynamoDBContextConfig object for configuration. /// /// The region to configure the AmazonDynamoDBClient to use. public DynamoDBContext(RegionEndpoint region) : this(new AmazonDynamoDBClient(region), true, new DynamoDBContextConfig()) { } /// /// Constructs a DynamoDBContext object with the specified configuration. /// Uses a default AmazonDynamoDBClient as the client. /// /// public DynamoDBContext(DynamoDBContextConfig config) : this(new AmazonDynamoDBClient(), config) { } /// /// Constructs a DynamoDBContext object with the specified configuration. /// Uses a default AmazonDynamoDBClient as the client. /// /// The region to configure the AmazonDynamoDBClient to use. /// public DynamoDBContext(RegionEndpoint region, DynamoDBContextConfig config) : this(new AmazonDynamoDBClient(region), true, config) { } #endif /// /// Constructs a DynamoDBContext object with the specified DynamoDB client. /// Uses default DynamoDBContextConfig object for configuration. /// /// Client to use for making calls public DynamoDBContext(IAmazonDynamoDB client) : this(client, false, new DynamoDBContextConfig()) { } /// /// Constructs a DynamoDBContext object with the specified DynamoDB client /// and configuration. /// /// Client to use for making calls /// Configuration to use public DynamoDBContext(IAmazonDynamoDB client, DynamoDBContextConfig config) : this(client, false, config) { } private DynamoDBContext(IAmazonDynamoDB client, bool ownClient, DynamoDBContextConfig config) { if (client == null) throw new ArgumentNullException("client"); this.ConverterCache = new Dictionary(); this.ConverterCache.Add(typeof(S3Link), new S3Link.S3LinkConverter(this)); this.Client = client; this.tablesMap = new Dictionary(); this.ownClient = ownClient; this.Config = config ?? new DynamoDBContextConfig(); this.StorageConfigCache = new ItemStorageConfigCache(this); } #endregion #region Dispose Pattern Implementation /// /// Implements the Dispose pattern /// /// Whether this object is being disposed via a call to Dispose /// or garbage collected. protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing && Client != null) { if (ownClient) { StorageConfigCache.Dispose(); Client.Dispose(); } Client = null; } this.disposed = true; } } /// /// Disposes of all managed and unmanaged resources. /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// /// The destructor for the client class. /// ~DynamoDBContext() { this.Dispose(false); } #endregion #region Factory Creates /// /// Creates a strongly-typed BatchGet object, allowing /// a batch-get operation against DynamoDB. /// /// Type of objects to get /// Empty strongly-typed BatchGet object public BatchGet CreateBatchGet() { return CreateBatchGet(null); } /// /// Creates a strongly-typed BatchGet object, allowing /// a batch-get operation against DynamoDB. /// /// Type of objects to get /// Config object which can be used to override that table used. /// Empty strongly-typed BatchGet object public BatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config); return new BatchGet(this, config); } /// /// Creates a MultiTableBatchGet object, composed of multiple /// individual BatchGet objects. /// /// Individual BatchGet objects /// Composite MultiTableBatchGet object public MultiTableBatchGet CreateMultiTableBatchGet(params BatchGet[] batches) { return new MultiTableBatchGet(batches); } /// /// Creates a strongly-typed BatchWrite object, allowing /// a batch-write operation against DynamoDB. /// /// Type of objects to write /// Empty strongly-typed BatchWrite object public BatchWrite CreateBatchWrite() { return CreateBatchWrite(null); } /// /// Creates a strongly-typed BatchWrite object, allowing /// a batch-write operation against DynamoDB. /// /// Type of objects to write /// Config object which can be used to override that table used. /// Empty strongly-typed BatchWrite object public BatchWrite CreateBatchWrite(DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config); return new BatchWrite(this, config); } /// /// Creates a strongly-typed BatchWrite object, allowing /// a batch-write operation against DynamoDB. /// /// This is intended for use only when the valuesType is not known at compile-time, for example, /// when hooking into EF's ChangeTracker to record audit logs from EF into DynamoDB. /// /// In scenarios when the valuesType is known at compile-time, the `BatchWrite CreateBatchWrite()` /// method is generally preferred. /// /// Type of objects to write /// Empty strongly-typed BatchWrite object public BatchWrite CreateBatchWrite(Type valuesType) { return CreateBatchWrite(valuesType, null); } /// /// Creates a strongly-typed BatchWrite object, allowing /// a batch-write operation against DynamoDB. /// /// This is intended for use only when the valuesType is not known at compile-time, for example, /// when hooking into EF's ChangeTracker to record audit logs from EF into DynamoDB. /// /// In scenarios when the valuesType is known at compile-time, the /// `BatchWrite CreateBatchWrite(DynamoDBOperationConfig operationConfig)` method is generally preferred. /// /// Type of objects to write /// Config object which can be used to override that table used. /// Empty strongly-typed BatchWrite object public BatchWrite CreateBatchWrite(Type valuesType, DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config); return new BatchWrite(this, valuesType, config); } /// /// Creates a MultiTableBatchWrite object, composed of multiple /// individual BatchWrite objects. /// /// Individual BatchWrite objects /// Composite MultiTableBatchWrite object public MultiTableBatchWrite CreateMultiTableBatchWrite(params BatchWrite[] batches) { return new MultiTableBatchWrite(batches); } /// /// Creates a strongly-typed TransactGet object, allowing /// a transactional get operation against DynamoDB. /// /// Type of objects to get. /// Empty strongly-typed TransactGet object. public TransactGet CreateTransactGet() { return CreateTransactGet(null); } /// /// Creates a strongly-typed TransactGet object, allowing /// a transactional get operation against DynamoDB. /// /// Type of objects to get. /// Config object which can be used to override that table used. /// Empty strongly-typed TransactGet object. public TransactGet CreateTransactGet(DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config); return new TransactGet(this, config); } /// /// Creates a MultiTableTransactGet object, composed of multiple /// individual TransactGet objects. /// /// Individual TransactGet objects. /// Composite MultiTableTransactGet object. public MultiTableTransactGet CreateMultiTableTransactGet(params TransactGet[] transactionParts) { return new MultiTableTransactGet(transactionParts); } /// /// Creates a strongly-typed TransactWrite object, allowing /// a transactional write operation against DynamoDB. /// /// Type of objects to write. /// Empty strongly-typed TransactWrite object. public TransactWrite CreateTransactWrite() { return CreateTransactWrite(null); } /// /// Creates a strongly-typed TransactWrite object, allowing /// a transactional write operation against DynamoDB. /// /// Type of objects to write. /// Config object which can be used to override that table used. /// Empty strongly-typed TransactWrite object. public TransactWrite CreateTransactWrite(DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config); return new TransactWrite(this, config); } /// /// Creates a MultiTableTransactWrite object, composed of multiple /// individual TransactWrite objects. /// /// Individual TransactWrite objects. /// Composite MultiTableTransactWrite object. public MultiTableTransactWrite CreateMultiTableTransactWrite(params TransactWrite[] transactionParts) { return new MultiTableTransactWrite(transactionParts); } #endregion #region Save/serialize private void SaveHelper(T value, DynamoDBOperationConfig operationConfig) { if (value == null) return; DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config); ItemStorage storage = ObjectToItemStorage(value, false, flatConfig); if (storage == null) return; Table table = GetTargetTable(storage.Config, flatConfig); if ((flatConfig.SkipVersionCheck.HasValue && flatConfig.SkipVersionCheck.Value) || !storage.Config.HasVersion) { table.UpdateHelper(storage.Document, table.MakeKey(storage.Document), null); } else { Document expectedDocument = CreateExpectedDocumentForVersion(storage); SetNewVersion(storage); var updateItemOperationConfig = new UpdateItemOperationConfig { Expected = expectedDocument, ReturnValues = ReturnValues.None, }; table.UpdateHelper(storage.Document, table.MakeKey(storage.Document), updateItemOperationConfig); PopulateInstance(storage, value, flatConfig); } } #if AWS_ASYNC_API private async Task SaveHelperAsync(T value, DynamoDBOperationConfig operationConfig, CancellationToken cancellationToken) { await SaveHelperAsync(typeof(T), value, operationConfig, cancellationToken).ConfigureAwait(false); } private async Task SaveHelperAsync(Type valueType, object value, DynamoDBOperationConfig operationConfig, CancellationToken cancellationToken) { if (value == null) return; DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config); ItemStorage storage = ObjectToItemStorage(value, valueType, false, flatConfig); if (storage == null) return; Table table = GetTargetTable(storage.Config, flatConfig); if ( (flatConfig.SkipVersionCheck.HasValue && flatConfig.SkipVersionCheck.Value) || !storage.Config.HasVersion) { await table.UpdateHelperAsync(storage.Document, table.MakeKey(storage.Document), null, cancellationToken).ConfigureAwait(false); } else { Document expectedDocument = CreateExpectedDocumentForVersion(storage); SetNewVersion(storage); await table.UpdateHelperAsync( storage.Document, table.MakeKey(storage.Document), new UpdateItemOperationConfig { Expected = expectedDocument, ReturnValues = ReturnValues.None }, cancellationToken).ConfigureAwait(false); PopulateInstance(storage, value, flatConfig); } } #endif /// /// Serializes an object to a Document. /// /// Type to serialize as. /// Object to serialize. /// Document with attributes populated from object. public Document ToDocument(T value) { return ToDocument(value, null); } /// /// Serializes an object to a Document. /// /// Type to serialize as. /// Object to serialize. /// Config object which can be used to override the table used. /// Document with attributes populated from object. public Document ToDocument(T value, DynamoDBOperationConfig operationConfig) { if (value == null) return null; DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, Config); ItemStorage storage = ObjectToItemStorage(value, false, flatConfig); if (storage == null) return null; return storage.Document; } #endregion #region Load/deserialize private T LoadHelper(object hashKey, object rangeKey, DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config); ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(flatConfig); Key key = MakeKey(hashKey, rangeKey, storageConfig, flatConfig); return LoadHelper(key, flatConfig, storageConfig); } #if AWS_ASYNC_API private Task LoadHelperAsync(object hashKey, object rangeKey, DynamoDBOperationConfig operationConfig, CancellationToken cancellationToken) { DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config); ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(flatConfig); Key key = MakeKey(hashKey, rangeKey, storageConfig, flatConfig); return LoadHelperAsync(key, flatConfig, storageConfig, cancellationToken); } #endif private T LoadHelper(T keyObject, DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config); ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(flatConfig); Key key = MakeKey(keyObject, storageConfig, flatConfig); return LoadHelper(key, flatConfig, storageConfig); } #if AWS_ASYNC_API private Task LoadHelperAsync(T keyObject, DynamoDBOperationConfig operationConfig, CancellationToken cancellationToken) { DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config); ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(flatConfig); Key key = MakeKey(keyObject, storageConfig, flatConfig); return LoadHelperAsync(key, flatConfig, storageConfig, cancellationToken); } #endif private T LoadHelper(Key key, DynamoDBFlatConfig flatConfig, ItemStorageConfig storageConfig) { GetItemOperationConfig getConfig = new GetItemOperationConfig { ConsistentRead = flatConfig.ConsistentRead.Value, AttributesToGet = storageConfig.AttributesToGet }; Table table = GetTargetTable(storageConfig, flatConfig); ItemStorage storage = new ItemStorage(storageConfig); storage.Document = table.GetItemHelper(key, getConfig); T instance = DocumentToObject(storage, flatConfig); return instance; } #if AWS_ASYNC_API private async Task LoadHelperAsync(Key key, DynamoDBFlatConfig flatConfig, ItemStorageConfig storageConfig, CancellationToken cancellationToken) { GetItemOperationConfig getConfig = new GetItemOperationConfig { ConsistentRead = flatConfig.ConsistentRead.Value, AttributesToGet = storageConfig.AttributesToGet }; Table table = GetTargetTable(storageConfig, flatConfig); ItemStorage storage = new ItemStorage(storageConfig); storage.Document = await table.GetItemHelperAsync(key, getConfig, cancellationToken).ConfigureAwait(false); T instance = DocumentToObject(storage, flatConfig); return instance; } #endif /// /// Deserializes a document to an instance of type T. /// /// Type to populate. /// Document with properties to use. /// /// Object of type T, populated with properties from the document. /// public T FromDocument(Document document) { return FromDocument(document, null); } /// /// Deserializes a document to an instance of type T. /// /// Type to populate. /// Document with properties to use. /// Config object which can be used to override the table used. /// /// Object of type T, populated with properties from the document. /// public T FromDocument(Document document, DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, Config); return FromDocumentHelper(document, flatConfig); } internal T FromDocumentHelper(Document document, DynamoDBFlatConfig flatConfig) { ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(flatConfig); ItemStorage storage = new ItemStorage(storageConfig); storage.Document = document; T instance = DocumentToObject(storage, flatConfig); return instance; } /// /// Deserializes a collections of documents to a collection of instances of type T. /// /// Type to populate. /// Documents to deserialize. /// /// Collection of items of type T, each populated with properties from a corresponding document. /// public IEnumerable FromDocuments(IEnumerable documents) { return FromDocuments(documents, null); } /// /// Deserializes a collections of documents to a collection of instances of type T. /// /// Type to populate. /// Documents to deserialize. /// Config object which can be used to override the table used. /// /// Collection of items of type T, each populated with properties from a corresponding document. /// public IEnumerable FromDocuments(IEnumerable documents, DynamoDBOperationConfig operationConfig) { foreach (var document in documents) { T item = FromDocument(document, operationConfig); yield return item; } } internal IEnumerable FromDocumentsHelper(IEnumerable documents, DynamoDBFlatConfig flatConfig) { foreach (var document in documents) { T item = FromDocumentHelper(document, flatConfig); yield return item; } } #endregion #region Delete private void DeleteHelper(object hashKey, object rangeKey, DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config); ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(config); Key key = MakeKey(hashKey, rangeKey, storageConfig, config); Table table = GetTargetTable(storageConfig, config); table.DeleteHelper(key, null); } #if AWS_ASYNC_API private Task DeleteHelperAsync(object hashKey, object rangeKey, DynamoDBOperationConfig operationConfig, CancellationToken cancellationToken) { DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config); ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(config); Key key = MakeKey(hashKey, rangeKey, storageConfig, config); Table table = GetTargetTable(storageConfig, config); return table.DeleteHelperAsync(key, null, cancellationToken); } #endif private void DeleteHelper(T value, DynamoDBOperationConfig operationConfig) { if (value == null) throw new ArgumentNullException("value"); DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config); flatConfig.IgnoreNullValues = true; ItemStorage storage = ObjectToItemStorage(value, true, flatConfig); if (storage == null) return; Table table = GetTargetTable(storage.Config, flatConfig); if (flatConfig.SkipVersionCheck.Value || !storage.Config.HasVersion) { table.DeleteHelper(table.MakeKey(storage.Document), null); } else { Document expectedDocument = CreateExpectedDocumentForVersion(storage); table.DeleteHelper( table.MakeKey(storage.Document), new DeleteItemOperationConfig { Expected = expectedDocument }); } } #if AWS_ASYNC_API private static readonly Task CompletedTask = Task.FromResult(null); private Task DeleteHelperAsync(T value, DynamoDBOperationConfig operationConfig, CancellationToken cancellationToken) { if (value == null) throw new ArgumentNullException("value"); DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config); flatConfig.IgnoreNullValues = true; ItemStorage storage = ObjectToItemStorage(value, true, flatConfig); if (storage == null) return CompletedTask; Table table = GetTargetTable(storage.Config, flatConfig); if (flatConfig.SkipVersionCheck.Value || !storage.Config.HasVersion) { return table.DeleteHelperAsync(table.MakeKey(storage.Document), null, cancellationToken); } else { Document expectedDocument = CreateExpectedDocumentForVersion(storage); return table.DeleteHelperAsync( table.MakeKey(storage.Document), new DeleteItemOperationConfig { Expected = expectedDocument }, cancellationToken); } } #endif #endregion } }