Nethereum.Mud.Repositories.EntityFramework 5.8.0

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Nethereum.Mud.Repositories.EntityFramework --version 5.8.0
                    
NuGet\Install-Package Nethereum.Mud.Repositories.EntityFramework -Version 5.8.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Nethereum.Mud.Repositories.EntityFramework" Version="5.8.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nethereum.Mud.Repositories.EntityFramework" Version="5.8.0" />
                    
Directory.Packages.props
<PackageReference Include="Nethereum.Mud.Repositories.EntityFramework" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nethereum.Mud.Repositories.EntityFramework --version 5.8.0
                    
#r "nuget: Nethereum.Mud.Repositories.EntityFramework, 5.8.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nethereum.Mud.Repositories.EntityFramework@5.8.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nethereum.Mud.Repositories.EntityFramework&version=5.8.0
                    
Install as a Cake Addin
#tool nuget:?package=Nethereum.Mud.Repositories.EntityFramework&version=5.8.0
                    
Install as a Cake Tool

Nethereum.Mud.Repositories.EntityFramework

Nethereum.Mud.Repositories.EntityFramework provides Entity Framework Core abstractions for persisting MUD (Onchain Engine) table data. It enables you to sync on-chain MUD state to a relational database for querying, caching, and offline access.

Features

  • Entity Framework Core Integration - Abstract base repository for any EF Core provider
  • StoredRecord Persistence - Store raw MUD table records in relational databases
  • Block Progress Tracking - Resume synchronization from last processed block
  • Batch Processing - Efficient paging for large datasets
  • SQL Predicate Builder - Convert TablePredicates to SQL queries
  • Change Tracker Optimization - AsNoTracking for memory-efficient reads
  • Table Record Mapping - Convert StoredRecords to strongly-typed TableRecords

Installation

dotnet add package Nethereum.Mud.Repositories.EntityFramework

Dependencies

  • Microsoft.EntityFrameworkCore 8.0+
  • Microsoft.EntityFrameworkCore.Relational 8.0+
  • Nethereum.Mud
  • Nethereum.Mud.Contracts

Key Concepts

StoredRecord Entity

The StoredRecord entity represents a persisted MUD table record:

public class StoredRecord : EncodedValues
{
    public long RowId { get; set; }              // Auto-incrementing primary key
    public string Address { get; set; }          // World contract address
    public string TableId { get; set; }          // MUD table resource ID
    public string Key { get; set; }              // Combined key (key0 + key1 + ...)
    public string Key0 { get; set; }             // Individual key components
    public string Key1 { get; set; }
    public string Key2 { get; set; }
    public string Key3 { get; set; }
    public byte[] StaticData { get; set; }       // Static field data
    public byte[] EncodedLengths { get; set; }   // Dynamic field lengths
    public byte[] DynamicData { get; set; }      // Dynamic field data
    public bool IsDeleted { get; set; }          // Soft delete flag
    public BigInteger? BlockNumber { get; set; } // Block where change occurred
    public int? LogIndex { get; set; }           // Log index within block
}

IMudStoreRecordsDbSets Interface

Your DbContext must implement this interface:

public interface IMudStoreRecordsDbSets
{
    public DbSet<StoredRecord> StoredRecords { get; set; }
    public DbSet<BlockProgress> BlockProgress { get; set; }
}

MudEFTableRepository<TDbContext>

Abstract base class providing:

  • CRUD operations for StoredRecords
  • Batch processing with paging
  • AsNoTracking optimization for reads
  • Conversion to strongly-typed TableRecords
  • Block progress tracking

BlockProgressRepository

Tracks synchronization progress:

public interface IBlockProgressRepository
{
    Task<BigInteger?> GetLastBlockNumberProcessedAsync();
    Task UpsertProgressAsync(BigInteger blockNumber);
}

Usage Examples

Example 1: Create Custom DbContext

Extend your EF Core DbContext to implement IMudStoreRecordsDbSets:

using Microsoft.EntityFrameworkCore;
using Nethereum.Mud.Repositories.EntityFramework;
using Nethereum.BlockchainProcessing.BlockStorage.Entities;

public class MyMudDbContext : DbContext, IMudStoreRecordsDbSets
{
    public DbSet<StoredRecord> StoredRecords { get; set; }
    public DbSet<BlockProgress> BlockProgress { get; set; }

