Properties

Properties define the data fields on an entity. Each property requires a [Property<T>] attribute that specifies the data type and a display label.

[Property<T>("Label")]

The generic type parameter T is a data type that controls validation, formatting, and database column type. The string argument is the human-readable label.

[Property<DataTypes.Text>("Name")]
public partial string Name { get; set; }

Properties must be partial — the source generator provides the implementation with change tracking and event hooks.

Optional Parameters

Parameter Type Description
Description string? User-facing description explaining the property's purpose
Technical string? Internal notes for developers (not shown to end users)
[Property<DataTypes.ProperNoun>("Full name",
    Technical = "Cached property from PrimaryContact.FullName so that we do a full-text search.")]
public partial string FullName { get; }

Common Data Types

Data Type CLR Type Example
DataTypes.Text string Names, short text (max 100)
DataTypes.IdText string IDs, SKUs — uppercase, barcode-safe (max 15)
DataTypes.MultilineText string Long descriptions, notes (unlimited)
DataTypes.ProperNoun string People/business names — auto title-cased (max 50)
DataTypes.Integer int Whole numbers
DataTypes.Decimal decimal Numbers with 2 decimal places
DataTypes.Currency decimal Money values (2 decimals)
DataTypes.Boolean bool True/false flags
DataTypes.DateOnly DateOnly? Dates without time
DataTypes.DateTime DateTime? Date and time
DataTypes.Enum TEnum Any C# enum

See Data Types for the complete list.

[Required]

Marks a property as non-nullable. The value must be provided before saving.

[Required]
[Property<DataTypes.IdText>("SKU")]
public partial string Sku { get; set; }

[MaxLength(n)]

Overrides the data type's default maximum length. Only applies to string properties.

[MaxLength(20)]
[Property<DataTypes.IdText>("SKU")]
public partial string Sku { get; set; }

If omitted, the data type's default max length is used (e.g., IdText defaults to 15, Text defaults to 100).

[DefaultValue(...)]

Sets the initial value when a new entity is created. Uses System.ComponentModel.DefaultValueAttribute.

using System.ComponentModel;

[Property<DataTypes.Text>("Color")]
[DefaultValue("#ffffff")]
public partial string Color { get; set; }

[Property<DataTypes.Enum>("Status")]
[DefaultValue(SalesOrderStatus.Open)]
public partial SalesOrderStatus Status { get; set; }

[DefaultValue(1)]
[Property<DataTypes.PositiveDecimal>("Factor")]
public partial decimal Factor { get; set; }

Read-Only Properties

Use ReadOnly for server-set properties:

[Property<DataTypes.Text>("Calculated status")]
[ApiAccess(PropertyAccess.ReadOnly)]
public partial string? CalculatedStatus { get; set; }

Clients can read the value. Writes return 403, including nested writes and generated reference GUID fields. Server code can still set it. Use PropertyAccess.Hide to block reads too. See API access for the full rules and metadata.

Use { get; } (no setter) for computed properties whose value is set by business logic:

[Property<DataTypes.ProperNoun>("Full name")]
public partial string FullName { get; }

[Virtual]
[Property<DataTypes.Currency>("Total Price")]
public partial decimal TotalPrice { get; }

Read-only properties are typically populated by Compute events in business logic.

[Virtual] on Properties

Marks a property as computed — it is not persisted to the database. The value is calculated at runtime by business logic.

[Virtual]
[Property<DataTypes.Currency>("Subtotal")]
public partial decimal Subtotal { get; }

[Virtual]
[Property<DataTypes.Currency>("Total")]
public partial decimal Total { get; }

Virtual properties:

  • Have no database column
  • Are recalculated when their dependencies change
  • Are typically read-only ({ get; })
  • Can also be read-write ({ get; set; }) for user-editable computed values
// Virtual but editable — user can override the computed value
[Virtual]
[Property<DataTypes.Decimal>("Quantity")]
public partial decimal QuantityUom { get; set; }

Custom Data Types on Properties

You can use domain-specific data types defined in your project (see Creating Data Types):

[Property<ProductUnitPrice>("Cost",
    Description = "The cost of the product")]
public partial decimal Cost { get; set; }

[Property<ProductQuantity>("Quantity (Each)")]
public partial decimal Quantity { get; set; }

Complete Example

using System.ComponentModel;

namespace Benevia.ERP.Model.Products;

[ApiEntity]
[NaturalKey(nameof(Sku))]
public partial class Product
{
    [Required]
    [MaxLength(20)]
    [Searchable(1, Dictionary = "simple")]
    [Property<DataTypes.IdText>("SKU",
        Description = "Your part number for this product")]
    public partial string Sku { get; set; }

    [Searchable(2)]
    [Property<DataTypes.MultilineText>("Description",
        Description = "The description of the product that is used in sales documents.")]
    public partial string SalesDescription { get; set; }

    [Property<DataTypes.Decimal>("Unit weight")]
    public partial decimal UnitWeight { get; set; }

    [Property<ProductUnitPrice>("Cost",
        Description = "The cost of the product. This is often used to calculate the price")]
    public partial decimal Cost { get; set; }

    [Property<DataTypes.Website>("Website")]
    public partial string Website { get; set; }

    [Property<DataTypes.Text>("Color")]
    [DefaultValue("#ffffff")]
    public partial string Color { get; set; }

    // Virtual — computed, not in database
    [Virtual]
    [Property<DataTypes.Boolean>("Require all units to be sold in integer quantities")]
    public partial bool RequireIntegerQuantitiesForAllUOMs { get; set; }
}