Files
mixtape/Finch.Raven/Operations/RavenOperations.cs
T
2026-04-07 15:24:48 +02:00

325 lines
11 KiB
C#

using FluentValidation.Results;
using Microsoft.Extensions.DependencyInjection;
using Raven.Client.Documents.Indexes;
using Raven.Client.Documents.Linq;
using System.Linq.Expressions;
namespace Finch.Raven;
public partial class RavenOperations : IRavenOperations
{
/// <inheritdoc />
public IAsyncDocumentSession Session => Store.Session();
protected record OperationOptions(bool IncludeInactive);
protected IFinchContext Context { get; private set; }
protected IInterceptors Interceptors { get; }
protected FlavorOptions Flavors { get; }
protected IServiceProvider Services { get; }
protected IFinchStore Store { get; }
protected StoreInterceptorBlocker InterceptorBlocker { get; private set; }
public RavenOperations(StoreContext context)
{
Store = context.Store;
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 : FinchIdEntity
{
IAsyncDocumentSession session = Session;
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 : FinchIdEntity
{
// set IDs
AutoSetIds(model);
if (model is FinchEntity finchModel)
{
// get current user
string userId = null;
//string userId = Context.BackofficeUser.FindFirstValue(Constants.Auth.Claims.UserId).Or(Constants.Auth.SystemUser);
// set default properties
if (finchModel.CreatedDate == default)
{
finchModel.CreatedDate = DateTimeOffset.Now;
}
if (finchModel.CreatedById.IsNullOrEmpty())
{
finchModel.CreatedById = userId;
}
// update name alias and last modified
finchModel.Alias = Safenames.Alias(finchModel.Name);
finchModel.LastModifiedById = userId;
finchModel.LastModifiedDate = DateTimeOffset.Now;
finchModel.CreatedById ??= userId;
finchModel.Hash ??= IdGenerator.Create();
}
return model;
}
/// <inheritdoc />
public async Task<ValidationResult> Validate<T>(T model) where T : FinchIdEntity, new()
{
IFinchMergedValidator<T> validator = Services.GetService<IFinchMergedValidator<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 : FinchIdEntity, new()
{
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 FinchEntity || (model as FinchEntity).IsActive) && (model is not ISupportsSoftDelete || !(model as ISupportsSoftDelete).IsDeleted) ? model : default;
}
}
public class StoreInterceptorBlocker : IDisposable
{
Action _onRelease;
internal StoreInterceptorBlocker(Action onRelease)
{
_onRelease = onRelease;
}
public void Dispose()
{
_onRelease();
}
}
public interface IRavenOperations
{
/// <summary>
/// Access to the current session
/// </summary>
IAsyncDocumentSession Session { get; }
/// <summary>
/// Get new instance of an entity (with an optional flavor)
/// </summary>
Task<T> Empty<T>(string flavorAlias = null) where T : FinchIdEntity, 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 : FinchIdEntity, 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 : FinchIdEntity;
/// <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 FinchEntity
/// </summary>
T PrepareForSave<T>(T model) where T : FinchIdEntity;
/// <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 : FinchIdEntity, new();
/// <summary>
/// Get an entity by Id
/// </summary>
Task<T> Load<T>(string id, string changeVector = null) where T : FinchIdEntity, new();
/// <summary>
/// Get entities by ids
/// </summary>
Task<Dictionary<string, T>> Load<T>(IEnumerable<string> ids) where T : FinchIdEntity, new();
/// <summary>
/// Get entities by ids
/// </summary>
Task<List<T>> LoadAsList<T>(IEnumerable<string> ids) where T : FinchIdEntity, new();
/// <summary>
/// Get entities by query
/// </summary>
Task<Paged<T>> Load<T>(int pageNumber, int pageSize, Func<IRavenQueryable<T>, IQueryable<T>> expression = default) where T : FinchIdEntity, 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 : FinchIdEntity, new() where TIndex : AbstractCommonApiForIndexes, new();
/// <summary>
/// Get entities by query
/// </summary>
Task<List<T>> Load<T>(Func<IRavenQueryable<T>, IQueryable<T>> expression) where T : FinchIdEntity, 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 : FinchIdEntity, new() where TIndex : AbstractCommonApiForIndexes, new();
/// <summary>
/// Get entities by query
/// </summary>
Task<List<T>> Load<T>(Expression<Func<T, bool>> predicate) where T : FinchIdEntity, 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 : FinchIdEntity, new() where TIndex : AbstractCommonApiForIndexes, new();
/// <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 : FinchIdEntity, new()
where TProjection : FinchIdEntity, new()
where TIndex : AbstractCommonApiForIndexes, new();
/// <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 : FinchIdEntity, new();
/// <summary>
/// Stream the collection
/// </summary>
IAsyncEnumerable<T> Stream<T>(Func<IRavenQueryable<T>, IQueryable<T>> expression) where T : FinchIdEntity, new();
/// <summary>
/// Get the change vector for a model
/// </summary>
string GetChangeToken<T>(T model) where T : FinchIdEntity, new();
/// <summary>
/// Validates an entity
/// </summary>
Task<ValidationResult> Validate<T>(T model) where T : FinchIdEntity, 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 : FinchIdEntity, new();
/// <summary>
/// Creates an entity with an optional validator
/// </summary>
Task<Result<T>> Create<T>(T model, Func<T, Task<ValidationResult>> validate = null, Action<IAsyncDocumentSession> onAfterStore = null) where T : FinchIdEntity, new();
/// <summary>
/// Updates an entity with an optional validator
/// </summary>
Task<Result<T>> Update<T>(T model, Func<T, Task<ValidationResult>> validate = null, Action<IAsyncDocumentSession> onAfterStore = null) where T : FinchIdEntity, new();
/// <inheritdoc />
Task<Result<IOrderedEnumerable<T>>> Sort<T>(string[] sortedIds) where T : FinchIdEntity, ISupportsSorting, new();
/// <summary>
/// Batch create entities
/// </summary>
Task<Result<IEnumerable<T>>> CreateAll<T>(IEnumerable<T> models) where T : FinchIdEntity, new();
/// <summary>
/// Deletes an entity
/// </summary>
Task<Result<T>> Delete<T>(T model) where T : FinchIdEntity, new();
/// <summary>
/// Deletes an entity
/// </summary>
Task<Result<T>> Delete<T>(string id) where T : FinchIdEntity, new();
/// <summary>
/// Deletes the whole collection
/// </summary>
Task Purge<T>(string querySuffix = null, Parameters parameters = null) where T : FinchIdEntity, new();
/// <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 : FinchIdEntity, 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 : FinchIdEntity, ISupportsTrees, new() where TIndex : AbstractCommonApiForIndexes, new();
/// <summary>
/// Get tree hierarchy for an entity
/// </summary>
Task<T[]> GetHierarchy<T, TIndex>(string id) where T : FinchIdEntity, ISupportsTrees, new() where TIndex : FinchTreeHierarchyIndex<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 : FinchIdEntity, 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 : FinchIdEntity, 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 : FinchIdEntity, ISupportsTrees, new();
/// <summary>
/// Deletes an entity with all descendents
/// </summary>
Task<Result<string[]>> DeleteWithDescendants<T>(T model) where T : FinchIdEntity, ISupportsTrees, new();
}