--- title: "Lifecycle Features" description: "Soft delete, global filters, encrypted properties, auditable entities, and query cache behavior." canonical: "https://roomsharp.dev/docs/v0.5.4/lifecycle-features" source: "src/content/v0.5.4/lifecycle-features.mdx" --- # Lifecycle Features RoomSharp includes generated and runtime lifecycle features that keep common data rules close to the entity model without adding reflection-heavy runtime behavior. ## Soft Delete Use `[SoftDelete]` when rows should be hidden from normal queries but still retained in the table. ```csharp [SoftDelete(SoftDeleteType.DateTime, Column = "deleted_at")] [Entity(TableName = "customers")] public sealed class Customer { [PrimaryKey(AutoGenerate = true)] public long Id { get; set; } [ColumnInfo(Name = "deleted_at")] public DateTime? DeletedAt { get; set; } } ``` Generated DAO behavior: - `Delete` updates the soft-delete marker instead of removing the row. - DAOs with `[Delete]` methods for the soft-delete entity also get `Restore` / `RestoreAsync` and `HardDelete` / `HardDeleteAsync`. - `[Query]` methods targeting soft-delete entities automatically filter deleted rows. | Soft delete type | Active-row filter | |------------------|-------------------| | `DateTime` | `deleted_at IS NULL` | | `Bool` | `is_deleted = 0` | ## Global Filters Global filters apply SQL predicates automatically to matching entity queries. They are useful for tenant isolation, security boundaries, and cross-cutting row visibility. ```csharp db.GlobalFilters.Add( "tenant_id = @tenantId", new Dictionary> { ["tenantId"] = () => tenantContext.CurrentTenantId }); var orders = await db.OrderDao.GetOpenOrdersAsync(); ``` QueryExtensions session queries inherit database global filters: ```csharp await using var session = await db.OpenSessionAsync(); var orders = await session.From() .Where(o => o.Status == "open") .GetAsync(); ``` Temporarily disable filters in a scoped flow: ```csharp using (db.GlobalFilters.Disable()) { var allOrders = await db.From().GetAsync(); } ``` Disable scopes are async-flow-local, so disabling a filter in one request does not disable it globally for other requests. ## Encrypted Properties Mark sensitive string properties with `[Encrypted]`. ```csharp public sealed class Customer { [Encrypted] public string? NationalId { get; set; } [Encrypted(Algorithm = "aes-gcm")] public string? PrivateNote { get; set; } } ``` Generated insert/update paths encrypt string values before storage without mutating the caller's entity instance. Generated SELECT paths automatically decrypt single-entity and collection results. Register an encryption provider in the database builder or through dependency injection: ```csharp RoomDatabase.Builder() .UseSqlite("app.db") .SetEncryptionProvider(new MyEncryptionProvider()) .Build(); ``` ## Auditable Entities Use `[Auditable]` to populate timestamps automatically. ```csharp [Auditable(TrackCreatedBy = true)] [Entity(TableName = "invoices")] public sealed class Invoice { [ColumnInfo(Name = "created_at")] public DateTime CreatedAt { get; set; } [ColumnInfo(Name = "updated_at")] public DateTime UpdatedAt { get; set; } [ColumnInfo(Name = "created_by")] public string? CreatedBy { get; set; } } ``` Generated behavior: - Insert sets `CreatedAt` and `UpdatedAt`. - Update refreshes `UpdatedAt`. - `TrackCreatedBy = true` uses `IAuditContext.GetCurrentUser()` to populate the creator field. Timestamp assignment currently uses UTC time in generated DAO code. `IAuditContext` is used for the current user when `TrackCreatedBy` is enabled. ## Query Cache RoomSharp includes query-cache helpers for read-heavy query paths. ```csharp var activeUsers = await db.From() .Where(u => u.IsActive) .CachedAsync(TimeSpan.FromMinutes(5)); db.From().InvalidateTableCache(); ``` Use cache invalidation when external writes or raw SQL change a table that cached queries depend on. Every `RoomDatabase` also exposes `db.Cache` for direct key-based caching when you need explicit cache keys outside QueryExtensions. For domain DAOs, prefer generated query methods first. Use QueryExtensions caching for ad-hoc or reporting queries where a short TTL is acceptable. ## Production Notes - Keep lifecycle columns indexed when they are used in high-cardinality filters. - For multi-tenant systems, combine global filters with database-level constraints where possible. - For encrypted columns, avoid filtering by encrypted values unless your encryption provider explicitly supports deterministic lookup semantics. - For audit fields, keep application clocks synchronized in distributed deployments.