diff --git a/CHANGELOG.md b/CHANGELOG.md index f738dfc1..08a88c48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ Represents the **NuGet** versions. +## v3.23.5 +- *Fixed:* `CosmosDbValue.PrepareBefore` corrected to set the `PartitionKey` where the underlying `Value` implements `IPartitionKey`. +- *Fixed:* `CosmosDbBatch` corrected to default to the `CosmosDbContainerBase.DbArgs` where not specified. +- *Fixed:* `CosmosDbArgs.AutoMapETag` added, indicates whether when mapping the model to the corresponding entity that the `IETag.ETag` is to be automatically mapped (default is `true`, existing behavior). + ## v3.23.4 - *Fixed:* Added `Result.AdjustsAsync` to support asynchronous adjustments. diff --git a/Common.targets b/Common.targets index 2b4c81c6..1ddb6e86 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@  - 3.23.4 + 3.23.5 preview Avanade Avanade diff --git a/src/CoreEx.Cosmos/Batch/CosmosDbBatch.cs b/src/CoreEx.Cosmos/Batch/CosmosDbBatch.cs index 4e22ae7a..1e64631b 100644 --- a/src/CoreEx.Cosmos/Batch/CosmosDbBatch.cs +++ b/src/CoreEx.Cosmos/Batch/CosmosDbBatch.cs @@ -67,7 +67,7 @@ public static async Task ImportBatchAsync(this ICosmosDb cosmosDb, strin /// The . /// Each item is added individually and is not transactional. public static Task ImportBatchAsync(this CosmosDbContainer container, IEnumerable items, Func? modelUpdater = null, CosmosDbArgs? dbArgs = null, CancellationToken cancellationToken = default) where T : class, IEntityKey, new() where TModel : class, IEntityKey, new() - => ImportBatchAsync(container?.CosmosDb!, container?.Container.Id!, items, modelUpdater, dbArgs, cancellationToken); + => ImportBatchAsync(container?.CosmosDb!, container?.Container.Id!, items, modelUpdater, dbArgs ?? container.ThrowIfNull().DbArgs, cancellationToken); /// /// Imports (creates) a batch of named items from the into the specified . @@ -106,7 +106,7 @@ public static async Task ImportBatchAsync(this ICosmosDb cosmosDb, strin /// true indicates that one or more items were deserialized and imported; otherwise, false for none found. /// Each item is added individually and is not transactional. public static Task ImportBatchAsync(this CosmosDbContainer container, JsonDataReader jsonDataReader, string? name = null, Func? modelUpdater = null, CosmosDbArgs? dbArgs = null, CancellationToken cancellationToken = default) where T : class, IEntityKey, new() where TModel : class, IEntityKey, new() - => ImportBatchAsync(container?.CosmosDb!, container?.Container.Id!, jsonDataReader, name, modelUpdater, dbArgs, cancellationToken); + => ImportBatchAsync(container?.CosmosDb!, container?.Container.Id!, jsonDataReader, name, modelUpdater, dbArgs ?? container.ThrowIfNull().DbArgs, cancellationToken); /// /// Imports (creates) a batch of . @@ -154,7 +154,7 @@ public static async Task ImportBatchAsync(this ICosmosDb cosmosDb, strin /// The . /// Each item is added individually and is not transactional. public static Task ImportValueBatchAsync(this CosmosDbValueContainer container, IEnumerable items, Func, CosmosDbValue>? modelUpdater = null, CosmosDbArgs? dbArgs = null, CancellationToken cancellationToken = default) where T : class, IEntityKey, new() where TModel : class, IEntityKey, new() - => ImportValueBatchAsync(container?.CosmosDb!, container?.Container.Id!, items, modelUpdater, dbArgs, cancellationToken); + => ImportValueBatchAsync(container?.CosmosDb!, container?.Container.Id!, items, modelUpdater, dbArgs ?? container.ThrowIfNull().DbArgs, cancellationToken); /// /// Imports (creates) a batch of named items from the into the specified . @@ -193,7 +193,7 @@ public static async Task ImportBatchAsync(this ICosmosDb cosmosDb, strin /// true indicates that one or more items were deserialized and imported; otherwise, false for none found. /// Each item is added individually and is not transactional. public static Task ImportValueBatchAsync(this CosmosDbValueContainer container, JsonDataReader jsonDataReader, string? name = null, Func, CosmosDbValue>? modelUpdater = null, CosmosDbArgs? dbArgs = null, CancellationToken cancellationToken = default) where T : class, IEntityKey, new() where TModel : class, IEntityKey, new() - => ImportValueBatchAsync(container?.CosmosDb!, container?.Container.Id!, jsonDataReader, name, modelUpdater, dbArgs, cancellationToken); + => ImportValueBatchAsync(container?.CosmosDb!, container?.Container.Id!, jsonDataReader, name, modelUpdater, dbArgs ?? container.ThrowIfNull().DbArgs, cancellationToken); /// /// Imports (creates) a batch of named items from the into the specified . diff --git a/src/CoreEx.Cosmos/CosmosDbArgs.cs b/src/CoreEx.Cosmos/CosmosDbArgs.cs index 0f1617b2..d4c21580 100644 --- a/src/CoreEx.Cosmos/CosmosDbArgs.cs +++ b/src/CoreEx.Cosmos/CosmosDbArgs.cs @@ -21,18 +21,18 @@ public CosmosDbArgs() { } /// Initializes a new instance of the struct. /// /// The template to copy from. - /// The . + /// The override . public CosmosDbArgs(CosmosDbArgs template, PartitionKey? partitionKey = null) { PartitionKey = partitionKey ?? template.PartitionKey; ItemRequestOptions = template.ItemRequestOptions; QueryRequestOptions = template.QueryRequestOptions; NullOnNotFound = template.NullOnNotFound; + AutoMapETag = template.AutoMapETag; CleanUpResult = template.CleanUpResult; FilterByTenantId = template.FilterByTenantId; GetTenantId = template.GetTenantId; FormatIdentifier = template.FormatIdentifier; - ParseIdentifier = template.ParseIdentifier; } /// @@ -98,12 +98,17 @@ private readonly QueryRequestOptions UpdateQueryRequestionOptionsPartitionKey(Qu } /// - /// Indicates that a null is to be returned where the response has a of on Get. + /// Indicates whether a null is to be returned where the response has a of on Get. Defaults to true. /// public bool NullOnNotFound { get; set; } = true; /// - /// Indicates whether the result should be cleaned up. + /// Indicates whether when mapping the model to the corresponding entity that the is to be automatically mapped. Defaults to true. + /// + public bool AutoMapETag { get; set; } = true; + + /// + /// Indicates whether the result should be cleaned up. Defaults to false. /// public bool CleanUpResult { get; set; } = false; @@ -121,35 +126,12 @@ private readonly QueryRequestOptions UpdateQueryRequestionOptionsPartitionKey(Qu /// Formats a to a representation (used by and ). /// /// The identifier as a . + /// Defaults to . public Func FormatIdentifier { get; set; } = DefaultFormatIdentifier; /// - /// Parses a identifier and updates the underlying value where it implements (used by the ). - /// - /// The parsed identifier. - public Action ParseIdentifier { get; set; } = DefaultParseIdentifier; - - /// - /// Provides the default implementation. + /// Provides the default implementation; being the . /// public static Func DefaultFormatIdentifier { get; } = key => key.ToString(); - - /// - /// Provides the default implementation. - /// - public static Action DefaultParseIdentifier { get; } = (value, id) => - { - if (value.ThrowIfNull(nameof(value)) is IIdentifier iid) - { - iid.Id = iid.IdType switch - { - Type t when t == typeof(string) => id, - Type t when t == typeof(int) => id == null ? 0 : int.Parse(id, System.Globalization.CultureInfo.InvariantCulture), - Type t when t == typeof(long) => id == null ? 0 : long.Parse(id, System.Globalization.CultureInfo.InvariantCulture), - Type t when t == typeof(Guid) => id == null ? Guid.Empty : Guid.Parse(id), - _ => throw new NotSupportedException("An IIdentifier.IdType must be one of the following types: string, int, long, or Guid.") - }; - } - }; } } \ No newline at end of file diff --git a/src/CoreEx.Cosmos/CosmosDbContainer.cs b/src/CoreEx.Cosmos/CosmosDbContainer.cs index 47f25c74..64f8c3ff 100644 --- a/src/CoreEx.Cosmos/CosmosDbContainer.cs +++ b/src/CoreEx.Cosmos/CosmosDbContainer.cs @@ -1,119 +1,43 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx -using CoreEx.Abstractions; -using CoreEx.Cosmos.Model; using CoreEx.Entities; -using CoreEx.Mapping; -using CoreEx.Results; using Microsoft.Azure.Cosmos; using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace CoreEx.Cosmos { /// - /// Provides operations for a container. + /// Provides the core capabilities. /// - /// The entity . - /// The cosmos model . - public class CosmosDbContainer : CosmosDbContainerBase> where T : class, IEntityKey, new() where TModel : class, IEntityKey, new() + /// The . + /// The identifier. + /// The optional . + public class CosmosDbContainer(ICosmosDb cosmosDb, string containerId, CosmosDbArgs? dbArgs = null) : ICosmosDbContainer { - private readonly Lazy> _modelContainer; + private CosmosDbArgs? _dbArgs = dbArgs; - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The identifier. - /// The optional . - public CosmosDbContainer(ICosmosDb cosmosDb, string containerId, CosmosDbArgs? dbArgs = null) : base(cosmosDb, containerId, dbArgs) - => _modelContainer = new(() => new CosmosDbModelContainer(CosmosDb, Container.Id, DbArgs)); - - /// - /// Gets the underlying . - /// - public CosmosDbModelContainer ModelContainer => _modelContainer.Value; + /// + public ICosmosDb CosmosDb { get; } = cosmosDb.ThrowIfNull(nameof(cosmosDb)); - /// - /// Sets the function to determine the ; used for (only Create and Update operations). - /// - /// The function to determine the . - /// The instance to support fluent-style method-chaining. - /// This is used where there is a value and the corresponding needs to be dynamically determined. - public CosmosDbContainer UsePartitionKey(Func partitionKey) - { - ModelContainer.UsePartitionKey(partitionKey); - return this; - } + /// + public Container Container { get; } = cosmosDb.GetCosmosContainer(containerId); /// - /// Gets the value formatting/updating any special properties as required. + /// Gets or sets the Container-specific . /// - /// The model value. - /// The entity value. - [return: NotNullIfNotNull(nameof(model))] - public T? GetValue(TModel? model) + /// Defaults to on first access. + public CosmosDbArgs DbArgs { - var val = CosmosDb.Mapper.Map(model, OperationTypes.Get)!; - if (val is IETag et && et.ETag != null) - et.ETag = ETagGenerator.ParseETag(et.ETag); - - return DbArgs.CleanUpResult ? Cleaner.Clean(val) : val; + get => _dbArgs ??= new CosmosDbArgs(CosmosDb.DbArgs); + set => _dbArgs = value; } /// - /// Gets (creates) a to enable LINQ-style queries. - /// - /// The function to perform additional query execution. - /// The . - public CosmosDbQuery Query(Func, IQueryable>? query) => Query(new CosmosDbArgs(DbArgs), query); - - /// - /// Gets (creates) a to enable LINQ-style queries. + /// Gets the CosmosDb identifier from the . /// - /// The . - /// The function to perform additional query execution. - /// The . - public CosmosDbQuery Query(PartitionKey? partitionKey = null, Func, IQueryable>? query = null) => Query(new CosmosDbArgs(DbArgs, partitionKey), query); - - /// - /// Gets (creates) a to enable LINQ-style queries. - /// - /// The . - /// The function to perform additional query execution. - /// The . - public CosmosDbQuery Query(CosmosDbArgs dbArgs, Func, IQueryable>? query = null) => new(this, dbArgs, query); - - /// - public async override Task> GetWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) - { - var result = await ModelContainer.GetWithResultAsync(dbArgs, key, cancellationToken).ConfigureAwait(false); - return result.ThenAs(GetValue); - } - - /// - public override async Task> CreateWithResultAsync(CosmosDbArgs dbArgs, T value, CancellationToken cancellationToken = default) - { - ChangeLog.PrepareCreated(value.ThrowIfNull(nameof(value))); - TModel model = CosmosDb.Mapper.Map(value, OperationTypes.Create)!; - - var result = await ModelContainer.CreateWithResultAsync(dbArgs, model, cancellationToken).ConfigureAwait(false); - return result.ThenAs(model => GetValue(model)!); - } - - /// - public override async Task> UpdateWithResultAsync(CosmosDbArgs dbArgs, T value, CancellationToken cancellationToken = default) - { - ChangeLog.PrepareUpdated(value); - var model = CosmosDb.Mapper.Map(value.ThrowIfNull(nameof(value)), OperationTypes.Update)!; - var result = await ModelContainer.UpdateWithResultInternalAsync(dbArgs, model, m => CosmosDb.Mapper.Map(value, m, OperationTypes.Update), cancellationToken).ConfigureAwait(false); - return result.ThenAs(model => GetValue(model)!); - } - - /// - public override Task DeleteWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) => ModelContainer.DeleteWithResultAsync(dbArgs, key, cancellationToken); + /// The . + /// The CosmosDb identifier. + /// Uses the to format the as a string (as required). + public virtual string GetCosmosId(CompositeKey key) => DbArgs.FormatIdentifier(key) ?? throw new InvalidOperationException("The CompositeKey formatting must not result in a null."); } } \ No newline at end of file diff --git a/src/CoreEx.Cosmos/CosmosDbContainerBase.cs b/src/CoreEx.Cosmos/CosmosDbContainerBase.cs deleted file mode 100644 index cd8bb441..00000000 --- a/src/CoreEx.Cosmos/CosmosDbContainerBase.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx - -using CoreEx.Entities; -using Microsoft.Azure.Cosmos; -using System; - -namespace CoreEx.Cosmos -{ - /// - /// Provides the base capabilities. - /// - /// The itself. - /// The . - /// The identifier. - /// The optional . - public class CosmosDbContainerBase(ICosmosDb cosmosDb, string containerId, CosmosDbArgs? dbArgs = null) : ICosmosDbContainer where TSelf : CosmosDbContainerBase - { - private CosmosDbArgs? _dbArgs = dbArgs; - - /// - public ICosmosDb CosmosDb { get; } = cosmosDb.ThrowIfNull(nameof(cosmosDb)); - - /// - public Container Container { get; } = cosmosDb.GetCosmosContainer(containerId); - - /// - /// Gets or sets the Container-specific . - /// - /// Defaults to on first access. - public CosmosDbArgs DbArgs - { - get => _dbArgs ??= new CosmosDbArgs(CosmosDb.DbArgs); - set => _dbArgs = value; - } - - /// - /// Gets the CosmosDb identifier from the . - /// - /// The . - /// The CosmosDb identifier. - /// Uses the to format the as a string (as required). - public virtual string GetCosmosId(CompositeKey key) => DbArgs.FormatIdentifier(key) ?? throw new InvalidOperationException("The CompositeKey formatting must not result in a null."); - } -} \ No newline at end of file diff --git a/src/CoreEx.Cosmos/CosmosDbContainerBaseT.cs b/src/CoreEx.Cosmos/CosmosDbContainerBaseT.cs index f697e6d2..c03867b0 100644 --- a/src/CoreEx.Cosmos/CosmosDbContainerBaseT.cs +++ b/src/CoreEx.Cosmos/CosmosDbContainerBaseT.cs @@ -18,7 +18,7 @@ namespace CoreEx.Cosmos /// The . /// The identifier. /// The optional . - public abstract class CosmosDbContainerBase(ICosmosDb cosmosDb, string containerId, CosmosDbArgs? dbArgs = null) : CosmosDbContainerBase(cosmosDb, containerId, dbArgs), ICosmosDbContainer + public abstract class CosmosDbContainerBase(ICosmosDb cosmosDb, string containerId, CosmosDbArgs? dbArgs = null) : CosmosDbContainer(cosmosDb, containerId, dbArgs), ICosmosDbContainer where T : class, IEntityKey, new() where TModel : class, IEntityKey, new() where TSelf : CosmosDbContainerBase { /// @@ -48,7 +48,7 @@ public abstract class CosmosDbContainerBase(ICosmosDb cosmosDb /// Gets the entity for the specified . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . /// The entity value where found; otherwise, null (see ). public async Task GetAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => await GetWithResultAsync(key, partitionKey, cancellationToken).ConfigureAwait(false); @@ -57,7 +57,7 @@ public abstract class CosmosDbContainerBase(ICosmosDb cosmosDb /// Gets the entity for the specified with a . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . /// The entity value where found; otherwise, null (see ). public Task> GetWithResultAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => GetWithResultAsync(new CosmosDbArgs(DbArgs, partitionKey), key, cancellationToken); @@ -130,7 +130,7 @@ public abstract class CosmosDbContainerBase(ICosmosDb cosmosDb /// Deletes the entity for the specified . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . public async Task DeleteAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => (await DeleteWithResultAsync(key, partitionKey, cancellationToken).ConfigureAwait(false)).ThrowOnError(); @@ -138,7 +138,7 @@ public abstract class CosmosDbContainerBase(ICosmosDb cosmosDb /// Deletes the entity for the specified with a . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . public Task DeleteWithResultAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => DeleteWithResultAsync(new CosmosDbArgs(DbArgs, partitionKey), key, cancellationToken); diff --git a/src/CoreEx.Cosmos/CosmosDbContainerT.cs b/src/CoreEx.Cosmos/CosmosDbContainerT.cs new file mode 100644 index 00000000..0c86adbd --- /dev/null +++ b/src/CoreEx.Cosmos/CosmosDbContainerT.cs @@ -0,0 +1,119 @@ +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx + +using CoreEx.Abstractions; +using CoreEx.Cosmos.Model; +using CoreEx.Entities; +using CoreEx.Mapping; +using CoreEx.Results; +using Microsoft.Azure.Cosmos; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace CoreEx.Cosmos +{ + /// + /// Provides operations for a container. + /// + /// The entity . + /// The cosmos model . + public class CosmosDbContainer : CosmosDbContainerBase> where T : class, IEntityKey, new() where TModel : class, IEntityKey, new() + { + private readonly Lazy> _modelContainer; + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The identifier. + /// The optional . + public CosmosDbContainer(ICosmosDb cosmosDb, string containerId, CosmosDbArgs? dbArgs = null) : base(cosmosDb, containerId, dbArgs) + => _modelContainer = new(() => new CosmosDbModelContainer(CosmosDb, Container.Id, DbArgs)); + + /// + /// Gets the underlying . + /// + public CosmosDbModelContainer ModelContainer => _modelContainer.Value; + + /// + /// Sets the function to determine the ; used for (only Create and Update operations). + /// + /// The function to determine the . + /// The instance to support fluent-style method-chaining. + /// This is used where there is a value and the corresponding needs to be dynamically determined. + public CosmosDbContainer UsePartitionKey(Func partitionKey) + { + ModelContainer.UsePartitionKey(partitionKey); + return this; + } + + /// + /// Gets the value formatting/updating any special properties as required. + /// + /// The model value. + /// The entity value. + [return: NotNullIfNotNull(nameof(model))] + public T? GetValue(TModel? model) + { + var val = CosmosDb.Mapper.Map(model, OperationTypes.Get)!; + if (DbArgs.AutoMapETag && val is IETag et && et.ETag != null) + et.ETag = ETagGenerator.ParseETag(et.ETag); + + return DbArgs.CleanUpResult ? Cleaner.Clean(val) : val; + } + + /// + /// Gets (creates) a to enable LINQ-style queries. + /// + /// The function to perform additional query execution. + /// The . + public CosmosDbQuery Query(Func, IQueryable>? query) => Query(new CosmosDbArgs(DbArgs), query); + + /// + /// Gets (creates) a to enable LINQ-style queries. + /// + /// The . + /// The function to perform additional query execution. + /// The . + public CosmosDbQuery Query(PartitionKey? partitionKey = null, Func, IQueryable>? query = null) => Query(new CosmosDbArgs(DbArgs, partitionKey), query); + + /// + /// Gets (creates) a to enable LINQ-style queries. + /// + /// The . + /// The function to perform additional query execution. + /// The . + public CosmosDbQuery Query(CosmosDbArgs dbArgs, Func, IQueryable>? query = null) => new(this, dbArgs, query); + + /// + public async override Task> GetWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) + { + var result = await ModelContainer.GetWithResultAsync(dbArgs, key, cancellationToken).ConfigureAwait(false); + return result.ThenAs(GetValue); + } + + /// + public override async Task> CreateWithResultAsync(CosmosDbArgs dbArgs, T value, CancellationToken cancellationToken = default) + { + ChangeLog.PrepareCreated(value.ThrowIfNull(nameof(value))); + TModel model = CosmosDb.Mapper.Map(value, OperationTypes.Create)!; + + var result = await ModelContainer.CreateWithResultAsync(dbArgs, model, cancellationToken).ConfigureAwait(false); + return result.ThenAs(model => GetValue(model)!); + } + + /// + public override async Task> UpdateWithResultAsync(CosmosDbArgs dbArgs, T value, CancellationToken cancellationToken = default) + { + ChangeLog.PrepareUpdated(value); + var model = CosmosDb.Mapper.Map(value.ThrowIfNull(nameof(value)), OperationTypes.Update)!; + var result = await ModelContainer.UpdateWithResultInternalAsync(dbArgs, model, m => CosmosDb.Mapper.Map(value, m, OperationTypes.Update), cancellationToken).ConfigureAwait(false); + return result.ThenAs(model => GetValue(model)!); + } + + /// + public override Task DeleteWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) => ModelContainer.DeleteWithResultAsync(dbArgs, key, cancellationToken); + } +} \ No newline at end of file diff --git a/src/CoreEx.Cosmos/CosmosDbValue.cs b/src/CoreEx.Cosmos/CosmosDbValue.cs index 256bf630..dc453883 100644 --- a/src/CoreEx.Cosmos/CosmosDbValue.cs +++ b/src/CoreEx.Cosmos/CosmosDbValue.cs @@ -61,6 +61,9 @@ void ICosmosDbValue.PrepareBefore(CosmosDbArgs dbArgs) if (Value is IETag etag) ETag = ETagGenerator.FormatETag(etag.ETag); + + if (Value is IPartitionKey pk) + PartitionKey = pk.PartitionKey; } Type = typeof(TModel).Name; @@ -72,8 +75,6 @@ void ICosmosDbValue.PrepareAfter(CosmosDbArgs dbArgs) if (Value == default) return; - dbArgs.ParseIdentifier(Value, Id); - if (Value is IETag etag) etag.ETag = ETagGenerator.ParseETag(ETag); } diff --git a/src/CoreEx.Cosmos/CosmosDbValueContainer.cs b/src/CoreEx.Cosmos/CosmosDbValueContainer.cs index 6bd876df..ecdd1542 100644 --- a/src/CoreEx.Cosmos/CosmosDbValueContainer.cs +++ b/src/CoreEx.Cosmos/CosmosDbValueContainer.cs @@ -76,7 +76,7 @@ public CosmosDbValueContainer UsePartitionKey(Func(model.Value, OperationTypes.Get)!; - if (val is IETag et) + if (DbArgs.AutoMapETag && val is IETag et) { if (et.ETag is not null) et.ETag = ETagGenerator.ParseETag(et.ETag); diff --git a/src/CoreEx.Cosmos/Model/CosmosDbModelContainer.cs b/src/CoreEx.Cosmos/Model/CosmosDbModelContainer.cs index 409cf257..79bf69d7 100644 --- a/src/CoreEx.Cosmos/Model/CosmosDbModelContainer.cs +++ b/src/CoreEx.Cosmos/Model/CosmosDbModelContainer.cs @@ -40,7 +40,7 @@ public CosmosDbModelContainer UsePartitionKey(Func /// The model to infer from. /// The . /// The . - /// Will be thrown where the infered is not equal to (where not null). + /// Will be thrown where the infered is not equal to (where not null). public PartitionKey GetPartitionKey(TModel model, CosmosDbArgs dbArgs) { var dbpk = DbArgs.PartitionKey; @@ -123,7 +123,7 @@ internal Result CheckAuthorized(TModel model) /// Gets the model for the specified . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . /// The model value where found; otherwise, null (see ). public async Task GetAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => await GetWithResultAsync(key, partitionKey, cancellationToken).ConfigureAwait(false); @@ -132,7 +132,7 @@ internal Result CheckAuthorized(TModel model) /// Gets the model for the specified with a . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . /// The model value where found; otherwise, null (see ). public Task> GetWithResultAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => GetWithResultAsync(new CosmosDbArgs(DbArgs, partitionKey), key, cancellationToken); @@ -153,12 +153,12 @@ internal Result CheckAuthorized(TModel model) /// The . /// The . /// The model value where found; otherwise, null (see ). - public Task> GetWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, GetCosmosId(key), dbArgs, async (_, key, args, ct) => + public Task> GetWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, GetCosmosId(key), dbArgs, async (_, id, args, ct) => { try { var pk = args.PartitionKey ?? DbArgs.PartitionKey ?? PartitionKey.None; - var resp = await Container.ReadItemAsync(key, pk, args.GetItemRequestOptions(), ct).ConfigureAwait(false); + var resp = await Container.ReadItemAsync(id, pk, args.GetItemRequestOptions(), ct).ConfigureAwait(false); if (resp.Resource == null || args.FilterByTenantId && resp.Resource is ITenantId tenantId && tenantId.TenantId != DbArgs.GetTenantId() || resp.Resource is ILogicallyDeleted ld && ld.IsDeleted.HasValue && ld.IsDeleted.Value) return args.NullOnNotFound ? Result.None : Result.NotFoundError(); @@ -259,9 +259,9 @@ internal Task> UpdateWithResultInternalAsync(CosmosDbArgs dbArgs, ro.IfMatchEtag = ETagGenerator.FormatETag(etag.ETag); // Must read existing to update. - var key = GetCosmosId(m); + var id = GetCosmosId(m); var pk = GetPartitionKey(model, dbArgs); - var resp = await Container.ReadItemAsync(key, pk, ro, ct).ConfigureAwait(false); + var resp = await Container.ReadItemAsync(id, pk, ro, ct).ConfigureAwait(false); if (resp.Resource == null || (args.FilterByTenantId && resp.Resource is ITenantId tenantId && tenantId.TenantId != DbArgs.GetTenantId()) || (resp.Resource is ILogicallyDeleted ld && ld.IsDeleted.HasValue && ld.IsDeleted.Value)) return Result.NotFoundError(); @@ -279,7 +279,7 @@ internal Task> UpdateWithResultInternalAsync(CosmosDbArgs dbArgs, }) .ThenAsAsync(async () => { - resp = await Container.ReplaceItemAsync(resp.Resource, key, pk, ro, ct).ConfigureAwait(false); + resp = await Container.ReplaceItemAsync(resp.Resource, id, pk, ro, ct).ConfigureAwait(false); return GetResponseValue(resp)!; }); }, cancellationToken, nameof(UpdateWithResultAsync)); @@ -302,7 +302,7 @@ internal Task> UpdateWithResultInternalAsync(CosmosDbArgs dbArgs, /// Deletes the model for the specified . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . public async Task DeleteAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => (await DeleteWithResultAsync(key, partitionKey, cancellationToken).ConfigureAwait(false)).ThrowOnError(); @@ -310,7 +310,7 @@ internal Task> UpdateWithResultInternalAsync(CosmosDbArgs dbArgs, /// Deletes the model for the specified with a . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . public Task DeleteWithResultAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => DeleteWithResultAsync(new CosmosDbArgs(DbArgs, partitionKey), key, cancellationToken); @@ -328,14 +328,14 @@ internal Task> UpdateWithResultInternalAsync(CosmosDbArgs dbArgs, /// The . /// The . /// The . - public Task DeleteWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, GetCosmosId(key), dbArgs, async (_, key, args, ct) => + public Task DeleteWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, GetCosmosId(key), dbArgs, async (_, id, args, ct) => { try { // Must read the existing to validate. var ro = args.GetItemRequestOptions(); var pk = args.PartitionKey ?? DbArgs.PartitionKey ?? PartitionKey.None; - var resp = await Container.ReadItemAsync(key, pk, ro, ct).ConfigureAwait(false); + var resp = await Container.ReadItemAsync(id, pk, ro, ct).ConfigureAwait(false); if (resp.Resource == null || (args.FilterByTenantId && resp.Resource is ITenantId tenantId && tenantId.TenantId != DbArgs.GetTenantId())) return Result.Success; @@ -351,7 +351,7 @@ public Task DeleteWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, .ThenAsync(async () => { ro.SessionToken = resp.Headers?.Session; - await Container.ReplaceItemAsync(resp.Resource, key, pk, ro, ct).ConfigureAwait(false); + await Container.ReplaceItemAsync(resp.Resource, id, pk, ro, ct).ConfigureAwait(false); return Result.Success; }); } @@ -361,7 +361,7 @@ public Task DeleteWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, .ThenAsync(async () => { ro.SessionToken = resp.Headers?.Session; - await Container.DeleteItemAsync(key, pk, ro, ct).ConfigureAwait(false); + await Container.DeleteItemAsync(id, pk, ro, ct).ConfigureAwait(false); return Result.Success; }); } diff --git a/src/CoreEx.Cosmos/Model/CosmosDbModelContainerBase.cs b/src/CoreEx.Cosmos/Model/CosmosDbModelContainerBase.cs index d37dfb21..2d5f69b5 100644 --- a/src/CoreEx.Cosmos/Model/CosmosDbModelContainerBase.cs +++ b/src/CoreEx.Cosmos/Model/CosmosDbModelContainerBase.cs @@ -14,6 +14,6 @@ namespace CoreEx.Cosmos.Model /// The . /// The identifier. /// The optional . - public abstract class CosmosDbModelContainerBase(ICosmosDb cosmosDb, string containerId, CosmosDbArgs? dbArgs = null) : CosmosDbContainerBase(cosmosDb, containerId, dbArgs), ICosmosDbModelContainer + public abstract class CosmosDbModelContainerBase(ICosmosDb cosmosDb, string containerId, CosmosDbArgs? dbArgs = null) : CosmosDbContainer(cosmosDb, containerId, dbArgs), ICosmosDbModelContainer where TModel : class, IEntityKey, new () where TSelf : CosmosDbModelContainerBase { } } \ No newline at end of file diff --git a/src/CoreEx.Cosmos/Model/CosmosDbValueModelContainer.cs b/src/CoreEx.Cosmos/Model/CosmosDbValueModelContainer.cs index 93e357c7..a2e382e3 100644 --- a/src/CoreEx.Cosmos/Model/CosmosDbValueModelContainer.cs +++ b/src/CoreEx.Cosmos/Model/CosmosDbValueModelContainer.cs @@ -41,7 +41,7 @@ public CosmosDbValueModelContainer UsePartitionKey(FuncThe model to infer from. /// The . /// The . - /// Will be thrown where the infered is not equal to (where not null). + /// Will be thrown where the infered is not equal to (where not null). public PartitionKey GetPartitionKey(CosmosDbValue model, CosmosDbArgs dbArgs) { var dbpk = DbArgs.PartitionKey; @@ -117,7 +117,7 @@ private Result CheckAuthorized(CosmosDbValue model) /// Gets the model for the specified . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . /// The model value where found; otherwise, null (see ). public async Task?> GetAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => await GetWithResultAsync(key, partitionKey, cancellationToken).ConfigureAwait(false); @@ -126,7 +126,7 @@ private Result CheckAuthorized(CosmosDbValue model) /// Gets the model for the specified with a . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . /// The model value where found; otherwise, null (see ). public Task?>> GetWithResultAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => GetWithResultAsync(new CosmosDbArgs(DbArgs, partitionKey), key, cancellationToken); @@ -147,12 +147,12 @@ private Result CheckAuthorized(CosmosDbValue model) /// The . /// The . /// The model value where found; otherwise, null (see ). - public Task?>> GetWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, GetCosmosId(key), dbArgs, async (_, key, args, ct) => + public Task?>> GetWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, GetCosmosId(key), dbArgs, async (_, id, args, ct) => { try { var pk = args.PartitionKey ?? DbArgs.PartitionKey ?? PartitionKey.None; - var resp = await Container.ReadItemAsync>(key, pk, args.GetItemRequestOptions(), ct).ConfigureAwait(false); + var resp = await Container.ReadItemAsync>(id, pk, args.GetItemRequestOptions(), ct).ConfigureAwait(false); if (resp.Resource == null || resp.Resource.Type != _typeName || args.FilterByTenantId && resp.Resource.Value is ITenantId tenantId && tenantId.TenantId != DbArgs.GetTenantId() || resp.Resource.Value is ILogicallyDeleted ld && ld.IsDeleted.HasValue && ld.IsDeleted.Value) return args.NullOnNotFound ? Result?>.None : Result?>.NotFoundError(); @@ -258,9 +258,9 @@ internal Task>> UpdateWithResultInternalAsync(Cosmo // Must read existing to update. ((ICosmosDbValue)m).PrepareBefore(dbArgs); - var key = m.Id; + var id = m.Id; var pk = GetPartitionKey(m, dbArgs); - var resp = await Container.ReadItemAsync>(key, pk, ro, ct).ConfigureAwait(false); + var resp = await Container.ReadItemAsync>(id, pk, ro, ct).ConfigureAwait(false); if (resp?.Resource == null || resp.Resource.Type != _typeName) return Result>.NotFoundError(); @@ -282,7 +282,7 @@ internal Task>> UpdateWithResultInternalAsync(Cosmo }) .ThenAsAsync(async () => { - resp = await Container.ReplaceItemAsync(resp.Resource, key, pk, ro, ct).ConfigureAwait(false); + resp = await Container.ReplaceItemAsync(resp.Resource, id, pk, ro, ct).ConfigureAwait(false); return GetResponseValue(resp)!; }); }, cancellationToken, nameof(UpdateWithResultAsync)); @@ -305,7 +305,7 @@ internal Task>> UpdateWithResultInternalAsync(Cosmo /// Deletes the model for the specified . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . public async Task DeleteAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => (await DeleteWithResultAsync(key, partitionKey, cancellationToken).ConfigureAwait(false)).ThrowOnError(); @@ -313,7 +313,7 @@ internal Task>> UpdateWithResultInternalAsync(Cosmo /// Deletes the model for the specified with a . /// /// The . - /// The . Defaults to . + /// The . Defaults to . /// The . public Task DeleteWithResultAsync(CompositeKey key, PartitionKey? partitionKey, CancellationToken cancellationToken = default) => DeleteWithResultAsync(new CosmosDbArgs(DbArgs, partitionKey), key, cancellationToken); @@ -331,14 +331,14 @@ internal Task>> UpdateWithResultInternalAsync(Cosmo /// The . /// The . /// The . - public Task DeleteWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, GetCosmosId(key), dbArgs, async (_, key, args, ct) => + public Task DeleteWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) => CosmosDb.Invoker.InvokeAsync(CosmosDb, GetCosmosId(key), dbArgs, async (_, id, args, ct) => { try { // Must read existing to delete and to make sure we are deleting for the correct Type; don't just trust the key. var ro = args.GetItemRequestOptions(); var pk = args.PartitionKey ?? DbArgs.PartitionKey ?? PartitionKey.None; - var resp = await Container.ReadItemAsync>(key, pk, ro, ct).ConfigureAwait(false); + var resp = await Container.ReadItemAsync>(id, pk, ro, ct).ConfigureAwait(false); if (resp?.Resource == null || resp.Resource.Type != _typeName) return Result.Success; @@ -357,7 +357,7 @@ public Task DeleteWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, .ThenAsync(async () => { ro.SessionToken = resp.Headers?.Session; - await Container.ReplaceItemAsync(resp.Resource, key, pk, ro, ct).ConfigureAwait(false); + await Container.ReplaceItemAsync(resp.Resource, id, pk, ro, ct).ConfigureAwait(false); return Result.Success; }); } @@ -367,7 +367,7 @@ public Task DeleteWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, .ThenAsync(async () => { ro.SessionToken = resp.Headers?.Session; - await Container.DeleteItemAsync(key, pk, ro, ct).ConfigureAwait(false); + await Container.DeleteItemAsync(id, pk, ro, ct).ConfigureAwait(false); return Result.Success; }); }