    public MyMudDbContext(DbContextOptions<MyMudDbContext> options)
        : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // Configure StoredRecord primary key
        modelBuilder.Entity<StoredRecord>()
            .HasKey(r => new { r.Address, r.TableId, r.Key });

        // Add indexes for common queries
        modelBuilder.Entity<StoredRecord>()
            .HasIndex(r => new { r.Address, r.TableId })
            .HasDatabaseName("IX_Address_TableId");

        modelBuilder.Entity<StoredRecord>()
            .HasIndex(r => r.RowId)
            .HasDatabaseName("IX_RowId");

        // Configure BlockProgress primary key
        modelBuilder.Entity<BlockProgress>()
            .HasKey(b => b.RowIndex);
    }
}

Example 2: Create Custom Repository

Extend MudEFTableRepository with your DbContext:

using Nethereum.Mud.Repositories.EntityFramework;
using Nethereum.Mud.TableRepository;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;

public class MyMudTableRepository : MudEFTableRepository<MyMudDbContext>
{
    public MyMudTableRepository(MyMudDbContext context) : base(context)
    {
    }

    // Implement abstract methods for SQL predicate handling
    public override Task<List<StoredRecord>> GetRecordsAsync(TablePredicate predicate)
    {
        var builder = new EFSqlHexPredicateBuilder();
        var sqlPredicate = builder.BuildSql(predicate);

        string sqlQuery = $"SELECT * FROM StoredRecords WHERE {sqlPredicate.Sql}";

        return Context.StoredRecords
            .FromSqlRaw(sqlQuery, sqlPredicate.GetParameterValues())
            .ToListAsync();
    }

    public override async Task<IEnumerable<TTableRecord>> GetTableRecordsAsync<TTableRecord>(
        TablePredicate predicate)
    {
        var storedRecords = await GetRecordsAsync(predicate);
        var result = new List<TTableRecord>();

        foreach (var storedRecord in storedRecords)
        {
            var tableRecord = new TTableRecord();
            tableRecord.DecodeValues(storedRecord);

            if (tableRecord is ITableRecord tableRecordKey)
            {
                tableRecordKey.DecodeKey(ConvertKeyFromCombinedHex(storedRecord.Key));
            }

            result.Add(tableRecord);
        }

        return result;
    }
}

Example 3: Database Migrations

Create and apply migrations:

# Add initial migration
dotnet ef migrations add InitialMudSchema --context MyMudDbContext

# Update database
dotnet ef database update --context MyMudDbContext

Or in code:

using Microsoft.EntityFrameworkCore;

// Apply migrations at startup
using (var context = new MyMudDbContext(options))
{
    await context.Database.MigrateAsync();
    Console.WriteLine("Database migrated successfully");
}

Example 4: Sync MUD Events to Database

Process Store events and save to database:

using Nethereum.Web3;
using Nethereum.Mud.Contracts.Core.StoreEvents;
using Nethereum.Mud.Repositories.EntityFramework;
using Microsoft.Extensions.Logging;

var web3 = new Web3("https://rpc.mud.game");
var worldAddress = "0xWorldAddress";

using (var context = new MyMudDbContext(options))
{
    var repository = new MyMudTableRepository(context);
    var progressRepository = new BlockProgressRepository<MyMudDbContext>(context);
    var logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger("MudSync");

    var storeEventsService = new StoreEventsLogProcessingService(web3, worldAddress);
    var processor = storeEventsService.CreateProcessor(
        repository,
        progressRepository,
        logger,
        blocksPerRequest: 1000,
        retryWeight: 50,
        minimumBlockConfirmations: 0
    );

    // Start syncing from block 0 (or resume from last processed block)
    await processor.ExecuteAsync(
        startAtBlockNumberIfNotProcessed: 0,
        cancellationToken: CancellationToken.None
    );
}

Example 5: Query StoredRecords with Paging

Efficiently process large datasets using paging:

