From 4821c409161fdf9f54cf8e8cb6900be926ae8417 Mon Sep 17 00:00:00 2001 From: "Eric Sibly [chullybun]" Date: Thu, 3 Oct 2024 14:27:20 -0700 Subject: [PATCH] Fix published version number. (#124) * - Update documentation. - Correct the version number previously incorrectly published. * Changelog update. * Doco updates. * Final doco tweak. --- CHANGELOG.md | 1 + Common.targets | 2 +- src/CoreEx.Cosmos/README.md | 218 ++++++++++++++++++ src/CoreEx.Database/README.md | 1 - src/CoreEx.EntityFrameworkCore/README.md | 8 +- src/CoreEx.OData/README.md | 2 +- .../Events/Subscribing/EventSubscriberArgs.cs | 2 +- 7 files changed, 226 insertions(+), 8 deletions(-) create mode 100644 src/CoreEx.Cosmos/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b84522b..2299df42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Represents the **NuGet** versions. ## v3.26.0 - *Enhancement:* Enable JSON serialization of database parameter values; added `DatabaseParameterCollection.AddJsonParameter` method and associated `JsonParam`, `JsonParamWhen` and `JsonParamWith` extension methods. - *Enhancement:* Updated (simplified) `EventOutboxEnqueueBase` to pass events to the underlying stored procedures as JSON versus existing TVP removing database dependency on a UDT (user-defined type). +- **Note:** Accidently published as `v3.25.6`, re-publishing as `v3.26.0` as intended - includes no code changes. ## v3.25.5 - *Fixed:* Fixed the unit testing `CreateServiceBusMessage` extension method so that it no longer invokes a `TesterBase.ResetHost` (this reset should now be invoked explicitly by the developer as required). diff --git a/Common.targets b/Common.targets index 83d44ee2..02f4ffc4 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@  - 3.25.6 + 3.26.0 preview Avanade Avanade diff --git a/src/CoreEx.Cosmos/README.md b/src/CoreEx.Cosmos/README.md new file mode 100644 index 00000000..f79f80c8 --- /dev/null +++ b/src/CoreEx.Cosmos/README.md @@ -0,0 +1,218 @@ +# CoreEx.Cosmos + +The `CoreEx.Cosmos` namespace provides extended [_Azure Cosmos DB_](https://learn.microsoft.com/en-us/azure/cosmos-db/) capabilities, specifically focused on the [API for NoSQL](https://learn.microsoft.com/en-us/azure/cosmos-db/choose-api#api-for-nosql). + +
+ +## Motivation + +The motivation is to provide supporting Cosmos DB capabilities for [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) related access that support standardized _CoreEx_ data access patterns. This for the most part will simplify and unify the approach to ensure consistency of implementation where needed. + +
+ +## Requirements + +The requirements for usage are as follows. +- An **entity** (DTO) that represents the data that must as a minimum implement [`IEntityKey`](../CoreEx/Entities/IEntityKey.cs); generally via either the implementation of [`IIdentifier`](../CoreEx/Entities/IIdentifierT.cs) or [`IPrimaryKey`](../CoreEx/Entities/IPrimaryKey.cs). +- A **model** being the underlying data representation that will be persisted within Cosmos DB itself. +- An [`IMapper`](../CoreEx/Mapping/IMapper.cs) that contains the mapping logic to map to and from the **entity** and **model**. + +The **entity** and **model** are different types to encourage separation between the externalized **entity** representation and the underlying **model**; which may be shaped differently, and have different property naming conventions, internalized properties, etc. + +
+ +## Railway-oriented programming + +To support [railway-oriented programming](../CoreEx/Results/README.md) whenever a method name includes `WithResult` this indicates that it will return a `Result` or `Result` including the resulting success or failure information. In these instances an `Exception` will only be thrown when considered truly exceptional. + +
+ +## Resource model + +This article provides a good overview of the [Azure Cosmos DB resource model](https://learn.microsoft.com/en-us/azure/cosmos-db/resource-model); these concepts are important to understand when working with Cosmos DB. + +_CoreEx_ provides encapsulated capabilities for each of the following: +- [Databases](#Database) - contains one-or-more Containers. +- [Containers](#Containers) (Collections) - contains one-or-more Items. +- [Items](#Items) (Documents) - the JSON object being persisted. + +Each of the above are further described, in reverse order, as this is intended to make it easier to understand. + +
+ +## Items + +From a Cosmos DB perspective, an Item (aka Document) is a JSON object that represents the data that is being persisted. The are two key patterns for persisting a Document that _CoreEx_ enables: + +- **Untyped** - where a single Document _type_ is persisted within the Container; being one or more Documents of the same _type_ (schema/structure). This is the simpliest pattern, and requires no special support to enable; i.e. works out-of-the-box. +- **Typed** - where one or more Document _types_ are persisted within the Container; being one or more Documents of different _types_ (schema/structure). This is a more complex pattern, and requires additional support to enable - this is enabled in a consistent manner via the [`CosmosDbValue`](./CosmosDbValue.cs) class. + +The **Typed** document JSON structure is as follows (standard Cosmos DB properties have been removed for brevity purposes): + +``` json +{ + "type": "document-type-name", # The unique name of the document type; used to query/filter the documents. + "value": { # The actual document _model_ data. + "property1": "value1", + "property2": "value2" + } +} +``` + +
+ +## Containers + +From a Cosmos DB perspective, a [Container](https://learn.microsoft.com/en-us/azure/cosmos-db/resource-model#azure-cosmos-db-containers) is a logical entity that represents a collection of items. The are two key patterns for interacting with a container that _CoreEx_ enables: +- **Entity** - pattern in which there is separation between the externalized **entity** and the underlying **model**, and the requisite mapping between the two is fully integrated. This is the preferred pattern as it allows for a clear separation of concerns. These capabilities largely exist in the root `CoreEx.Cosmos` namespace. +- **Model** - pattern in which the persisted **model** is directly interacted with, with the expectation that the developer would handle any mapping manually. This is useful in scenarios where the full **entity** is an overhead to the operations that needs to be performed. These capabilities largely exist in the `CoreEx.Cosmos.Model` namespace. + +A Cosmos DB Container is encapsulated within one of the following _CoreEx_ capabilities depending on the patterns required: + +Type | Container Pattern | Document Pattern | [`IMapper`](../CoreEx/Mapping/IMapper.cs) support +-|-|-|- +[`CosmosDbContainer`](CosmosDbContainer.cs) | Entity | Untyped | Yes +[`CosmosDbValueContainer`](CosmosDbValueContainer.cs) | Entity | Typed | Yes +[`CosmosDbModelContainer`](Model/CosmosDbModelContainer.cs) | Model | Untyped | No +[`CosmosDbValueModelContainer`](Model/CosmosDbValueModelContainer.cs) | Model | Typed | No + +Where more advanced CosmosDB capabilities are required, for example, Partitioning, etc., then the [`CosmosDbArgs`](./CosmosDbArgs.cs) enables the configuration of these capabilities, as well as other extended _CoreEx_ capabilities such as multi-tenancy support. + +Additionally, given how important Partitioning is to Cosmos DB performance, many methods have been provided with an optional `partitionKey` parameter to enable the developer to specify the partition key for the operation. + +Finally, where a Container contains multiple typed documents, an advanced query capability is provided to select and return one-or-more types in a single performant operation; see [`CosmosDb.SelectMultiSetWithResultAsync`](./CosmosDb.cs). The [`SelectMultiSetAsync`](../../tests/CoreEx.Cosmos.Test/CosmosDbContainerPartitioningTest.cs) unit test provides example usage. + +
+ +## Database + +From a Cosmos DB perspective, a [Database](https://learn.microsoft.com/en-us/azure/cosmos-db/resource-model#azure-cosmos-db-databases) is a means to group one-or-more Containers. + +The [`ICosmoDb`](./ICosmosDb.cs) and corresponding [`CosmosDb`](./CosmosDb.cs) provides the base Database capabilities: +- `Container()` - instantiates a `CosmosDbContainer` instance. +- `ValueContainer()` - instantiates a `CosmosDbValueContainer` instance. +- `ModelContainer()` - instantiates a `CosmosDbModelContainer` instance. +- `ValueModelContainer()` - instantiates a `CosmosDbValueModelContainer` instance. +- `UserAuhtorizeFilter()` - enables an authorization filter to be applied to a specified Container. + +The following represents an example usage of the [`CosmosDb`](https://github.com/Avanade/Beef/blob/master/samples/Cdr.Banking/Cdr.Banking.Business/Data/CosmosDb.cs) class: + +``` csharp +public class CosmosDb : CoreEx.Cosmos.CosmosDb +{ + private readonly Lazy> _accounts; + private readonly Lazy> _accountDetails; + private readonly Lazy> _transactions; + + /// + /// Initializes a new instance of the class. + /// + public CosmosDb(Mac.Database database, IMapper mapper) : base(database, mapper) + { + // Apply an authorization filter to all operations to ensure only the valid data is available based on the users context; i.e. only allow access to Accounts within list defined on ExecutionContext. + UseAuthorizeFilter("Account", (q) => ((IQueryable)q).Where(x => ExecutionContext.Current.Accounts.Contains(x.Id!))); + UseAuthorizeFilter("Transaction", (q) => ((IQueryable)q).Where(x => ExecutionContext.Current.Accounts.Contains(x.AccountId!))); + + // Lazy create the containers. + _accounts = new(() => Container("Account")); + _accountDetails = new(() => Container("Account")); + _transactions = new(() => Container("Transaction")); + } + + /// + /// Exposes entity from Account container. + /// + public CosmosDbContainer Accounts => _accounts.Value; + + /// + /// Exposes entity from Account container. + /// + public CosmosDbContainer AccountDetails => _accountDetails.Value; + + /// + /// Exposes entity from Account container. + /// + public CosmosDbContainer Transactions => _transactions.Value; +} +``` + +
+ +## CRUD capabilities + +The **entity** [`ICosmosDbContainer`](./ICosmosDbContainerT.cs) and **model** [`CosmosDbModelContainer`](./Model/CosmosDbModelContainer.cs) provides the base CRUD capabilities as follows. + +
+ +### Query (Read) + +A query is actioned using the [`CosmosDbQuery`](./CosmosDbQuery.cs) and [`CosmosDbModelQuery`](./Model/CosmosDbModelQuery.cs) which is ostensibly a lightweight wrapper over an `IQueryable` that automatically maps from the **model** to the **entity** (where applicable). + +Uses the [`Container.GetItemLinqQueryable`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.container.getitemlinqqueryable?view=azure-dotnet) internally to create. + +The following methods provide additional capabilities: + +Method | Description +-|- +`WithPaging` | Adds `Skip` and `Take` paging to the query. +`SelectSingleAsync`, `SelectSingleWithResult` | Selects a single item. +`SelectSingleOrDefaultAsync`, `SelectSingleOrDefaultWithResultAsync` | Selects a single item or default. +`SelectFirstAsync`, `SelectFirstWithResultAsync` | Selects first item. +`SelectFirstOrDefaultAsync`, `SelectFirstOrDefaultWithResultAsync` | Selects first item or default. +`SelectQueryAsync`, `SelectQueryWithResultAsync` | Select items into or creating a resultant collection. +`SelectResultAsync`, `SelectResultWithResultAsync` | Select items creating a [`ICollectionResult`](../CoreEx/Entities/ICollectionResultT2.cs) which also contains corresponding [`PagingResult`](../CoreEx/Entities/PagingResult.cs). +`ToArrayAsync`, `ToArrayWithResultAsync` | Select items into a resulting array. + +
+ +### Get (Read) + +Gets (`GetAsync` or `GetWithResultAsync`) the **entity** for the specified key mapping from the **model**. Uses the [`Container.ReadItemAsync`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.container.readitemasync?view=azure-dotnet) internally for the **model** and specified key. + +Where the data is not found, then a `null` will be returned. Where the **model** implements [`ILogicallyDeleted`](../CoreEx/Entities/ILogicallyDeleted.cs) and `IsDeleted` then this acts as if not found and returns a `null`. + +
+ +### Create + +Creates (`CreateAsync` or `CreateWithResultAsync`) the **entity** by firstly mapping to the **model**. Uses the [`Container.CreateItemAsync`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.container.createitemasync?view=azure-dotnet) internally to create. + +Where the **entity** implements [`IChangeLogAuditLog`](../CoreEx/Entities/IChangeLogAuditLog.cs) generally via [`ChangeLog`](../CoreEx/Entities/IChangeLog.cs) or [`ChangeLogEx`](../CoreEx/Entities/Extended/IChangeLogEx.cs), then the `CreatedBy` and `CreatedDate` properties will be automatically set from the [`ExecutionContext`](../CoreEx/ExecutionContext.cs). + +Where the **entity** and/or **model** implements [`ITenantId`](../CoreEx/Entities/ITenantId.cs) then the `TenantId` property will be automatically set from the [`ExecutionContext`](../CoreEx/ExecutionContext.cs). + +The inserted **model** is then re-mapped to the **entity** and returned; this will ensure all properties updated as part of the _insert_ are included in the refreshed **entity**. + +
+ +### Update + +Updates (`UpdateAsync` or `UpdateWithResultAsync`) the **entity** by firstly mapping to the **model**. Uses the [`Container.ReplaceItemAsync`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.container.replaceitemasync?view=azure-dotnet) internally to update. + +First will check existence of the **model** by performing a [`Container.ReadItemAsync`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.container.readitemasync?view=azure-dotnet). Where the data is not found, then a [`NotFoundException`](../CoreEx/NotFoundException.cs) will be thrown. Where the **model** implements [`ILogicallyDeleted`](../CoreEx/Entities/ILogicallyDeleted.cs) and `IsDeleted` then this acts as if not found and will also result in a `NotFoundException`. + +Where the entity implements [`IETag`](../CoreEx/Entities/IETag.cs) this will be checked against the just read version, and where not matched a [`ConcurrencyException`](../CoreEx/ConcurrencyException.cs) will be thrown. Also, any [`CosmosException`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.cosmosexception?view=azure-dotnet) with a `HttpStatusCode.PreconditionFailed` thrown will be converted to a corresponding `ConcurrencyException` for consistency. + +Where the **entity** implements [`IChangeLogAuditLog`](../CoreEx/Entities/IChangeLogAuditLog.cs) generally via [`ChangeLog`](../CoreEx/Entities/IChangeLog.cs) or [`ChangeLogEx`](../CoreEx/Entities/Extended/IChangeLogEx.cs), then the `UpdatedBy` and `UpdatedDate` properties will be automatically set from the [`ExecutionContext`](../CoreEx/ExecutionContext.cs). + +Where the **entity** and/or **model** implements [`ITenantId`](../CoreEx/Entities/ITenantId.cs) then the `TenantId` property will be automatically set from the [`ExecutionContext`](../CoreEx/ExecutionContext.cs). + +The updated **model** is then re-mapped to the **entity** and returned; this will ensure all properties updated as part of the _update_ are included in the refreshed **entity**. + +
+ +### Delete + +Deletes (`DeleteAsync` or `DeleteWithResultAsync`) the **entity**/**model** either physically or logically. + +First will check existence of the **model** by performing a [`Container.ReadItemAsync`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.container.readitemasync?view=azure-dotnet). Where the data is not found, then a [`NotFoundException`](../CoreEx/NotFoundException.cs) will be thrown. Where the **model** implements [`ILogicallyDeleted`](../CoreEx/Entities/ILogicallyDeleted.cs) and `IsDeleted` then this acts as if not found and will also result in a `NotFoundException`. + +Where the **model** implements [`ILogicallyDeleted`](../CoreEx/Entities/ILogicallyDeleted.cs) then an update will occur after setting `IsDeleted` to `true`. Uses the [`Container.ReplaceItemAsync`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.container.replaceitemasync?view=azure-dotnet) internally to update. + +Otherwise, will physically delete. Uses the [`Container.DeleteItemAsync`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.container.deleteitemasync?view=azure-dotnet) internally to delete. + +
+ +## Usage + +Review the unit tests and/or _Beef_ [Cdr.Banking](https://github.com/Avanade/Beef/tree/master/samples/Cdr.Banking) sample implementation. \ No newline at end of file diff --git a/src/CoreEx.Database/README.md b/src/CoreEx.Database/README.md index 0712a93f..45b200f0 100644 --- a/src/CoreEx.Database/README.md +++ b/src/CoreEx.Database/README.md @@ -170,7 +170,6 @@ To simplify the support for the [retrieval of multiple result sets](https://lear The following `IMultiSetArgs` implementations are provided. The `StopOnNull` property indicates whether to stop further query result set processing where the current set has resulted in a `null` (i.e. no records). - Class | Description -|- [`MultiSetCollArgs`](./MultiSetCollArgsT.cs) | Provides the multi-set arguments when expecting a collection of items/records. The `MinRows` and `MaxRows` properties can also be specified to ensure/validate correctness of returned rows. diff --git a/src/CoreEx.EntityFrameworkCore/README.md b/src/CoreEx.EntityFrameworkCore/README.md index fd9019bb..b4682aca 100644 --- a/src/CoreEx.EntityFrameworkCore/README.md +++ b/src/CoreEx.EntityFrameworkCore/README.md @@ -1,4 +1,4 @@ -# CoreEx +# CoreEx.EntityFrameworkCore The `CoreEx.EntityFrameworkCore` namespace provides extended [_Entity Framework Core (EF)_](https://learn.microsoft.com/en-us/ef/core/) capabilities. @@ -17,7 +17,7 @@ The requirements for usage are as follows. - A **model** being the underlying configured EF Core [data source model](https://learn.microsoft.com/en-us/ef/core/modeling/). - An [`IMapper`](../CoreEx/Mapping/IMapper.cs) that contains the mapping logic to map to and from the **entity** and **model**. -The **entity** and **model** are different types to encourage separation between the externalized **entity** representation and the underlying **model**; which may be shaped differently, and have different property to column naming conventions, etc. +The **entity** and **model** are different types to encourage separation between the externalized **entity** representation and the underlying **model**; which may be shaped differently, and have different property to column naming conventions, internalized columns, etc.
@@ -35,13 +35,13 @@ The [`IEfDb`](./IEfDb.cs) and corresponding [`EfDb`](./EfDb.cs) provides the bas ### Query (read) -A query is actioned using the [`EfDbQuery`](./EfDbQuery.cs) which is obstensibly a lightweight wrapper over an `IQueryable` that automatically maps from the **model** to the **entity**. +A query is actioned using the [`EfDbQuery`](./EfDbQuery.cs) which is ostensibly a lightweight wrapper over an `IQueryable` that automatically maps from the **model** to the **entity**. Queried entities are not tracked by default; internally uses [`AsNoTracking`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.asnotracking); this behaviour can be overridden using [`EfDbArgs.QueryNoTracking`](./EfDbArgs.cs). Note: a consumer should also consider using [`IgnoreAutoIncludes`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.ignoreautoincludes) to exclude related data, where not required, to improve query performance. -The following methods provide additional capabilities +The following methods provide additional capabilities: Method | Description -|- diff --git a/src/CoreEx.OData/README.md b/src/CoreEx.OData/README.md index a13d8796..471a4431 100644 --- a/src/CoreEx.OData/README.md +++ b/src/CoreEx.OData/README.md @@ -39,7 +39,7 @@ The [`IOData`](./IOData.cs) and corresponding [`ODataClient`](./ODataClient.cs) ### Query (read) -A query is actioned using the [`ODataQuery`](./ODataQuery.cs) which is obstensibly a lighweight wrapper over an `IBoundClient`(https://github.com/simple-odata-client/Simple.OData.Client/blob/master/src/Simple.OData.Client.Core/Fluent/IBoundClient.cs) that automatically maps from the **model** to the **entity**. +A query is actioned using the [`ODataQuery`](./ODataQuery.cs) which is ostensibly a lighweight wrapper over an `IBoundClient`(https://github.com/simple-odata-client/Simple.OData.Client/blob/master/src/Simple.OData.Client.Core/Fluent/IBoundClient.cs) that automatically maps from the **model** to the **entity**. The following methods provide additional capabilities: diff --git a/src/CoreEx/Events/Subscribing/EventSubscriberArgs.cs b/src/CoreEx/Events/Subscribing/EventSubscriberArgs.cs index 7313ee7e..c8432fa1 100644 --- a/src/CoreEx/Events/Subscribing/EventSubscriberArgs.cs +++ b/src/CoreEx/Events/Subscribing/EventSubscriberArgs.cs @@ -9,7 +9,7 @@ namespace CoreEx.Events.Subscribing { /// - /// Provides the arguments; is obstensibly a with a key and value. + /// Provides the arguments; is ostensibly a with a key and value. /// /// This enables runtime state to passed through to the underlying subscriber receive logic where applicable. public class EventSubscriberArgs : Dictionary