Skip to content

Repository files navigation

EntglDb

Peer-to-Peer Data Synchronization Middleware & Platform for .NET

.NET VersionLicense

Status

VersionBuild

EntglDb is not a database — it's a sync layer and P2P platform that plugs into your existing data store and enables automatic peer-to-peer replication across nodes in a mesh network. The mesh infrastructure also serves as a foundation for building additional distributed services.

Architecture | Quick Start | Integration Guide | Custom P2P Services | Documentation


Table of Contents


Overview

EntglDb is a lightweight, embeddable data synchronization middleware and P2P platform for .NET. It observes changes in your database via Change Data Capture (CDC), records them in an append-only, hash-chained Oplog, and replicates them across nodes connected via a P2P mesh network.

Your application continues to read and write to its database as usual. EntglDb works in the background, providing automatic discovery, secure transport, conflict resolution, and eventually consistent replication.

[LAN] Designed for Local Area Networks (LAN)
Built for trusted environments: offices, retail stores, edge deployments, factories. Cross-platform (Windows, Linux, macOS).

[Cloud] Cloud Ready
ASP.NET Core hosting with Entity Framework Core support (SQL Server, PostgreSQL, MySQL, SQLite) and OAuth2 authentication for public deployments.

[Platform] P2P Platform
The mesh networking, discovery, and secure transport infrastructure can be leveraged to build additional distributed services beyond data synchronization.


Architecture

+---------------------------------------------------+
| Your Application |
| db.Users.InsertAsync(user) |
| db.Users.Find(u => u.Age > 18) |
+---------------------------------------------------+
| uses your DbContext directly
+---------------------------------------------------+
| Your Database (BLite / EF Core) |
| +---------------------------------------------+ |
| | Users | Orders | Products | ... | |
| +---------------------------------------------+ |
| | CDC (Change Data Capture) |
| | |
| +---------------------------------------------+ |
| | EntglDb Sync Engine | |
| | - Oplog (append-only hash-chained journal) | |
| | - Vector Clock (causal ordering) | |
| | - Conflict Resolution (LWW / Custom Merge) | |
| +---------------------------------------------+ |
+---------------------------------------------------+
| P2P Network (TCP + UDP Discovery)
+---------------------------------------------------+
| Other Nodes (same setup) |
| Node-A <-----> Node-B <-----> Node-C |
+---------------------------------------------------+

Core Concepts

ConceptDescription
OplogAppend-only journal of changes, hash-chained (SHA-256) per node for integrity
Vector ClockTracks causal ordering — knows who has what across the mesh
CDCChange Data Capture — watches your registered collections for local writes
Document StoreYour bridge class — maps between your entities and the sync engine
DocumentMetadataTracks HLC timestamps and ContentHash (SHA-256) per document
Conflict ResolutionPluggable strategy (Last-Write-Wins or recursive merge with field-level tracking)
VectorClockServiceShared singleton keeping the Vector Clock in sync between CDC and OplogStore
Snapshot ServiceFast reconnection via hash-based delta sync and boundary convergence

Sync Flow

Local Write -> CDC Trigger -> OplogEntry Created -> VectorClock Updated
|
v
SyncOrchestrator
(gossip every 2s)
|
+---------+----------+
| |
Push changes Pull changes
to peers from peers
| |
v v
Remote node Apply to local
applies via OplogStore +
ApplyBatchAsync DocumentStore

Key Features

[Select] Selective Collection Sync

Only collections registered via WatchCollection() are tracked. Your database can have hundreds of tables - only the ones you opt-in participate in replication.

[Gossip] Interest-Aware Gossip

Nodes advertise which collections they sync. The orchestrator prioritizes peers sharing common interests, reducing unnecessary traffic.

[Offline] Offline First

  • Read/write operations work offline - they're direct database operations
  • Automatic sync when peers reconnect
  • Oplog-based gap recovery and snapshot fallback

[Secure] Secure Networking

  • Noise Protocol handshake with ECDH key exchange
  • AES-256 encryption for data in transit
  • HMAC authentication
  • Brotli compression for bandwidth efficiency

[Conflict] Conflict Resolution

  • Last Write Wins (LWW) - default, HLC timestamp-based
  • Recursive Merge - deep JSON merge for concurrent edits
  • Custom - implement IConflictResolver for your business logic

