From fd05f713bc9ddc64e64c3c734a6012500681ce24 Mon Sep 17 00:00:00 2001 From: Damien Daspit Date: Wed, 4 Dec 2024 13:53:50 -0500 Subject: [PATCH] Add SetAll operator to IUpdateBuilder --- .../src/SIL.DataAccess/ArrayPosition.cs | 2 +- .../DataAccessFieldDefinition.cs | 11 ++- .../src/SIL.DataAccess/ExpressionHelper.cs | 10 ++ .../src/SIL.DataAccess/IRepository.cs | 10 +- .../src/SIL.DataAccess/IUpdateBuilder.cs | 9 +- .../src/SIL.DataAccess/MemoryRepository.cs | 11 --- .../src/SIL.DataAccess/MemoryUpdateBuilder.cs | 92 +++++++++++++------ .../src/SIL.DataAccess/MongoRepository.cs | 32 ++----- .../src/SIL.DataAccess/MongoUpdateBuilder.cs | 86 +++++++++++++---- .../src/SIL.DataAccess/ParameterReplacer.cs | 16 ++++ .../MemoryRepositoryTests.cs | 30 ++++++ .../Services/EngineService.cs | 91 ++++++++++-------- .../TranslationEngineTests.cs | 5 +- 13 files changed, 271 insertions(+), 134 deletions(-) create mode 100644 src/DataAccess/src/SIL.DataAccess/ParameterReplacer.cs diff --git a/src/DataAccess/src/SIL.DataAccess/ArrayPosition.cs b/src/DataAccess/src/SIL.DataAccess/ArrayPosition.cs index d47cb47a..4a824ac5 100644 --- a/src/DataAccess/src/SIL.DataAccess/ArrayPosition.cs +++ b/src/DataAccess/src/SIL.DataAccess/ArrayPosition.cs @@ -4,5 +4,5 @@ public static class ArrayPosition { public const int FirstMatching = int.MaxValue; public const int All = int.MaxValue - 1; - public const int ArrayFilter = int.MaxValue - 2; + internal const int ArrayFilter = int.MaxValue - 2; } diff --git a/src/DataAccess/src/SIL.DataAccess/DataAccessFieldDefinition.cs b/src/DataAccess/src/SIL.DataAccess/DataAccessFieldDefinition.cs index e400de08..f31016f1 100644 --- a/src/DataAccess/src/SIL.DataAccess/DataAccessFieldDefinition.cs +++ b/src/DataAccess/src/SIL.DataAccess/DataAccessFieldDefinition.cs @@ -1,9 +1,12 @@ namespace SIL.DataAccess; -public class DataAccessFieldDefinition(Expression> expression) - : FieldDefinition +public class DataAccessFieldDefinition( + Expression> expression, + string arrayFilterId = "" +) : FieldDefinition { private readonly ExpressionFieldDefinition _internalDef = new(expression); + private readonly string _arrayFilterId = arrayFilterId; public override RenderedFieldDefinition Render( IBsonSerializer documentSerializer, @@ -17,11 +20,11 @@ LinqProvider linqProvider linqProvider ); string fieldName = rendered.FieldName.Replace(ArrayPosition.All.ToString(CultureInfo.InvariantCulture), "$[]"); + fieldName = fieldName.Replace(ArrayPosition.FirstMatching.ToString(CultureInfo.InvariantCulture), "$"); fieldName = fieldName.Replace( ArrayPosition.ArrayFilter.ToString(CultureInfo.InvariantCulture), - "$[arrayFilter]" + $"$[{_arrayFilterId}]" ); - fieldName = fieldName.Replace(ArrayPosition.FirstMatching.ToString(CultureInfo.InvariantCulture), "$"); if (fieldName != rendered.FieldName) { return new RenderedFieldDefinition( diff --git a/src/DataAccess/src/SIL.DataAccess/ExpressionHelper.cs b/src/DataAccess/src/SIL.DataAccess/ExpressionHelper.cs index b41948fb..b7a2eb0a 100644 --- a/src/DataAccess/src/SIL.DataAccess/ExpressionHelper.cs +++ b/src/DataAccess/src/SIL.DataAccess/ExpressionHelper.cs @@ -38,4 +38,14 @@ Expression expression finder.Visit(expression); return finder.Value; } + + public static Expression> Concatenate( + Expression> left, + Expression> right + ) + { + ParameterReplacer replacer = new(right.Parameters[0], left.Body); + Expression merged = replacer.Visit(right.Body); + return Expression.Lambda>(merged, left.Parameters[0]); + } } diff --git a/src/DataAccess/src/SIL.DataAccess/IRepository.cs b/src/DataAccess/src/SIL.DataAccess/IRepository.cs index bc454bd8..d817859a 100644 --- a/src/DataAccess/src/SIL.DataAccess/IRepository.cs +++ b/src/DataAccess/src/SIL.DataAccess/IRepository.cs @@ -9,6 +9,7 @@ public interface IRepository Task InsertAsync(T entity, CancellationToken cancellationToken = default); Task InsertAllAsync(IReadOnlyCollection entities, CancellationToken cancellationToken = default); + Task UpdateAsync( Expression> filter, Action> update, @@ -16,19 +17,12 @@ public interface IRepository bool returnOriginal = false, CancellationToken cancellationToken = default ); - Task UpdateAllAsync( - Expression> filter, - Action> update, - string jsonArrayFilterDefinition, - CancellationToken cancellationToken = default - ); - Task UpdateAllAsync( Expression> filter, Action> update, - UpdateOptions? updateOptions = null, CancellationToken cancellationToken = default ); + Task DeleteAsync(Expression> filter, CancellationToken cancellationToken = default); Task DeleteAllAsync(Expression> filter, CancellationToken cancellationToken = default); Task> SubscribeAsync( diff --git a/src/DataAccess/src/SIL.DataAccess/IUpdateBuilder.cs b/src/DataAccess/src/SIL.DataAccess/IUpdateBuilder.cs index 90df3882..6eaf27e5 100644 --- a/src/DataAccess/src/SIL.DataAccess/IUpdateBuilder.cs +++ b/src/DataAccess/src/SIL.DataAccess/IUpdateBuilder.cs @@ -13,10 +13,17 @@ public interface IUpdateBuilder IUpdateBuilder RemoveAll( Expression?>> field, - Expression> predicate + Expression>? predicate = null ); IUpdateBuilder Remove(Expression?>> field, TItem value); IUpdateBuilder Add(Expression?>> field, TItem value); + + IUpdateBuilder SetAll( + Expression?>> collectionField, + Expression> itemField, + TField value, + Expression>? predicate = null + ); } diff --git a/src/DataAccess/src/SIL.DataAccess/MemoryRepository.cs b/src/DataAccess/src/SIL.DataAccess/MemoryRepository.cs index c0d062e4..93c0ca47 100644 --- a/src/DataAccess/src/SIL.DataAccess/MemoryRepository.cs +++ b/src/DataAccess/src/SIL.DataAccess/MemoryRepository.cs @@ -233,20 +233,9 @@ public async Task InsertAllAsync(IReadOnlyCollection entities, CancellationTo return returnOriginal ? original : entity; } - public async Task UpdateAllAsync( - Expression> filter, - Action> update, - string jsonArrayFilterDefinition, - CancellationToken cancellationToken = default - ) - { - return await UpdateAllAsync(filter, update, null, cancellationToken); - } - public async Task UpdateAllAsync( Expression> filter, Action> update, - UpdateOptions? updateOptions = null, CancellationToken cancellationToken = default ) { diff --git a/src/DataAccess/src/SIL.DataAccess/MemoryUpdateBuilder.cs b/src/DataAccess/src/SIL.DataAccess/MemoryUpdateBuilder.cs index fca815e7..ec595d3f 100644 --- a/src/DataAccess/src/SIL.DataAccess/MemoryUpdateBuilder.cs +++ b/src/DataAccess/src/SIL.DataAccess/MemoryUpdateBuilder.cs @@ -9,23 +9,20 @@ public class MemoryUpdateBuilder(Expression> filter, T entity, public IUpdateBuilder Set(Expression> field, TField value) { - (IEnumerable owners, PropertyInfo? prop, object? index) = GetFieldOwners(field); - object[]? indices = index == null ? null : [index]; - foreach (object owner in owners) - prop.SetValue(owner, value, indices); + Set(_entity, _filter, field, value); return this; } public IUpdateBuilder SetOnInsert(Expression> field, TField value) { if (_isInsert) - Set(field, value); + Set(_entity, _filter, field, value); return this; } public IUpdateBuilder Unset(Expression> field) { - (IEnumerable owners, PropertyInfo prop, object? index) = GetFieldOwners(field); + (IEnumerable owners, PropertyInfo prop, object? index) = GetFieldOwners(_entity, _filter, field); if (index != null) { // remove value from a dictionary @@ -49,7 +46,7 @@ public IUpdateBuilder Unset(Expression> field) public IUpdateBuilder Inc(Expression> field, int value = 1) { - (IEnumerable owners, PropertyInfo prop, object? index) = GetFieldOwners(field); + (IEnumerable owners, PropertyInfo prop, object? index) = GetFieldOwners(_entity, _filter, field); object[]? indices = index == null ? null : [index]; foreach (object owner in owners) { @@ -62,12 +59,12 @@ public IUpdateBuilder Inc(Expression> field, int value = 1) public IUpdateBuilder RemoveAll( Expression?>> field, - Expression> predicate + Expression>? predicate = null ) { - (IEnumerable owners, PropertyInfo? prop, object? index) = GetFieldOwners(field); + (IEnumerable owners, PropertyInfo? prop, object? index) = GetFieldOwners(_entity, _filter, field); object[]? indices = index == null ? null : [index]; - Func predicateFunc = predicate.Compile(); + Func? predicateFunc = predicate?.Compile(); foreach (object owner in owners) { var collection = (IEnumerable?)prop.GetValue(owner, indices); @@ -75,7 +72,7 @@ Expression> predicate if (collection is not null && removeMethod is not null) { // the collection is mutable, so use Remove method to remove item - TItem[] toRemove = collection.Where(predicateFunc).ToArray(); + TItem[] toRemove = collection.Where(i => predicateFunc?.Invoke(i) ?? true).ToArray(); foreach (TItem item in toRemove) removeMethod.Invoke(collection, [item]); } @@ -84,14 +81,17 @@ Expression> predicate if (prop.PropertyType.IsArray || prop.PropertyType.IsInterface) { // the collection type is an array or interface, so construct a new array and set property - TItem[] newValue = collection.Where(i => !predicateFunc(i)).ToArray(); + TItem[] newValue = collection.Where(i => !(predicateFunc?.Invoke(i) ?? false)).ToArray(); prop.SetValue(owner, newValue, indices); } else { // the collection type is a collection class, so construct a new collection and set property var newValue = (IEnumerable?) - Activator.CreateInstance(prop.PropertyType, collection.Where(i => !predicateFunc(i)).ToArray()); + Activator.CreateInstance( + prop.PropertyType, + collection.Where(i => !(predicateFunc?.Invoke(i) ?? false)).ToArray() + ); prop.SetValue(owner, newValue, indices); } } @@ -101,7 +101,7 @@ Expression> predicate public IUpdateBuilder Remove(Expression?>> field, TItem value) { - (IEnumerable owners, PropertyInfo? prop, object? index) = GetFieldOwners(field); + (IEnumerable owners, PropertyInfo? prop, object? index) = GetFieldOwners(_entity, _filter, field); object[]? indices = index == null ? null : [index]; foreach (object owner in owners) { @@ -134,7 +134,7 @@ public IUpdateBuilder Remove(Expression?>> public IUpdateBuilder Add(Expression?>> field, TItem value) { - (IEnumerable owners, PropertyInfo? prop, object? index) = GetFieldOwners(field); + (IEnumerable owners, PropertyInfo? prop, object? index) = GetFieldOwners(_entity, _filter, field); object[]? indices = index == null ? null : [index]; foreach (object owner in owners) { @@ -147,7 +147,7 @@ public IUpdateBuilder Add(Expression?>> fie } else { - collection ??= Array.Empty(); + collection ??= []; if (prop.PropertyType.IsArray || prop.PropertyType.IsInterface) { // the collection type is an array or interface, so construct a new array and set property @@ -166,6 +166,47 @@ public IUpdateBuilder Add(Expression?>> fie return this; } + public IUpdateBuilder SetAll( + Expression?>> collectionField, + Expression> itemField, + TField value, + Expression>? predicate = null + ) + { + (IEnumerable owners, PropertyInfo? prop, object? index) = GetFieldOwners( + _entity, + _filter, + collectionField + ); + object[]? indices = index == null ? null : [index]; + Func? predicateFunc = predicate?.Compile(); + foreach (object owner in owners) + { + var collection = (IEnumerable?)prop.GetValue(owner, indices); + if (collection is null) + continue; + foreach (TItem item in collection) + { + if (predicateFunc == null || predicateFunc(item)) + Set(item, i => true, itemField, value); + } + } + return this; + } + + private static void Set( + TEntity entity, + Expression> filter, + Expression> field, + TField value + ) + { + (IEnumerable owners, PropertyInfo? prop, object? index) = GetFieldOwners(entity, filter, field); + object[]? indices = index == null ? null : [index]; + foreach (object owner in owners) + prop.SetValue(owner, value, indices); + } + private static bool IsAnyMethod(MethodInfo mi) { return mi.DeclaringType == typeof(Enumerable) && mi.Name == "Any"; @@ -180,8 +221,10 @@ private static MethodInfo GetFirstOrDefaultMethod(Type type) .MakeGenericMethod(type); } - private (IEnumerable Owners, PropertyInfo Property, object? Index) GetFieldOwners( - Expression> field + private static (IEnumerable Owners, PropertyInfo Property, object? Index) GetFieldOwners( + TEntity entity, + Expression> filter, + Expression> field ) { List? owners = null; @@ -192,8 +235,8 @@ Expression> field var newOwners = new List(); if (owners == null) { - if (_entity != null) - newOwners.Add(_entity); + if (entity != null) + newOwners.Add(entity); } else { @@ -206,17 +249,14 @@ Expression> field switch (index) { case ArrayPosition.FirstMatching: - foreach (Expression expression in ExpressionHelper.Flatten(_filter)) + foreach (Expression expression in ExpressionHelper.Flatten(filter)) { if (expression is MethodCallExpression callExpr && IsAnyMethod(callExpr.Method)) { var predicate = (LambdaExpression)callExpr.Arguments[1]; Type itemType = predicate.Parameters[0].Type; MethodInfo firstOrDefault = GetFirstOrDefaultMethod(itemType); - newOwner = firstOrDefault.Invoke( - null, - new object[] { owner, predicate.Compile() } - ); + newOwner = firstOrDefault.Invoke(null, [owner, predicate.Compile()]); if (newOwner != null) newOwners.Add(newOwner); break; @@ -247,7 +287,7 @@ Expression> field } else { - newOwner = method.Invoke(owner, new object[] { index }); + newOwner = method.Invoke(owner, [index]); if (newOwner != null) newOwners.Add(newOwner); } diff --git a/src/DataAccess/src/SIL.DataAccess/MongoRepository.cs b/src/DataAccess/src/SIL.DataAccess/MongoRepository.cs index 92d521ea..be2fbfaf 100644 --- a/src/DataAccess/src/SIL.DataAccess/MongoRepository.cs +++ b/src/DataAccess/src/SIL.DataAccess/MongoRepository.cs @@ -122,7 +122,7 @@ await _collection var updateBuilder = new MongoUpdateBuilder(); update(updateBuilder); updateBuilder.Inc(e => e.Revision, 1); - UpdateDefinition updateDef = updateBuilder.Build(); + (UpdateDefinition updateDef, IReadOnlyList arrayFilters) = updateBuilder.Build(); var options = new FindOneAndUpdateOptions { IsUpsert = upsert, @@ -151,50 +151,32 @@ await _collection return entity; } - public async Task UpdateAllAsync( - Expression> filter, - Action> update, - string jsonArrayFilterDefinition, - CancellationToken cancellationToken = default - ) - { - var updateOptions = new UpdateOptions - { - ArrayFilters = [new JsonArrayFilterDefinition(jsonArrayFilterDefinition)] - }; - return await UpdateAllAsync(filter, update, updateOptions, cancellationToken).ConfigureAwait(false); - } - public async Task UpdateAllAsync( Expression> filter, Action> update, - UpdateOptions? updateOptions = null, CancellationToken cancellationToken = default ) { var updateBuilder = new MongoUpdateBuilder(); update(updateBuilder); updateBuilder.Inc(e => e.Revision, 1); - UpdateDefinition updateDef = updateBuilder.Build(); + (UpdateDefinition updateDef, IReadOnlyList arrayFilters) = updateBuilder.Build(); + UpdateOptions? updateOptions = null; + if (arrayFilters.Count > 0) + updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; UpdateResult result; try { if (_context.Session is not null) { result = await _collection - .UpdateManyAsync( - _context.Session, - filter, - updateDef, - updateOptions, - cancellationToken: cancellationToken - ) + .UpdateManyAsync(_context.Session, filter, updateDef, updateOptions, cancellationToken) .ConfigureAwait(false); } else { result = await _collection - .UpdateManyAsync(filter, updateDef, updateOptions, cancellationToken: cancellationToken) + .UpdateManyAsync(filter, updateDef, updateOptions, cancellationToken) .ConfigureAwait(false); } } diff --git a/src/DataAccess/src/SIL.DataAccess/MongoUpdateBuilder.cs b/src/DataAccess/src/SIL.DataAccess/MongoUpdateBuilder.cs index e684563f..662c3f92 100644 --- a/src/DataAccess/src/SIL.DataAccess/MongoUpdateBuilder.cs +++ b/src/DataAccess/src/SIL.DataAccess/MongoUpdateBuilder.cs @@ -3,71 +3,123 @@ namespace SIL.DataAccess; public class MongoUpdateBuilder : IUpdateBuilder where T : IEntity { - private readonly UpdateDefinitionBuilder _updateBuilder; - private readonly FilterDefinitionBuilder _filterBuilder; + private readonly UpdateDefinitionBuilder _builder; private readonly List> _defs; + private readonly Dictionary FilterDef)> _arrayFilters; public MongoUpdateBuilder() { - _updateBuilder = Builders.Update; - _filterBuilder = Builders.Filter; + _builder = Builders.Update; _defs = new List>(); + _arrayFilters = new Dictionary)>(); } public IUpdateBuilder Set(Expression> field, TField value) { - _defs.Add(_updateBuilder.Set(ToFieldDefinition(field), value)); + _defs.Add(_builder.Set(ToFieldDefinition(field), value)); return this; } public IUpdateBuilder SetOnInsert(Expression> field, TField value) { - _defs.Add(_updateBuilder.SetOnInsert(ToFieldDefinition(field), value)); + _defs.Add(_builder.SetOnInsert(ToFieldDefinition(field), value)); return this; } public IUpdateBuilder Unset(Expression> field) { - _defs.Add(_updateBuilder.Unset(ToFieldDefinition(field))); + _defs.Add(_builder.Unset(ToFieldDefinition(field))); return this; } public IUpdateBuilder Inc(Expression> field, int value = 1) { - _defs.Add(_updateBuilder.Inc(ToFieldDefinition(field), value)); + _defs.Add(_builder.Inc(ToFieldDefinition(field), value)); return this; } public IUpdateBuilder RemoveAll( Expression?>> field, - Expression> predicate + Expression>? predicate = null ) { - _defs.Add(_updateBuilder.PullFilter(ToFieldDefinition(field), Builders.Filter.Where(predicate))); + _defs.Add(_builder.PullFilter(ToFieldDefinition(field), Builders.Filter.Where(predicate))); return this; } public IUpdateBuilder Remove(Expression?>> field, TItem value) { - _defs.Add(_updateBuilder.Pull(ToFieldDefinition(field), value)); + _defs.Add(_builder.Pull(ToFieldDefinition(field), value)); return this; } public IUpdateBuilder Add(Expression?>> field, TItem value) { - _defs.Add(_updateBuilder.Push(ToFieldDefinition(field), value)); + _defs.Add(_builder.Push(ToFieldDefinition(field), value)); return this; } - public UpdateDefinition Build() + public IUpdateBuilder SetAll( + Expression?>> collectionField, + Expression> itemField, + TField value, + Expression>? predicate = null + ) + { + Expression> itemExpr = ExpressionHelper.Concatenate( + collectionField, + (collection) => ((IReadOnlyList?)collection)![ArrayPosition.ArrayFilter] + ); + Expression> fieldExpr = ExpressionHelper.Concatenate(itemExpr, itemField); + if (predicate != null) + { + ExpressionFilterDefinition filter = new(predicate); + BsonDocument bsonDoc = filter.Render( + BsonSerializer.SerializerRegistry.GetSerializer(), + BsonSerializer.SerializerRegistry, + LinqProvider.V2 + ); + string filterId; + if (_arrayFilters.TryGetValue(bsonDoc, out var existingArrayFilter)) + { + filterId = existingArrayFilter.Id; + } + else + { + filterId = "f" + ObjectId.GenerateNewId().ToString(); + _arrayFilters.Add( + bsonDoc, + ( + filterId, + new BsonDocument( + $"{filterId}.{bsonDoc.Elements.Single().Name}", + bsonDoc.Elements.Single().Value + ) + ) + ); + } + _defs.Add(_builder.Set(ToFieldDefinition(fieldExpr, filterId), value)); + } + else + { + _defs.Add(_builder.Set(ToFieldDefinition(fieldExpr), value)); + } + return this; + } + + public (UpdateDefinition, IReadOnlyList) Build() { + ArrayFilterDefinition[] arrayFilters = _arrayFilters.Values.Select(f => f.FilterDef).ToArray(); if (_defs.Count == 1) - return _defs.Single(); - return _updateBuilder.Combine(_defs); + return (_defs.Single(), arrayFilters); + return (_builder.Combine(_defs), arrayFilters); } - private static FieldDefinition ToFieldDefinition(Expression> field) + private static FieldDefinition ToFieldDefinition( + Expression> field, + string arrayFilterId = "" + ) { - return new DataAccessFieldDefinition(field); + return new DataAccessFieldDefinition(field, arrayFilterId); } } diff --git a/src/DataAccess/src/SIL.DataAccess/ParameterReplacer.cs b/src/DataAccess/src/SIL.DataAccess/ParameterReplacer.cs new file mode 100644 index 00000000..0f5c36e0 --- /dev/null +++ b/src/DataAccess/src/SIL.DataAccess/ParameterReplacer.cs @@ -0,0 +1,16 @@ +namespace SIL.DataAccess; + +internal class ParameterReplacer(ParameterExpression oldExpression, Expression newExpression) + : System.Linq.Expressions.ExpressionVisitor +{ + private readonly ParameterExpression _oldExpression = oldExpression; + private readonly Expression _newExpression = newExpression; + + protected override Expression VisitParameter(ParameterExpression node) + { + if (node == _oldExpression) + return _newExpression; + + return base.VisitParameter(node); + } +} diff --git a/src/DataAccess/test/SIL.DataAccess.Tests/MemoryRepositoryTests.cs b/src/DataAccess/test/SIL.DataAccess.Tests/MemoryRepositoryTests.cs index 7859a93c..c56833c4 100644 --- a/src/DataAccess/test/SIL.DataAccess.Tests/MemoryRepositoryTests.cs +++ b/src/DataAccess/test/SIL.DataAccess.Tests/MemoryRepositoryTests.cs @@ -293,6 +293,30 @@ public async Task UpdateAsync_RemoveAll_ReadOnlyCollection() Assert.That(repo.Get("1").ReadOnlyCollection, Is.EqualTo(new int[] { 1 })); } + [Test] + public async Task UpdateAsync_SetAll() + { + MemoryRepository repo = new(); + repo.Add( + new TestEntity() + { + Id = "1", + Children = [new Child { Field = 1 }, new Child { Field = 2 }, new Child { Field = 3 }] + } + ); + + TestEntity? entity = await repo.UpdateAsync( + "1", + u => u.SetAll(e => e.Children, c => c.Field, 0, c => c.Field >= 2) + ); + + Assert.That(entity, Is.Not.Null); + Assert.That(entity.Children, Is.Not.Null); + Assert.That(entity.Children[0].Field, Is.EqualTo(1)); + Assert.That(entity.Children[1].Field, Is.EqualTo(0)); + Assert.That(entity.Children[2].Field, Is.EqualTo(0)); + } + [Test] public async Task DeleteAsync_DoesNotExist() { @@ -349,5 +373,11 @@ private record TestEntity : IEntity public List? List { get; init; } public int[]? Array { get; init; } public ReadOnlyCollection? ReadOnlyCollection { get; init; } + public IReadOnlyList? Children { get; init; } + } + + private record Child + { + public int Field { get; init; } } } diff --git a/src/Serval/src/Serval.Translation/Services/EngineService.cs b/src/Serval/src/Serval.Translation/Services/EngineService.cs index 53932848..b0cfb5be 100644 --- a/src/Serval/src/Serval.Translation/Services/EngineService.cs +++ b/src/Serval/src/Serval.Translation/Services/EngineService.cs @@ -567,16 +567,18 @@ public Task DeleteAllCorpusFilesAsync(string dataFileId, CancellationToken cance || c.TargetCorpora.Any(mc => mc.Files.Any(f => f.Id == dataFileId)) ), u => - u.RemoveAll(e => e.Corpora[ArrayPosition.All].SourceFiles, f => f.Id == dataFileId) - .RemoveAll(e => e.Corpora[ArrayPosition.All].TargetFiles, f => f.Id == dataFileId) - .RemoveAll( - e => e.ParallelCorpora[ArrayPosition.All].SourceCorpora[ArrayPosition.All].Files, - f => f.Id == dataFileId - ) - .RemoveAll( - e => e.ParallelCorpora[ArrayPosition.All].TargetCorpora[ArrayPosition.All].Files, - f => f.Id == dataFileId - ), + { + u.RemoveAll(e => e.Corpora[ArrayPosition.All].SourceFiles, f => f.Id == dataFileId); + u.RemoveAll(e => e.Corpora[ArrayPosition.All].TargetFiles, f => f.Id == dataFileId); + u.RemoveAll( + e => e.ParallelCorpora[ArrayPosition.All].SourceCorpora[ArrayPosition.All].Files, + f => f.Id == dataFileId + ); + u.RemoveAll( + e => e.ParallelCorpora[ArrayPosition.All].TargetCorpora[ArrayPosition.All].Files, + f => f.Id == dataFileId + ); + }, cancellationToken: cancellationToken ); } @@ -587,7 +589,7 @@ public Task UpdateDataFileFilenameFilesAsync( CancellationToken cancellationToken = default ) { - return Entities.UpdateAllAsync( + return Entities.UpdateAllAsync( e => e.Corpora.Any(c => c.SourceFiles.Any(f => f.Id == dataFileId) || c.TargetFiles.Any(f => f.Id == dataFileId) @@ -597,25 +599,32 @@ public Task UpdateDataFileFilenameFilesAsync( || c.TargetCorpora.Any(mc => mc.Files.Any(f => f.Id == dataFileId)) ), u => - u.Set(e => e.Corpora[ArrayPosition.All].SourceFiles[ArrayPosition.ArrayFilter].Filename, filename) - .Set(e => e.Corpora[ArrayPosition.All].TargetFiles[ArrayPosition.ArrayFilter].Filename, filename) - .Set( - e => - e.ParallelCorpora[ArrayPosition.All] - .SourceCorpora[ArrayPosition.All] - .Files[ArrayPosition.ArrayFilter] - .Filename, - filename - ) - .Set( - e => - e.ParallelCorpora[ArrayPosition.All] - .TargetCorpora[ArrayPosition.All] - .Files[ArrayPosition.ArrayFilter] - .Filename, - filename - ), - jsonArrayFilterDefinition: $"{{ \"arrayFilter._id\": {{$eq: ObjectId(\"{dataFileId}\") }} }}", + { + u.SetAll( + e => e.Corpora[ArrayPosition.All].SourceFiles, + f => f.Filename, + filename, + f => f.Id == dataFileId + ); + u.SetAll( + e => e.Corpora[ArrayPosition.All].TargetFiles, + f => f.Filename, + filename, + f => f.Id == dataFileId + ); + u.SetAll( + e => e.ParallelCorpora[ArrayPosition.All].SourceCorpora[ArrayPosition.All].Files, + f => f.Filename, + filename, + f => f.Id == dataFileId + ); + u.SetAll( + e => e.ParallelCorpora[ArrayPosition.All].TargetCorpora[ArrayPosition.All].Files, + f => f.Filename, + filename, + f => f.Id == dataFileId + ); + }, cancellationToken: cancellationToken ); } @@ -626,18 +635,26 @@ public Task UpdateCorpusFilesAsync( CancellationToken cancellationToken = default ) { - return Entities.UpdateAllAsync( + return Entities.UpdateAllAsync( e => e.ParallelCorpora.Any(c => c.SourceCorpora.Any(mc => mc.Id == corpusId) || c.TargetCorpora.Any(mc => mc.Id == corpusId) ), u => - u.Set(e => e.ParallelCorpora[ArrayPosition.All].SourceCorpora[ArrayPosition.ArrayFilter].Files, files) - .Set( - e => e.ParallelCorpora[ArrayPosition.All].TargetCorpora[ArrayPosition.ArrayFilter].Files, - files - ), - jsonArrayFilterDefinition: $"{{ \"arrayFilter._id\": {{$eq: ObjectId(\"{corpusId}\") }} }}", + { + u.SetAll( + e => e.ParallelCorpora[ArrayPosition.All].SourceCorpora, + mc => mc.Files, + files, + mc => mc.Id == corpusId + ); + u.SetAll( + e => e.ParallelCorpora[ArrayPosition.All].TargetCorpora, + mc => mc.Files, + files, + mc => mc.Id == corpusId + ); + }, cancellationToken: cancellationToken ); } diff --git a/src/Serval/test/Serval.ApiServer.IntegrationTests/TranslationEngineTests.cs b/src/Serval/test/Serval.ApiServer.IntegrationTests/TranslationEngineTests.cs index f0cdbdff..07ccb9f2 100644 --- a/src/Serval/test/Serval.ApiServer.IntegrationTests/TranslationEngineTests.cs +++ b/src/Serval/test/Serval.ApiServer.IntegrationTests/TranslationEngineTests.cs @@ -2048,10 +2048,7 @@ public async Task DataFileUpdate_Propagated() Assert.That(orgCorpusFromRepo.Files[0].FileId, Is.EqualTo(FILE2_TRG_ID)); // Update the file - await dataFilesClient.UpdateAsync( - FILE1_SRC_ID, - new FileParameter(new MemoryStream(new byte[] { 1, 2, 3 }), "test.txt") - ); + await dataFilesClient.UpdateAsync(FILE1_SRC_ID, new FileParameter(new MemoryStream([1, 2, 3]), "test.txt")); await corporaClient.UpdateAsync( TARGET_CORPUS_ID, [new CorpusFileConfig { FileId = FILE4_TRG_ZIP_ID, TextId = "all" }]