Skip to content

Commit f0759ff

Browse files
committed
FindOneAndDeleteAsync
1 parent b2b0da6 commit f0759ff

File tree

6 files changed

+268
-6
lines changed

6 files changed

+268
-6
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
using FluentAssertions;
2+
using MongoDB.Bson;
3+
using MongoDB.Driver;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using Xunit;
10+
11+
namespace JohnKnoop.MongoRepository.IntegrationTests
12+
{
13+
public class MyBaseEntity
14+
{
15+
public MyBaseEntity(string name)
16+
{
17+
Id = ObjectId.GenerateNewId().ToString();
18+
Name = name;
19+
}
20+
21+
public string Id { get; private set; }
22+
public string Name { get; private set; }
23+
}
24+
25+
public class MyDerivedEntity : MyBaseEntity {
26+
public MyDerivedEntity(string name, int age) : base(name)
27+
{
28+
Age = age;
29+
}
30+
31+
public int Age { get; private set; }
32+
}
33+
34+
[CollectionDefinition("IntegrationTests", DisableParallelization = true)]
35+
public class FindOneAndDeleteTests : IClassFixture<LaunchSettingsFixture>
36+
{
37+
private const string DbName = "TestDb";
38+
private const string CollectionName = "MyEntities";
39+
private readonly MongoClient _mongoClient;
40+
private readonly IRepository<MyBaseEntity> _repository;
41+
private readonly string _baseEntityId;
42+
private readonly string _derivedEntityId;
43+
44+
public FindOneAndDeleteTests(LaunchSettingsFixture launchSettingsFixture)
45+
{
46+
_mongoClient = new MongoClient(Environment.GetEnvironmentVariable("MongoDbConnectionString"));
47+
48+
MongoRepository.Configure()
49+
.Database(DbName, x => x
50+
.MapAlongWithSubclassesInSameAssebmly<MyBaseEntity>(CollectionName)
51+
)
52+
.AutoEnlistWithTransactionScopes()
53+
.Build();
54+
55+
// Empty all collections in database
56+
foreach (var collectionName in _mongoClient.GetDatabase(DbName).ListCollectionNames().ToEnumerable())
57+
{
58+
_mongoClient.GetDatabase(DbName).GetCollection<BsonDocument>(collectionName).DeleteMany(x => true);
59+
}
60+
61+
// Add test documents to db
62+
var baseEntityDocument = new MyBaseEntity("Mary");
63+
var derivedEntityDocument = new MyDerivedEntity("Joe", 45);
64+
65+
_baseEntityId = baseEntityDocument.Id;
66+
_derivedEntityId = derivedEntityDocument.Id;
67+
68+
_repository = _mongoClient.GetRepository<MyBaseEntity>();
69+
_repository.InsertAsync(baseEntityDocument).Wait();
70+
_repository.InsertAsync(derivedEntityDocument).Wait();
71+
72+
AssertNumberOfDocumentsInCollection(2).Wait();
73+
AssertNumberOfDocumentsInTrash(0).Wait();
74+
}
75+
76+
private async Task AssertNumberOfDocumentsInCollection(int expected)
77+
{
78+
var documentsInCollection = await _mongoClient.GetDatabase(DbName).GetCollection<MyBaseEntity>(CollectionName).CountDocumentsAsync(x => true);
79+
documentsInCollection.Should().Be(expected);
80+
}
81+
82+
private async Task AssertNumberOfDocumentsInTrash(int expected)
83+
{
84+
var docsInTrash = await _mongoClient.GetDatabase(DbName).GetCollection<BsonDocument>("DeletedObjects").CountDocumentsAsync(x => true);
85+
docsInTrash.Should().Be(expected);
86+
}
87+
88+
[Fact]
89+
public async Task BaseEntity_HardDeleted_ShouldBeRemovedFromCollection()
90+
{
91+
// By id
92+
var doc = await _repository.FindOneAndDeleteAsync(_baseEntityId);
93+
doc.Name.Should().Be("Mary");
94+
await AssertNumberOfDocumentsInCollection(1);
95+
}
96+
97+
[Fact]
98+
public async Task DerivedEntity_HardDeleted_ShouldBeRemovedFromCollection()
99+
{
100+
// By expression
101+
var doc = await _repository.FindOneAndDeleteAsync<MyDerivedEntity>(x => x.Age == 45);
102+
doc.Name.Should().Be("Joe");
103+
await AssertNumberOfDocumentsInCollection(1);
104+
}
105+
106+
[Fact]
107+
public async Task BaseEntity_SoftDeleted_ShouldBeRemovedFromCollectionAndAddedToTrash()
108+
{
109+
// By expression
110+
var doc = await _repository.FindOneAndDeleteAsync(x => x.Name == "Mary", softDelete: true);
111+
doc.Name.Should().Be("Mary");
112+
await AssertNumberOfDocumentsInCollection(1);
113+
await AssertNumberOfDocumentsInTrash(1);
114+
}
115+
116+
[Fact]
117+
public async Task DerivedEntity_SoftDeleted_ShouldBeRemovedFromCollectionAndAddedToTrash()
118+
{
119+
// By id
120+
var doc = await _repository.FindOneAndDeleteAsync(_derivedEntityId, softDelete: true);
121+
doc.Name.Should().Be("Joe");
122+
await AssertNumberOfDocumentsInCollection(1);
123+
await AssertNumberOfDocumentsInTrash(1);
124+
}
125+
126+
[Fact]
127+
public async Task SoftDelete_WithAbortedTransactions_ShouldNotAddAnythingToTrash()
128+
{
129+
// By id
130+
using (var transaction = _repository.StartTransaction())
131+
{
132+
var doc = await _repository.FindOneAndDeleteAsync(_baseEntityId, softDelete: true);
133+
doc.Name.Should().Be("Mary");
134+
await transaction.AbortAsync();
135+
}
136+
137+
await AssertNumberOfDocumentsInCollection(2);
138+
await AssertNumberOfDocumentsInTrash(0);
139+
}
140+
141+
[Fact]
142+
public async Task HardDeleted_WithAbortedTransactions_ShouldNotRemoveFromCollection()
143+
{
144+
// By expression
145+
using (var transaction = _repository.StartTransaction())
146+
{
147+
var doc = await _repository.FindOneAndDeleteAsync(x => x.Id == _derivedEntityId);
148+
doc.Name.Should().Be("Joe");
149+
}
150+
151+
await AssertNumberOfDocumentsInCollection(2);
152+
await AssertNumberOfDocumentsInTrash(0);
153+
}
154+
155+
[Fact]
156+
public async Task CanRestoreSoftdeletedBaseEntity()
157+
{
158+
await _repository.FindOneAndDeleteAsync(_baseEntityId, softDelete: true);
159+
160+
await AssertNumberOfDocumentsInCollection(1);
161+
await AssertNumberOfDocumentsInTrash(1);
162+
163+
await _repository.RestoreSoftDeletedAsync(_baseEntityId);
164+
165+
await AssertNumberOfDocumentsInCollection(2);
166+
await AssertNumberOfDocumentsInTrash(0);
167+
}
168+
169+
[Fact]
170+
public async Task CanRestoreSoftdeletedDerivedEntity()
171+
{
172+
await _repository.FindOneAndDeleteAsync<MyDerivedEntity>(x => x.Age == 45, softDelete: true);
173+
174+
await AssertNumberOfDocumentsInCollection(1);
175+
await AssertNumberOfDocumentsInTrash(1);
176+
177+
await _repository.RestoreSoftDeletedAsync<MyDerivedEntity>(x => x.Entity.Age == 45);
178+
179+
await AssertNumberOfDocumentsInCollection(2);
180+
await AssertNumberOfDocumentsInTrash(0);
181+
}
182+
}
183+
}