using (var context = new MyMudDbContext(options))
{
    var repository = new MyMudTableRepository(context);
    long? lastRowId = null;
    int pageSize = 100;

    while (true)
    {
        var page = await repository.GetStoredRecordsAsync(pageSize, lastRowId);

        Console.WriteLine($"Processing page: {page.Records.Count} records");
        Console.WriteLine($"Total records in database: {page.TotalRecords}");

        foreach (var record in page.Records)
        {
            Console.WriteLine($"RowId: {record.RowId}, TableId: {record.TableId}, Key: {record.Key}");
        }

        // Break if no more records
        if (!page.LastRowId.HasValue || page.Records.Count == 0)
            break;

        lastRowId = page.LastRowId;
    }
}

Example 6: Query by Block Number Range

Process records incrementally by block number:

using (var context = new MyMudDbContext(options))
{
    var repository = new MyMudTableRepository(context);
    BigInteger? lastBlockNumber = null;
    long? lastRowId = null;
    int pageSize = 100;

    while (true)
    {
        var page = await repository.GetStoredRecordsGreaterThanBlockNumberAsync(
            pageSize,
            lastBlockNumber,
            lastRowId
        );

        Console.WriteLine($"Processing {page.Records.Count} records from block {page.LastBlockNumber}");

        foreach (var record in page.Records)
        {
            Console.WriteLine($"Block: {record.BlockNumber}, TableId: {record.TableId}");
            // Process record...
        }

        if (!page.LastBlockNumber.HasValue || page.Records.Count == 0)
            break;

        lastBlockNumber = page.LastBlockNumber;
        lastRowId = page.LastRowId;
    }
}

Example 7: Convert StoredRecords to TableRecords

Retrieve strongly-typed MUD table records:

using Nethereum.Mud.TableRepository;

// Assume PlayerTableRecord is a generated MUD table record
using (var context = new MyMudDbContext(options))
{
    var repository = new MyMudTableRepository(context);

    // Get all records for a specific table
    var playerResource = new Resource("Game", "Player");
    var tableIdHex = playerResource.ResourceIdEncoded.ToHex(true);

    var playerRecords = await repository.GetTableRecordsAsync<PlayerTableRecord>(tableIdHex);

    foreach (var player in playerRecords)
    {
        Console.WriteLine($"Player ID: {player.Keys.PlayerId}");
        Console.WriteLine($"Name: {player.Values.Name}");
        Console.WriteLine($"Level: {player.Values.Level}");
    }
}

Example 8: Query with TablePredicate

Use predicates for complex queries:

using Nethereum.Mud.TableRepository;

using (var context = new MyMudDbContext(options))
{
    var repository = new MyMudTableRepository(context);

    // Build a predicate
    var predicate = new TablePredicate
    {
        Conditions = new List<TableCondition>
        {
            new TableCondition
            {
                TableId = "0x..." + playerResource.ResourceIdEncoded.ToHex(),
                Address = worldAddress.ToLowerInvariant(),
                Key = "key0",
                ComparisonOperator = "=",
                HexValue = "0x000000000000000000000000000000000000000000000000000000000000002a",
                UnionOperator = "AND"
            }
        }
    };

    var records = await repository.GetRecordsAsync(predicate);
    Console.WriteLine($"Found {records.Count} records matching predicate");
}

Example 9: Track Block Progress

Resume processing from last synced block:

using Nethereum.Mud.Repositories.EntityFramework;

using (var context = new MyMudDbContext(options))
{
    var progressRepository = new BlockProgressRepository<MyMudDbContext>(context);

    // Check last processed block
    var lastBlock = await progressRepository.GetLastBlockNumberProcessedAsync();

    if (lastBlock.HasValue)
    {
        Console.WriteLine($"Last processed block: {lastBlock.Value}");
    }
    else
    {
        Console.WriteLine("No blocks processed yet");
    }

    // Update progress after processing
    BigInteger newBlock = lastBlock.GetValueOrDefault() + 1000;
    await progressRepository.UpsertProgressAsync(newBlock);

    Console.WriteLine($"Updated progress to block {newBlock}");
}

Example 10: Production Sync Service

Complete background service for MUD synchronization:

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Nethereum.Web3;
using Nethereum.Mud.Contracts.Core.StoreEvents;
using Nethereum.Mud.Repositories.EntityFramework;
using System.Threading;
using System.Threading.Tasks;

public class MudSyncBackgroundService : BackgroundService
{
    private readonly ILogger<MudSyncBackgroundService> _logger;
    private readonly MyMudDbContext _context;
    private readonly string _rpcUrl;
    private readonly string _worldAddress;

