Skip to content

Attribute Reference

All Data Store attributes live in the Deepstaging.DataStore namespace. See also the Generated Code and Effects Composition pages for what these attributes produce.

[DataStore]

Marks a static partial class as the data store container. Only one [DataStore] per assembly is allowed.

[DataStore]
public static partial class AppStore;
Property Type Default Description
Instrumented bool true Enables OpenTelemetry tracing spans on generated effect methods via .WithActivity()
Requirement Diagnostic
Must be partial DSDS01 (Error)
Should be static DSDS02 (Warning)
One per assembly DSDS05 (Error)

[StoredEntity]

Marks a class or record as a persistent entity. The entity must have a property with a [TypedId] type, which becomes the primary key.

[StoredEntity]
public partial record Article(ArticleId Id, string Title, string Body);
Property Type Default Description
PluralName string? Inferred (e.g., Articles) Override the nested class name used for effect methods and store grouping
Requirement Diagnostic
Must be partial DSDS03 (Error)
Must have a [TypedId] property DSDS04 (Error)
Requires [DataStore] in assembly DSDS08 (Error)

TypedId requirement

Every [StoredEntity] needs a property whose type is marked with [TypedId]. See the Typed IDs module for details.

[ForeignKey<TEntity>]

Declares a relationship between stored entities. The generic parameter must reference another [StoredEntity].

[StoredEntity]
public partial record Comment(
    CommentId Id,
    [ForeignKey<Article>] ArticleId ArticleId,
    string Body);
Requirement Diagnostic
Target must be a [StoredEntity] DSDS06 (Error)

[CompositeKey]

Declares a composite primary key instead of a single [TypedId] property. The named properties must exist on the entity.

[StoredEntity]
[CompositeKey(nameof(UserId), nameof(ArticleId))]
public partial record Bookmark(UserId UserId, ArticleId ArticleId, DateTimeOffset CreatedAt);

For composite key entities, GetByIdAsync and DeleteAsync accept all key properties as separate parameters. See Generated Code for the generated interface shape.

Requirement Diagnostic
Named properties must exist DSDS07 (Error)

[Lookup]

Marks a property for indexed lookup. The generator emits a GetBy{Property} method returning StoreQuery<T> on the store interface and all implementations. In the Postgres layer, HasIndex is also configured on the property in OnModelCreating.

[StoredEntity]
public partial record CatalogItem(
    CatalogItemId Id,
    string Name,
    [Lookup] CatalogBrandId BrandId);

This generates:

// On ICatalogItemStore — returns a composable query with the lookup predicate pre-applied
StoreQuery<CatalogItem> GetByCatalogBrandId(CatalogBrandId catalogBrandId);

// Usage — further filter, sort, paginate as needed
var page = await store.GetByCatalogBrandId(brandId)
    .OrderBy(i => i.Name)
    .ToPageAsync(1, 20);

On the effects layer, five terminal methods are generated per lookup:

// All accept optional additional filter/orderBy
CatalogStore.CatalogItems.QueryPageByBrandId<RT>(brandId, page, pageSize, filter?, orderBy?)
CatalogStore.CatalogItems.QueryListByBrandId<RT>(brandId, filter?, orderBy?)
CatalogStore.CatalogItems.QueryCursorByBrandId<RT>(brandId, cursor, limit, filter?)
CatalogStore.CatalogItems.QueryFirstByBrandId<RT>(brandId, filter?)
CatalogStore.CatalogItems.QueryCountByBrandId<RT>(brandId, filter?)

And in the Postgres DbContext:

modelBuilder.Entity<CatalogItem>().HasIndex(e => e.BrandId);
Target Applies To
AttributeTargets.Property Any property on a [StoredEntity]

Multiple lookups

You can apply [Lookup] to multiple properties on the same entity. Each generates its own GetBy{Prop} method and database index.

Rules and Constraints

  • Exactly one [DataStore] per assembly — it acts as the root container for all entities.
  • Every [StoredEntity] must be partial so the generator can extend it.
  • Every [StoredEntity] needs either a [TypedId] property or a [CompositeKey] attribute.
  • [ForeignKey<T>] targets must themselves be [StoredEntity] types.
  • The [DataStore] class should be static (warning if not) and must be partial (error if not).