// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using MongoDB.Bson; using MongoDB.Driver; using Squidex.Infrastructure.States; #pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body namespace Squidex.Infrastructure.MongoDb { public static class MongoExtensions { private static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true }; public static async Task InsertOneIfNotExistsAsync(this IMongoCollection collection, T document) { try { await collection.InsertOneAsync(document); } catch (MongoWriteException ex) { if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) { return false; } throw; } return true; } public static async Task TryDropOneAsync(this IMongoIndexManager indexes, string name) { try { await indexes.DropOneAsync(name); } catch { /* NOOP */ } } public static IFindFluent Only(this IFindFluent find, Expression> include) { return find.Project(Builders.Projection.Include(include)); } public static IFindFluent Only(this IFindFluent find, Expression> include1, Expression> include2) { return find.Project(Builders.Projection.Include(include1).Include(include2)); } public static IFindFluent Only(this IFindFluent find, Expression> include1, Expression> include2, Expression> include3) { return find.Project(Builders.Projection.Include(include1).Include(include2).Include(include3)); } public static IFindFluent Not(this IFindFluent find, Expression> exclude) { return find.Project(Builders.Projection.Exclude(exclude)); } public static IFindFluent Not(this IFindFluent find, Expression> exclude1, Expression> exclude2) { return find.Project(Builders.Projection.Exclude(exclude1).Exclude(exclude2)); } public static IFindFluent Not(this IFindFluent find, Expression> exclude1, Expression> exclude2, Expression> exclude3) { return find.Project(Builders.Projection.Exclude(exclude1).Exclude(exclude2).Exclude(exclude3)); } public static async Task UpsertVersionedAsync(this IMongoCollection collection, TKey key, long oldVersion, long newVersion, Func, UpdateDefinition> updater) where T : IVersionedEntity { try { var update = updater(Builders.Update.Set(x => x.Version, newVersion)); await collection.UpdateOneAsync(x => x.Id.Equals(key) && x.Version == oldVersion, update .Set(x => x.Version, newVersion), Upsert); } catch (MongoWriteException ex) { if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) { var existingVersion = await collection.Find(x => x.Id.Equals(key)).Only(x => x.Id, x => x.Version) .FirstOrDefaultAsync(); if (existingVersion != null) { throw new InconsistentStateException(existingVersion[nameof(IVersionedEntity.Version)].AsInt64, oldVersion, ex); } } else { throw; } } } public static async Task UpsertVersionedAsync(this IMongoCollection collection, TKey key, long oldVersion, long newVersion, T doc) where T : IVersionedEntity { try { await collection.ReplaceOneAsync(x => x.Id.Equals(key) && x.Version == oldVersion, doc, Upsert); } catch (MongoWriteException ex) { if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) { var existingVersion = await collection.Find(x => x.Id.Equals(key)).Only(x => x.Id, x => x.Version) .FirstOrDefaultAsync(); if (existingVersion != null) { throw new InconsistentStateException(existingVersion[nameof(IVersionedEntity.Version)].AsInt64, oldVersion, ex); } } else { throw; } } } public static async Task ForEachPipelineAsync(this IAsyncCursorSource source, Func processor, CancellationToken cancellationToken = default(CancellationToken)) { var cursor = await source.ToCursorAsync(cancellationToken); await cursor.ForEachPipelineAsync(processor, cancellationToken); } public static async Task ForEachPipelineAsync(this IAsyncCursor source, Func processor, CancellationToken cancellationToken = default(CancellationToken)) { using (var selfToken = new CancellationTokenSource()) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(selfToken.Token, cancellationToken)) { var actionBlock = new ActionBlock(async x => { if (!combined.IsCancellationRequested) { await processor(x); } }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1, MaxMessagesPerTask = 1, BoundedCapacity = 100 }); try { await source.ForEachAsync(async i => { if (!await actionBlock.SendAsync(i, combined.Token)) { selfToken.Cancel(); } }, combined.Token); actionBlock.Complete(); } catch (Exception ex) { ((IDataflowBlock)actionBlock).Fault(ex); } finally { await actionBlock.Completion; } } } } } }