Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3.23.5 #114

Merged
merged 1 commit into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

Represents the **NuGet** versions.

## v3.23.5
- *Fixed:* `CosmosDbValue<TModel>.PrepareBefore` corrected to set the `PartitionKey` where the underlying `Value` implements `IPartitionKey`.
- *Fixed:* `CosmosDbBatch` corrected to default to the `CosmosDbContainerBase<TSelf>.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<T>.AdjustsAsync` to support asynchronous adjustments.

Expand Down
2 changes: 1 addition & 1 deletion Common.targets
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.23.4</Version>
<Version>3.23.5</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
8 changes: 4 additions & 4 deletions src/CoreEx.Cosmos/Batch/CosmosDbBatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public static async Task ImportBatchAsync<TModel>(this ICosmosDb cosmosDb, strin
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <remarks>Each item is added individually and is not transactional.</remarks>
public static Task ImportBatchAsync<T, TModel>(this CosmosDbContainer<T, TModel> container, IEnumerable<TModel> items, Func<TModel, TModel>? 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);

/// <summary>
/// Imports (creates) a batch of named items from the <paramref name="jsonDataReader"/> into the specified <paramref name="containerId"/>.
Expand Down Expand Up @@ -106,7 +106,7 @@ public static async Task ImportBatchAsync<TModel>(this ICosmosDb cosmosDb, strin
/// <returns><c>true</c> indicates that one or more items were deserialized and imported; otherwise, <c>false</c> for none found.</returns>
/// <remarks>Each item is added individually and is not transactional.</remarks>
public static Task<bool> ImportBatchAsync<T, TModel>(this CosmosDbContainer<T, TModel> container, JsonDataReader jsonDataReader, string? name = null, Func<TModel, TModel>? 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);

/// <summary>
/// Imports (creates) a batch of <see cref="CosmosDbValue{TModel}"/> <paramref name="items"/>.
Expand Down Expand Up @@ -154,7 +154,7 @@ public static async Task ImportBatchAsync<TModel>(this ICosmosDb cosmosDb, strin
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <remarks>Each item is added individually and is not transactional.</remarks>
public static Task ImportValueBatchAsync<T, TModel>(this CosmosDbValueContainer<T, TModel> container, IEnumerable<TModel> items, Func<CosmosDbValue<TModel>, CosmosDbValue<TModel>>? 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);

/// <summary>
/// Imports (creates) a batch of named <see cref="CosmosDbValue{TModel}"/> items from the <paramref name="jsonDataReader"/> into the specified <paramref name="containerId"/>.
Expand Down Expand Up @@ -193,7 +193,7 @@ public static async Task ImportBatchAsync<TModel>(this ICosmosDb cosmosDb, strin
/// <returns><c>true</c> indicates that one or more items were deserialized and imported; otherwise, <c>false</c> for none found.</returns>
/// <remarks>Each item is added individually and is not transactional.</remarks>
public static Task<bool> ImportValueBatchAsync<T, TModel>(this CosmosDbValueContainer<T, TModel> container, JsonDataReader jsonDataReader, string? name = null, Func<CosmosDbValue<TModel>, CosmosDbValue<TModel>>? 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);

/// <summary>
/// Imports (creates) a batch of named <see cref="CosmosDbValue{TModel}"/> items from the <paramref name="jsonDataReader"/> into the specified <paramref name="containerId"/>.
Expand Down
40 changes: 11 additions & 29 deletions src/CoreEx.Cosmos/CosmosDbArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ public CosmosDbArgs() { }
/// Initializes a new instance of the <see cref="CosmosDbArgs"/> struct.
/// </summary>
/// <param name="template">The template <see cref="CosmosDbArgs"/> to copy from.</param>
/// <param name="partitionKey">The <see cref="Microsoft.Azure.Cosmos.PartitionKey"/>.</param>
/// <param name="partitionKey">The override <see cref="Microsoft.Azure.Cosmos.PartitionKey"/>.</param>
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;
}

/// <summary>
Expand Down Expand Up @@ -98,12 +98,17 @@ private readonly QueryRequestOptions UpdateQueryRequestionOptionsPartitionKey(Qu
}

/// <summary>
/// Indicates that a <c>null</c> is to be returned where the <b>response</b> has a <see cref="HttpStatusCode"/> of <see cref="HttpStatusCode.NotFound"/> on <b>Get</b>.
/// Indicates whether a <c>null</c> is to be returned where the <b>response</b> has a <see cref="HttpStatusCode"/> of <see cref="HttpStatusCode.NotFound"/> on <b>Get</b>. Defaults to <c>true</c>.
/// </summary>
public bool NullOnNotFound { get; set; } = true;

/// <summary>
/// Indicates whether the result should be <see cref="Entities.Cleaner.Clean{T}(T)">cleaned up</see>.
/// Indicates whether when mapping the model to the corresponding entity that the <see cref="IETag.ETag"/> is to be automatically mapped. Defaults to <c>true</c>.
/// </summary>
public bool AutoMapETag { get; set; } = true;

/// <summary>
/// Indicates whether the result should be <see cref="Entities.Cleaner.Clean{T}(T)">cleaned up</see>. Defaults to <c>false</c>.
/// </summary>
public bool CleanUpResult { get; set; } = false;

Expand All @@ -121,35 +126,12 @@ private readonly QueryRequestOptions UpdateQueryRequestionOptionsPartitionKey(Qu
/// Formats a <see cref="CompositeKey"/> to a <see cref="string"/> representation (used by <see cref="CosmosDbContainerBase{T, TModel, TSelf}.GetCosmosId(T)"/> and <see cref="ICosmosDbValue.PrepareBefore"/>).
/// </summary>
/// <returns>The identifier as a <see cref="string"/>.</returns>
/// <remarks>Defaults to <see cref="DefaultFormatIdentifier"/>.</remarks>
public Func<CompositeKey, string?> FormatIdentifier { get; set; } = DefaultFormatIdentifier;

/// <summary>
/// Parses a <see cref="string"/> identifier and updates the underlying value where it implements <see cref="IIdentifier.Id"/> (used by the <see cref="ICosmosDbValue.PrepareAfter"/>).
/// </summary>
/// <returns>The parsed identifier.</returns>
public Action<object, string?> ParseIdentifier { get; set; } = DefaultParseIdentifier;

/// <summary>
/// Provides the default <see cref="FormatIdentifier"/> implementation.
/// Provides the default <see cref="FormatIdentifier"/> implementation; being the <see cref="CompositeKey"/> <see cref="object.ToString"/>.
/// </summary>
public static Func<CompositeKey, string?> DefaultFormatIdentifier { get; } = key => key.ToString();

/// <summary>
/// Provides the default <see cref="ParseIdentifier"/> implementation.
/// </summary>
public static Action<object, string?> 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.")
};
}
};
}
}
116 changes: 20 additions & 96 deletions src/CoreEx.Cosmos/CosmosDbContainer.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Provides <see cref="Container"/> operations for a <see cref="CosmosDb"/> container.
/// Provides the core <see cref="Container"/> capabilities.
/// </summary>
/// <typeparam name="T">The entity <see cref="Type"/>.</typeparam>
/// <typeparam name="TModel">The cosmos model <see cref="Type"/>.</typeparam>
public class CosmosDbContainer<T, TModel> : CosmosDbContainerBase<T, TModel, CosmosDbContainer<T, TModel>> where T : class, IEntityKey, new() where TModel : class, IEntityKey, new()
/// <param name="cosmosDb">The <see cref="ICosmosDb"/>.</param>
/// <param name="containerId">The <see cref="Microsoft.Azure.Cosmos.Container"/> identifier.</param>
/// <param name="dbArgs">The optional <see cref="CosmosDbArgs"/>.</param>
public class CosmosDbContainer(ICosmosDb cosmosDb, string containerId, CosmosDbArgs? dbArgs = null) : ICosmosDbContainer
{
private readonly Lazy<CosmosDbModelContainer<TModel>> _modelContainer;
private CosmosDbArgs? _dbArgs = dbArgs;

/// <summary>
/// Initializes a new instance of the <see cref="CosmosDbContainer{T, TModel}"/> class.
/// </summary>
/// <param name="cosmosDb">The <see cref="ICosmosDb"/>.</param>
/// <param name="containerId">The <see cref="Microsoft.Azure.Cosmos.Container"/> identifier.</param>
/// <param name="dbArgs">The optional <see cref="CosmosDbArgs"/>.</param>
public CosmosDbContainer(ICosmosDb cosmosDb, string containerId, CosmosDbArgs? dbArgs = null) : base(cosmosDb, containerId, dbArgs)
=> _modelContainer = new(() => new CosmosDbModelContainer<TModel>(CosmosDb, Container.Id, DbArgs));

/// <summary>
/// Gets the underlying <see cref="CosmosDbModelContainer{TModel}"/>.
/// </summary>
public CosmosDbModelContainer<TModel> ModelContainer => _modelContainer.Value;
/// <inheritdoc/>
public ICosmosDb CosmosDb { get; } = cosmosDb.ThrowIfNull(nameof(cosmosDb));

/// <summary>
/// Sets the function to determine the <see cref="PartitionKey"/>; used for <see cref="CosmosDbModelContainer{TModel}.GetPartitionKey(TModel, CosmosDbArgs)"/> (only <b>Create</b> and <b>Update</b> operations).
/// </summary>
/// <param name="partitionKey">The function to determine the <see cref="PartitionKey"/>.</param>
/// <returns>The <see cref="CosmosDbContainer{T, TModel}"/> instance to support fluent-style method-chaining.</returns>
/// <remarks>This is used where there is a value and the corresponding <see cref="PartitionKey"/> needs to be dynamically determined.</remarks>
public CosmosDbContainer<T, TModel> UsePartitionKey(Func<TModel, PartitionKey> partitionKey)
{
ModelContainer.UsePartitionKey(partitionKey);
return this;
}
/// <inheritdoc/>
public Container Container { get; } = cosmosDb.GetCosmosContainer(containerId);

/// <summary>
/// Gets the <b>value</b> formatting/updating any special properties as required.
/// Gets or sets the Container-specific <see cref="CosmosDbArgs"/>.
/// </summary>
/// <param>The model value.</param>
/// <returns>The entity value.</returns>
[return: NotNullIfNotNull(nameof(model))]
public T? GetValue(TModel? model)
/// <remarks>Defaults to <see cref="ICosmosDb.DbArgs"/> on first access.</remarks>
public CosmosDbArgs DbArgs
{
var val = CosmosDb.Mapper.Map<TModel, T>(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;
}

/// <summary>
/// Gets (creates) a <see cref="CosmosDbQuery{T, TModel}"/> to enable LINQ-style queries.
/// </summary>
/// <param name="query">The function to perform additional query execution.</param>
/// <returns>The <see cref="CosmosDbQuery{T, TModel}"/>.</returns>
public CosmosDbQuery<T, TModel> Query(Func<IQueryable<TModel>, IQueryable<TModel>>? query) => Query(new CosmosDbArgs(DbArgs), query);

/// <summary>
/// Gets (creates) a <see cref="CosmosDbQuery{T, TModel}"/> to enable LINQ-style queries.
/// Gets the <b>CosmosDb</b> identifier from the <see cref="CompositeKey"/>.
/// </summary>
/// <param name="partitionKey">The <see cref="PartitionKey"/>.</param>
/// <param name="query">The function to perform additional query execution.</param>
/// <returns>The <see cref="CosmosDbQuery{T, TModel}"/>.</returns>
public CosmosDbQuery<T, TModel> Query(PartitionKey? partitionKey = null, Func<IQueryable<TModel>, IQueryable<TModel>>? query = null) => Query(new CosmosDbArgs(DbArgs, partitionKey), query);

/// <summary>
/// Gets (creates) a <see cref="CosmosDbQuery{T, TModel}"/> to enable LINQ-style queries.
/// </summary>
/// <param name="dbArgs">The <see cref="CosmosDbArgs"/>.</param>
/// <param name="query">The function to perform additional query execution.</param>
/// <returns>The <see cref="CosmosDbQuery{T, TModel}"/>.</returns>
public CosmosDbQuery<T, TModel> Query(CosmosDbArgs dbArgs, Func<IQueryable<TModel>, IQueryable<TModel>>? query = null) => new(this, dbArgs, query);

/// <inheritdoc/>
public async override Task<Result<T?>> GetWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default)
{
var result = await ModelContainer.GetWithResultAsync(dbArgs, key, cancellationToken).ConfigureAwait(false);
return result.ThenAs(GetValue);
}

/// <inheritdoc/>
public override async Task<Result<T>> CreateWithResultAsync(CosmosDbArgs dbArgs, T value, CancellationToken cancellationToken = default)
{
ChangeLog.PrepareCreated(value.ThrowIfNull(nameof(value)));
TModel model = CosmosDb.Mapper.Map<T, TModel>(value, OperationTypes.Create)!;

var result = await ModelContainer.CreateWithResultAsync(dbArgs, model, cancellationToken).ConfigureAwait(false);
return result.ThenAs(model => GetValue(model)!);
}

/// <inheritdoc/>
public override async Task<Result<T>> UpdateWithResultAsync(CosmosDbArgs dbArgs, T value, CancellationToken cancellationToken = default)
{
ChangeLog.PrepareUpdated(value);
var model = CosmosDb.Mapper.Map<T, TModel>(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)!);
}

/// <inheritdoc/>
public override Task<Result> DeleteWithResultAsync(CosmosDbArgs dbArgs, CompositeKey key, CancellationToken cancellationToken = default) => ModelContainer.DeleteWithResultAsync(dbArgs, key, cancellationToken);
/// <param name="key">The <see cref="CompositeKey"/>.</param>
/// <returns>The <b>CosmosDb</b> identifier.</returns>
/// <remarks>Uses the <see cref="CosmosDbArgs.FormatIdentifier"/> to format the <paramref name="key"/> as a string (as required).</remarks>
public virtual string GetCosmosId(CompositeKey key) => DbArgs.FormatIdentifier(key) ?? throw new InvalidOperationException("The CompositeKey formatting must not result in a null.");
}
}
Loading
Loading