2022-12-07 14:05:11 +01:00
using FluentValidation.Results ;
using Microsoft.Extensions.DependencyInjection ;
2022-12-19 16:20:10 +01:00
using Raven.Client ;
2022-12-07 14:05:11 +01:00
using Raven.Client.Documents.Indexes ;
using Raven.Client.Documents.Linq ;
2022-12-14 13:27:06 +01:00
using System.Linq.Expressions ;
2022-12-07 14:05:11 +01:00
using System.Security.Claims ;
2022-12-07 15:20:53 +01:00
namespace zero.Raven ;
2022-12-07 14:05:11 +01:00
2022-12-07 14:29:07 +01:00
public partial class RavenOperations : IRavenOperations
2022-12-07 14:05:11 +01:00
{
/// <inheritdoc />
2025-02-25 11:31:32 +01:00
public IAsyncDocumentSession Session => Store . Session ();
2022-12-07 14:05:11 +01:00
protected record OperationOptions ( bool IncludeInactive );
protected IZeroContext Context { get ; private set ; }
protected IInterceptors Interceptors { get ; private set ; }
protected FlavorOptions Flavors { get ; private set ; }
protected IServiceProvider Services { get ; private set ; }
2022-12-07 15:20:53 +01:00
protected IZeroStore Store { get ; private set ; }
2022-12-07 14:05:11 +01:00
protected StoreInterceptorBlocker InterceptorBlocker { get ; private set ; }
2022-12-07 14:29:07 +01:00
public RavenOperations ( StoreContext context )
2022-12-07 14:05:11 +01:00
{
2022-12-07 15:20:53 +01:00
Store = context . Store ;
2022-12-07 14:05:11 +01:00
Context = context . Context ;
Interceptors = context . Interceptors ;
Services = context . Services ;
Flavors = context . Options . For < FlavorOptions >();
}
/// <inheritdoc />
public async Task < string > GenerateId < T >( T model ) where T : ZeroIdEntity
{
2025-02-25 11:31:32 +01:00
IAsyncDocumentSession session = Session ;
2022-12-07 14:05:11 +01:00
return await session . Advanced . DocumentStore . Conventions . GenerateDocumentIdAsync ( session . Advanced . DocumentStore . Database , model );
}
/// <inheritdoc />
public T AutoSetIds < T >( T model )
{
return IdGenerator . Autofill ( model );
}
/// <inheritdoc />
public T PrepareForSave < T >( T model ) where T : ZeroIdEntity
{
// set IDs
AutoSetIds ( model );
if ( model is ZeroEntity zeroModel )
{
// get current user
string userId = null ;
//string userId = Context.BackofficeUser.FindFirstValue(Constants.Auth.Claims.UserId).Or(Constants.Auth.SystemUser);
// set default properties
if ( zeroModel . CreatedDate == default )
{
zeroModel . CreatedDate = DateTimeOffset . Now ;
}
if ( zeroModel . CreatedById . IsNullOrEmpty ())
{
zeroModel . CreatedById = userId ;
}
// update name alias and last modified
zeroModel . Alias = Safenames . Alias ( zeroModel . Name );
zeroModel . LastModifiedById = userId ;
zeroModel . LastModifiedDate = DateTimeOffset . Now ;
zeroModel . CreatedById ??= userId ;
zeroModel . Hash ??= IdGenerator . Create ();
}
return model ;
}
/// <inheritdoc />
public async Task < ValidationResult > Validate < T >( T model ) where T : ZeroIdEntity , new ()
{
IZeroMergedValidator < T > validator = Services . GetService < IZeroMergedValidator < T >>();
if ( validator == null )
{
return new ();
}
return await validator . ValidateAsync ( model );
}
/// <inheritdoc />
public StoreInterceptorBlocker WithoutInterceptors ()
{
return InterceptorBlocker ?? ( InterceptorBlocker = new (() => InterceptorBlocker = null ));
}
/// <inheritdoc />
public virtual T WhenActive < T >( T model ) where T : ZeroIdEntity , new ()
{
2023-03-11 00:05:55 +01:00
return model ; // TODO should we really use this? I tried to get data in a custom backend but couldn't because of this
//return model != null && (model is not ZeroEntity || (model as ZeroEntity).IsActive) && (model is not ISupportsSoftDelete || !(model as ISupportsSoftDelete).IsDeleted) ? model : default;
2022-12-07 14:05:11 +01:00
}
}
public class StoreInterceptorBlocker : IDisposable
{
Action _onRelease ;
internal StoreInterceptorBlocker ( Action onRelease )
{
_onRelease = onRelease ;
}
public void Dispose ()
{
_onRelease ();
}
}
2022-12-07 14:29:07 +01:00
public interface IRavenOperations
2022-12-07 14:05:11 +01:00
{
/// <summary>
/// Access to the current session
/// </summary>
2025-02-25 11:31:32 +01:00
IAsyncDocumentSession Session { get ; }
2022-12-07 14:05:11 +01:00
/// <summary>
/// Get new instance of an entity (with an optional flavor)
/// </summary>
Task < T > Empty < T >( string flavorAlias = null ) where T : ZeroIdEntity , ISupportsFlavors , new ();
/// <summary>
/// Get new instance of an entity with a specific flavor
/// </summary>
/// <param name="flavorAlias">Optional alias. If left out the default flavor is used (if configured)</param>
Task < TFlavor > Empty < T , TFlavor >( string flavorAlias = null )
where T : ZeroIdEntity , ISupportsFlavors , new ()
where TFlavor : T , new ();
/// <summary>
/// Generate model Id by using configured document store conventions
/// </summary>
Task < string > GenerateId < T >( T model ) where T : ZeroIdEntity ;
/// <summary>
/// Generate values for all properties (incl. nested) which contain the [GenerateId] attribute
/// </summary>
T AutoSetIds < T >( T model );
/// <summary>
/// Automatically fill base properties of a ZeroEntity
/// </summary>
T PrepareForSave < T >( T model ) where T : ZeroIdEntity ;
/// <summary>
/// Check if any items exist in this collection (with optional query)
/// </summary>
Task < bool > Any < T >( Func < IRavenQueryable < T >, IQueryable < T >> querySelector = default ) where T : ZeroIdEntity , new ();
/// <summary>
/// Get an entity by Id
/// </summary>
Task < T > Load < T >( string id , string changeVector = null ) where T : ZeroIdEntity , new ();
/// <summary>
/// Get entities by ids
/// </summary>
Task < Dictionary < string , T >> Load < T >( IEnumerable < string > ids ) where T : ZeroIdEntity , new ();
2025-01-17 10:52:06 +01:00
/// <summary>
/// Get entities by ids
/// </summary>
Task < List < T >> LoadAsList < T >( IEnumerable < string > ids ) where T : ZeroIdEntity , new ();
2022-12-07 14:05:11 +01:00
/// <summary>
/// Get entities by query
/// </summary>
Task < Paged < T >> Load < T >( int pageNumber , int pageSize , Func < IRavenQueryable < T >, IQueryable < T >> expression = default ) where T : ZeroIdEntity , new ();
/// <summary>
/// Get entities by query (by using the specified index)
/// </summary>
Task < Paged < T >> Load < T , TIndex >( int pageNumber , int pageSize , Func < IRavenQueryable < T >, IQueryable < T >> expression = default ) where T : ZeroIdEntity , new () where TIndex : AbstractCommonApiForIndexes , new ();
2022-12-14 13:27:06 +01:00
/// <summary>
/// Get entities by query
/// </summary>
Task < List < T >> Load < T >( Func < IRavenQueryable < T >, IQueryable < T >> expression ) where T : ZeroIdEntity , new ();
/// <summary>
/// Get entities by query (by using the specified index)
/// </summary>
Task < List < T >> Load < T , TIndex >( Func < IRavenQueryable < T >, IQueryable < T >> expression ) where T : ZeroIdEntity , new () where TIndex : AbstractCommonApiForIndexes , new ();
/// <summary>
/// Get entities by query
/// </summary>
Task < List < T >> Load < T >( Expression < Func < T , bool >> predicate ) where T : ZeroIdEntity , new ();
/// <summary>
/// Get entities by query (by using the specified index)
/// </summary>
Task < List < T >> Load < T , TIndex >( Expression < Func < T , bool >> predicate ) where T : ZeroIdEntity , new () where TIndex : AbstractCommonApiForIndexes , new ();
2023-06-06 13:19:49 +02:00
/// <summary>
/// Get entities by query (by using the specified index) and project into a result
/// </summary>
Task < Paged < TProjection >> Load < T , TIndex , TProjection >( int pageNumber , int pageSize , Func < IRavenQueryable < T >, IQueryable < T >> querySelector = default )
where T : ZeroIdEntity , new ()
where TProjection : ZeroIdEntity , new ()
where TIndex : AbstractCommonApiForIndexes , new ();
2022-12-07 14:05:11 +01:00
/// <summary>
/// Get all entities from this collection.
/// Warning: Don't use this method for large collections. Stream the results instead.
/// </summary>
Task < List < T >> LoadAll < T >() where T : ZeroIdEntity , new ();
/// <summary>
/// Stream the collection
/// </summary>
IAsyncEnumerable < T > Stream < T >( Func < IRavenQueryable < T >, IQueryable < T >> expression ) where T : ZeroIdEntity , new ();
/// <summary>
/// Get the change vector for a model
/// </summary>
string GetChangeToken < T >( T model ) where T : ZeroIdEntity , new ();
/// <summary>
/// Validates an entity
/// </summary>
Task < ValidationResult > Validate < T >( T model ) where T : ZeroIdEntity , new ();
/// <summary>
/// Do not run interceptors for create/update/delete operations while this disposable is active
/// </summary>
StoreInterceptorBlocker WithoutInterceptors ();
/// <summary>
/// Do only return the model when it is set to active or inactive entities are included with IncludeInactive()
/// </summary>
T WhenActive < T >( T model ) where T : ZeroIdEntity , new ();
/// <summary>
/// Creates an entity with an optional validator
/// </summary>
2025-02-25 11:31:32 +01:00
Task < Result < T >> Create < T >( T model , Func < T , Task < ValidationResult >> validate = null , Action < IAsyncDocumentSession > onAfterStore = null ) where T : ZeroIdEntity , new ();
2022-12-07 14:05:11 +01:00
/// <summary>
/// Updates an entity with an optional validator
/// </summary>
2025-02-25 11:31:32 +01:00
Task < Result < T >> Update < T >( T model , Func < T , Task < ValidationResult >> validate = null , Action < IAsyncDocumentSession > onAfterStore = null ) where T : ZeroIdEntity , new ();
2022-12-07 14:05:11 +01:00
/// <inheritdoc />
Task < Result < IOrderedEnumerable < T >>> Sort < T >( string [] sortedIds ) where T : ZeroIdEntity , ISupportsSorting , new ();
2022-12-19 16:20:10 +01:00
/// <summary>
/// Batch create entities
/// </summary>
Task < Result < IEnumerable < T >>> CreateAll < T >( IEnumerable < T > models ) where T : ZeroIdEntity , new ();
2022-12-07 14:05:11 +01:00
/// <summary>
/// Deletes an entity
/// </summary>
Task < Result < T >> Delete < T >( T model ) where T : ZeroIdEntity , new ();
2022-12-13 16:25:36 +01:00
/// <summary>
/// Deletes an entity
/// </summary>
Task < Result < T >> Delete < T >( string id ) where T : ZeroIdEntity , new ();
2022-12-07 14:05:11 +01:00
2022-12-19 16:20:10 +01:00
/// <summary>
/// Deletes the whole collection
/// </summary>
Task Purge < T >( string querySuffix = null , Parameters parameters = null ) where T : ZeroIdEntity , new ();
2022-12-07 14:05:11 +01:00
/// <summary>
/// Loads all children for an entity
/// </summary>
Task < Paged < T >> LoadChildren < T >( string parentId , int pageNumber , int pageSize , Func < IRavenQueryable < T >, IQueryable < T >> querySelector = default ) where T : ZeroIdEntity , ISupportsTrees , new ();
/// <summary>
/// Get descendants by query (by using the specified index)
/// </summary>
Task < Paged < T >> LoadChildren < T , TIndex >( string parentId , int pageNumber , int pageSize , Func < IRavenQueryable < T >, IQueryable < T >> querySelector = default ) where T : ZeroIdEntity , ISupportsTrees , new () where TIndex : AbstractCommonApiForIndexes , new ();
/// <summary>
/// Get tree hierarchy for an entity
/// </summary>
Task < T []> GetHierarchy < T , TIndex >( string id ) where T : ZeroIdEntity , ISupportsTrees , new () where TIndex : ZeroTreeHierarchyIndex < T >, new ();
/// <summary>
/// Move an entity to a new parent
/// </summary>
Task < Result < T >> Move < T >( string id , string newParentId , Func < T , string , Task < bool >> isParentAllowed = null ) where T : ZeroIdEntity , ISupportsTrees , new ();
/// <summary>
/// Copies an entity to a new location
/// </summary>
Task < Result < T >> Copy < T >( string id , string newParentId , Func < T , string , Task < bool >> isParentAllowed = null ) where T : ZeroIdEntity , ISupportsTrees , new ();
/// <summary>
/// Copies an entity with descendants to a new location
/// </summary>
Task < Result < T >> CopyWithDescendants < T >( string id , string newParentId , Func < T , string , Task < bool >> isParentAllowed = null ) where T : ZeroIdEntity , ISupportsTrees , new ();
/// <summary>
/// Deletes an entity with all descendents
/// </summary>
Task < Result < string []>> DeleteWithDescendants < T >( T model ) where T : ZeroIdEntity , ISupportsTrees , new ();
}