An in-process, zero-dependency stand-in for Microsoft.Data.SqlClient and SQL Server.
Consumers create a Simulation, get a DbConnection from CreateDbConnection(), and use it with Entity Framework Core or raw ADO.NET the same way they would with a real SQL Server.
Intended for fast unit testing of SQL Server-backed applications. Can create and discard thousands of databases every second, enabling test scenarios with conflicting data dependencies to run concurrently.
using Microsoft.EntityFrameworkCore;
using SqlServerSimulator;
var simulation = new Simulation();
// If you have a bacpac file, you can import it with simulation.ImportBacpac.
// Commands can be run directly against the simulation, used here to create a table.
using (var connection = simulation.CreateDbConnection())
using (var command = connection.CreateCommand())
{
command.CommandText = "create table ExampleRecord ( Id int )";
connection.Open();
_ = command.ExecuteNonQuery();
}
// Entity Framework thinks it's talking to a real SQL Server.
using (var context = new SimulatedContext(simulation))
{
_ = context.ExampleRecord.Add(new() { Id = 1 });
_ = context.SaveChanges();
}
// The simulation state is preserved across EF DbContexts.
using (var context = new SimulatedContext(simulation))
{
var receivedValue = context.ExampleRecord.Select(x => x.Id);
Console.Write(receivedValue.FirstOrDefault()); // Will write "1", as we stored earlier.
}
// Entity Framework can be used mostly normally.
sealed class ExampleRecord
{
public required int Id { get; set; }
}
// Below is the minimum required to get entity framework to use the simulation.
sealed class SimulatedContext(Simulation simulation) : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Redirect database connection creation to the simulation instead of a real SQL Server.
_ = optionsBuilder.UseSqlServer(simulation.CreateDbConnection());
}
public DbSet<ExampleRecord> ExampleRecord => Set<ExampleRecord>();
}A simulation can optionally listen on a real TDS endpoint over loopback TCP with TLS, so genuine SQL Server clients connect to it exactly as they would to a real server:
await using var listener = await simulation.ListenLocalAsync(11433);
// Now connect with any SQL Server client, e.g.:
// Server=127.0.0.1,11433;User ID=dev;Password=anything;TrustServerCertificate=TrueReal Microsoft.Data.SqlClient works end-to-end — including parameterized RPC, table-valued parameters, SqlBulkCopy, MARS, and query cancellation — as does Entity Framework Core over the wire via a plain connection string.
SQL Server Management Studio connects and browses: Object Explorer, the query editor, and object scripting run against the simulator, which presents itself as SQL Server 2025 (build 17.0.4065.4).
By default any credentials are accepted; run CREATE LOGIN to switch the endpoint to enforced authentication.
For clients on other machines, ListenNetworkAsync binds all interfaces — it requires at least one registered login up front, so an open endpoint can never face a network.
A simulation can be seeded from a .bacpac, the schema-and-data archive SSMS writes under Tasks → Export Data-tier Application, and sqlpackage /Action:Export produces from the command line.
It's the quick way to point a test suite at the shape of a real database instead of maintaining a creation script that might drift from it:
var simulation = new Simulation();
// Without options, the database takes its name from the file.
simulation.ImportBacpac("prod-export.bacpac", out var result, new() { DatabaseName = "Northwind" });
// Elements the simulator doesn't model end-to-end are reported rather than thrown.
// A single gap can't stop the rest of the database from loading.
foreach (var skipped in result.Skipped)
Console.WriteLine($"{skipped.ElementType} {skipped.ElementName}: {skipped.Reason}");That reporting is worth wiring up: Skipped names what the loader couldn't translate at all, Warnings what it accepted with reduced fidelity, and together they're a feature-gap report.
Import is create-only, so a database name already present in the simulation raises InvalidOperationException.
A Stream overload covers bacpacs that don't come from a file path.
Behavior was probed against a live SQL Server reference instance before being modeled.
SQL Server's quirks, inconsistencies, and surprises are mostly preserved.
Error messages usually match, down to the Msg number, severity, and wording of common diagnostics.
Entity Framework Core trusts the simulator end-to-end: LINQ queries, migrations, change tracking, and the SaveChanges pipeline all flow through unchanged. The test suite — more than 8,000 cases — also drives real SqlClient, real SMO (the library behind SSMS), and EF Core against the simulator as independent oracles.
Coverage is broad; the compact map below is the shape of it, not the full inventory:
- Types and storage.
Every base scalar type family including MAX-typed LOBs,
sql_variant,xml,hierarchyid,geography/geometry, and the legacytext/ntext/imagetrio; per-column collations; real 8KB pages with byte-encoded rows and off-row LOB storage. - Query surface.
All JOIN and APPLY forms, correlated subqueries at arbitrary depth, window functions, recursive CTEs, set operations,
PIVOT/UNPIVOT,OFFSET/FETCH, cursors (T-SQL and API server cursors). - DML and DDL.
INSERT/UPDATE/DELETE/MERGE/SELECT INTOwithOUTPUT, statement-level atomicity, andCREATE/ALTER/DROPacross tables, views, procedures, functions, triggers, sequences, indexes (including filtered and indexed views), types, and schemas. - Programmability.
Stored procedures, scalar UDFs and TVFs, DML + DDL triggers, dynamic SQL, table-valued parameters, control flow with
TRY/CATCH/THROW. - Concurrency.
The full lock-mode matrix with escalation and timeouts,
SNAPSHOTandREAD_COMMITTED_SNAPSHOTisolation with a versioned store, deadlock detection, application locks, and nested transactions with savepoints. - Constraints.
PRIMARY KEY/UNIQUE/CHECK/NOT NULLandFOREIGN KEYwith all four referential actions on bothON DELETEandON UPDATE. - System surfaces.
A
sys.*/INFORMATION_SCHEMA.*catalog broad enough to satisfy SSMS, SMO, and DacFx; temporal tables;SERVERPROPERTYand friends as truesql_variant. - JSON, XML, spatial, full-text DDL.
The
JSON_*/OPENJSONfamily, XML methods and schema collections, the spatial method surface, and full-text catalog/index DDL. - Security.
Logins, users, and database + server roles (fixed and custom), with authenticated logins mapped to their database user the way SQL Server does.
GRANT/DENY/REVOKEare enforced for restricted principals at column, object, schema, and database scope — alongsideEXECUTE ASimpersonation, moduleWITH EXECUTE AS, ownership chaining, catalog metadata filtered to a principal's visibility, andVIEW SERVER STATE-gated DMVs. - Scale-out shapes. Multiple databases with cross-database reads, linked servers between simulations, and BACPAC import for bootstrapping from a real database.
Deeper per-feature notes live under docs/claude/.
SQL Server's surface is enormous, and while coverage is broad it is not complete: you may encounter a feature that hasn't been modeled.
In general, when that happens the simulator raises NotSupportedException naming the missing feature, so gaps fail loudly rather than returning wrong results.
Write a bug report if you're blocked.
A few examples:
- Cross-server DML - writes through a 4-part linked-server name.
Four-part reads work, as does cross-database DML inside one
Simulation; open a connection on the targetSimulationto write to it. - CLR stored procedures, table-valued functions, aggregates and UDTs; logon triggers; natively-compiled procedures beyond parser fidelity.
CLR scalar functions work: set
EnableClron theSimulation, thenCREATE ASSEMBLYandCREATE FUNCTION ... AS EXTERNAL NAME. Enabling it runs the assembly's code inside your process — .NET has no Code Access Security, soPERMISSION_SET = SAFEcannot be enforced. - A few
ALTER TABLEshapes:SWITCH PARTITION, andALTER COLUMN ... ADD | DROP PERSISTED | MASKED.
- No physical storage - all data lives in memory for the lifetime of the
Simulation. Suited to test runs and bounded workloads, not larger-than-RAM datasets. - The network endpoint is meant for development tooling and tests, not untrusted clients.
Authorization is enforced once you configure principals - logins, users, roles, and grants; with none configured a
Simulationruns open (every connection isdbo), and sysadmin members and unauthenticated in-process connections aredboby design. It's an in-memory simulator built for fidelity, not a hardened security boundary - so lean on it to test your access model, not to stand in front of hostile input.