    public MudSyncBackgroundService(
        ILogger<MudSyncBackgroundService> logger,
        MyMudDbContext context,
        string rpcUrl,
        string worldAddress)
    {
        _logger = logger;
        _context = context;
        _rpcUrl = rpcUrl;
        _worldAddress = worldAddress;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("MUD Sync Service starting...");

        try
        {
            var web3 = new Web3(_rpcUrl);
            var repository = new MyMudTableRepository(_context);
            var progressRepository = new BlockProgressRepository<MyMudDbContext>(_context);

            var storeEventsService = new StoreEventsLogProcessingService(web3, _worldAddress);
            var processor = storeEventsService.CreateProcessor(
                repository,
                progressRepository,
                _logger,
                blocksPerRequest: 1000,
                retryWeight: 50,
                minimumBlockConfirmations: 12 // Wait for 12 confirmations
            );

            await processor.ExecuteAsync(
                startAtBlockNumberIfNotProcessed: 0,
                cancellationToken: stoppingToken
            );
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "MUD Sync Service failed");
            throw;
        }
    }
}

// Register in Startup.cs or Program.cs
services.AddDbContext<MyMudDbContext>(options =>
    options.UseSqlServer(connectionString));

services.AddHostedService<MudSyncBackgroundService>(provider =>
    new MudSyncBackgroundService(
        provider.GetRequiredService<ILogger<MudSyncBackgroundService>>(),
        provider.GetRequiredService<MyMudDbContext>(),
        rpcUrl: "https://rpc.mud.game",
        worldAddress: "0xWorldAddress"
    ));

Core Classes

MudEFTableRepository<TDbContext>

Base repository with optimized database operations:

public abstract class MudEFTableRepository<TDbContext> : ITableRepository
    where TDbContext : DbContext, IMudStoreRecordsDbSets
{
    // Paging and batch operations
    Task<PagedResult<StoredRecord>> GetStoredRecordsAsync(int pageSize = 100, long? startingRowId = null);
    Task<PagedBlockNumberResult<StoredRecord>> GetStoredRecordsGreaterThanBlockNumberAsync(int pageSize = 100, BigInteger? startingBlockNumber = null, long? lastProcessedRowId = null);

    // CRUD operations (AsNoTracking optimized)
    Task<StoredRecord> GetRecordAsync(string tableIdHex, string keyHex);
    Task<IEnumerable<EncodedTableRecord>> GetRecordsAsync(string tableIdHex);
    Task SetRecordAsync(byte[] tableId, List<byte[]> key, EncodedValues encodedValues, string address = null, BigInteger? blockNumber = null, int? logIndex = null);
    Task DeleteRecordAsync(byte[] tableId, List<byte[]> key, string address = null, BigInteger? blockNumber = null, int? logIndex = null);

    // Splice operations for partial updates
    Task SetSpliceStaticDataAsync(byte[] tableId, List<byte[]> key, ulong start, byte[] newData, string address = null, BigInteger? blockNumber = null, int? logIndex = null);
    Task SetSpliceDynamicDataAsync(byte[] tableId, List<byte[]> key, ulong start, byte[] newData, ulong deleteCount, byte[] encodedLengths, string address = null, BigInteger? blockNumber = null, int? logIndex = null);

    // Strongly-typed table record operations
    Task<IEnumerable<TTableRecord>> GetTableRecordsAsync<TTableRecord>(string tableIdHex) where TTableRecord : ITableRecord, new();

    // Abstract methods for SQL predicate support
    abstract Task<List<StoredRecord>> GetRecordsAsync(TablePredicate predicate);
    abstract Task<IEnumerable<TTableRecord>> GetTableRecordsAsync<TTableRecord>(TablePredicate predicate) where TTableRecord : ITableRecord, new();
}

BlockProgressRepository<TDbContext>

public class BlockProgressRepository<TDbContext> : IBlockProgressRepository
    where TDbContext : DbContext, IMudStoreRecordsDbSets
{
    Task<BigInteger?> GetLastBlockNumberProcessedAsync();
    Task UpsertProgressAsync(BigInteger blockNumber);
}

EFSqlHexPredicateBuilder

Converts TablePredicate to SQL queries:

