Data Subject Access
Tayra provides built-in support for GDPR Article 15 (Right of Access) and Article 20 (Data Portability) through ITayraCompliance. This service collects, decrypts, and exports all personal data held for a given data subject.
GDPR Requirements
Article 15 — Right of Access requires that you provide a data subject with:
- Confirmation of whether their data is being processed
- A copy of all personal data held about them
- Information about the processing purposes, categories, and recipients
Article 20 — Data Portability requires that you provide personal data in:
- A structured, commonly used, machine-readable format
- A format that can be transmitted to another controller
Setup
Package Requirement
This feature requires the Tayra.Compliance package and a Compliance edition license. Data protection itself is handled by Tayra.Core — this package provides the reporting tooling.
dotnet add package Tayra.ComplianceRegister the compliance services with entity providers for each type that contains PII:
// Register the data subject access service and entity providers.
var accessServices = new ServiceCollection();
accessServices.AddTayra(opts => opts.LicenseKey = licenseKey)
.AddCompliance(complianceOpts =>
{
complianceOpts.AddEntityProvider<Customer, CustomerEntityProvider>();
complianceOpts.AddEntityProvider<Order, OrderEntityProvider>();
});.AddCompliance() registers all compliance services including data subject access. Each AddEntityProvider call tells the service where to find entities of a given type for a data subject.
Entity Providers
Implement IDataSubjectEntityProvider<T> for each entity type that contains personal data. The provider is responsible for loading all instances of that entity for a given subject ID.
/// <summary>
/// Provides Customer entities for a given data subject.
/// Implement IDataSubjectEntityProvider<T> for each entity type that contains PII.
/// </summary>
public class CustomerEntityProvider : IDataSubjectEntityProvider<Customer>
{
// In a real app, this would query your database
public static readonly Dictionary<string, Customer> Customers = new();
public Task<IReadOnlyList<Customer>> GetTypedEntitiesForSubjectAsync(
string subjectId, CancellationToken ct = default)
{
if (Customers.TryGetValue(subjectId, out var customer))
{
return Task.FromResult<IReadOnlyList<Customer>>([customer]);
}
return Task.FromResult<IReadOnlyList<Customer>>([]);
}
}
public class OrderEntityProvider : IDataSubjectEntityProvider<Order>
{
public static readonly Dictionary<string, Order> Orders = new();
public Task<IReadOnlyList<Order>> GetTypedEntitiesForSubjectAsync(
string subjectId, CancellationToken ct = default)
{
if (Orders.TryGetValue(subjectId, out var order))
{
return Task.FromResult<IReadOnlyList<Order>>([order]);
}
return Task.FromResult<IReadOnlyList<Order>>([]);
}
}In a real application, your entity provider would query your database:
public class CustomerEntityProvider : IDataSubjectEntityProvider<Customer>
{
private readonly ICustomerRepository _repo;
public CustomerEntityProvider(ICustomerRepository repo) => _repo = repo;
public async Task<IReadOnlyList<Customer>> GetTypedEntitiesForSubjectAsync(
string subjectId, CancellationToken ct = default)
{
var customer = await _repo.FindBySubjectIdAsync(subjectId, ct);
return customer is not null ? [customer] : [];
}
}Structured Export (Art. 15)
Use ExportAsync to generate a structured report of all personal data for a subject:
// Export all personal data for a data subject (GDPR Art. 15 — Right of Access)
var report = await compliance.ExportAccessAsync("subject-access-001");
Console.WriteLine($"Subject: {report.SubjectId}");
Console.WriteLine($"Generated: {report.GeneratedAt}");
foreach (var entityReport in report.Entities)
{
Console.WriteLine($" Entity: {entityReport.EntityType}");
foreach (var instance in entityReport.Instances)
{
foreach (var field in instance.Fields)
{
Console.WriteLine($" {field.FieldName}: {field.Value} (redacted: {field.IsRedacted})");
}
}
}The DataSubjectAccessReport contains:
| Property | Type | Description |
|---|---|---|
SubjectId | string | The data subject identifier |
GeneratedAt | DateTimeOffset | When the report was generated |
Entities | IReadOnlyList<EntityDataReport> | Reports grouped by entity type |
Each EntityDataReport contains:
EntityType— The name of the entity class (e.g.,"Customer")Instances— A list ofEntityInstanceReport, each containing the PII fields and their decrypted values
Each FieldDataReport contains:
FieldName— The property nameValue— The decrypted value (or replacement if the key was shredded)IsRedacted— Whether the value is a replacement due to crypto-shredding
Portable JSON Export (Art. 20)
Use ExportPortableAsync to generate a machine-readable JSON document:
// Export personal data as machine-readable JSON (GDPR Art. 20 — Data Portability)
var portableJson = await compliance.ExportPortableAsync("subject-access-001");
Console.WriteLine("Portable JSON export:");
Console.WriteLine(portableJson);The JSON output uses camelCase property names and is formatted for readability. The structure matches the DataSubjectAccessReport record:
{
"subjectId": "subject-access-001",
"generatedAt": "2025-01-15T10:30:00+00:00",
"entities": [
{
"entityType": "Customer",
"instances": [
{
"fields": [
{ "fieldName": "FullName", "value": "Diana Prince", "isRedacted": false },
{ "fieldName": "Email", "value": "diana@example.com", "isRedacted": false }
]
}
]
}
]
}Audit Events
Both export operations emit audit events:
DataSubjectAccessExported— Emitted byExportAsync, includes the number of entities and types exportedDataSubjectPortableExported— Emitted byExportPortableAsync
These events record that a data access request was fulfilled, supporting your GDPR Art. 30 records of processing.
Handling Shredded Data
If a data subject's key has been shredded (via crypto-shredding), the export will still work. Fields whose keys are missing will show replacement values, and IsRedacted will be true. This allows you to demonstrate that data was held but has been erased.
INFO
It is recommended to export data before processing an erasure request. This gives you a record of what was erased and allows you to confirm the erasure was complete.
See Also
- Crypto-Shredding — Erase data by deleting encryption keys
- Partial Redaction — Show masked values after shredding
- Breach Notification — Assess impact when data may be compromised
