Skip to content

Commit

Permalink
Fix published version number. (#124)
Browse files Browse the repository at this point in the history
* - Update documentation.
- Correct the version number previously incorrectly published.

* Changelog update.

* Doco updates.

* Final doco tweak.
  • Loading branch information
chullybun authored Oct 3, 2024
1 parent 61e2ded commit 4821c40
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
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.25.6</Version>
<Version>3.26.0</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
218 changes: 218 additions & 0 deletions src/CoreEx.Cosmos/README.md
Original file line number Diff line number Diff line change
@@ -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).

<br/>

## 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.

<br/>

## 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.

<br/>

## 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<T>` including the resulting success or failure information. In these instances an `Exception` will only be thrown when considered truly exceptional.

<br/>

## 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.

<br/>

## 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<TModel>`](./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"
}
}
```

<br/>

## 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.

<br/>

## 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<T, TModel>()` - instantiates a `CosmosDbContainer` instance.
- `ValueContainer<T, TModel>()` - instantiates a `CosmosDbValueContainer` instance.
- `ModelContainer<TModel>()` - instantiates a `CosmosDbModelContainer` instance.
- `ValueModelContainer<TModel>()` - instantiates a `CosmosDbValueModelContainer` instance.
- `UserAuhtorizeFilter<TModel>()` - 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<CosmosDbContainer<Account, Model.Account>> _accounts;
private readonly Lazy<CosmosDbContainer<AccountDetail, Model.Account>> _accountDetails;
private readonly Lazy<CosmosDbContainer<Transaction, Model.Transaction>> _transactions;

/// <summary>
/// Initializes a new instance of the <see cref="CosmosDb"/> class.
/// </summary>
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<Model.Account>("Account", (q) => ((IQueryable<Model.Account>)q).Where(x => ExecutionContext.Current.Accounts.Contains(x.Id!)));
UseAuthorizeFilter<Model.Account>("Transaction", (q) => ((IQueryable<Model.Transaction>)q).Where(x => ExecutionContext.Current.Accounts.Contains(x.AccountId!)));

// Lazy create the containers.
_accounts = new(() => Container<Account, Model.Account>("Account"));
_accountDetails = new(() => Container<AccountDetail, Model.Account>("Account"));
_transactions = new(() => Container<Transaction, Model.Transaction>("Transaction"));
}

/// <summary>
/// Exposes <see cref="Account"/> entity from <b>Account</b> container.
/// </summary>
public CosmosDbContainer<Account, Model.Account> Accounts => _accounts.Value;

/// <summary>
/// Exposes <see cref="AccountDetail"/> entity from <b>Account</b> container.
/// </summary>
public CosmosDbContainer<AccountDetail, Model.Account> AccountDetails => _accountDetails.Value;

/// <summary>
/// Exposes <see cref="AccountDetail"/> entity from <b>Account</b> container.
/// </summary>
public CosmosDbContainer<Transaction, Model.Transaction> Transactions => _transactions.Value;
}
```

<br/>

## CRUD capabilities

The **entity** [`ICosmosDbContainer<T, TModel>`](./ICosmosDbContainerT.cs) and **model** [`CosmosDbModelContainer<TModel>`](./Model/CosmosDbModelContainer.cs) provides the base CRUD capabilities as follows.

<br/>

### Query (Read)

A query is actioned using the [`CosmosDbQuery<T, TModel>`](./CosmosDbQuery.cs) and [`CosmosDbModelQuery<TModel>`](./Model/CosmosDbModelQuery.cs) which is ostensibly a lightweight wrapper over an `IQueryable<TModel>` 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.

<br/>

### 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`.

<br/>

### 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**.

<br/>

### 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**.

<br/>

### 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.

<br/>

## Usage

Review the unit tests and/or _Beef_ [Cdr.Banking](https://github.com/Avanade/Beef/tree/master/samples/Cdr.Banking) sample implementation.
1 change: 0 additions & 1 deletion src/CoreEx.Database/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<TColl, TItem>`](./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.
Expand Down
8 changes: 4 additions & 4 deletions src/CoreEx.EntityFrameworkCore/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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.

<br/>

Expand All @@ -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<TModel>` 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<TModel>` 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
-|-
Expand Down
Loading

0 comments on commit 4821c40

Please sign in to comment.