Attributes Overview
Tayra uses attributes to declaratively mark personal data on your model classes. At runtime, the metadata cache scans your types via reflection and drives the encryption engine automatically.
Fluent API Alternative
All attributes have fluent API equivalents that can be used instead of (or alongside) attributes. The fluent API is useful when you cannot modify the model classes directly, or when you prefer centralized configuration.
| Attribute | Fluent Equivalent | Builder Options |
|---|---|---|
[DataSubjectId] | e.DataSubjectId(x => x.Prop) | .WithGroup(), .WithPrefix() |
[PersonalData] | e.PersonalData(x => x.Prop) | .WithGroup(), .WithMaskValue(), .WithMaskAfter(), .WithMaskBefore(), .WithMaskEmailDomain(), .WithMask() |
[DeepPersonalData] | e.DeepPersonalData(x => x.Prop) | -- |
[SerializedPersonalData] | e.SerializedPersonalData(x => x.Prop) | .WithGroup(), .StoredIn() |
[BlindIndex] | e.BlindIndex(x => x.Prop) | .WithLowercase(), .WithTrim(), .WithTransform(), .StoredIn(), .WithScope(), .WithBitLength() |
[CompoundBlindIndex] | e.CompoundBlindIndex("name") | .Field(), .StoredIn(), .WithScope(), .WithBitLength() |
See Fluent API for complete documentation.
Attribute Summary
| Attribute | Target | Purpose |
|---|---|---|
[PersonalData] | string properties | Marks a field for AES-256-GCM encryption |
[DataSubjectId] | Guid or string properties | Identifies the data owner; derives the encryption key ID |
[DeepPersonalData] | Class-type properties | Recurses into nested objects to encrypt their [PersonalData] fields |
[SerializedPersonalData] | Non-string properties (int, DateTime, etc.) | Serializes to binary, encrypts, and stores in a companion byte[] field |
[BlindIndex] | string properties with [PersonalData] | Computes an HMAC hash for equality queries on encrypted fields |
[CompoundBlindIndex] | Classes | Combines multiple fields into a single HMAC hash for multi-field lookups |
Example
Here is a typical annotated model:
public class Customer
{
[DataSubjectId]
public Guid Id { get; set; }
[PersonalData]
public string Name { get; set; } = "";
[PersonalData(MaskValue = "redacted@example.com")]
public string Email { get; set; } = "";
/// <summary>
/// Not annotated — stored and retrieved as plaintext.
/// </summary>
public string AccountType { get; set; } = "";
}In this example:
Idis the data subject identifier. Tayra derives the encryption key ID from this property's value.NameandEmailare personal data fields that will be encrypted in-place.Emailspecifies a custom replacement value for after crypto-shredding.AccountTypehas no annotation and is never encrypted.
How Metadata Discovery Works
When Tayra first encounters a type (via EncryptAsync<T> or DecryptAsync<T>), the PersonalDataMetadataCache scans the type using reflection:
- Property scan -- All public instance properties are inspected for attributes.
- Subject resolution -- Properties with
[DataSubjectId]are recorded, including their group and prefix. - Field classification -- Properties with
[PersonalData],[DeepPersonalData], or[SerializedPersonalData]are classified by kind:Text,Deep,Serialized,TextCollection, orDeepCollection. - Caching -- The result is stored in a
ConcurrentDictionarykeyed byType. Subsequent calls for the same type return the cached metadata with no reflection overhead.
The metadata cache is thread-safe and is registered as a singleton. The reflection cost is paid only once per type for the lifetime of the application.
Roslyn Analyzers
The Tayra.Core package includes Roslyn analyzers that validate attribute usage at compile time:
| Rule | Severity | Description |
|---|---|---|
| TAYRA001 | Warning | Entity with [PersonalData] must have a [DataSubjectId] property |
| TAYRA002 | Info | [DataSubjectId] without [PersonalData] fields is unused |
| TAYRA003 | Error | [DeepPersonalData] must be on a class or record type |
| TAYRA004 | Warning | Multiple [DataSubjectId] properties require Group |
| TAYRA005 | Warning | [BlindIndex] without companion property (e.g. EmailIndex) |
| TAYRA006 | Warning | [BlindIndex] on a non-string property |
These analyzers catch common mistakes before your code runs. No separate package installation is needed.
See Also
[PersonalData]-- Encryption of string fields[DataSubjectId]-- Key derivation and grouping[DeepPersonalData]-- Nested object encryption[SerializedPersonalData]-- Non-string type encryption[BlindIndex]-- HMAC blind indexes for searchable encryption- Getting Started -- End-to-end tutorial
