- Build the solution from the repo root with
dotnet build --configuration Release. - Run the full test suite with
dotnet test --configuration Release. - Run a single MSTest with
dotnet test TestSqlLiteDatabase/TestSqlLiteDatabase.csproj --filter FullyQualifiedName~TestSqlLiteDatabase.QueryTest.TestQuery. - The projects target
net8.0. CI uses .NET 8.0.100, andglobal.jsonallows rolling forward from 7.0.100 to a newer major SDK. DirectSQL/DirectSQL.csprojhasGeneratePackageOnBuild=true, so builds also produce a NuGet package inDirectSQL/bin/<Configuration>/.- In the current dependency set, SQLite tests fail on macOS before assertions run because
System.Data.SQLite.Corecannot loadSQLite.Interop.dll.
DirectSQL/Database.csis the core of the library. It owns connection lifecycle (Process,ProcessAsync), transaction wrappers, parameter creation/conversion, and the provider-agnostic SQL helpers (ExecuteNonQuery,ExecuteScalar,Query,LoadSqlResult, and theFormattableStringoverloads).- Each database-specific entry point is a thin subclass of
Database<...>underDirectSQL/SqlLite,DirectSQL/SqlServer,DirectSQL/npgsql,DirectSQL/MySql,DirectSQL/DB2, andDirectSQL/Odbc. These classes mostly capture a connection string and implementCreateConnection(). DirectSQL/SqlResult.cswraps the activeIDataReaderand exposes the current row asdynamic, tuple arrays, typed projections, and enumerable loaders. This is the main abstraction for reading query results.- The normal execution flow is: instantiate a concrete provider database -> enter
Process(...)/ProcessAsync(...)to get an open connection -> call static query helpers on the provider type -> letDatabase.csbuild the command and parameters -> read rows throughSqlResult. LoadSqlResult*convenience APIs are thin wrappers overQuery(...)that materialize the cursor intodynamic[]; they do not introduce a separate query pipeline.- Error handling is centralized in
Database.Transaction(...)andDatabase.TransactionAsync(...): exceptions from the callback are wrapped inDatabaseException, while the low-level command and reader behavior remains straight ADO.NET. TestSqlLiteDatabaseis the only test project and doubles as the main executable specification of library behavior. Most usage patterns in the README are mirrored there.
- Treat the concrete
*Databaseclass as the entry point for connection management, then use static methods on that same concrete type inside the callback. The common pattern isdb.Process((conn, tran) => { SqlLiteDatabase.Query(...); }), not instance methods for query execution. - Transaction callbacks are manual-commit.
Process((conn, tran) => ...)andDatabase.Transaction(...)provide a transaction object, but the library does not commit for you; tests that need persistence calltran.Commit()explicitly and usetran.Rollback()explicitly for rollback scenarios. The library only rolls back automatically when it catches an exception and rethrowsDatabaseException. SqlResultis cursor-based. Callresult.Next()before readingResultValuesorResultTuples; attempting to read row values before the firstNext()or after the cursor is exhausted raises the underlying reader error.ResultValuesandResultTuplesare cached for the current row and reset on the nextNext().- Prefer the built-in parameter helpers over handwritten command setup. The codebase uses tuple parameters like
new (string, object)[] { ("@val1", value) }andCreateParameter(...)when an explicitDbTypeis needed. - Prefer the
FormattableStringhelpers when authoring inline SQL in code.ExecuteFormattableNonQuery,ExecuteFormattableScalar,QueryFormattable, andLoadFormattableSqlResultrewrite interpolated values into generated bound parameters like@0,@1, and tests assert against that rewritten SQL. - The async APIs are wrappers around synchronous ADO.NET work via
Task.Run, not provider-native async I/O. Keep that in mind when changing behavior or adding new async surfaces.