Relatude Help

3.1 Overriding attributes

An override changes an attribute without editing its declaration. Use one when the type comes from an assembly you do not build, when the property is declared on a base type that other types share, or when one installation needs another value without a rebuild. Three layers decide what a type ends up with, and a later layer wins:

  1. Declared: the attributes on the type or the property, as described above.
  2. Overridden in code: a derived type sets its own value for a switch or a property attribute it inherits.
  3. Overridden in the database: values set in the admin UI's datamodel editor, or by hand in JSON. They are applied when the database opens, as if the source said so.

Text index switches are inherited. TextIndex, SemanticIndex and InstantTextIndexing on [Node] apply to the type they are set on and to the types that inherit from it. A type that does not set one takes it from its closest base type that does, and then from the database default (EnableTextIndexByDefault and friends, §12.1).

[Node(TextIndex = BoolValue.True)]
public interface IContent { /* ... */ }            // every IContent type is text indexed...

[Node(TextIndex = BoolValue.False)]
public class InternalNote : IContent { /* ... */ } // ...except this one

If two unrelated base types disagree, the type gets the database default, and the database logs a warning naming both. Set the switch on the type itself to choose. Semantic indexing needs the text index, so a type that is semantically indexed stays text indexed whatever its TextIndex says.

Inherited properties: [PropertyOverride]. A derived type can set six things for a property it inherits:

On [PropertyOverride] What it does for the type
DefaultValue The value a new node starts with, and what a stored node reads while it has no value
ExcludeFromTextIndex BoolValue.True leaves the property out of the type's text index; False puts it back
TextIndexBoost Extra times the value goes into the type's text index; 0 cancels an inherited boost
DisplayName BoolValue.True makes the property part of the type's display name; False takes it out
Indexed BoolValue.True asks for the property's value index, to filter, sort and facet on it
NotFacet BoolValue.False asks for a facet where the declaration says NotFacet

Anything you leave out comes from the base types. In this example, news articles get their own defaults and an index on Status, and support tickets keep their body out of search:

public enum ContentStatus { Draft = 0, Published = 1, Archived = 2 }

[Node(TextIndex = BoolValue.True)]
public interface IContent {
    Guid Id { get; set; }
    [StringProperty(DefaultValue = "Untitled", DisplayName = true)] string Title { get; set; }
    [HtmlProperty] string Body { get; set; }
    [IntegerProperty] ContentStatus Status { get; set; }
}

[PropertyOverride(nameof(IContent.Title), DefaultValue = "Untitled news", TextIndexBoost = 2)]
[PropertyOverride(nameof(IContent.Status), DefaultValue = ContentStatus.Published, Indexed = BoolValue.True)]
public class NewsArticle : IContent { /* ... */ }

// The body holds customer details, so it stays out of search.
[PropertyOverride(nameof(IContent.Body), ExcludeFromTextIndex = BoolValue.True)]
public class SupportTicket : IContent { /* ... */ }

db.Create<NewsArticle>() starts with the title "Untitled news" and the status Published. Other IContent types keep "Untitled" and Draft. A news title goes into the text index two extra times, so it weighs more in search. new NewsArticle() is plain C#, so it starts with the property initialisers instead.

A few more rules:

  • On a member. A class member that implements an interface property can carry the attribute without the name: [PropertyOverride(TextIndexBoost = 2)] public string Title { get; set; }. It is the only property attribute such a member can carry.
  • Down a class chain. An override reaches every type that inherits from the type it is set on, and a more specific type can override it again. Interfaces can carry overrides too.
  • Decimals, dates, durations and guids are strings, in the formats of the property attributes (Numbers), for example DefaultValue = "25". An enum default is the enum value.
  • Unrelated base types that disagree cancel out: the type gets what the declaration says, and the database logs a warning.

The index and the facet are shared. A property has one value index and one set of facet counts, whichever types have it. Indexed = BoolValue.True on NewsArticle gives the property its index, and that index holds the status of every IContent node, not only of news articles. A property is indexed when its declaration says so or any type asks. BoolValue.False only takes back a type's own request: to drop an index that the declaration has, override Indexed on the declaring type. Facets work the same way. A facet counts the values in the value index, so it needs an index: ask for both when the base type has neither. Everything else about a property, such as its other index settings and its rules, is one setting wherever the property is used.

