You can opt into using "soft deletes" for certain document types. Using this option means that documents are
never actually deleted out of the database. Rather, a mt_deleted
field is marked as true and a mt_deleted_at
field is updated with the transaction timestamp. If a document type is "soft deleted," Marten will automatically filter out
documents marked as deleted unless you explicitly state otherwise in the Linq Where
clause.
Configuring a Document Type as Soft Deleted
You can direct Marten to make a document type soft deleted by either marking the class with an attribute:
[SoftDeleted]
public class SoftDeletedDoc
{
public Guid Id;
}
Or by using the fluent interface off of StoreOptions
:
DocumentStore.For(_ =>
{
_.Schema.For<User>().SoftDeleted();
});
Querying a "Soft Deleted" Document Type
By default, Marten quietly filters out documents marked as deleted from Linq queries as demonstrated in this acceptance test from the Marten codebase:
[Fact]
public void query_soft_deleted_docs()
{
var user1 = new User { UserName = "foo" };
var user2 = new User { UserName = "bar" };
var user3 = new User { UserName = "baz" };
var user4 = new User { UserName = "jack" };
using (var session = theStore.OpenSession())
{
session.Store(user1, user2, user3, user4);
session.SaveChanges();
// Deleting 'bar' and 'baz'
session.DeleteWhere<User>(x => x.UserName.StartsWith("b"));
session.SaveChanges();
// no where clause, deleted docs should be filtered out
session.Query<User>().OrderBy(x => x.UserName).Select(x => x.UserName)
.ToList().ShouldHaveTheSameElementsAs("foo", "jack");
// with a where clause
session.Query<User>().Where(x => x.UserName != "jack")
.ToList().Single().UserName.ShouldBe("foo");
}
}
The SQL generated for the first call to Query<User>()
above would be:
select d.data ->> 'UserName' from public.mt_doc_user as d where mt_deleted = False order by d.data ->> 'UserName'
Fetching All Documents, Deleted or Not
You can include deleted documents with Marten's MaybeDeleted()
method in a Linq Where
clause
as shown in this acceptance tests:
[Fact]
public void query_maybe_soft_deleted_docs()
{
var user1 = new User { UserName = "foo" };
var user2 = new User { UserName = "bar" };
var user3 = new User { UserName = "baz" };
var user4 = new User { UserName = "jack" };
using (var session = theStore.OpenSession())
{
session.Store(user1, user2, user3, user4);
session.SaveChanges();
session.DeleteWhere<User>(x => x.UserName.StartsWith("b"));
session.SaveChanges();
// no where clause, all documents are returned
session.Query<User>().Where(x => x.MaybeDeleted()).OrderBy(x => x.UserName).Select(x => x.UserName)
.ToList().ShouldHaveTheSameElementsAs("bar", "baz", "foo", "jack");
// with a where clause, all documents are returned
session.Query<User>().Where(x => x.UserName != "jack" && x.MaybeDeleted())
.OrderBy(x => x.UserName)
.ToList()
.Select(x => x.UserName)
.ShouldHaveTheSameElementsAs("bar", "baz", "foo");
}
}
Fetching Only Deleted Documents
You can also query for only documents that are marked as deleted with Marten's IsDeleted()
method
as shown below:
[Fact]
public void query_is_soft_deleted_docs()
{
var user1 = new User { UserName = "foo" };
var user2 = new User { UserName = "bar" };
var user3 = new User { UserName = "baz" };
var user4 = new User { UserName = "jack" };
using (var session = theStore.OpenSession())
{
session.Store(user1, user2, user3, user4);
session.SaveChanges();
session.DeleteWhere<User>(x => x.UserName.StartsWith("b"));
session.SaveChanges();
// no where clause
session.Query<User>().Where(x => x.IsDeleted()).OrderBy(x => x.UserName).Select(x => x.UserName)
.ToList().ShouldHaveTheSameElementsAs("bar", "baz");
// with a where clause
session.Query<User>().Where(x => x.UserName != "baz" && x.IsDeleted())
.OrderBy(x => x.UserName)
.ToList()
.Select(x => x.UserName)
.Single().ShouldBe("bar");
}
}
Fetching Documents Deleted before or after a specific time
To search for documents that have been deleted before a specific time use Marten's DeletedBefore(DateTimeOffset)
method
and the counterpart DeletedSince(DateTimeOffset)
as show below:
[Fact]
public void query_is_soft_deleted_since_docs()
{
var user1 = new User { UserName = "foo" };
var user2 = new User { UserName = "bar" };
var user3 = new User { UserName = "baz" };
var user4 = new User { UserName = "jack" };
using (var session = theStore.OpenSession())
{
session.Store(user1, user2, user3, user4);
session.SaveChanges();
session.Delete(user3);
session.SaveChanges();
var epoch = session.DocumentStore.Tenancy.Default.MetadataFor(user3).DeletedAt;
session.Delete(user4);
session.SaveChanges();
session.Query<User>().Where(x => x.DeletedSince(epoch.Value)).Select(x => x.UserName)
.ToList().ShouldHaveTheSameElementsAs("jack");
}
}
Neither DeletedSince
nor DeletedBefore
are inclusive searches as shown