-
Notifications
You must be signed in to change notification settings - Fork 11
Documents
In Nevermore, a document is a class with a DocumentMap
. The DocumentMap
helps Nevermore to understand all the things it needs to treat your class as a document: what the ID property is, which properties should be stored as columns vs. in JSON, and so on.
You create a class that inherits the generic DocumentMap
class, passing your document class as a parameter. Most document maps should look quite simple.
class CustomerMap : DocumentMap<Customer>
{
public CustomerMap()
{
Column(c => c.FirstName);
Column(c => c.LastName);
}
}
The default conventions are:
- If you have a property called "Id", it will be mapped automatically
- Id values will be generated automatically, and formatted as
TableNames-{0}
(e.g.,Customers-123
) - The table name will be your class name ("Customer" in this example)
Everything else in the class will be serialized as JSON.
Here's a big document map showing all the options:
public class CustomerMap : DocumentMap<Customer>
{
public CustomerMap()
{
// Name of the table this document is saved to (defaults to class name)
TableName = "TblCustomers";
// Use something like "CUST0000001292" for ID's instead of "Customers-123" (default)
IdFormat = i => "C" + i.ToString("n0").PadLeft(10, '0');
Id().MaxLength(100);
// Max length prevents you saving the record with a bigger string - it will throw a friendly
// exception and tell you which property was at fault - which SQL doesn't do!
Column(m => m.FirstName).MaxLength(200);
Column(m => m.Nickname).MaxLength(200);
// Custom property handlers let you control how the property is read/written from the database
Column(m => m.Roles).CustomPropertyHandler(new MyCustomPropertyHandler());
// Read from the database only, Nevermore won't attempt to insert/update it. Use this for
// values that are calculated or changed only within SQL Server.
Column(m => m.RowVersion).LoadOnly();
// Save to the database only, Nevermore won't attempt to read it when querying
// Use this for properties that are calculated in .NET and useful to query against
Column(m => m.HasNickname).SaveOnly();
Unique("UniqueCustomerNames", new[] { "FirstName", "LastName" }, "Customers must have a unique name");
// Default false. See performance page for details.
ExpectLargeDocuments = true;
// See the compression page for details
JsonStorageFormat = JsonStorageFormat.MixedPreferCompressed;
}
}
Every property you map to a column will need a public get
accessor. The set accessor is optional:
- If there's no
set
accessor at all, Nevermore will require you to identify the column asSaveOnly()
- The set accessor can be
private
orprotected
, and Nevermore will call it
This allows you to design properties in a few ways:
- If a property is calculated, it can be stored in a column, and just have a
get
accessor - If a property is usually only set in the constructor or upon creation, it can have a private
set
accessor. This prevents you from setting it outside of the class in code, but Nevermore can still set it when it loads an object
Your tables will typically look like this:
- The
Id
column, as a non-null column and primary key - The other columns you want to map
- The
JSON
(and/orJSONBlob
if you use compression) column
For example:
create table Customer (
Id nvarchar(50) not null constraint PK_Customer_Id primary key,
-- ... other columns here ...
[JSON] nvarchar(max) not null
)
Id column
Every table should have an Id
column. It's possible to use a custom column name or property name (see below), but you should try to stick with this convention.
For string Ids, Nevermore assumes columns are nvarchar(50)
. If you use a different length (for example, to support longer keys) you can configure it in the document map:
public CustomerMap()
{
Id().MaxLength(200);
Column(c => C.FirstName);
...
}
If you break from the convention and want to give the column a different name, you can map it like this:
public CustomerMap()
{
Id(c => c.CustomerId, "Customer_Id");
...
}
Nevermore also supports Id types of string, int, long, and Guid out of the box. For more details on how to extend that support see Primary Key Handlers.
Nevermore lets you store JSON either as text, or compressed, or a mix of the two to allow for migrations. See the section on compression to learn why you might want to use it.
Every table used for documents needs a JSON
column, a JSONBlob
column, or both. Those names have special meaning and cannot be changed.
The [JSON]
column must be nvarchar(max)
. It's used when JsonStorageMode
is TextOnly
(the default) or one of the mixed options.
The [JSONBlob]
column must be varbinary(max)
. It's used when JsonStorageMode
is CompressedOnly
or one of the mixed options.
If you have both columns (because you use one of the mixed JsonStorageMode
options, again see compression), then you should set the columns as null
:
create table Customer (
-- ...
[JSON] nvarchar(max) null,
[JSONBlob] varbinary(max) null
)
A column with the name Type
has special meaning - it tells Nevermore to look for an IInstanceTypeResolver
. If it exists, the Type
column must appear before the JSON columns in the select clause.
The column can be any type (though it's typically an nvarchar
) as long as there is an ITypeHandler
that can map it to a CLR type. See Instance Type Resolvers for more details on how the Type
column works.
All other columns should be mapped using Column
. Here are some of the options for a column:
If you try to insert a string into SQL Server, and the string is too long for the type, you'll receive an error. However, SQL Server doesn't make it particularly clear which string was at fault. You might have a dozen string properties on the table.
For properties named "Id" or that end with "Id" (foreign keys etc.), the max length will default to 50. Otherwise, the default is null
.
If a max length is specified, and you try to insert a value larger than the max length, Nevermore will throw a helpful StringTooLongException
with the name of the column.
If the max length is null (default for non-Id
columns) then you'll receive the error from SQL instead. So it's a good practice to add MaxLength
properties to your document map.
You can add indexes, computed columns, and all other types of things to your table, and most of this doesn't need to appear in the document map.
If you add unique constraints to the table, you can tell Nevermore about this (though you don't have to). If you do, and an exception is thrown when inserting a record, Nevermore will try to translate the error message to something more useful. For example:
ALTER TABLE [Person] ADD CONSTRAINT [UQ_UniquePersonEmail] UNIQUE([Email])
class PersonMap : DocumentMap<Person>
{
public PersonMap()
{
Column(m => m.FirstName).MaxLength(20);
Column(m => m.LastName).Nullable();
Column(m => m.Email);
Unique("UniquePersonEmail", new[] { "Email" }, "People must have unique emails");
}
}
In this example, if you insert a person with an email that is already in use, SQL will return an error code with the name of the constraint. Nevermore will attempt to find a Unique
rule on your document map with a name that matches the unique constraint name from SQL (it's a partial match - the string you pass to Unique needs to be contained somewhere in the unique constraint name).
If Nevermore matches it, you'll get a friendly UniqueConstraintViolationException
with the message "People must have unique emails". This is much more useful than the generic error from SqlCommand
would be.
If you use RowVersion
, declare the property as either a byte[]
or Int64/long
(rowversions are always 8 bytes). Alternatively, you might want to create a custom type, and supply a type handler (there's a bit of detail).
Overview
Getting started
Extensibility
Misc