Mistakes stop the build. A [PropertyOverride] that cannot apply fails the model build at startup, with a message naming the type and the property. It fails when the attribute:

  • names a property the type declares itself (set that property's own attribute instead);
  • names no member of the type (use nameof to avoid typos), or a member marked [Exclude];
  • names a system member: the id, NodeMeta, the created or changed date, or the display-name or address marker;
  • gives a DefaultValue that does not convert to the property's type, or gives a default to a property that has none (arrays, files, references and the like);
  • sits on a type without naming a property, or sits on a member and names another property;
  • names a property that another [PropertyOverride] on the same type also names. Combine them.

What can be overridden. In code, [PropertyOverride] takes the six attributes above. In the database you can override these:

What Attributes
Node type, inherited by derived types TextIndex, SemanticIndex, InstantTextIndexing, Icon, NameProperty
Node type, that type only Hidden, Title, Description, DefaultReadAccess, DefaultEditViewAccess
Property, per type DefaultValue, ExcludeFromTextIndex, IndexBoost (TextIndexBoost on the attributes), DisplayName
Property, asked for by any type Indexed, NotFacet
Property, on the declaring type only IndexType, IndexedByWords, TextIndexType, CultureSensitive, MinLength, MaxLength, RegularExpression, RegularExpressionMessage, LegalCharacters, Trim, CaseTransform, MinValue, MaxValue, FacetRangePowerBase, FacetRangeCount, Facet, TextIndexRelatedContent, TextIndexRelatedDisplayName, Display (§10)

Nothing else can be overridden: not ids or names, the property type, UniqueValues, LegalValues, CultureSpecific, enum metadata, or what a relation, reference or embedded property points at. [Node] also has a TextIndexBoost, which the engine does not use yet.

In the database: the datamodel editor. The admin UI's Models page writes a change into the source that defines the type when it can. Where it cannot, as for compiled classes or types added in OnDatamodelInit, it saves what you set as an override. Each field shows where its value comes from, and you can remove an override there. Activating saves the overrides and reopens the database.

A database keeps its overrides in two files, the same way it keeps its settings in relatude.db.json and relatude.db.overrides.json (§12):

File What it is
SHARED relatude.settings/datamodel.json, or relatude.settings/<ShortName>/datamodel.json Part of the application: commit it and deploy it with the code
THIS SERVER overrides/datamodel.overrides.json in the database's storage (relatude.data/overrides/ by default) What the editor changed on this installation: survives a redeploy, not in source control

When the database opens, THIS SERVER is merged over SHARED. A value in THIS SERVER replaces the SHARED value, and null removes it, so the attribute follows the source again on this installation. Activating writes only the THIS SERVER file, and only what differs from SHARED. Both files are keyed by ids, with names beside them for people to read:

// relatude.data/overrides/datamodel.overrides.json (THIS SERVER)
{
  "NodeTypes": {
    "3ad77060-788d-21b2-31c1-4305dcae3437": {
      "Name": "Shop.Models.Product",
      "TextIndex": null,                  // SHARED says false; here the source decides
      "Properties": {
        "8b1aa6c4-205e-740c-73ca-26d1e39111dc": { "Name": "Price", "Indexed": true, "DefaultValue": 0 }
      }
    }
  }
}

The SHARED file has the same shape, without the nulls. Move to shared, at the top right of the editor, moves the overrides you select from THIS SERVER into SHARED, as on the settings pages. Do it in the Development environment, where relatude.settings is the folder in your project, and commit the file. On a deployed server the next deployment would replace it. The SHARED file is found by the database's short name, so set the short name in relatude.db.json.

An override in the database that cannot apply is skipped with a warning in the log and the editor: its type or property is gone, its value does not parse, or the attribute cannot be overridden. It never stops the database from opening. From code, set Datamodel.Overrides before the store is created.

Before you change a value on a running installation:

  • A default is late bound. A node with no stored value reads the current default, so a new default reaches nodes that never had a value. Stored values are never touched. If the property is indexed, queries may not see the new default for those nodes until the indexes are rebuilt.
  • Rules apply to new writes. A tighter MaxLength or MinValue does not check stored values.
  • Index changes rebuild. Turning on Indexed or IndexedByWords, or asking for an index from a derived type, changes the model. The database then rebuilds its state and indexes from the log at the next open. When text indexing changes for a type, its nodes are queued for text indexing. Settings that only change how a type is shown (Icon, Title, Description, NameProperty, Display) rebuild nothing.