diff --git a/MongoDB.Entities/Commands/Transaction.cs b/MongoDB.Entities/Commands/Transaction.cs index 3779c7463..32c824ade 100644 --- a/MongoDB.Entities/Commands/Transaction.cs +++ b/MongoDB.Entities/Commands/Transaction.cs @@ -249,9 +249,10 @@ public Task SavePreservingAsync(T entity, Expression /// The type of entity /// The Id of the entity to delete - public Task DeleteAsync(string ID) where T : IEntity + /// An optional cancellation token + public Task DeleteAsync(string ID, CancellationToken cancellation = default) where T : IEntity { - return DB.DeleteAsync(ID, Session); + return DB.DeleteAsync(ID, Session, cancellation); } /// @@ -261,9 +262,10 @@ public Task DeleteAsync(string ID) where T : IEntity /// /// The type of entity /// A lambda expression for matching entities to delete. - public Task DeleteAsync(Expression> expression) where T : IEntity + /// An optional cancellation token + public Task DeleteAsync(Expression> expression, CancellationToken cancellation = default) where T : IEntity { - return DB.DeleteAsync(expression, Session); + return DB.DeleteAsync(expression, Session, cancellation); } /// @@ -273,9 +275,10 @@ public Task DeleteAsync(Expression> expression) w /// /// The type of entity /// An IEnumerable of entity IDs - public Task DeleteAsync(IEnumerable IDs) where T : IEntity + /// An optional cancellation token + public Task DeleteAsync(IEnumerable IDs, CancellationToken cancellation = default) where T : IEntity { - return DB.DeleteAsync(IDs, Session); + return DB.DeleteAsync(IDs, Session, cancellation); } /// diff --git a/MongoDB.Entities/DB.Delete.cs b/MongoDB.Entities/DB.Delete.cs index 03801a79f..00ac3bcec 100644 --- a/MongoDB.Entities/DB.Delete.cs +++ b/MongoDB.Entities/DB.Delete.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Threading; using System.Threading.Tasks; namespace MongoDB.Entities @@ -11,7 +12,7 @@ public static partial class DB { private static readonly int deleteBatchSize = 100000; - private static async Task DeleteCascadingAsync(IEnumerable IDs, IClientSessionHandle session = null) where T : IEntity + private static async Task DeleteCascadingAsync(IEnumerable IDs, IClientSessionHandle session = null, CancellationToken cancellation = default) where T : IEntity { // note: cancellation should not be enabled because multiple collections are involved // and premature cancellation could cause data inconsistencies. @@ -24,18 +25,22 @@ private static async Task DeleteCascadingAsync(IEnumerable(); - foreach (var cName in await db.ListCollectionNames(options).ToListAsync().ConfigureAwait(false)) + // note: db.listCollections() does not support transactions. + // so don't add session support here: + var collNamesCursor = await db.ListCollectionNamesAsync(options, cancellation).ConfigureAwait(false); + + foreach (var cName in await collNamesCursor.ToListAsync(cancellation).ConfigureAwait(false)) { tasks.Add( session == null ? db.GetCollection(cName).DeleteManyAsync(r => IDs.Contains(r.ChildID) || IDs.Contains(r.ParentID)) - : db.GetCollection(cName).DeleteManyAsync(session, r => IDs.Contains(r.ChildID) || IDs.Contains(r.ParentID), null)); + : db.GetCollection(cName).DeleteManyAsync(session, r => IDs.Contains(r.ChildID) || IDs.Contains(r.ParentID), null, cancellation)); } var delResTask = session == null ? Collection().DeleteManyAsync(x => IDs.Contains(x.ID)) - : Collection().DeleteManyAsync(session, x => IDs.Contains(x.ID), null); + : Collection().DeleteManyAsync(session, x => IDs.Contains(x.ID), null, cancellation); tasks.Add(delResTask); @@ -44,7 +49,7 @@ private static async Task DeleteCascadingAsync(IEnumerable(CollectionName()).DeleteManyAsync(x => IDs.Contains(x.FileID)) - : db.GetCollection(CollectionName()).DeleteManyAsync(session, x => IDs.Contains(x.FileID), null)); + : db.GetCollection(CollectionName()).DeleteManyAsync(session, x => IDs.Contains(x.FileID), null, cancellation)); } await Task.WhenAll(tasks).ConfigureAwait(false); @@ -58,10 +63,12 @@ private static async Task DeleteCascadingAsync(IEnumerable /// Any class that implements IEntity /// The Id of the entity to delete - /// An optional session if using within a transaction - public static Task DeleteAsync(string ID, IClientSessionHandle session = null) where T : IEntity + /// An optional session if using within a transaction + /// An optional cancellation token + public static Task DeleteAsync(string ID, IClientSessionHandle session = null, CancellationToken cancellation = default) where T : IEntity { - return DeleteCascadingAsync(new[] { ID }, session); + ThrowIfCancellationNotSupported(session, cancellation); + return DeleteCascadingAsync(new[] { ID }, session, cancellation); } /// @@ -72,16 +79,26 @@ public static Task DeleteAsync(string ID, IClientSessionHandle /// Any class that implements IEntity /// A lambda expression for matching entities to delete. /// An optional session if using within a transaction - public static async Task DeleteAsync(Expression> expression, IClientSessionHandle session = null) where T : IEntity + /// An optional cancellation token + public static async Task DeleteAsync(Expression> expression, IClientSessionHandle session = null, CancellationToken cancellation = default) where T : IEntity { + ThrowIfCancellationNotSupported(session, cancellation); + long deletedCount = 0; - using (var cursor = await new Find(session).Match(expression).Project(e => e.ID).Option(o => o.BatchSize = deleteBatchSize).ExecuteCursorAsync().ConfigureAwait(false)) + var cursor = await new Find(session) + .Match(expression) + .Project(e => e.ID) + .Option(o => o.BatchSize = deleteBatchSize) + .ExecuteCursorAsync(cancellation) + .ConfigureAwait(false); + + using (cursor) { - while (await cursor.MoveNextAsync().ConfigureAwait(false)) + while (await cursor.MoveNextAsync(cancellation).ConfigureAwait(false)) { if (cursor.Current.Any()) - deletedCount += (await DeleteCascadingAsync(cursor.Current, session).ConfigureAwait(false)).DeletedCount; + deletedCount += (await DeleteCascadingAsync(cursor.Current, session, cancellation).ConfigureAwait(false)).DeletedCount; } } @@ -96,19 +113,28 @@ public static async Task DeleteAsync(Expression> /// Any class that implements IEntity /// An IEnumerable of entity IDs /// An optional session if using within a transaction - public static async Task DeleteAsync(IEnumerable IDs, IClientSessionHandle session = null) where T : IEntity + /// An optional cancellation token + public static async Task DeleteAsync(IEnumerable IDs, IClientSessionHandle session = null, CancellationToken cancellation = default) where T : IEntity { + ThrowIfCancellationNotSupported(session, cancellation); + if (IDs.Count() <= deleteBatchSize) - return await DeleteCascadingAsync(IDs, session).ConfigureAwait(false); + return await DeleteCascadingAsync(IDs, session, cancellation).ConfigureAwait(false); long deletedCount = 0; foreach (var batch in IDs.ToBatches(deleteBatchSize)) { - deletedCount += (await DeleteCascadingAsync(batch, session).ConfigureAwait(false)).DeletedCount; + deletedCount += (await DeleteCascadingAsync(batch, session, cancellation).ConfigureAwait(false)).DeletedCount; } return new DeleteResult.Acknowledged(deletedCount); } + + private static void ThrowIfCancellationNotSupported(IClientSessionHandle session = null, CancellationToken cancellation = default) + { + if (cancellation != default && session == null) + throw new NotSupportedException("Cancellation is only supported within transactions for delete operations!"); + } } } diff --git a/MongoDB.Entities/MongoDB.Entities.csproj b/MongoDB.Entities/MongoDB.Entities.csproj index 168b4df7c..7885be8eb 100644 --- a/MongoDB.Entities/MongoDB.Entities.csproj +++ b/MongoDB.Entities/MongoDB.Entities.csproj @@ -8,12 +8,13 @@ A data access library for MongoDB with an elegant api, LINQ support and built-in entity relationship management. LICENSE https://mongodb-entities.com - 20.1.0-rc + 20.1.0 Đĵ ΝιΓΞΗΛψΚ - added methods for counting entities to the DB class - added transaction support for the new count methods +- added cancellation support for deletions +- improved deletions by performing high volume deletes in batches - improved high concurrency handling -- improved deletes by performing high volume deletes in batches - new tests MongoDB.Entities MongoDB.Entities