diff --git a/README.md b/README.md index cc50393..b1def78 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,11 @@ Full documentation is available in [`docs/README.md`](docs/README.md): - Quick start - Dynamic table API - Fluent builder API +- Syntax and procedure deep dive - Mapping and entity lifecycle - Transaction/disposal semantics - Stored procedures +- ADO.NET extensions and cached reader - .NET 4.0 amalgamation workflow - Test-driven examples diff --git a/docs/README.md b/docs/README.md index ce2fc63..d5b2993 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,9 +9,11 @@ This documentation is based on: - [Quick Start](quick-start.md) - [Dynamic Table API](dynamic-table-api.md) - [Fluent Builder API](fluent-builder-api.md) +- [Syntax and Procedure Deep Dive](syntax-and-procedures-deep-dive.md) - [Mapping and Entities](mapping-and-entities.md) - [Transactions and Disposal](transactions-and-disposal.md) - [Stored Procedures](stored-procedures.md) +- [ADO.NET Extensions](ado-net-extensions.md) - [.NET 4.0 Amalgamation](net40-amalgamation.md) - [Test-Driven Examples](test-driven-examples.md) diff --git a/docs/ado-net-extensions.md b/docs/ado-net-extensions.md new file mode 100644 index 0000000..ff8cc52 --- /dev/null +++ b/docs/ado-net-extensions.md @@ -0,0 +1,163 @@ +# ADO.NET Extensions Reference + +DynamORM exposes extension helpers mainly in: + +- `DynamORM/DynamicExtensions.cs` +- `DynamORM/Helpers/DataReaderExtensions.cs` +- `DynamORM/Helpers/ReaderExtensions.cs` + +## `IDbCommand` Extensions + +## Connection and Transaction + +- `SetConnection(this IDbCommand, IDbConnection)` +- `SetTransaction(this IDbCommand, IDbTransaction)` + +These enable fluent command setup. + +## `SetCommand` Overloads + +Core overloads: + +- `SetCommand(CommandType, int timeout, string text, params object[] args)` +- `SetCommand(int timeout, string text, params object[] args)` +- `SetCommand(CommandType, string text, params object[] args)` +- `SetCommand(string text, params object[] args)` +- `SetCommand(IDynamicQueryBuilder builder)` + +Examples: + +```csharp +cmd.SetCommand("SELECT * FROM users WHERE id = {0}", 19); +cmd.SetCommand(CommandType.StoredProcedure, "sp_DoWork"); +cmd.SetCommand(builder); +``` + +## Parameter Helpers + +Bulk: + +- `AddParameters(DynamicDatabase, params object[] args)` +- `AddParameters(DynamicDatabase, ExpandoObject)` +- `AddParameters(DynamicDatabase, DynamicExpando)` + +Single parameter helpers: + +- `AddParameter(DynamicDatabase, object item)` +- `AddParameter(DynamicDatabase, string name, object item)` +- `AddParameter(IDynamicQueryBuilder, DynamicSchemaColumn? col, object value)` +- `AddParameter(IDynamicQueryBuilder, DynamicColumn item)` + +Advanced overloads support explicit `ParameterDirection`, `DbType`, `size`, `precision`, `scale`. + +Example: + +```csharp +cmd.AddParameter("@Result", ParameterDirection.Output, DbType.String, 256, 0, 0, DBNull.Value); +cmd.AddParameter("@Name", DbType.String, 50, "Alice"); +``` + +## Updating Existing Parameters + +- `SetParameter(this IDbCommand, string parameterName, object value)` +- `SetParameter(this IDbCommand, int index, object value)` + +## Execution and Conversion + +- `ExecuteScalarAs()` +- `ExecuteScalarAs(defaultValue)` +- `ExecuteScalarAs(TryParseHandler)` +- `ExecuteScalarAs(defaultValue, TryParseHandler)` +- `ExecuteEnumeratorOf(defaultValue, TryParseHandler)` + +These convert ADO.NET results to requested types with fallback behavior. + +## Command Debugging + +- `DumpToString()` +- `Dump(StringBuilder)` +- `Dump(TextWriter)` + +Useful with `DynamicDatabase.DumpCommands` and custom log sinks. + +## `IDataReader` and Row Helpers + +From `DynamicExtensions.cs`: + +- `ToList(this IDataReader)` +- `RowToDynamic(this IDataReader)` +- `RowToExpando(this IDataReader)` +- `RowToDynamic(this DataRow)` +- `RowToExpando(this DataRow)` +- upper-case variants (`RowToDynamicUpper`, `RowToExpandoUpper`) +- `GetFieldDbType(this IDataReader, int i)` + +## Reader Caching + +- `CachedReader(this IDataReader, int offset = 0, int limit = -1, Func progress = null)` + +This creates an in-memory `DynamicCachedReader` snapshot. + +## `DataReaderExtensions` + +- `ToDataTable(this IDataReader, string name = null, string nameSpace = null)` + +Converts current reader rows/schema to a `DataTable`. + +## `ReaderExtensions` Null-Safe Accessors + +Typed nullable access by column name: + +- `GetBooleanIfNotNull` +- `GetByteIfNotNull` +- `GetCharIfNotNull` +- `GetDateTimeIfNotNull` +- `GetDecimalIfNotNull` +- `GetDoubleIfNotNull` +- `GetFloatIfNotNull` +- `GetGuidIfNotNull` +- `GetInt16IfNotNull` +- `GetInt32IfNotNull` +- `GetInt64IfNotNull` +- `GetStringIfNotNull` +- `GetValueIfNotNull` + +Each method accepts an optional default value and returns it when DB value is null. + +## `DynamicCachedReader` + +`DynamicCachedReader` implements `IDataReader` and stores: + +- schema metadata +- field names/types/ordinals +- full row cache (supports multi-result sets) + +Construction options: + +- `new DynamicCachedReader(IDataReader, offset, limit, progress)` +- `DynamicCachedReader.FromDynamicEnumerable(...)` +- `DynamicCachedReader.FromEnumerable(...)` +- `DynamicCachedReader.FromEnumerable(Type, IEnumerable)` + +Typical usage: + +```csharp +using (var rdr = cmd.ExecuteReader()) +using (var cached = rdr.CachedReader()) +{ + while (cached.Read()) + { + var row = cached.RowToDynamic(); + } +} +``` + +When to use it: + +- You need disconnected reader semantics. +- You need multiple passes or deferred mapping. +- You need stable materialization before connection disposal. + +Tradeoff: + +- Higher memory use proportional to result size. diff --git a/docs/stored-procedures.md b/docs/stored-procedures.md index 0a5e09e..05c1ba4 100644 --- a/docs/stored-procedures.md +++ b/docs/stored-procedures.md @@ -2,6 +2,8 @@ Stored procedure support is available through `db.Procedures` when `DynamicDatabaseOptions.SupportStoredProcedures` is enabled. +For an in-depth comparison with query syntax styles and advanced return-shape behavior, see [Syntax Deep Dive and Procedure Calls](syntax-and-procedures-deep-dive.md). + ## Basic Invocation ```csharp diff --git a/docs/syntax-and-procedures-deep-dive.md b/docs/syntax-and-procedures-deep-dive.md new file mode 100644 index 0000000..e377b9c --- /dev/null +++ b/docs/syntax-and-procedures-deep-dive.md @@ -0,0 +1,173 @@ +# Syntax Deep Dive and Procedure Calls + +This guide focuses on the two query syntaxes and advanced stored procedure invocation patterns. + +## Two Query Syntax Styles + +DynamORM supports: + +- Dynamic table syntax: concise runtime calls via `dynamic`. +- Fluent builder syntax: explicit SQL composition via interfaces and lambda parser. + +## Dynamic Table Syntax + +Entry point: + +```csharp +dynamic users = db.Table("users"); +``` + +Typical reads: + +```csharp +var count = users.Count(columns: "id"); +var first = users.First(columns: "id,first,last"); +var one = users.Single(id: 19); +var list = (users.Query(columns: "id,first", order: "id:desc") as IEnumerable).ToList(); +``` + +Dynamic filters with `DynamicColumn`: + +```csharp +users.Count(where: new DynamicColumn("id").Greater(100)); +users.Count(where: new DynamicColumn("last").In("Hendricks", "Goodwin", "Freeman")); +``` + +Modifications: + +```csharp +users.Insert(code: "201", first: "Juri", last: "Gagarin"); +users.Update(values: new { first = "Yuri" }, where: new { code = "201" }); +users.Delete(code: "201"); +``` + +Coverage references: + +- `DynamORM.Tests/Select/DynamicAccessTests.cs` +- `DynamORM.Tests/Modify/DynamicModificationTests.cs` + +## Fluent Builder Syntax + +Entry points: + +```csharp +var q1 = db.From("users", "u"); +var q2 = db.From("u"); +``` + +Composable select: + +```csharp +using (var query = db.From("u") + .Where(x => x.u.id > 10) + .Select(x => x.u.id, x => x.u.first) + .OrderBy(x => x.u.id.Desc())) +{ + var rows = query.Execute().ToList(); +} +``` + +Parser forms supported by tests: + +- `From(x => x.dbo.Users)` +- `From(x => x.dbo.Users.As(x.u))` +- `From(x => "dbo.Users AS u")` +- `From(x => x(subQuery).As("u"))` +- `Join(x => x.Left().Accounts.As(x.a).On(x.a.userId == x.u.id))` + +Coverage references: + +- `DynamORM.Tests/Select/ParserTests.cs` +- `DynamORM.Tests/Select/LegacyParserTests.cs` + +## Choosing Between Syntaxes + +Use dynamic table syntax when: + +- You want short CRUD calls quickly. +- Table/column selection is runtime-driven. + +Use fluent builder syntax when: + +- You want explicit SQL structure and deterministic command text. +- You need complex joins/subqueries/parser features. +- You prefer typed projections (`Execute()`, `ScalarAs()`). + +## Stored Procedure Deep Dive + +`db.Procedures` provides dynamic invocation of stored procedures. + +## Basic Calls + +```csharp +var r0 = db.Procedures.sp_Exp_Scalar(); +var r1 = db.Procedures.sp_Exp_Scalar(); +``` + +## Namespaced Procedure Names + +`TryGetMember` composes member segments before invocation: + +```csharp +var r = db.Procedures.dbo.reporting.sp_MonthlySales(); +``` + +Final procedure name becomes `dbo.reporting.sp_MonthlySales`. + +## Direction Prefixes + +Argument name prefixes drive parameter direction: + +- `out_` => `ParameterDirection.Output` +- `ret_` => `ParameterDirection.ReturnValue` +- `both_` => `ParameterDirection.InputOutput` + +Example: + +```csharp +dynamic res = db.Procedures.sp_Test_Scalar_In_Out( + inp: Guid.NewGuid(), + out_outp: Guid.Empty, + ret_Return: 0); +``` + +Returned object exposes values without prefixes. + +## Advanced Parameter Shape + +You can pass: + +- plain values +- `DynamicColumn` with schema and direction metadata +- `DynamicSchemaColumn` +- `DynamicExpando` / `ExpandoObject` + +Example with explicit output schema: + +```csharp +var res = db.Procedures.sp_Exp_SomeInputAndOutput( + Name: "G4g4r1n", + out_Result: new DynamicSchemaColumn { Name = "Result", Size = 256 }, + ret_Return: 0); +``` + +## Generic Result Shapes + +From `DynamicProcedureInvoker` behavior: + +- `T == IDataReader`: returns cached reader (`CachedReader()`). +- `T == DataTable`: materializes via `ToDataTable()`. +- `T == IEnumerable`: dynamic row enumeration. +- `T == IEnumerable`: scalar conversion per row. +- `T == IEnumerable`: mapping with mapper cache. +- `T == class`: mapped single/object payload. + +## Return and Output Aggregation + +When output/return params are present, DynamORM aggregates: + +- main result +- each output parameter +- return value + +into a dynamic object or mapped result type (if a mapping type is provided).