Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Document Deletions #2818

Merged
merged 1 commit into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions src/DocumentDbTests/Bugs/Bug_2660_deletes_in_complex_order.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Marten;
using Marten.Services;
using Marten.Testing.Documents;
using Marten.Testing.Harness;
using Xunit;
using Shouldly;

namespace DocumentDbTests.Bugs;

public class Bug_2660_deletes_in_complex_order : BugIntegrationContext
{
// works
[Fact]
public async Task Deleting_Single_DocType_In_One_Session_Works()
{
// store & delete within same session
await using var session = theStore.IdentitySession();
var id = Guid.NewGuid();
session.Store(new FooModel() { Id = id });
session.Delete<FooModel>(id);
await session.SaveChangesAsync();

await using var session2 = theStore.IdentitySession();
var model = await session.LoadAsync<FooModel>(id);
model.ShouldBeNull();
}

// fails
[Fact]
public async Task Deleting_Multiple_DocTypes_In_One_Session_Works()
{
// store & delete within same session
await using var session = theStore.IdentitySession();
var id = Guid.NewGuid();
session.Store(new FooModel() { Id = id });
session.Store(new BarModel() { Id = id });
session.Delete<FooModel>(id);
session.Delete<BarModel>(id);

session.PendingChanges.Operations().Count().ShouldBe(2);
session.PendingChanges.Deletions().Count().ShouldBe(2);

await session.SaveChangesAsync();

await using var session2 = theStore.IdentitySession();
var model = await session.LoadAsync<FooModel>(id);
model.ShouldBeNull();
}

// also fails
[Fact]
public async Task Delete_Where_Within_Same_Session_Doesnt_Affect_Actual_Delete()
{
// store & delete in the same session
using var session = theStore.IdentitySession();
var id = Guid.NewGuid();
session.Store(new FooModel() { Id = id });
session.Store(new BarModel() { Id = Guid.NewGuid(), RelGuid = id });
session.Delete<FooModel>(id);
session.DeleteWhere<BarModel>(x => x.RelGuid == id);
await session.SaveChangesAsync();

await using var session2 = theStore.IdentitySession();
var model = await session.LoadAsync<FooModel>(id);
model.ShouldBeNull();
}

[Fact]
public async Task mixing_id_types_lightweight()
{
await theStore.Advanced.Clean.DeleteDocumentsByTypeAsync(typeof(IntDoc));
await theStore.Advanced.Clean.DeleteDocumentsByTypeAsync(typeof(GuidDoc));

await using var session1 = theStore.LightweightSession();

const int id = 5;

// store & delete guid doc
var guid = Guid.NewGuid();

session1.Store(new GuidDoc{Id = guid});
session1.Delete<GuidDoc>(guid);

// store & delete int doc
session1.Store(new IntDoc(id));
session1.Delete<IntDoc>(id);

await session1.SaveChangesAsync();

await using var session2 = theStore.QuerySession();
(await session2.Query<IntDoc>().CountAsync()).ShouldBe(0);
(await session2.Query<GuidDoc>().CountAsync()).ShouldBe(0);
}

[Fact]
public async Task mixing_id_types_identity()
{
await theStore.Advanced.Clean.DeleteDocumentsByTypeAsync(typeof(IntDoc));
await theStore.Advanced.Clean.DeleteDocumentsByTypeAsync(typeof(GuidDoc));

await using var session1 = theStore.IdentitySession();

const int id = 5;

// store & delete guid doc
var guid = Guid.NewGuid();

session1.Store(new GuidDoc{Id = guid});
session1.Delete<GuidDoc>(guid);

// store & delete int doc
session1.Store(new IntDoc(id));
session1.Delete<IntDoc>(id);

await session1.SaveChangesAsync();

await using var session2 = theStore.QuerySession();
(await session2.Query<IntDoc>().CountAsync()).ShouldBe(0);
(await session2.Query<GuidDoc>().CountAsync()).ShouldBe(0);
}

[Fact]
public async Task mixing_id_types_dirty_checking()
{
await theStore.Advanced.Clean.DeleteDocumentsByTypeAsync(typeof(IntDoc));
await theStore.Advanced.Clean.DeleteDocumentsByTypeAsync(typeof(GuidDoc));

await using var session1 = theStore.DirtyTrackedSession();

const int id = 5;

// store & delete guid doc
var guid = Guid.NewGuid();

session1.Store(new GuidDoc{Id = guid});
session1.Delete<GuidDoc>(guid);

// store & delete int doc
session1.Store(new IntDoc(id));
session1.Delete<IntDoc>(id);

await session1.SaveChangesAsync();

await using var session2 = theStore.QuerySession();
(await session2.Query<IntDoc>().CountAsync()).ShouldBe(0);
(await session2.Query<GuidDoc>().CountAsync()).ShouldBe(0);
}

}