[Cloud] Cloud Infrastructure

  • ASP.NET Core hosting (Single/Multi cluster modes)
  • Entity Framework Core: SQL Server, PostgreSQL, MySQL, SQLite
  • OAuth2 JWT authentication

Installation

Packages

PackagePurpose
EntglDb.CoreInterfaces, models, conflict resolution (.NET Standard 2.0+)
EntglDb.PersistenceBase OplogStore, VectorClockService (.NET 8+)
EntglDb.Persistence.BLiteBLite embedded document DB provider (.NET 10+)
EntglDb.Persistence.EntityFrameworkEF Core provider (.NET 8+)
EntglDb.NetworkTCP sync, UDP discovery, Protobuf protocol (.NET Standard 2.0+)
# For BLite (embedded document DB)
dotnet add package EntglDb.Core
dotnet add package EntglDb.Persistence.BLite
dotnet add package EntglDb.Network
# For EF Core (SQL Server, PostgreSQL, etc.)
dotnet add package EntglDb.Core
dotnet add package EntglDb.Persistence.EntityFramework
dotnet add package EntglDb.Network

Quick Start

1. Define Your Database Context

publicclassMyDbContext:EntglDocumentDbContext{publicDocumentCollection<string,Customer>Customers{get;privateset;}publicDocumentCollection<string,Order>Orders{get;privateset;}publicMyDbContext(stringdbPath):base(dbPath){}}

2. Create Your Document Store (the Sync Bridge)

This is where you tell EntglDb which collections to sync and how to map between your entities and the sync engine:

publicclassMyDocumentStore:BLiteDocumentStore<MyDbContext>{publicMyDocumentStore(MyDbContextcontext,IPeerNodeConfigurationProviderconfigProvider,IVectorClockServicevectorClockService,ILogger<MyDocumentStore>?logger=null):base(context,configProvider,vectorClockService,logger:logger){// Register collections for CDC - only these will be syncedWatchCollection("Customers",context.Customers, c =>c.Id);WatchCollection("Orders",context.Orders, o =>o.Id);}// Map incoming sync data back to your entitiesprotectedoverrideasyncTaskApplyContentToEntityAsync(stringcollection,stringkey,JsonElementcontent,CancellationTokenct){switch(collection){case"Customers":varcustomer=content.Deserialize<Customer>()!;customer.Id=key;varexisting=_context.Customers.Find(c =>c.Id==key).FirstOrDefault();if(existing!=null)_context.Customers.Update(customer);else_context.Customers.Insert(customer);break;case"Orders":varorder=content.Deserialize<Order>()!;order.Id=key;varexistingOrder=_context.Orders.Find(o =>o.Id==key).FirstOrDefault();if(existingOrder!=null)_context.Orders.Update(order);else_context.Orders.Insert(order);break;}await_context.SaveChangesAsync(ct);}protectedoverrideTask<JsonElement?>GetEntityAsJsonAsync(stringcollection,stringkey,CancellationTokenct){object?entity=collectionswitch{"Customers"=>_context.Customers.Find(c =>c.Id==key).FirstOrDefault(),"Orders"=>_context.Orders.Find(o =>o.Id==key).FirstOrDefault(),
_ =>null};returnTask.FromResult(entity!=null?(JsonElement?)JsonSerializer.SerializeToElement(entity):null);}protectedoverrideasyncTaskRemoveEntityAsync(stringcollection,stringkey,CancellationTokenct){switch(collection){case"Customers":_context.Customers.Delete(key);break;case"Orders":_context.Orders.Delete(key);break;}await_context.SaveChangesAsync(ct);}protectedoverrideTask<IEnumerable<(stringKey,JsonElementContent)>>GetAllEntitiesAsJsonAsync(stringcollection,CancellationTokenct){IEnumerable<(string,JsonElement)>result=collectionswitch{"Customers"=>_context.Customers.FindAll().Select(c =>(c.Id,JsonSerializer.SerializeToElement(c))),"Orders"=>_context.Orders.FindAll().Select(o =>(o.Id,JsonSerializer.SerializeToElement(o))),
_ =>Enumerable.Empty<(string,JsonElement)>()};returnTask.FromResult(result);}}

3. Wire It Up