JohnKnoop.MongoRepository.IntegrationTests/JohnKnoop.MongoRepository.IntegrationTests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<PackageReference Include="FluentAssertions" Version="5.10.3" />
1212
<PackageReference Include="Json.Net" Version="1.0.23" />
1313
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
14-
<PackageReference Include="MongoDB.Driver" Version="2.11.0" />
14+
<PackageReference Include="MongoDB.Driver" Version="2.11.4" />
1515
<PackageReference Include="xunit" Version="2.4.0" />
1616
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
1717
<PackageReference Include="coverlet.collector" Version="1.2.0" />

JohnKnoop.MongoRepository/source/IRepository.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ IFindFluent<TDerivedEntity, TDerivedEntity> Find<TDerivedEntity>(FilterDefinitio
8989
Task<DeleteResult> DeleteManyAsync(Expression<Func<TEntity, bool>> filter, bool softDelete = false);
9090
Task<DeleteResult> DeleteManyAsync<TDerived>(Expression<Func<TDerived, bool>> filter, bool softDelete = false) where TDerived : TEntity;
9191

92+
Task<TEntity> FindOneAndDeleteAsync(string id, bool softDelete = false);
93+
Task<TEntity> FindOneAndDeleteAsync(Expression<Func<TEntity, bool>> filter, bool softDelete = false);
94+
95+
Task<TDerivedEntity> FindOneAndDeleteAsync<TDerivedEntity>(string id, bool softDelete = false) where TDerivedEntity : TEntity;
96+
Task<TDerivedEntity> FindOneAndDeleteAsync<TDerivedEntity>(Expression<Func<TDerivedEntity, bool>> filter, bool softDelete = false) where TDerivedEntity : TEntity;
97+
9298
Task<TEntity> GetFromTrashAsync(Expression<Func<SoftDeletedEntity<TEntity>, bool>> filter);
9399
Task<IList<TEntity>> RestoreSoftDeletedAsync(Expression<Func<SoftDeletedEntity<TEntity>, bool>> filter);
94100
Task<IList<SoftDeletedEntity>> ListTrashAsync(int? offset = null, int? limit = null);

JohnKnoop.MongoRepository/source/JohnKnoop.MongoRepository.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
5-
<Version>5.4.0</Version>
5+
<Version>5.5.0</Version>
66
<Description>An easily configurable repository abstraction for MongoDB with first-class support for multi-tenancy</Description>
77
<PackageProjectUrl>https://github.com/johnknoop/MongoRepository</PackageProjectUrl>
88
<PackageTags>repository mongodb multitenant</PackageTags>
99
<Authors>John Knoop</Authors>
1010
<LangVersion>7.1</LangVersion>
11-
<PackageReleaseNotes>FindOneAsync convenience methods</PackageReleaseNotes>
11+
<PackageReleaseNotes>FindOneAndDeleteAsync</PackageReleaseNotes>
1212
<GenerateDocumentationFile>true</GenerateDocumentationFile>
1313
</PropertyGroup>
1414

1515
<ItemGroup>
16-
<PackageReference Include="MongoDB.Bson" version="2.11.0" />
17-
<PackageReference Include="MongoDB.Driver" version="2.11.0" />
18-
<PackageReference Include="MongoDB.Driver.Core" version="2.11.0" />
16+
<PackageReference Include="MongoDB.Bson" version="2.11.4" />
17+
<PackageReference Include="MongoDB.Driver" version="2.11.4" />
18+
<PackageReference Include="MongoDB.Driver.Core" version="2.11.4" />
1919
<PackageReference Include="Newtonsoft.Json" version="12.0.2" />
2020
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
2121
</ItemGroup>

JohnKnoop.MongoRepository/source/MongoRepository.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,72 @@ public async Task WithTransactionAsync(Func<Task> transactionBody, TransactionTy
11341134
}
11351135
}
11361136
}
1137+
1138+
#region FindOneAndDelete
1139+
1140+
public Task<TEntity> FindOneAndDeleteAsync(string id, bool softDelete = false)
1141+
{
1142+
if (id is null)
1143+
{
1144+
throw new ArgumentNullException(nameof(id));
1145+
}
1146+
1147+
var filter = new BsonDocumentFilterDefinition<TEntity>(new BsonDocument("_id", ObjectId.Parse(id)));
1148+
1149+
return FindOneAndDeleteImplAsync(MongoCollection, filter, softDelete);
1150+
}
1151+
1152+
public Task<TEntity> FindOneAndDeleteAsync(Expression<Func<TEntity, bool>> filter, bool softDelete = false)
1153+
{
1154+
return FindOneAndDeleteImplAsync(MongoCollection, Builders<TEntity>.Filter.Where(filter), softDelete);
1155+
}
1156+
1157+
private async Task<TDerived> FindOneAndDeleteImplAsync<TDerived>(IMongoCollection<TDerived> collection, FilterDefinition<TDerived> filter, bool softDelete = false) where TDerived : TEntity
1158+
{
1159+
TryAutoEnlistWithCurrentTransactionScope();
1160+
1161+
if (softDelete)
1162+
{
1163+
return await WithTransaction(async session =>
1164+
{
1165+
var entity = await collection.FindOneAndDeleteAsync(session, filter).ConfigureAwait(false);
1166+
1167+
if (entity == null)
1168+
{
1169+
return default;
1170+
}
1171+
1172+
await this._trash.InsertOneAsync(session, new DeletedObject<TEntity>(entity, this.MongoCollection.CollectionNamespace.CollectionName, DateTime.UtcNow)).ConfigureAwait(false);
1173+
1174+
return entity;
1175+
});
1176+
}
1177+
else
1178+
{
1179+
return (AmbientSession != null
1180+
? await collection.FindOneAndDeleteAsync(AmbientSession, filter).ConfigureAwait(false)
1181+
: await collection.FindOneAndDeleteAsync(filter).ConfigureAwait(false));
1182+
}
1183+
}
1184+
1185+
public Task<TDerivedEntity> FindOneAndDeleteAsync<TDerivedEntity>(string id, bool softDelete = false) where TDerivedEntity : TEntity
1186+
{
1187+
if (id is null)
1188+
{
1189+
throw new ArgumentNullException(nameof(id));
1190+
}
1191+
1192+
var filter = new BsonDocumentFilterDefinition<TDerivedEntity>(new BsonDocument("_id", ObjectId.Parse(id)));
1193+
1194+
return FindOneAndDeleteImplAsync(MongoCollection.OfType<TDerivedEntity>(), filter, softDelete);
1195+
}
1196+
1197+
public Task<TDerivedEntity> FindOneAndDeleteAsync<TDerivedEntity>(Expression<Func<TDerivedEntity, bool>> filter, bool softDelete = false) where TDerivedEntity : TEntity
1198+
{
1199+
return FindOneAndDeleteImplAsync(MongoCollection.OfType<TDerivedEntity>(), Builders<TDerivedEntity>.Filter.Where(filter), softDelete);
1200+
}
1201+
1202+
#endregion
11371203
}
11381204

11391205
public class NoBulkWriteResult<T> : BulkWriteResult<T>

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,19 @@ Returns an [IAggregateFluent](http://api.mongodb.com/csharp/current/html/T_Mongo
176176
### Deleting
177177
```csharp
178178
await repository.DeleteByIdAsync("id");
179+
// or
179180
await repository.DeleteManyAsync(x => x.SomeProperty === someValue);
181+
// or
182+
var deleted = await repository.FindOneAndDeleteAsync("id");
183+
// or
184+
var deleted = await repository.FindOneAndDeleteAsync<DerivedType>(x => x.SomeProp == someValue);
180185
```
181186
#### Soft-deleting
182187
Soft-deleting an entity will move it to a different collection, preserving type-information.
183188
```csharp
184189
await repository.DeleteByIdAsync("id", softDelete: true);
190+
// or
191+
var deleted = await repository.FindOneAndDeleteAsync("id", softDelete: true);
185192
```
186193
Listing soft-deleted entities:
187194
```csharp

0 commit comments

Comments
 (0)