public class FooModel
{
public Guid Id { get; set; }
}

public class BarModel
{
public Guid Id { get; set; }
public Guid RelGuid { get; set; }
}
5 changes: 5 additions & 0 deletions src/Marten/Events/Daemon/ProjectionUpdateBatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ public void EjectAll()
throw new NotSupportedException();
}

public void PurgeOperations<T, TId>(TId id) where T : notnull
{
// Do nothing here
}

void IUpdateBatch.ApplyChanges(IMartenSession session)
{
if (_token.IsCancellationRequested)
Expand Down
2 changes: 2 additions & 0 deletions src/Marten/Internal/Operations/StorageOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public StorageOperation(T document, TId id, Dictionary<TId, Guid> versions, Docu
_tableName = mapping.TableName.Name;
}

public TId Id => _id;

public object Document => _document;

public IChangeTracker ToTracker(IMartenSession session)
Expand Down
10 changes: 10 additions & 0 deletions src/Marten/Internal/Sessions/DocumentSessionBase.Deletes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public void Delete<T>(T entity) where T : notnull
assertNotDisposed();
var documentStorage = StorageFor<T>();

if (documentStorage is IDocumentStorage<T, Guid> g) _workTracker.PurgeOperations<T, Guid>(g.Identity(entity));
if (documentStorage is IDocumentStorage<T, string> s) _workTracker.PurgeOperations<T, string>(s.Identity(entity));
if (documentStorage is IDocumentStorage<T, int> i) _workTracker.PurgeOperations<T, int>(i.Identity(entity));
if (documentStorage is IDocumentStorage<T, long> l) _workTracker.PurgeOperations<T, long>(l.Identity(entity));

var deletion = documentStorage.DeleteForDocument(entity, TenantId);
_workTracker.Add(deletion);

Expand All @@ -33,12 +38,14 @@ public void Delete<T>(int id) where T : notnull
{
_workTracker.Add(i.DeleteForId(id, TenantId));

_workTracker.PurgeOperations<T, int>(id);
ejectById<T>(id);
}
else if (storage is IDocumentStorage<T, long> l)
{
_workTracker.Add(l.DeleteForId(id, TenantId));

_workTracker.PurgeOperations<T, long>(id);
ejectById<T>((long)id);
}
else
Expand All @@ -53,13 +60,15 @@ public void Delete<T>(long id) where T : notnull
var deletion = StorageFor<T, long>().DeleteForId(id, TenantId);
_workTracker.Add(deletion);

_workTracker.PurgeOperations<T, long>(id);
ejectById<T>(id);
}

public void Delete<T>(Guid id) where T : notnull
{
assertNotDisposed();
var deletion = StorageFor<T, Guid>().DeleteForId(id, TenantId);
_workTracker.PurgeOperations<T, Guid>(id);
_workTracker.Add(deletion);

ejectById<T>(id);
Expand All @@ -72,6 +81,7 @@ public void Delete<T>(string id) where T : notnull
var deletion = StorageFor<T, string>().DeleteForId(id, TenantId);
_workTracker.Add(deletion);

_workTracker.PurgeOperations<T, string>(id);
ejectById<T>(id);
}

Expand Down
5 changes: 5 additions & 0 deletions src/Marten/Internal/UnitOfWork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ public void EjectAll()
Streams.Clear();
}

public void PurgeOperations<T, TId>(TId id) where T : notnull
{
_operations.RemoveAll(op => op is StorageOperation<T, TId> storage && storage.Id.Equals(id));
}

public bool TryFindStream(string streamKey, out StreamAction stream)
{
stream = Streams
Expand Down
8 changes: 8 additions & 0 deletions src/Marten/Services/ISessionWorkTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ internal interface ISessionWorkTracker: IUnitOfWork, IChangeSet
bool TryFindStream(Guid streamId, out StreamAction stream);
bool HasOutstandingWork();
void EjectAll();

/// <summary>
/// Remove all outstanding operations for the designated document
/// </summary>
/// <param name="id"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TId"></typeparam>
void PurgeOperations<T, TId>(TId id) where T : notnull;
}
Loading