Summary#
Markdown is the type for a member that holds a document rather than a line of text. You use it exactly like a
string — assign markdown to it, read markdown back — but it is not stored as one blob. The platform splits the text on
its headings and keeps one row per section.
That storage is what buys you the behaviour you actually want from a document:
- Two writers can work at once. A person editing one section and an agent rewriting another do not collide, because they are writing different rows.
- An edit costs what the edit is worth. Ticking a checkbox rewrites one section, not the whole document.
- Search sees sections, not files. With
[Searchable], each section is indexed on its own, so a search result points at the part that answered rather than at a ten-page document.
You do not have to think about any of that to use one. Assign a string, read a string.
Signature#
entity Article {
Markdown Body; // document-backed; reads null until something writes it
Markdown? Notes; // `?` is accepted but changes nothing — see below
[Searchable] Markdown Manual; // each SECTION is indexed separately
}Description#
Reading and writing#
A markdown member reads and writes as text. Assigning replaces the whole document; reading returns the document reassembled in order.
entity Article {
[MaxLength(200)] string Title;
Markdown Body;
}
void PublishDraft(string title) {
var a = new Article {
Title = title,
Body = "# Overview\n\nWhat this is about.\n\n## Details\n\nThe specifics."
};
}
string ReadBody(Article a) {
return a.Body;
}The document written above is stored as two sections — Overview and Details — because those are its two
headings. ReadBody returns the text you wrote, reassembled from them.
A markdown member is never required#
Most value-shaped types with no natural zero — string, an enum, DateTime, Guid — are required: you must
supply a value before a row can be saved (see Optional and required members). Markdown is not one of them.
A document that has never been written to genuinely is not there, and there is nothing you could "supply" at create
time that would make it there. So a bare Markdown Body; is optional, and a row that never touches it saves fine:
entity Page {
[MaxLength(200)] string Title;
Markdown Body;
}
void CreateEmptyPage(string title) {
var p = new Page { Title = title }; // Body is never set — this is fine
}Reading p.Body afterwards returns null, not an empty string: nothing has been written, and the platform does not
invent a document to hand you. Writing Markdown? Body; is accepted and means the same thing — the ? is redundant
here rather than wrong.
Assigning null clears it#
a.Body = null; empties the document — it removes its sections. It does not delete the row that owns it.
It has no column of its own#
A markdown member is stored in its own section rows, keyed by the owning row and the member's name, so it adds no column to its entity's table. Two consequences worth knowing:
- Do not filter on it in a query. There is no column to compare against, so
Where(a => a.Body.Contains("x"))is not the way to find text. Use[Searchable]and search it — that is what section-level indexing is for. - Deleting the row deletes its document. You do not clean it up yourself.
Searching it#
[Searchable] on a markdown member indexes each section separately, which is almost always what you want from a
long document — a hit points at the section that matched.
entity Manual {
[Searchable] Markdown Body; // sections indexed individually
}See [Searchable] for how the results are queried.
Examples#
entity Kb {
[MaxLength(200)] string Title;
Markdown Body;
}
void Seed(string title) {
var k = new Kb { Title = title, Body = "# Intro\n\nStart here." };
}
void Rewrite(Kb k, string body) {
k.Body = body;
}See also#
- Optional and required members — why most no-natural-zero types are required, and why this one is not
- [Searchable] — indexing a document so its sections can be searched
- entity — declaring the entity a markdown member lives on