using FluentValidation.Results; using Rv = Raven.Client; namespace zero.Raven; public partial class RavenOperations : IRavenOperations { /// public virtual Task> Create(T model, Func> validate = null, Action onAfterStore = null) where T : ZeroIdEntity, new() => Save(model, validate, onAfterStore); /// public virtual Task> Update(T model, Func> validate = null, Action onAfterStore = null) where T : ZeroIdEntity, new() => Save(model, validate, onAfterStore, true); /// protected virtual async Task> Save(T model, Func> validate = null, Action onAfterStore = null, bool update = false) where T : ZeroIdEntity, new() { if (model == null) { return Result.Fail("@errors.onsave.empty"); } T previousModel = null; bool isUpdate = false; // check if the Id for a model already exists if (!model.Id.IsNullOrEmpty()) { using (IZeroDocumentSession session = Store.Session(options: new Rv.Documents.Session.SessionOptions() { NoCaching = true })) { previousModel = await session.LoadAsync(model.Id); } if (update && previousModel == null) { return Result.Fail("@errors.onsave.noidmatch"); } else if (!update && previousModel != null) { return Result.Fail("@errors.oncreate.idmismatch"); } isUpdate = true; } // validate flavor if (model is ISupportsFlavors flavorModel && !flavorModel.Flavor.IsNullOrEmpty()) { if (!Flavors.Exists(flavorModel.Flavor)) { return Result.Fail("@errors.onsave.flavornotfound"); } } // prepare model PrepareForSave(model); // run validator if (validate != null) { ValidationResult validation = await validate(model); if (!validation.IsValid) { return Result.Fail(validation); } } // create ID before-hand so interceptors can use it if (!isUpdate) { model.Id = await GenerateId(model); } // run interceptor InterceptorInstruction instruction = isUpdate ? Interceptors.ForUpdate(model, previousModel) : Interceptors.ForCreate(model); if (InterceptorBlocker == null && !await instruction.Start(this)) { return instruction.Result; } // store our model await Session.StoreAsync(model); onAfterStore?.Invoke(Session); await Session.SaveChangesAsync(); if (InterceptorBlocker == null) { await instruction.Complete(); } await Session.SaveChangesAsync(); return Result.Success(model); } /// public async Task>> Sort(string[] sortedIds) where T : ZeroIdEntity, ISupportsSorting, new() { Dictionary items = await Load(sortedIds); uint index = 10; // contains multiple parents, therefore fail if (typeof(ISupportsTrees).IsAssignableFrom(typeof(T)) && items.Select(x => (x.Value as ISupportsTrees)?.ParentId).Distinct().Count() > 1) { return Result>.Fail("@errors.treeentity.sortingmultipleparents"); } foreach (var item in items) { item.Value.Sort = index; index += 10; await Update(item.Value); } return Result>.Success(items.Select(x => x.Value).OrderByDescending(x => x.Sort)); } /// public virtual async Task>> CreateAll(IEnumerable models) where T : ZeroIdEntity, new() { using var bulkInsert = Store.Raven.BulkInsert(); foreach (T model in models) { // prepare model PrepareForSave(model); // create ID before-hand so interceptors can use it model.Id = await GenerateId(model); // run interceptor InterceptorInstruction instruction = Interceptors.ForCreate(model); if (InterceptorBlocker == null && !await instruction.Start(this)) { await bulkInsert.AbortAsync(); return instruction.Result.ConvertTo>(null); } // store our model await bulkInsert.StoreAsync(model); if (InterceptorBlocker == null) { await instruction.Complete(); } } return Result>.Success(models); } }