Entity Basics
An entity is a C# class that represents a database table and an OData API endpoint. Source generators automatically create the API controller, dependency injection registrations, and database schema from the entity definition.
Minimal Entity
Every entity needs two things: the [ApiEntity] attribute and the partial keyword.
using Benevia.Core.Annotations;
namespace Benevia.ERP.Model.Products;
[ApiEntity]
public partial class ShippingMethod : EntityBase
{
[Required]
[Property<DataTypes.Text>("Name")]
public partial string Name { get; set; }
}
This generates:
- An OData controller at
/odata/ShippingMethod - A database table with columns
Guid(PK),Name, andRowVersion - DI registrations for the entity's services
Register your model project
If you haven't registered your Model
var builder = WebApplication.CreateBuilder(args);
builder
.AddCoreOpenTelemetry()
.AddErpBlobs()
.AddCorsFromConfig();
builder.Services
.AddCorePostgres()
.AddCoreDataGeneration()
.AddCoreLargeData()
.AddCoreContacts()
.AddYourProjectNameHere();
EntityBase
All persisted entities inherit from EntityBase, which provides:
| Member | Type | Description |
|---|---|---|
Guid |
Guid |
Primary key, auto-generated on construction |
RowVersion |
int? |
Concurrency tracking, incremented on each save |
Title |
string |
Virtual computed property for display (override in business logic) |
You do not define Guid or RowVersion in your entity — they come from EntityBase.
[ApiEntity]
Marks a class for source generation. Without this attribute, the class is ignored by the generators.
[ApiEntity]
public partial class Product : EntityBase
{
// ...
}
[NaturalKey]
Defines the business key for an entity — the human-readable identifier separate from the technical Guid primary key. Natural keys enforce uniqueness.
[ApiEntity]
[NaturalKey(nameof(Id))]
public partial class Customer : EntityBase
{
[Required]
[Property<DataTypes.IdText>("Id")]
public partial string Id { get; set; }
}
Partial Classes
The partial keyword is required on entity classes and on all properties that have [Property<T>] or [ReferenceProperty] attributes. Source generators add the implementation code in a separate partial file.
// You write this:
[Property<DataTypes.Text>("Name")]
public partial string Name { get; set; }
// Source generator adds the backing field, event hooks, and change tracking
Enums
Enums are standard C# enums. No special attributes are needed — use Property<DataTypes.Enum> on the property:
public enum SalesOrderStatus
{
Draft = 0,
Open = 1,
PartiallyFulfilled = 2,
Fulfilled = 3,
Late = 4,
Closed = 5
}
[ApiEntity]
public partial class SalesOrder : EntityBase
{
[Property<DataTypes.Enum>("Status")]
[DefaultValue(SalesOrderStatus.Open)]
public partial SalesOrderStatus Status { get; set; }
}
Namespaces
Place entities in the namespace matching their module:
Benevia.ERP.Model.Sales — Sales entities
Benevia.ERP.Model.Products — Product entities
Benevia.ERP.Model.Hatchery — Hatchery entities
Benevia.ERP.Model.Manufacturing — Manufacturing entities
Complete Example
using System.ComponentModel;
using Benevia.Core.Annotations;
namespace Benevia.ERP.Model.Products;
[ApiEntity]
[NaturalKey(nameof(Sku))]
public partial class Product : EntityBase
{
[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<DataTypes.Text>("Color")]
[DefaultValue("#ffffff")]
public partial string Color { get; set; }
}
What Gets Generated
When you build, source generators produce:
- API Controller — An
EntityController<Product>with standard OData CRUD operations - DI Registration — Services wired into the dependency injection container
- Database Schema — Table definition with columns, constraints, and indexes (auto-migrated on startup)
- Property Logic — Backing fields with change tracking and event hooks for each
[Property<T>]
You never write these manually. Just define the entity and build.