Skip to content

Commit

Permalink
Add SetAll operator to IUpdateBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
ddaspit committed Dec 5, 2024
1 parent de529ce commit 2434ed7
Show file tree
Hide file tree
Showing 12 changed files with 301 additions and 137 deletions.
2 changes: 1 addition & 1 deletion src/DataAccess/src/SIL.DataAccess/ArrayPosition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
11 changes: 9 additions & 2 deletions src/DataAccess/src/SIL.DataAccess/DataAccessFieldDefinition.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
namespace SIL.DataAccess;

public class DataAccessFieldDefinition<TDocument, TField>(Expression<Func<TDocument, TField>> expression)
: FieldDefinition<TDocument, TField>
public class DataAccessFieldDefinition<TDocument, TField>(
Expression<Func<TDocument, TField>> expression,
string arrayFilterId = ""
) : FieldDefinition<TDocument, TField>
{
private readonly ExpressionFieldDefinition<TDocument, TField> _internalDef = new(expression);
private readonly string _arrayFilterId = arrayFilterId;

public override RenderedFieldDefinition<TField> Render(
IBsonSerializer<TDocument> documentSerializer,
Expand All @@ -22,6 +25,10 @@ LinqProvider linqProvider
"$[arrayFilter]"
);
fieldName = fieldName.Replace(ArrayPosition.FirstMatching.ToString(CultureInfo.InvariantCulture), "$");
fieldName = fieldName.Replace(
ArrayPosition.ArrayFilter.ToString(CultureInfo.InvariantCulture),
$"$[{_arrayFilterId}]"
);
if (fieldName != rendered.FieldName)
{
return new RenderedFieldDefinition<TField>(
Expand Down
10 changes: 10 additions & 0 deletions src/DataAccess/src/SIL.DataAccess/ExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,14 @@ Expression expression
finder.Visit(expression);
return finder.Value;
}

public static Expression<Func<TIn, TOut>> Concatenate<TIn, TInter, TOut>(
Expression<Func<TIn, TInter>> left,
Expression<Func<TInter, TOut>> right
)
{
ParameterReplacer replacer = new(right.Parameters[0], left.Body);
Expression merged = replacer.Visit(right.Body);
return Expression.Lambda<Func<TIn, TOut>>(merged, left.Parameters[0]);
}
}
10 changes: 2 additions & 8 deletions src/DataAccess/src/SIL.DataAccess/IRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,20 @@ public interface IRepository<T>

Task InsertAsync(T entity, CancellationToken cancellationToken = default);
Task InsertAllAsync(IReadOnlyCollection<T> entities, CancellationToken cancellationToken = default);

Task<T?> UpdateAsync(
Expression<Func<T, bool>> filter,
Action<IUpdateBuilder<T>> update,
bool upsert = false,
bool returnOriginal = false,
CancellationToken cancellationToken = default
);
Task<int> UpdateAllAsync<TFilter>(
Expression<Func<T, bool>> filter,
Action<IUpdateBuilder<T>> update,
string jsonArrayFilterDefinition,
CancellationToken cancellationToken = default
);

Task<int> UpdateAllAsync(
Expression<Func<T, bool>> filter,
Action<IUpdateBuilder<T>> update,
UpdateOptions? updateOptions = null,
CancellationToken cancellationToken = default
);

Task<T?> DeleteAsync(Expression<Func<T, bool>> filter, CancellationToken cancellationToken = default);
Task<int> DeleteAllAsync(Expression<Func<T, bool>> filter, CancellationToken cancellationToken = default);
Task<ISubscription<T>> SubscribeAsync(
Expand Down
9 changes: 8 additions & 1 deletion src/DataAccess/src/SIL.DataAccess/IUpdateBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ public interface IUpdateBuilder<T>

IUpdateBuilder<T> RemoveAll<TItem>(
Expression<Func<T, IEnumerable<TItem>?>> field,
Expression<Func<TItem, bool>> predicate
Expression<Func<TItem, bool>>? predicate = null
);

IUpdateBuilder<T> Remove<TItem>(Expression<Func<T, IEnumerable<TItem>?>> field, TItem value);

IUpdateBuilder<T> Add<TItem>(Expression<Func<T, IEnumerable<TItem>?>> field, TItem value);

IUpdateBuilder<T> SetAll<TItem, TField>(
Expression<Func<T, IEnumerable<TItem>?>> collectionField,
Expression<Func<TItem, TField>> itemField,
TField value,
Expression<Func<TItem, bool>>? predicate = null
);
}
11 changes: 0 additions & 11 deletions src/DataAccess/src/SIL.DataAccess/MemoryRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,20 +233,9 @@ public async Task InsertAllAsync(IReadOnlyCollection<T> entities, CancellationTo
return returnOriginal ? original : entity;
}

public async Task<int> UpdateAllAsync<TFilter>(
Expression<Func<T, bool>> filter,
Action<IUpdateBuilder<T>> update,
string jsonArrayFilterDefinition,
CancellationToken cancellationToken = default
)
{
return await UpdateAllAsync(filter, update, null, cancellationToken);
}

public async Task<int> UpdateAllAsync(
Expression<Func<T, bool>> filter,
Action<IUpdateBuilder<T>> update,
UpdateOptions? updateOptions = null,
CancellationToken cancellationToken = default
)
{
Expand Down
92 changes: 66 additions & 26 deletions src/DataAccess/src/SIL.DataAccess/MemoryUpdateBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,20 @@ public class MemoryUpdateBuilder<T>(Expression<Func<T, bool>> filter, T entity,

public IUpdateBuilder<T> Set<TField>(Expression<Func<T, TField>> field, TField value)
{
(IEnumerable<object> 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<T> SetOnInsert<TField>(Expression<Func<T, TField>> field, TField value)
{
if (_isInsert)
Set(field, value);
Set(_entity, _filter, field, value);
return this;
}

public IUpdateBuilder<T> Unset<TField>(Expression<Func<T, TField>> field)
{
(IEnumerable<object> owners, PropertyInfo prop, object? index) = GetFieldOwners(field);
(IEnumerable<object> owners, PropertyInfo prop, object? index) = GetFieldOwners(_entity, _filter, field);
if (index != null)
{
// remove value from a dictionary
Expand All @@ -49,7 +46,7 @@ public IUpdateBuilder<T> Unset<TField>(Expression<Func<T, TField>> field)

public IUpdateBuilder<T> Inc(Expression<Func<T, int>> field, int value = 1)
{
(IEnumerable<object> owners, PropertyInfo prop, object? index) = GetFieldOwners(field);
(IEnumerable<object> owners, PropertyInfo prop, object? index) = GetFieldOwners(_entity, _filter, field);
object[]? indices = index == null ? null : [index];
foreach (object owner in owners)
{
Expand All @@ -62,20 +59,20 @@ public IUpdateBuilder<T> Inc(Expression<Func<T, int>> field, int value = 1)

public IUpdateBuilder<T> RemoveAll<TItem>(
Expression<Func<T, IEnumerable<TItem>?>> field,
Expression<Func<TItem, bool>> predicate
Expression<Func<TItem, bool>>? predicate = null
)
{
(IEnumerable<object> owners, PropertyInfo? prop, object? index) = GetFieldOwners(field);
(IEnumerable<object> owners, PropertyInfo? prop, object? index) = GetFieldOwners(_entity, _filter, field);
object[]? indices = index == null ? null : [index];
Func<TItem, bool> predicateFunc = predicate.Compile();
Func<TItem, bool>? predicateFunc = predicate?.Compile();
foreach (object owner in owners)
{
var collection = (IEnumerable<TItem>?)prop.GetValue(owner, indices);
MethodInfo? removeMethod = collection?.GetType().GetMethod("Remove");
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]);
}
Expand All @@ -84,14 +81,17 @@ Expression<Func<TItem, bool>> 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<TItem>?)
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);
}
}
Expand All @@ -101,7 +101,7 @@ Expression<Func<TItem, bool>> predicate

public IUpdateBuilder<T> Remove<TItem>(Expression<Func<T, IEnumerable<TItem>?>> field, TItem value)
{
(IEnumerable<object> owners, PropertyInfo? prop, object? index) = GetFieldOwners(field);
(IEnumerable<object> owners, PropertyInfo? prop, object? index) = GetFieldOwners(_entity, _filter, field);
object[]? indices = index == null ? null : [index];
foreach (object owner in owners)
{
Expand Down Expand Up @@ -134,7 +134,7 @@ public IUpdateBuilder<T> Remove<TItem>(Expression<Func<T, IEnumerable<TItem>?>>

public IUpdateBuilder<T> Add<TItem>(Expression<Func<T, IEnumerable<TItem>?>> field, TItem value)
{
(IEnumerable<object> owners, PropertyInfo? prop, object? index) = GetFieldOwners(field);
(IEnumerable<object> owners, PropertyInfo? prop, object? index) = GetFieldOwners(_entity, _filter, field);
object[]? indices = index == null ? null : [index];
foreach (object owner in owners)
{
Expand All @@ -147,7 +147,7 @@ public IUpdateBuilder<T> Add<TItem>(Expression<Func<T, IEnumerable<TItem>?>> fie
}
else
{
collection ??= Array.Empty<TItem>();
collection ??= [];
if (prop.PropertyType.IsArray || prop.PropertyType.IsInterface)
{
// the collection type is an array or interface, so construct a new array and set property
Expand All @@ -166,6 +166,47 @@ public IUpdateBuilder<T> Add<TItem>(Expression<Func<T, IEnumerable<TItem>?>> fie
return this;
}

public IUpdateBuilder<T> SetAll<TItem, TField>(
Expression<Func<T, IEnumerable<TItem>?>> collectionField,
Expression<Func<TItem, TField>> itemField,
TField value,
Expression<Func<TItem, bool>>? predicate = null
)
{
(IEnumerable<object> owners, PropertyInfo? prop, object? index) = GetFieldOwners(
_entity,
_filter,
collectionField
);
object[]? indices = index == null ? null : [index];
Func<TItem, bool>? predicateFunc = predicate?.Compile();
foreach (object owner in owners)
{
var collection = (IEnumerable<TItem>?)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, TField>(
TEntity entity,
Expression<Func<TEntity, bool>> filter,
Expression<Func<TEntity, TField>> field,
TField value
)
{
(IEnumerable<object> 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";
Expand All @@ -180,8 +221,10 @@ private static MethodInfo GetFirstOrDefaultMethod(Type type)
.MakeGenericMethod(type);
}

private (IEnumerable<object> Owners, PropertyInfo Property, object? Index) GetFieldOwners<TField>(
Expression<Func<T, TField>> field
private static (IEnumerable<object> Owners, PropertyInfo Property, object? Index) GetFieldOwners<TEntity, TField>(
TEntity entity,
Expression<Func<TEntity, bool>> filter,
Expression<Func<TEntity, TField>> field
)
{
List<object>? owners = null;
Expand All @@ -192,8 +235,8 @@ Expression<Func<T, TField>> field
var newOwners = new List<object>();
if (owners == null)
{
if (_entity != null)
newOwners.Add(_entity);
if (entity != null)
newOwners.Add(entity);
}
else
{
Expand All @@ -206,17 +249,14 @@ Expression<Func<T, TField>> 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;
Expand Down Expand Up @@ -247,7 +287,7 @@ Expression<Func<T, TField>> field
}
else
{
newOwner = method.Invoke(owner, new object[] { index });
newOwner = method.Invoke(owner, [index]);
if (newOwner != null)
newOwners.Add(newOwner);
}
Expand Down
32 changes: 7 additions & 25 deletions src/DataAccess/src/SIL.DataAccess/MongoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ await _collection
var updateBuilder = new MongoUpdateBuilder<T>();
update(updateBuilder);
updateBuilder.Inc(e => e.Revision, 1);
UpdateDefinition<T> updateDef = updateBuilder.Build();
(UpdateDefinition<T> updateDef, IReadOnlyList<ArrayFilterDefinition> arrayFilters) = updateBuilder.Build();
var options = new FindOneAndUpdateOptions<T>
{
IsUpsert = upsert,
Expand Down Expand Up @@ -151,50 +151,32 @@ await _collection
return entity;
}

public async Task<int> UpdateAllAsync<TFilter>(
Expression<Func<T, bool>> filter,
Action<IUpdateBuilder<T>> update,
string jsonArrayFilterDefinition,
CancellationToken cancellationToken = default
)
{
var updateOptions = new UpdateOptions
{
ArrayFilters = [new JsonArrayFilterDefinition<TFilter>(jsonArrayFilterDefinition)]
};
return await UpdateAllAsync(filter, update, updateOptions, cancellationToken).ConfigureAwait(false);
}

public async Task<int> UpdateAllAsync(
Expression<Func<T, bool>> filter,
Action<IUpdateBuilder<T>> update,
UpdateOptions? updateOptions = null,
CancellationToken cancellationToken = default
)
{
var updateBuilder = new MongoUpdateBuilder<T>();
update(updateBuilder);
updateBuilder.Inc(e => e.Revision, 1);
UpdateDefinition<T> updateDef = updateBuilder.Build();
(UpdateDefinition<T> updateDef, IReadOnlyList<ArrayFilterDefinition> 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

Check failure on line 172 in src/DataAccess/src/SIL.DataAccess/MongoRepository.cs

View workflow job for this annotation

GitHub Actions / NUnit Tests

Serval.ApiServer.TranslationEngineTests ► DataFileUpdate_Propagated

Failed test found in: src/Serval/test/Serval.ApiServer.IntegrationTests/TestResults/test-results.trx Error: MongoDB.Driver.MongoWriteException : A write operation resulted in an error. WriteError: { Category : "Uncategorized", Code : 2, Message : "No array filter found for identifier 'arrayFilter' in path 'corpora.$[].sourceFiles.$[arrayFilter].filename'" }. ----> MongoDB.Driver.MongoBulkWriteException`1[Serval.Translation.Models.Engine] : A bulk write operation resulted in one or more errors. WriteErrors: [ { Category : "Uncategorized", Code : 2, Message : "No array filter found for identifier 'arrayFilter' in path 'corpora.$[].sourceFiles.$[arrayFilter].filename'" } ].
Raw output
MongoDB.Driver.MongoWriteException : A write operation resulted in an error. WriteError: { Category : "Uncategorized", Code : 2, Message : "No array filter found for identifier 'arrayFilter' in path 'corpora.$[].sourceFiles.$[arrayFilter].filename'" }.
  ----> MongoDB.Driver.MongoBulkWriteException`1[Serval.Translation.Models.Engine] : A bulk write operation resulted in one or more errors. WriteErrors: [ { Category : "Uncategorized", Code : 2, Message : "No array filter found for identifier 'arrayFilter' in path 'corpora.$[].sourceFiles.$[arrayFilter].filename'" } ].
   at MongoDB.Driver.MongoCollectionBase`1.UpdateManyAsync(FilterDefinition`1 filter, UpdateDefinition`1 update, UpdateOptions options, Func`3 bulkWriteAsync)
   at SIL.DataAccess.MongoRepository`1.UpdateAllAsync(Expression`1 filter, Action`1 update, CancellationToken cancellationToken) in /home/runner/work/serval/serval/src/DataAccess/src/SIL.DataAccess/MongoRepository.cs:line 172
   at Serval.Translation.Consumers.DataFileUpdatedConsumer.Consume(ConsumeContext`1 context) in /home/runner/work/serval/serval/src/Serval/src/Serval.Translation/Consumers/DataFileUpdatedConsumer.cs:line 9
   at MassTransit.DependencyInjection.ScopeConsumerFactory`1.Send[TMessage](ConsumeContext`1 context, IPipe`1 next) in /_/src/MassTransit/DependencyInjection/DependencyInjection/ScopeConsumerFactory.cs:line 22
   at MassTransit.DependencyInjection.ScopeConsumerFactory`1.Send[TMessage](ConsumeContext`1 context, IPipe`1 next) in /_/src/MassTransit/DependencyInjection/DependencyInjection/ScopeConsumerFactory.cs:line 22
   at MassTransit.Middleware.ConsumerMessageFilter`2.MassTransit.IFilter<MassTransit.ConsumeContext<TMessage>>.Send(ConsumeContext`1 context, IPipe`1 next) in /_/src/MassTransit/Middleware/ConsumerMessageFilter.cs:line 48
   at MassTransit.Middleware.ConsumerMessageFilter`2.MassTransit.IFilter<MassTransit.ConsumeContext<TMessage>>.Send(ConsumeContext`1 context, IPipe`1 next) in /_/src/MassTransit/Middleware/ConsumerMessageFilter.cs:line 73
   at MassTransit.Middleware.TeeFilter`1.<>c__DisplayClass5_0.<<Send>g__SendAsync|1>d.MoveNext() in /_/src/MassTransit/Middleware/TeeFilter.cs:line 40
--- End of stack trace from previous location ---
   at MassTransit.Middleware.ConsumeContextOutputMessageTypeFilter`1.SendToOutput(IPipe`1 next, ConsumeContext`1 pipeContext) in /_/src/MassTransit/Middleware/ConsumeContextOutputMessageTypeFilter.cs:line 76
   at MassTransit.Middleware.ConsumeContextOutputMessageTypeFilter`1.SendToOutput(IPipe`1 next, ConsumeContext`1 pipeContext) in /_/src/MassTransit/Middleware/ConsumeContextOutputMessageTypeFilter.cs:line 108
   at MassTransit.Middleware.ConsumeContextMessageTypeFilter.<>c__DisplayClass8_0.<<Send>g__SendAsync|0>d.MoveNext() in /_/src/MassTransit/Middleware/MessageTypeFilter.cs:line 76
--- End of stack trace from previous location ---
   at MassTransit.Middleware.DeserializeFilter.Send(ReceiveContext context, IPipe`1 next) in /_/src/MassTransit/Middleware/DeserializeFilter.cs:line 40
   at MassTransit.Middleware.RescueFilter`2.MassTransit.IFilter<TContext>.Send(TContext context, IPipe`1 next) in /_/src/MassTransit/Middleware/RescueFilter.cs:line 43
   at MassTransit.Internals.ExceptionExtensions.Rethrow(Exception exception) in /_/src/MassTransit.Abstractions/Internals/Extensions/ExceptionExtensions.cs:line 15
   at MassTransit.Middleware.RethrowErrorTransportFilter.Send(ExceptionReceiveContext context, IPipe`1 next) in /_/src/MassTransit/Middleware/RethrowErrorTransportFilter.cs:line 14
   at MassTransit.Middleware.RescueFilter`2.MassTransit.IFilter<TContext>.Send(TContext context, IPipe`1 next) in /_/src/MassTransit/Middleware/RescueFilter.cs:line 61
   at MassTransit.Middleware.DeadLetterFilter.MassTransit.IFilter<MassTransit.ReceiveContext>.Send(ReceiveContext context, IPipe`1 next) in /_/src/MassTransit/Middleware/DeadLetterFilter.cs:line 32
   at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock) in /_/src/MassTransit/Transports/ReceivePipeDispatcher.cs:line 65
   at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock) in /_/src/MassTransit/Transports/ReceivePipeDispatcher.cs:line 108
   at MassTransit.Transports.ReceivePipeDispatcher.Dispatch(ReceiveContext context, ReceiveLockContext receiveLock) in /_/src/MassTransit/Transports/ReceivePipeDispatcher.cs:line 115
   at MassTransit.Mediator.Contexts.MediatorSendEndpoint.SendMessage[T](T message, IPipe`1 pipe, CancellationToken cancellationToken) in /_/src/MassTransit/Mediator/Contexts/MediatorSendEndpoint.cs:line 222
   at MassTransit.Mediator.Contexts.MediatorSendEndpoint.SendMessage[T](T message, IPipe`1 pipe, CancellationToken cancellationToken) in /_/src/MassTransit/Mediator/Contexts/MediatorSendEndpoint.cs:line 232
   at Serval.DataFiles.Services.DataFileService.<>c__DisplayClass9_0.<<UpdateAsync>b__0>d.MoveNext() in /home/runner/work/serval/serval/src/Serval/src/Serval.DataFiles/Services/DataFileService.cs:line 92
--- End of stack trace from previous location ---
   at SIL.DataAccess.MongoDataAccessContext.<>c__DisplayClass9_0.<<WithTransactionAsync>b__0>d.MoveNext() in /home/runner/work/serval/serval/src/DataAccess/src/SIL.DataAccess/MongoDataAccessContext.cs:line 33
--- End of stack trace from previous location ---
   at SIL.DataAccess.MongoDataAccessContext.<>c__DisplayClass8_0`1.<<WithTransactionAsync>b__0>d.MoveNext() in /home/runner/work/serval/serval/src/DataAccess/src/SIL.DataAccess/MongoDataAccessContext.cs:line 19
--- End of stack trace from previous location ---
   at MongoDB.Driver.TransactionExecutor.ExecuteCallbackAsync[TResult](IClientSessionHandle clientSession, Func`3 callbackAsync, DateTime startTime, IClock clock, CancellationToken cancellationToken)
   at MongoDB.Driver.TransactionExecutor.ExecuteCallbackAsync[TResult](IClientSessionHandle clientSession, Func`3 callbackAsync, DateTime startTime, IClock clock, CancellationToken cancellationToken)
   at MongoDB.Driver.TransactionExecutor.ExecuteWithRetriesAsync[TResult](IClientSessionHandle clientSession, Func`3 callbackAsync, TransactionOptions transactionOptions, IClock clock, CancellationToken cancellationToken)
   at SIL.DataAccess.MongoDataAccessContext.WithTransactionAsync[TResult](Func`2 callbackAsync, CancellationToken cancellationToken) in /home/runner/work/serval/serval/src/DataAccess/src/SIL.DataAccess/MongoDataAccessContext.cs:line 17
   at Serval.DataFiles.Services.DataFileService.UpdateAsync(String id, Stream stream, CancellationToken cancellationToken) in /home/runner/work/serval/serval/src/Serval/src/Serval.DataFiles/Services/DataFileService.cs:line 72
   at Serval.DataFiles.Controllers.DataFilesController.UpdateAsync(String id, IFormFile file, CancellationToken cancellationToken) in /home/runner/work/serval/serval/src/Serval/src/Serval.DataFiles/Controllers/DataFilesController.cs:line 187
   at lambda_method1064(Closure, Object)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ExceptionContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Http.Timeouts.RequestTimeoutsMiddleware.<>c__DisplayClass5_0.<<Invoke>g__SetTimeoutAsync|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at SIL.ServiceToolkit.Services.BugsnagMiddleware.Invoke(HttpContext context, IClient client) in /home/runner/work/serval/serval/src/ServiceToolkit/src/SIL.ServiceToolkit/Services/BugsnagMiddleware.cs:line 35
   at Microsoft.AspNetCore.TestHost.HttpContextBuilder.<>c__DisplayClass23_0.<<SendAsync>g__RunRequestAsync|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.ClientHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Testing.Handlers.CookieContainerHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Testing.Handlers.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Serval.Client.DataFilesClient.UpdateAsync(String id, FileParameter file, CancellationToken cancellationToken) in /home/runner/work/serval/serval/src/Serval/src/Serval.Client/Client.g.cs:line 1733
   at Serval.ApiServer.TranslationEngineTests.DataFileUpdate_Propagated() in /home/runner/work/serval/serval/src/Serval/test/Serval.ApiServer.IntegrationTests/TranslationEngineTests.cs:line 2107
   at NUnit.Framework.Internal.AsyncToSyncAdapter.Await[TResult](Func`1 invoke)
   at NUnit.Framework.Internal.Commands.TestMethodCommand.RunTestMethod(TestExecutionContext context)
   at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context)
   at NUnit.Framework.Internal.Commands.DelegatingTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)
--MongoBulkWriteException`1
   at MongoDB.Driver.MongoCollectionImpl`1.BulkWriteAsync(IClientSessionHandle session, IEnumerable`1 requests, BulkWriteOptions options, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionBase`1.UpdateManyAsync(FilterDefinition`1 filter, UpdateDefinition`1 update, UpdateOptions options, Func`3 bulkWriteAsync)
.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);
}
}
Expand Down
Loading

0 comments on commit 2434ed7

Please sign in to comment.