varbuilder=Host.CreateApplicationBuilder();// Configure the nodebuilder.Services.AddSingleton<IPeerNodeConfigurationProvider>(newStaticPeerNodeConfigurationProvider(newPeerNodeConfiguration{NodeId="node-1",TcpPort=8580,AuthToken="my-cluster-secret"}));// Register EntglDb servicesbuilder.Services.AddEntglDbCore().AddEntglDbBLite<MyDbContext,MyDocumentStore>(
sp =>newMyDbContext("mydata.blite")).AddEntglDbNetwork<StaticPeerNodeConfigurationProvider>();awaitbuilder.Build().RunAsync();

4. Use Your Database Normally

publicclassMyService{privatereadonlyMyDbContext_db;publicMyService(MyDbContextdb)=>_db=db;publicasyncTaskCreateCustomer(stringname){// Write directly - EntglDb handles sync automaticallyawait_db.Customers.InsertAsync(newCustomer{Id=Guid.NewGuid().ToString(),Name=name});await_db.SaveChangesAsync();// Changes are automatically:// 1. Detected via CDC// 2. Recorded in the Oplog with HLC timestamp + hash chain// 3. Pushed to connected peers via gossip// 4. Applied on remote nodes via conflict resolution}publicasyncTask<List<Customer>>GetYoungCustomers(){// Read directly from your DB - no EntglDb APIreturn_db.Customers.Find(c =>c.Age<30).ToList();}}

Integrating with Your Database

If you have an existing database and want to add P2P sync:

Step 1 - Wrap your context

Create a DbContext extending EntglDocumentDbContext (BLite) or use EF Core directly. This can wrap your existing collections/tables.

publicclassMyExistingDbContext:EntglDocumentDbContext{// Your existing collectionspublicDocumentCollection<string,Product>Products{get;privateset;}publicDocumentCollection<string,Inventory>Inventory{get;privateset;}publicMyExistingDbContext(stringdbPath):base(dbPath){}}

Step 2 - Create a DocumentStore

Extend BLiteDocumentStore<T> or implement against EF Core. This is the bridge between your data model and the sync engine.

publicclassMyDocumentStore:BLiteDocumentStore<MyExistingDbContext>{publicMyDocumentStore(MyExistingDbContextctx,IPeerNodeConfigurationProvidercfg,IVectorClockServicevc,ILogger<MyDocumentStore>?log=null):base(ctx,cfg,vc,logger:log){// Continue to next step...}// Implement abstract methods (see below)...}

Step 3 - Register only what you need

Call WatchCollection() in the constructor for each collection you want to replicate. Everything else is ignored by the sync engine.

publicMyDocumentStore(...):base(ctx,cfg,vc,logger: log){// Only these 2 collections will be synced across the meshWatchCollection("Products", ctx.Products, p => p.Id);
WatchCollection("Inventory",ctx.Inventory, i =>i.Id);// All other collections in your DB are local-only}

Step 4 - Implement the mapping methods

EntglDb stores data as JsonElement. You provide four mapping methods:

MethodPurpose
ApplyContentToEntityAsyncWrite incoming sync data to your entities
GetEntityAsJsonAsyncRead your entities for outbound sync
RemoveEntityAsyncHandle remote deletes
GetAllEntitiesAsJsonAsyncProvide full collection for snapshot sync
protectedoverrideasyncTaskApplyContentToEntityAsync(stringcollection,stringkey,JsonElementcontent,CancellationTokenct){switch(collection){case"Products":varproduct=content.Deserialize<Product>()!;product.Id=key;varexisting=_context.Products.Find(p =>p.Id==key).FirstOrDefault();if(existing!=null)_context.Products.Update(product);else_context.Products.Insert(product);break;case"Inventory":varinv=content.Deserialize<Inventory>()!;inv.Id=key;varexistingInv=_context.Inventory.Find(i =>i.Id==key).FirstOrDefault();if(existingInv!=null)_context.Inventory.Update(inv);else_context.Inventory.Insert(inv);break;}await_context.SaveChangesAsync(ct);}protectedoverrideTask<JsonElement?>GetEntityAsJsonAsync(stringcollection,stringkey,CancellationTokenct){object?entity=collectionswitch{"Products"=>_context.Products.Find(p =>p.Id==key).FirstOrDefault(),"Inventory"=>_context.Inventory.Find(i =>i.Id==key).FirstOrDefault(),
_ =>null};returnTask.FromResult(entity!=null?(JsonElement?)JsonSerializer.SerializeToElement(entity):null);}protectedoverrideasyncTaskRemoveEntityAsync(stringcollection,stringkey,CancellationTokenct){switch(collection){case"Products":_context.Products.Delete(key);break;case"Inventory":_context.Inventory.Delete(key);break;}await_context.SaveChangesAsync(ct);}protectedoverrideTask<IEnumerable<(stringKey,JsonElementContent)>>GetAllEntitiesAsJsonAsync(stringcollection,CancellationTokenct){IEnumerable<(string,JsonElement)>result=collectionswitch{"Products"=>_context.Products.FindAll().Select(p =>(p.Id,JsonSerializer.SerializeToElement(p))),"Inventory"=>_context.Inventory.FindAll().Select(i =>(i.Id,JsonSerializer.SerializeToElement(i))),
_ =>Enumerable.Empty<(string,JsonElement)>()};returnTask.FromResult(result);}// Optional: Batch operations for better performanceprotectedoverrideasyncTaskApplyContentToEntitiesBatchAsync(IEnumerable<(stringCollection,stringKey,JsonElementContent)>documents,CancellationTokenct){foreach(var(collection,key,content)indocuments){// Call the single-item method (you can optimize this further)awaitApplyContentToEntityAsync(collection,key,content,ct);}}

Your existing CRUD code stays unchanged. EntglDb plugs in alongside it.

What Happens Under the Hood

Your Code: db.Users.InsertAsync(user)
|
v
BLite/EF Core: SaveChangesAsync()
|
| CDC fires (WatchCollection observer)
DocumentStore: CreateOplogEntryAsync()
|
+-> OplogEntry written (hash-chained, HLC timestamped)
+-> VectorClockService.Update() -> sync sees it immediately
|
v
SyncOrchestrator (background, every 2s)
+-> Compare VectorClocks with peers
+-> Push local changes (interest-filtered)
+-> Pull remote changes -> ApplyBatchAsync
|
v
Remote DocumentStore: ApplyContentToEntityAsync()
|
v
Remote Database: Updated!

Custom P2P Services

The EntglDb mesh network is not limited to database sync — you can build additional distributed services on top of the same TCP connections and discovery infrastructure.

How It Works

Every node exposes a server side that dispatches incoming messages by type, and an injectable client side (IPeerMessenger) for sending outbound requests. Message types 32+ are reserved for custom use.

Node A (client) Node B (server)
────────────── ──────────────
IPeerMessenger.SendAndReceiveAsync() INetworkMessageHandler.HandleAsync()
│ wire: [type=32][payload] │
└──────────── TCP (shared conn) ───────┘

The TCP connection and handshake are shared with the sync protocol — no extra socket is opened.

Step 1 — Define your Protobuf messages

// my_service.protosyntax="proto3";
messagePingRequest { stringmessage=1; }
messagePingResponse { stringecho=1; }

Reserve a message type constant (≥ 32):

publicstaticclassMyMessageType{publicconstintPingRequest=100;// any value in the 32–999 custom rangepublicconstintPingResponse=101;}

Step 2 — Implement the server-side handler

Implement INetworkMessageHandler and register it in DI after AddEntglDbNetwork:

publicclassPingHandler:INetworkMessageHandler{publicintMessageType=>MyMessageType.PingRequest;publicasyncTask<(IMessage?Response,intResponseType)>HandleAsync(IMessageHandlerContextcontext){varrequest=PingRequest.Parser.ParseFrom(context.Payload);varresponse=newPingResponse{Echo=$"pong: {request.Message}"};return(response,MyMessageType.PingResponse);}}
services.AddEntglDbNetwork<MyConfigProvider>();services.AddSingleton<INetworkMessageHandler,PingHandler>();// must be after AddEntglDbNetwork

For streaming responses (multiple chunks), use context.SendMessageAsync() directly and return (null, 0):

publicasyncTask<(IMessage?,int)>HandleAsync(IMessageHandlerContextcontext){for(inti=0;i<3;i++)awaitcontext.SendMessageAsync(MyMessageType.PingResponse,newPingResponse{Echo=$"chunk {i}"});return(null,0);// response already sent}

Step 3 — Call the service from the client side

Inject IPeerMessenger and call SendAndReceiveAsync:

publicclassMyClientService{privatereadonlyIPeerMessenger_messenger;publicMyClientService(IPeerMessengermessenger)=>_messenger=messenger;publicasyncTask<string>PingAsync(stringpeerAddress,CancellationTokenct){var(responseType,payload)=await_messenger.SendAndReceiveAsync(peerAddress,// "192.168.1.10:7000"MyMessageType.PingRequest,newPingRequest{Message="hello"},ct);varresponse=PingResponse.Parser.ParseFrom(payload);returnresponse.Echo;}}

For fire-and-forget (no response expected):

await_messenger.SendAsync(peerAddress,MyMessageType.PingRequest,newPingRequest{Message="hello"},ct);

Step 4 — Wire up DI

builder.Services.AddEntglDbNetwork<MyConfigProvider>()// registers IPeerConnectionPool + IPeerMessenger.AddEntglDbSync();// if also using data sync// Register custom handler(s) after the above callsbuilder.Services.AddSingleton<INetworkMessageHandler,PingHandler>();// Register your client servicebuilder.Services.AddSingleton<MyClientService>();

Connection Sharing

IPeerMessenger draws connections from the same IPeerConnectionPool<TcpPeerClient> used by the sync engine. Calling SendAndReceiveAsync to a peer that is already connected for sync reuses the same TCP socket — no extra connections are opened.

Message Type Ranges

RangeOwner
0–2Protocol control (handshake, keepalive)
3–15Built-in sync messages (SyncMessageType)
16–31Reserved for future EntglDb use
32–999Your custom services
1000–1001EntglDb.Services.NodeStatus — peer diagnostics
1100–1104EntglDb.Services.FileTransfer — P2P file transfer

Cloud Deployment

EntglDb supports ASP.NET Core hosting with Entity Framework Core for cloud deployments.

Persistence Options

DatabaseBest ForNotes
SQLiteEdge computing, embeddedFile-based, serverless
SQL ServerEnterpriseAzure SQL, managed instances
PostgreSQLHigh-performanceJSONB optimization, GIN indexes
MySQLWide compatibilityMariaDB compatible

Example: SQL Server with OAuth2

varbuilder=WebApplication.CreateBuilder(args);builder.Services.AddEntglDbEntityFramework(options =>{options.UseSqlServer("Server=localhost;Database=EntglDb;Integrated Security=true");});builder.Services.AddEntglDbAspNetSingleCluster(options =>{options.TcpPort=5001;options.RequireAuthentication=true;options.OAuth2Authority="https://auth.example.com";options.OAuth2Audience="entgldb-api";});varapp=builder.Build();app.MapHealthChecks("/health");awaitapp.RunAsync();

Example: PostgreSQL with JSONB

builder.Services.AddEntglDbPostgreSql("Host=localhost;Database=EntglDb;Username=app;Password=secret");builder.Services.AddEntglDbAspNetSingleCluster(options =>{options.TcpPort=5001;});

Production Features

Configuration

{
"EntglDb": {
"KnownPeers": [
{
"NodeId": "gateway-1",
"Address": "192.168.1.10:5000",
"Type": "StaticRemote"
}
],
"RetentionHours": 24,
"SyncIntervalSeconds": 2
},
"Logging": {
"LogLevel": {
"Default": "Information",
"EntglDb": "Warning"
}
}
}

Health Monitoring

varhealthCheck=newEntglDbHealthCheck(store,syncTracker);varstatus=awaithealthCheck.CheckAsync();Console.WriteLine($"Database: {status.DatabaseHealthy}");Console.WriteLine($"Network: {status.NetworkHealthy}");Console.WriteLine($"Peers: {status.ConnectedPeers}");

Resilience

  • Exponential Backoff: Automatic retry for unreachable peers
  • Offline Queue: Buffer local changes when network is down
  • Snapshot Recovery: Fast catch-up after long disconnects
  • Hash Chain Validation: Detect and recover from oplog gaps

Performance

  • VectorClock Cache: In-memory tracking of node states
  • Brotli Compression: 70-80% bandwidth reduction
  • Batch Operations: Group changes for efficient network transfer
  • Interest Filtering: Only sync collections both peers care about

Security

  • Noise Protocol Handshake: XX pattern with ECDH key exchange
  • AES-256 Encryption: Protect data in transit
  • Auth Tokens: Shared secret or OAuth2 JWT validation
  • LAN Isolation: Designed for trusted network environments

Use Cases

Ideal For

  • Retail POS Systems - Terminals syncing inventory and sales across a store
  • Office Applications - Shared task lists, calendars, CRM data on LAN
  • Edge Computing - Distributed sensors and controllers at a facility
  • Offline-First Apps - Work without internet, sync when connected
  • Multi-Site Replication - Keep regional databases in sync (over VPN)
  • Existing Database Modernization - Add P2P sync without rewriting your app

Not Designed For

  • Public internet without HTTPS/VPN (P2P mesh mode, use ASP.NET Core mode instead)
  • Sub-millisecond consistency requirements (eventual consistency model, typical convergence < 5s)
  • Unstructured data (designed for document collections with keys)
  • Append-only event logs (oplog pruning after 24h retention)

Documentation

Getting Started

Concepts

Deployment

API


Examples

Sample Applications

SampleTypeDescription
ConsoleCLIInteractive two-node sync demo with conflict resolution switching
ASP.NET CoreREST APIHealth checks, Swagger API, telemetry endpoints
GameGameBattle log sync, game state, hero data
AvaloniaDesktop UICross-platform (Windows/Linux/macOS), security status
MAUIMobileiOS/Android, material design, network telemetry

Quick Start Demo

# Terminal 1cd samples/EntglDb.Sample.Console
dotnet run -- node-1 8580
# Terminal 2
dotnet run -- node-2 8581
# Create a user on node-1 with command "n"# Watch it appear on node-2 automatically!

Roadmap

  • Core P2P mesh networking (v0.1.0)
  • Secure networking — ECDH + AES-256 (v0.6.0)
  • Conflict resolution — LWW, Recursive Merge (v0.6.0)
  • Hash-chain sync with gap recovery (v0.7.0)
  • Brotli compression (v0.7.0)
  • Persistence snapshots (v0.8.6)
  • ASP.NET Core hosting — Single & Multi-cluster (v0.8.0)
  • Entity Framework Core — SQL Server, PostgreSQL, MySQL, SQLite (v0.8.0)
  • VectorClockService refactor & CDC-aware sync (v1.0.0)
  • BLite & EF Core DocumentStore abstract base classes (v1.0.0)
  • Full async BLite operations (v1.1.0)
  • Dynamic database paths & per-collection tables (v2.0.0)
  • Remote peer auto-sync via _system_remote_peers (v2.0.0)
  • Framework targeting: netstandard2.1;net10.0 (v2.0.0)
  • ContentHash on DocumentMetadata (v2.1.0)
  • Mobile support (.NET MAUI) (v2.0.0)
  • Merkle Trees for efficient sync verification
  • TLS/SSL support for secure LAN networks
  • Query optimization & advanced indexing
  • Admin UI / monitoring dashboard

Contributing

We welcome contributions! EntglDb is open-source and we'd love your help.

How to Contribute

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes with clear commit messages
  4. Add tests for new functionality
  5. Ensure all tests pass (dotnet test)
  6. Submit a Pull Request

Development Setup

# Clone the repository
git clone https://github.com/EntglDb/EntglDb.Net.git
cd EntglDb.Net
# Restore dependencies
dotnet restore
# Build
dotnet build
# Run all tests (69 tests)
dotnet test# Run samplecd samples/EntglDb.Sample.Console
dotnet run

Areas We Need Help

  • [Bug] Bug Reports - Found an issue? Let us know!
  • [Docs] Documentation - Improve guides and examples
  • [Feature] Features - Implement items from the roadmap
  • [Test] Testing - Add integration and performance tests
  • [Sample] Samples - Build example applications

Code of Conduct

Be respectful, inclusive, and constructive. We're all here to learn and build great software together.


License

EntglDb is licensed under the MIT License.

MIT License
Copyright (c) 2026 MrDevRobot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software...

See LICENSE file for full details.


Give it a Star!

If you find EntglDb useful, please give it a star on GitHub! It helps others discover the project and motivates us to keep improving it.

Thank you for your support!

Built with care for the .NET community

Report Bug | Request Feature | Discussions

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages