Foreign Keys
Marten is built on top of a relational database, so why not take advantage of those abilities where they still add value? In this case, Marten allows for a special kind of "Searchable" column that also adds a foreign key constraint to enforce referential integrity between document types.
One of our sample document types in Marten is the Issue
class that has a couple properties that link to the id's of related User
documents:
public class Issue
{
public Issue()
{
Id = Guid.NewGuid();
}
public string[] Tags { get; set; }
public Guid Id { get; set; }
public string Title { get; set; }
public int Number { get; set; }
public Guid? AssigneeId { get; set; }
public Guid? ReporterId { get; set; }
public Guid? BugId { get; set; }
public string Status { get; set; }
}
If I want to enforce referential integrity between the Issue
document and the User
documents, I can use this syntax shown below to configure Marten:
var store = DocumentStore
.For(_ =>
{
_.Connection("some database connection");
// In the following line of code, I'm setting
// up a foreign key relationship to the User document
_.Schema.For<Issue>().ForeignKey<User>(x => x.AssigneeId);
});
With the configuration above, Marten will make an assignee_id
field in the database table and build a foreign key constraint to the User
document like so:
ALTER TABLE public.mt_doc_issue
ADD CONSTRAINT mt_doc_issue_assignee_id_fkey FOREIGN KEY (assignee_id)
REFERENCES public.mt_doc_user (id);
CREATE INDEX mt_doc_issue_idx_assignee_id ON public.mt_doc_issue ("assignee_id");
And some other things you probably want to know about how this works internally:
Marten is smart enough to order the "upsert" operations to make the dependent documents be updated last. In the Issue
referencing User
example above, this means that if you create a new User
and a new Issue
in the same session, when you call IDocumentSession.SaveChanges()/SaveChangesAsync()
, Marten will know to save the new user first so that the issue will not fail with referential integrity violations.
Foreign Keys to non-Marten tables
Marten can also create a foreign key to tables that are not managed by Marten. Continuing the our sample of Issue
, we can create a foreign key from our Issue
to our external bug tracking system:
var store = DocumentStore
.For(_ =>
{
_.Connection("some database connection");
// Here we create a foreign key to table that is not
// created or managed by marten
_.Schema.For<Issue>().ForeignKey(i => i.BugId, "bugtracker", "bugs", "id");
});
With the configuration above, Marten will generate a foreign key constraint from the Issue
to a table in the bug-tracker
schema called bugs
on the id
column. The constraint would be defined as:
ALTER TABLE public.mt_doc_issue
ADD CONSTRAINT mt_doc_issue_bug_id_fkey FOREIGN KEY (bug_id)
REFERENCES bugtracker.bugs (id);
Cascading deletes
Marten can also cascade deletes on the foreign keys that it creates. The ForeignKeyDefinition
has a CascadeDeletes
property that indicates whether the foreign key should enable cascading deletes. One way to enable this is to use a configuration function like:
var store = DocumentStore
.For(_ =>
{
_.Connection("some database connection");
_.Schema.For<Issue>().ForeignKey<User>(x => x.AssigneeId, fkd => fkd.OnDelete = CascadeAction.Cascade);
});
Configuring with Attributes
You can optionally configure properties or fields as foreign key relationships with the [ForeignKey]
attribute:
public class Issue
{
public Issue()
{
Id = Guid.NewGuid();
}
public Guid Id { get; set; }
[ForeignKey(typeof(User))]
public Guid UserId { get; set; }
public Guid? OtherUserId { get; set; }
}