public class EFSqlHexPredicateBuilder : IEFSqlPredicateBuilder
{
    SqlPredicateResult BuildSql(TablePredicate predicate);
}

Advanced Topics

Performance Optimization

The repository uses several EF Core optimization techniques:

  1. AsNoTracking - Disables change tracking for read-only queries
  2. Batch Processing - Processes large datasets in chunks (default 1000 records)
  3. Manual Change Tracking - Disables auto-detect changes during bulk updates
  4. Change Tracker Clearing - Prevents memory bloat during long-running operations
// Example from SetRecordAsync
Context.ChangeTracker.AutoDetectChangesEnabled = false;
// ... perform operations ...
await Context.SaveChangesAsync();
Context.ChangeTracker.Clear();  // Clear tracking to avoid memory bloat
Context.ChangeTracker.AutoDetectChangesEnabled = true;

Handling Large Datasets

For production systems with millions of records:

// Use paging to avoid loading entire table into memory
const int batchSize = 1000;
long? lastRowId = null;

while (true)
{
    var page = await repository.GetStoredRecordsAsync(batchSize, lastRowId);

    if (page.Records.Count == 0)
        break;

    // Process batch
    await ProcessBatchAsync(page.Records);

    lastRowId = page.LastRowId;
}

Custom Database Providers

This package works with any EF Core provider (SQL Server, PostgreSQL, SQLite, etc.):

// SQL Server
services.AddDbContext<MyMudDbContext>(options =>
    options.UseSqlServer(connectionString));

// SQLite
services.AddDbContext<MyMudDbContext>(options =>
    options.UseSqlite(connectionString));

// In-Memory (for testing)
services.AddDbContext<MyMudDbContext>(options =>
    options.UseInMemoryDatabase("MudTestDb"));

Production Patterns

1. Continuous Sync with Retry Logic

while (!stoppingToken.IsCancellationRequested)
{
    try
    {
        await processor.ExecuteAsync(
            startAtBlockNumberIfNotProcessed: 0,
            cancellationToken: stoppingToken
        );
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Sync failed, retrying in 30 seconds...");
        await Task.Delay(30000, stoppingToken);
    }
}

2. Multiple World Synchronization

var worlds = new[]
{
    ("0xWorld1", "Database1"),
    ("0xWorld2", "Database2")
};

var tasks = worlds.Select(async world =>
{
    var context = CreateDbContext(world.Item2);
    var repository = new MyMudTableRepository(context);
    var progressRepo = new BlockProgressRepository<MyMudDbContext>(context);
    var processor = CreateProcessor(world.Item1, repository, progressRepo);
    await processor.ExecuteAsync(0);
});

await Task.WhenAll(tasks);

3. Read-Heavy Workloads

// Use read-only replicas for queries
services.AddDbContext<MyMudDbContext>(options =>
{
    options.UseSqlServer(readReplicaConnectionString);
    options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); // Default no tracking
});

Dependencies

  • Nethereum.Mud - Core MUD abstractions
  • Nethereum.Mud.Contracts - Store contract event processing
  • Microsoft.EntityFrameworkCore - EF Core framework

Implementations

  • Nethereum.Mud.Repositories.Postgres - PostgreSQL-specific implementation with bytea optimization and normalizer
  • Nethereum.BlockchainProcessing - Block and log processing infrastructure

Additional Resources

Support

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Nethereum.Mud.Repositories.EntityFramework:

Package Downloads
Nethereum.Mud.Repositories.Postgres

Nethereum.Mud.Repositories.Postgres Nethereum Web3 Class Library providing the EF Postgres context Table Repositories and Process Services to sync with the Store contracts of the Mud framework https://mud.dev/

Nethereum.Explorer

Nethereum.Explorer Blazor Server blockchain explorer component library for Ethereum-compatible chains. Provides pages for blocks, transactions, accounts, contracts, logs, token transfers, and MUD worlds with ABI decoding, contract interaction, and transaction tracing.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
6.0.1 0 3/17/2026
6.0.0 24 3/16/2026
5.8.0 126 1/6/2026
5.0.0 255 5/28/2025
4.29.0 197 2/10/2025
4.28.0 168 1/7/2025
4.27.1 178 12/24/2024
4.27.0 172 12/24/2024
4.26.0 211 10/1/2024
4.25.0 206 9/19/2024