Tyto.Materializer.EntityFrameworkCore
0.1.0-alpha.7
dotnet add package Tyto.Materializer.EntityFrameworkCore --version 0.1.0-alpha.7
NuGet\Install-Package Tyto.Materializer.EntityFrameworkCore -Version 0.1.0-alpha.7
<PackageReference Include="Tyto.Materializer.EntityFrameworkCore" Version="0.1.0-alpha.7" />
<PackageVersion Include="Tyto.Materializer.EntityFrameworkCore" Version="0.1.0-alpha.7" />
<PackageReference Include="Tyto.Materializer.EntityFrameworkCore" />
paket add Tyto.Materializer.EntityFrameworkCore --version 0.1.0-alpha.7
#r "nuget: Tyto.Materializer.EntityFrameworkCore, 0.1.0-alpha.7"
#:package Tyto.Materializer.EntityFrameworkCore@0.1.0-alpha.7
#addin nuget:?package=Tyto.Materializer.EntityFrameworkCore&version=0.1.0-alpha.7&prerelease
#tool nuget:?package=Tyto.Materializer.EntityFrameworkCore&version=0.1.0-alpha.7&prerelease
Tyto.Materializer.EntityFrameworkCore
This package provides a robust IViewStore<TView, TKey> implementation using Entity Framework Core. It allows you to persist your read models as structured tables in a relational database (SQL Server, PostgreSQL, SQLite, etc.) while seamlessly integrating with EF Core's change tracking and native optimistic concurrency mechanisms.
🚀 Features
- Relational Persistence: Store your materialized views in SQL databases, enabling complex querying, filtering, and reporting capabilities outside of the event loop.
- Optimistic Concurrency: Leverages EF Core's native concurrency tokens (
RowVersion/Timestamp) to prevent data overwrite during concurrent updates. - Change Tracking: Efficiently updates only modified fields using EF Core's change tracker.
- Automatic Conventions: Includes helper methods to automatically configure Tyto types (like
ProjectableVersion) within your DbContext.
📦 Installation
dotnet add package Tyto.Materializer.EntityFrameworkCore
⚡ Usage
Using this provider involves two steps: configuring your DbContext correctly and enabling the provider in your application startup.
1. Configure Your DbContext
Your EF Core DbContext must be configured to map your view entity. This includes defining a Primary Key and a Concurrency Token.
using Microsoft.EntityFrameworkCore;
using Tyto.Materializer;
using Tyto.Materializer.EntityFrameworkCore; // Required for conventions
public class MyAppContext : DbContext
{
public DbSet<UserBalanceView> UserBalances { get; set; }
public MyAppContext(DbContextOptions<MyAppContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// 1. Apply Tyto conventions
// This automatically configures value converters for types like ProjectableVersion.
modelBuilder.ApplyTytoMaterializerConventions();
var view = modelBuilder.Entity<UserBalanceView>();
// 2. CRITICAL: Define the primary key.
// For single-column keys:
view.HasKey(v => v.UserId);
// For composite keys (e.g., IdentifiedAs<RecipientKey>):
// The properties in the composite key must match the EF Core primary key components by name.
// modelBuilder.Entity<RecipientView>().HasKey(v => new { v.ApplicationId, v.RecipientId });
// 3. CRITICAL: Configure the 'Version' property as a concurrency token.
// This tells EF Core to include this column in the WHERE clause during updates
// (e.g., UPDATE ... WHERE Id = x AND Version = y).
view.Property(v => v.Version).IsConcurrencyToken();
}
}
Composite Keys Support
If your read model is partitioned across multiple tenants or identifiers (e.g. (ApplicationId, RecipientId)), define a record struct as the key and declare composite keys in EF Core:
public readonly record struct RecipientKey(long ApplicationId, string RecipientId);
public class RecipientView : IProjectableView
{
public long ApplicationId { get; set; }
public string RecipientId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public ProjectableVersion Version { get; set; }
}
// In DbContext:
modelBuilder.Entity<RecipientView>(b =>
{
b.HasKey(v => new { v.ApplicationId, v.RecipientId });
b.Property(v => v.Version).IsConcurrencyToken();
});
// In Program.cs:
endpoint.Materialize(views => views
.ForView<RecipientView>()
.IdentifiedAs<RecipientKey>(id => id.From<RecipientCreated>(e => new RecipientKey(e.ApplicationId, e.RecipientId)))
.Store.UseEntityFrameworkCore().InContext<MyAppContext>());
The EF Core store validates all key components against the model on startup and orders values automatically for FindAsync.
2. Register in Program.cs
Configure Tyto to use the EF Core provider for your specific view.
var builder = WebApplication.CreateBuilder(args);
// Standard EF Core Registration
builder.Services.AddDbContext<MyAppContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
// Tyto Configuration: the view is declared on the endpoint that receives its events
builder.AddTyto(tyto => tyto.Endpoints(endpoints => endpoints.Add("users", endpoint =>
{
endpoint.UseTransactions<MyAppContext>(tx => tx.UseInbox());
endpoint.Materialize(materializer =>
{
materializer.ForView<UserBalanceView>()
.IdentifiedAs<Guid>(c => c.From<UserCreated>(e => e.UserId))
// Configure Storage
// The view and key come from the chain; only the context is written.
.Store.UseEntityFrameworkCore().InContext<MyAppContext>();
// Note: This replaces any previously configured store for this view.
});
})));
On an endpoint with UseTransactions<MyAppContext>, the view is written into the transaction's own
context and commits with the inbox row, or not at all. A view in another DbContext than the
transaction's is refused rather than saved separately.
🔧 How It Works Internally
Runtime Validation
When the application starts, the EntityFrameworkCoreViewStore performs a safety check. It inspects your DbContext metadata to ensure:
- The
TViewentity is registered in the context. - A Primary Key is defined and matches the key type expected by Tyto.
- The
Versionproperty is explicitly configured as a Concurrency Token.
If any of these checks fail, it throws an InvalidOperationException immediately, preventing silent data consistency bugs.
Concurrency Handling
- Read: Uses
DbSet<T>.FindAsyncto load the entity. - Write:
- The
SaveAsyncmethod attaches the entity and sets the state toModified. - It increments the
Versionproperty in memory. - It calls
SaveChangesAsync. - EF Core generates an SQL statement like:
UPDATE Views SET Bal = 100, Version = 2 WHERE Id = 1 AND Version = 1. - If no rows are affected (meaning the version in DB was not 1), EF Core throws
DbUpdateConcurrencyException.
- The
- Retry: This provider catches that exception and re-throws it as a
Tyto...ConcurrencyException, which triggers the core library's automatic retry policy (Reload → Re-project → Save).
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.EntityFrameworkCore (>= 10.0.12)
- Tyto.Materializer (>= 0.1.0-alpha.7)
- Tyto.Transactions.EntityFrameworkCore (>= 0.1.0-alpha.7)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-alpha.7 | 23 | 9/25/2026 |
| 0.1.0-alpha.6 | 31 | 9/24/2026 |
| 0.1.0-alpha.5 | 42 | 9/21/2026 |
| 0.1.0-alpha.4 | 53 | 9/20/2026 |
| 0.1.0-alpha.3 | 48 | 9/20/2026 |
| 0.1.0-alpha.2 | 53 | 9/20/2026 |
| 0.1.0-alpha.1 | 42 | 9/20/2026 |
| 0.0.1-alpha.106 | 52 | 9/15/2026 |
| 0.0.1-alpha.105 | 49 | 9/14/2026 |
| 0.0.1-alpha.104 | 56 | 9/10/2026 |
| 0.0.1-alpha.103 | 60 | 9/4/2026 |
| 0.0.1-alpha.102 | 56 | 9/1/2026 |
| 0.0.1-alpha.101 | 56 | 9/1/2026 |
| 0.0.1-alpha.100 | 66 | 8/24/2026 |
| 0.0.1-alpha.99 | 64 | 8/20/2026 |
| 0.0.1-alpha.98 | 61 | 8/18/2026 |
| 0.0.1-alpha.97 | 69 | 8/18/2026 |
| 0.0.1-alpha.96 | 91 | 8/18/2026 |
| 0.0.1-alpha.95 | 75 | 8/17/2026 |
| 0.0.1-alpha.94 | 74 | 7/21/2026 |