Fork me on GitHub

Soft Deletes Edit on GitHub


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