Integrate deep-dive content into API docs and expand mapping
This commit is contained in:
@@ -9,7 +9,6 @@ Full documentation is available in [`docs/README.md`](docs/README.md):
|
|||||||
- Quick start
|
- Quick start
|
||||||
- Dynamic table API
|
- Dynamic table API
|
||||||
- Fluent builder API
|
- Fluent builder API
|
||||||
- Syntax and procedure deep dive
|
|
||||||
- Mapping and entity lifecycle
|
- Mapping and entity lifecycle
|
||||||
- Transaction/disposal semantics
|
- Transaction/disposal semantics
|
||||||
- Stored procedures
|
- Stored procedures
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ This documentation is based on:
|
|||||||
- [Quick Start](quick-start.md)
|
- [Quick Start](quick-start.md)
|
||||||
- [Dynamic Table API](dynamic-table-api.md)
|
- [Dynamic Table API](dynamic-table-api.md)
|
||||||
- [Fluent Builder API](fluent-builder-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)
|
- [Mapping and Entities](mapping-and-entities.md)
|
||||||
- [Transactions and Disposal](transactions-and-disposal.md)
|
- [Transactions and Disposal](transactions-and-disposal.md)
|
||||||
- [Stored Procedures](stored-procedures.md)
|
- [Stored Procedures](stored-procedures.md)
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
# Dynamic Table API
|
# Dynamic Table API
|
||||||
|
|
||||||
The dynamic table API centers on `DynamicTable` and allows expressive calls like:
|
The dynamic table API centers on `DynamicTable` and allows concise runtime calls.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
dynamic users = db.Table("users");
|
dynamic users = db.Table("users");
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This API is best when table/column selection is dynamic or you want very short CRUD code.
|
||||||
|
|
||||||
## Read Operations
|
## Read Operations
|
||||||
|
|
||||||
Examples backed by `DynamORM.Tests/Select/DynamicAccessTests.cs`:
|
Examples backed by `DynamORM.Tests/Select/DynamicAccessTests.cs`:
|
||||||
@@ -23,6 +25,14 @@ users.Single(id: 19);
|
|||||||
users.Query(columns: "first,last", order: "id:desc");
|
users.Query(columns: "first,last", order: "id:desc");
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Filtering with Named Arguments
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
users.Count(first: "Ori");
|
||||||
|
users.Single(code: "101");
|
||||||
|
users.Query(columns: "id,first", id: 19);
|
||||||
|
```
|
||||||
|
|
||||||
## Conditions with `DynamicColumn`
|
## Conditions with `DynamicColumn`
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
@@ -30,18 +40,32 @@ users.Count(where: new DynamicColumn("id").Greater(100));
|
|||||||
users.Count(where: new DynamicColumn("login").Like("Hoyt.%"));
|
users.Count(where: new DynamicColumn("login").Like("Hoyt.%"));
|
||||||
users.Count(where: new DynamicColumn("id").Between(75, 100));
|
users.Count(where: new DynamicColumn("id").Between(75, 100));
|
||||||
users.Count(where: new DynamicColumn("id").In(75, 99, 100));
|
users.Count(where: new DynamicColumn("id").In(75, 99, 100));
|
||||||
|
users.Count(where: new DynamicColumn("id").In(new[] { 75, 99, 100 }));
|
||||||
|
```
|
||||||
|
|
||||||
|
Using aggregate expressions in a condition:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
users.Count(condition1: new DynamicColumn
|
||||||
|
{
|
||||||
|
ColumnName = "email",
|
||||||
|
Aggregate = "length",
|
||||||
|
Operator = DynamicColumn.CompareOperator.Gt,
|
||||||
|
Value = 27
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Insert
|
## Insert
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
users.Insert(code: "201", first: "Juri", last: "Gagarin");
|
users.Insert(code: "201", first: "Juri", last: "Gagarin", email: "juri.gagarin@megacorp.com");
|
||||||
|
|
||||||
users.Insert(values: new
|
users.Insert(values: new
|
||||||
{
|
{
|
||||||
code = "202",
|
code = "202",
|
||||||
first = "Juri",
|
first = "Juri",
|
||||||
last = "Gagarin"
|
last = "Gagarin",
|
||||||
|
email = "juri.gagarin@megacorp.com"
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -69,10 +93,24 @@ users.Delete(where: new { id = 14, code = 14 });
|
|||||||
```csharp
|
```csharp
|
||||||
users.Count(type: typeof(User), columns: "id");
|
users.Count(type: typeof(User), columns: "id");
|
||||||
users.Query(type: typeof(User));
|
users.Query(type: typeof(User));
|
||||||
|
|
||||||
|
var list = (users.Query(type: typeof(User)) as IEnumerable<object>)
|
||||||
|
.Cast<User>()
|
||||||
|
.ToList();
|
||||||
```
|
```
|
||||||
|
|
||||||
These usage patterns are covered in `DynamORM.Tests/Select/TypedAccessTests.cs`.
|
These usage patterns are covered in `DynamORM.Tests/Select/TypedAccessTests.cs`.
|
||||||
|
|
||||||
|
## When to Prefer Fluent Builder Instead
|
||||||
|
|
||||||
|
Use fluent builders when:
|
||||||
|
|
||||||
|
- Query structure is complex (joins/subqueries/having).
|
||||||
|
- You need deterministic SQL text assertions.
|
||||||
|
- You prefer strongly typed lambda parser expressions.
|
||||||
|
|
||||||
|
See [Fluent Builder API](fluent-builder-api.md).
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Dynamic member names map to table/column names and builder conventions.
|
- Dynamic member names map to table/column names and builder conventions.
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
# Fluent Builder API
|
# Fluent Builder API
|
||||||
|
|
||||||
The fluent API is built around interfaces like `IDynamicSelectQueryBuilder`, `IDynamicInsertQueryBuilder`, `IDynamicUpdateQueryBuilder`, and `IDynamicDeleteQueryBuilder`.
|
The fluent API is built around interfaces like:
|
||||||
|
|
||||||
|
- `IDynamicSelectQueryBuilder`
|
||||||
|
- `IDynamicInsertQueryBuilder`
|
||||||
|
- `IDynamicUpdateQueryBuilder`
|
||||||
|
- `IDynamicDeleteQueryBuilder`
|
||||||
|
|
||||||
|
This API is best when SQL structure must be explicit and composable.
|
||||||
|
|
||||||
## Core Select Flow
|
## Core Select Flow
|
||||||
|
|
||||||
@@ -26,10 +33,11 @@ using (var query = db.From<User>("u")
|
|||||||
|
|
||||||
## Parser Lambda Patterns
|
## Parser Lambda Patterns
|
||||||
|
|
||||||
The parser supports patterns tested in `DynamORM.Tests/Select/ParserTests.cs`:
|
The parser supports patterns tested in `DynamORM.Tests/Select/ParserTests.cs` and `DynamORM.Tests/Select/LegacyParserTests.cs`:
|
||||||
|
|
||||||
- `From(x => x.dbo.Users)`
|
- `From(x => x.dbo.Users)`
|
||||||
- `From(x => x.dbo.Users.As(x.u))`
|
- `From(x => x.dbo.Users.As(x.u))`
|
||||||
|
- `From(x => "dbo.Users AS u")`
|
||||||
- `Join(x => x.Left().Accounts.As(x.a).On(x.a.userId == x.u.id))`
|
- `Join(x => x.Left().Accounts.As(x.a).On(x.a.userId == x.u.id))`
|
||||||
- `Where(x => x.Or(x.u.id > 100))`
|
- `Where(x => x.Or(x.u.id > 100))`
|
||||||
- `Select(x => x.u.first.As(x.firstName))`
|
- `Select(x => x.u.first.As(x.firstName))`
|
||||||
@@ -37,10 +45,28 @@ The parser supports patterns tested in `DynamORM.Tests/Select/ParserTests.cs`:
|
|||||||
- `Having(x => x.Count() > 1)`
|
- `Having(x => x.Count() > 1)`
|
||||||
- `OrderBy(x => x.u.id.Desc())`
|
- `OrderBy(x => x.u.id.Desc())`
|
||||||
|
|
||||||
|
## Joins and Projection Example
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using (var query = db.From("users", "u")
|
||||||
|
.Join(x => x.Left().profiles.As(x.p).On(x.p.user_id == x.u.id))
|
||||||
|
.Select(
|
||||||
|
x => x.u.id,
|
||||||
|
x => x.u.first.As(x.firstName),
|
||||||
|
x => x.p.city.As(x.city))
|
||||||
|
.Where(x => x.u.id > 10)
|
||||||
|
.OrderBy(x => x.u.id.Desc()))
|
||||||
|
{
|
||||||
|
var rows = query.Execute().ToList();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Subqueries
|
## Subqueries
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var sub = new DynamicSelectQueryBuilder(db).From(x => x.dbo.Users);
|
var sub = new DynamicSelectQueryBuilder(db)
|
||||||
|
.From(x => x.dbo.Users)
|
||||||
|
.Where(x => x.id > 100);
|
||||||
|
|
||||||
using (var query = new DynamicSelectQueryBuilder(db)
|
using (var query = new DynamicSelectQueryBuilder(db)
|
||||||
.From(x => x(sub).As("u"))
|
.From(x => x(sub).As("u"))
|
||||||
@@ -61,7 +87,10 @@ var count = db.From<User>()
|
|||||||
## Modify Builders
|
## Modify Builders
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
db.Insert("users").Values("code", "301").Values("first", "Ada").Execute();
|
db.Insert("users")
|
||||||
|
.Values("code", "301")
|
||||||
|
.Values("first", "Ada")
|
||||||
|
.Execute();
|
||||||
|
|
||||||
db.Update("users")
|
db.Update("users")
|
||||||
.Values("first", "Alicia")
|
.Values("first", "Alicia")
|
||||||
@@ -73,6 +102,15 @@ db.Delete("users")
|
|||||||
.Execute();
|
.Execute();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Typed variant:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
db.Insert<User>()
|
||||||
|
.Values(x => x.code, "302")
|
||||||
|
.Values(x => x.first, "Grace")
|
||||||
|
.Execute();
|
||||||
|
```
|
||||||
|
|
||||||
## SQL Inspection
|
## SQL Inspection
|
||||||
|
|
||||||
You can inspect generated SQL from builder objects:
|
You can inspect generated SQL from builder objects:
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
# Mapping and Entities
|
# Mapping and Entities
|
||||||
|
|
||||||
DynamORM supports attribute-driven mapping and entity lifecycle helpers.
|
DynamORM mapping is a core feature, not just a convenience layer.
|
||||||
|
|
||||||
## Mapping Attributes
|
It controls:
|
||||||
|
|
||||||
|
- class-to-table resolution
|
||||||
|
- property-to-column resolution
|
||||||
|
- key semantics for updates/deletes
|
||||||
|
- schema override details (`DbType`, size, precision, scale)
|
||||||
|
- lifecycle operations (`Insert`, `Update`, `Delete`, `Refresh`, `Save`)
|
||||||
|
|
||||||
|
## Mapping Resolution Model
|
||||||
|
|
||||||
|
At runtime, `DynamicMapperCache` builds and caches `DynamicTypeMap` for each type.
|
||||||
|
|
||||||
|
`DynamicTypeMap` is used by:
|
||||||
|
|
||||||
|
- typed query execution (`Execute<T>()`)
|
||||||
|
- dynamic projection to classes
|
||||||
|
- entity lifecycle operations in `DynamicEntityBase`
|
||||||
|
- procedure result mapping
|
||||||
|
- validation (`ValidateObject`)
|
||||||
|
|
||||||
## `TableAttribute`
|
## `TableAttribute`
|
||||||
|
|
||||||
@@ -14,9 +32,11 @@ public class User
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
- `Name`: table name override.
|
- `Name`: table name override.
|
||||||
- `Owner`: schema/owner segment.
|
- `Owner`: schema/owner segment.
|
||||||
- `Override`: prefer attribute schema info over provider schema.
|
- `Override`: prefer attribute schema over database schema (important when provider schema is limited).
|
||||||
|
|
||||||
## `ColumnAttribute`
|
## `ColumnAttribute`
|
||||||
|
|
||||||
@@ -28,6 +48,9 @@ public class User
|
|||||||
|
|
||||||
[Column("email")]
|
[Column("email")]
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
[Column("created_at", false, DbType.DateTime)]
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -39,13 +62,40 @@ Important flags:
|
|||||||
- `IsNoUpdate`
|
- `IsNoUpdate`
|
||||||
- `Type`, `Size`, `Precision`, `Scale`
|
- `Type`, `Size`, `Precision`, `Scale`
|
||||||
|
|
||||||
## Ignore Fields
|
These influence generated parameters and update/insert behavior.
|
||||||
|
|
||||||
Use `IgnoreAttribute` to skip properties in mapper-driven workflows.
|
## Advanced Mapping Example
|
||||||
|
|
||||||
## Validation
|
```csharp
|
||||||
|
[Table(Name = "users", Override = true)]
|
||||||
|
public class UserEntity : DynamicEntityBase
|
||||||
|
{
|
||||||
|
[Column("id", true, DbType.Int32)]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
`RequiredAttribute` can define value rules.
|
[Column("code", false, DbType.String, 32)]
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
[Column("email", false, DbType.String, 256)]
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
[Column("created_at", false, DbType.DateTime)]
|
||||||
|
[Ignore]
|
||||||
|
public DateTime CreatedAtInternal { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ignore and Partial Mapping
|
||||||
|
|
||||||
|
Use `[Ignore]` when:
|
||||||
|
|
||||||
|
- property is computed locally
|
||||||
|
- property is not persisted
|
||||||
|
- property should not participate in auto update/insert
|
||||||
|
|
||||||
|
## Validation (`RequiredAttribute`)
|
||||||
|
|
||||||
|
`RequiredAttribute` can enforce range and null/empty semantics.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public class Profile
|
public class Profile
|
||||||
@@ -63,33 +113,65 @@ var issues = DynamicMapperCache.GetMapper<Profile>().ValidateObject(profile);
|
|||||||
|
|
||||||
Validation scenarios are verified in `DynamORM.Tests/Helpers/Validation/ObjectValidationTest.cs`.
|
Validation scenarios are verified in `DynamORM.Tests/Helpers/Validation/ObjectValidationTest.cs`.
|
||||||
|
|
||||||
## `DynamicEntityBase`
|
## `DynamicEntityBase` Lifecycle
|
||||||
|
|
||||||
`DynamicEntityBase` tracks state and changed fields.
|
`DynamicEntityBase` tracks state and changed fields.
|
||||||
|
|
||||||
|
State-driven `Save(db)` behavior:
|
||||||
|
|
||||||
|
- `New` => `Insert`
|
||||||
|
- `Existing` + modified => `Update`
|
||||||
|
- `ToBeDeleted` => `Delete`
|
||||||
|
- `Deleted` => throws for write operations
|
||||||
|
- `Unknown` => throws (explicit state needed)
|
||||||
|
|
||||||
|
Example entity with property change tracking:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public class UserEntity : DynamicEntityBase
|
public class UserEntity : DynamicEntityBase
|
||||||
{
|
{
|
||||||
|
private string _first;
|
||||||
|
|
||||||
[Column("id", true)]
|
[Column("id", true)]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
[Column("first")]
|
[Column("first")]
|
||||||
public string First { get; set; }
|
public string First
|
||||||
|
{
|
||||||
|
get => _first;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
OnPropertyChanging(nameof(First), _first, value);
|
||||||
|
_first = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Available behaviors:
|
Typical flow:
|
||||||
|
|
||||||
- `Validate()`
|
```csharp
|
||||||
- `Insert(db)`
|
var user = db.From<UserEntity>()
|
||||||
- `Update(db)`
|
.Where(x => x.id == 19)
|
||||||
- `Delete(db)`
|
.Execute<UserEntity>()
|
||||||
- `Refresh(db)`
|
.First();
|
||||||
- `Save(db)` driven by `DynamicEntityState`
|
|
||||||
|
|
||||||
## Repository Base
|
user.SetDynamicEntityState(DynamicEntityState.Existing);
|
||||||
|
user.First = "Yuri";
|
||||||
|
user.Save(db); // updates only changed fields when possible
|
||||||
|
```
|
||||||
|
|
||||||
`DynamicRepositoryBase<T>` provides common operations:
|
## Refresh and Key Requirements
|
||||||
|
|
||||||
|
`Refresh(db)`, `Update(db)`, and `Delete(db)` rely on key columns.
|
||||||
|
|
||||||
|
If no key mapping is available, entity update/delete operations throw.
|
||||||
|
|
||||||
|
Always map at least one key (`IsKey = true`) for mutable entities.
|
||||||
|
|
||||||
|
## Repository Layer (`DynamicRepositoryBase<T>`)
|
||||||
|
|
||||||
|
`DynamicRepositoryBase<T>` provides:
|
||||||
|
|
||||||
- `GetAll()`
|
- `GetAll()`
|
||||||
- `GetByQuery(...)`
|
- `GetByQuery(...)`
|
||||||
@@ -98,4 +180,11 @@ Available behaviors:
|
|||||||
- `Delete(...)`
|
- `Delete(...)`
|
||||||
- `Save(...)`
|
- `Save(...)`
|
||||||
|
|
||||||
It ensures query/table compatibility for `T` unless explicitly bypassed.
|
It validates that query tables match mapped type `T` before executing typed enumeration.
|
||||||
|
|
||||||
|
## Practical Recommendations
|
||||||
|
|
||||||
|
- Always define keys explicitly in mapped models.
|
||||||
|
- Use `Override = true` when provider schema metadata is weak or inconsistent.
|
||||||
|
- Keep entities focused on persistence fields; use `[Ignore]` for non-persisted members.
|
||||||
|
- Use validation on boundary objects before persisting.
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
Stored procedure support is available through `db.Procedures` when `DynamicDatabaseOptions.SupportStoredProcedures` is enabled.
|
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
|
## Basic Invocation
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
@@ -11,26 +9,7 @@ var scalar = db.Procedures.sp_Exp_Scalar();
|
|||||||
var scalarTyped = db.Procedures.sp_Exp_Scalar<int>();
|
var scalarTyped = db.Procedures.sp_Exp_Scalar<int>();
|
||||||
```
|
```
|
||||||
|
|
||||||
## Input, Output, Return Parameters
|
## Schema-Qualified Invocation
|
||||||
|
|
||||||
Prefixes in argument names control parameter direction:
|
|
||||||
|
|
||||||
- `out_` for output
|
|
||||||
- `ret_` for return value
|
|
||||||
- `both_` for input/output
|
|
||||||
|
|
||||||
Example pattern:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
var res = db.Procedures.sp_Exp_SomeInputAndOutput<
|
|
||||||
string,
|
|
||||||
MyProcResult>(
|
|
||||||
Name: "G4g4r1n",
|
|
||||||
out_Result: new DynamicSchemaColumn { Size = 256 },
|
|
||||||
ret_Return: 0);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Nested Procedure Names
|
|
||||||
|
|
||||||
Dynamic member chaining builds qualified names:
|
Dynamic member chaining builds qualified names:
|
||||||
|
|
||||||
@@ -40,14 +19,82 @@ var res = db.Procedures.dbo.reporting.MyProc();
|
|||||||
|
|
||||||
This resolves to `dbo.reporting.MyProc`.
|
This resolves to `dbo.reporting.MyProc`.
|
||||||
|
|
||||||
## Result Mapping
|
## Input, Output, Return, InputOutput Parameters
|
||||||
|
|
||||||
If generic return types are provided, DynamORM attempts mapper-based projection into the target type.
|
Prefixes in argument names control parameter direction:
|
||||||
|
|
||||||
If output parameters are present, result payload is assembled from:
|
- `out_` for output
|
||||||
|
- `ret_` for return value
|
||||||
|
- `both_` for input/output
|
||||||
|
|
||||||
- main scalar/resultset-derived value
|
Example:
|
||||||
- output values
|
|
||||||
- optional return value
|
|
||||||
|
|
||||||
The behavior is implemented in `DynamORM/DynamicProcedureInvoker.cs` and documented in XML examples in `DynamORM/DynamicDatabase.cs`.
|
```csharp
|
||||||
|
dynamic res = db.Procedures.sp_Test_Scalar_In_Out(
|
||||||
|
inp: Guid.NewGuid(),
|
||||||
|
out_outp: Guid.Empty,
|
||||||
|
ret_Return: 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using `DynamicSchemaColumn` for Explicit Output Shape
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var res = db.Procedures.sp_Exp_SomeInputAndOutput<string, ProcResult>(
|
||||||
|
Name: "G4g4r1n",
|
||||||
|
out_Result: new DynamicSchemaColumn
|
||||||
|
{
|
||||||
|
Name = "Result",
|
||||||
|
Size = 256
|
||||||
|
},
|
||||||
|
ret_Return: 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using `DynamicColumn` for Direction + Value + Schema
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var res = db.Procedures.sp_WithInputOutput(
|
||||||
|
both_State: new DynamicColumn
|
||||||
|
{
|
||||||
|
ColumnName = "State",
|
||||||
|
ParameterDirection = ParameterDirection.InputOutput,
|
||||||
|
Value = "pending",
|
||||||
|
Schema = new DynamicSchemaColumn { Name = "State", Size = 32 }
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Result Shapes
|
||||||
|
|
||||||
|
From `DynamicProcedureInvoker` behavior:
|
||||||
|
|
||||||
|
- `T == IDataReader`: returns `CachedReader()` result.
|
||||||
|
- `T == DataTable`: materializes via `ToDataTable(...)`.
|
||||||
|
- `T == IEnumerable<object>`: dynamic row enumeration.
|
||||||
|
- `T == IEnumerable<primitive>`: converts first column of each row.
|
||||||
|
- `T == IEnumerable<class>`: maps rows via mapper cache.
|
||||||
|
- `T == class`: maps structured result to a class.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
IDataReader rdr = db.Procedures.MyProc<IDataReader>();
|
||||||
|
DataTable table = db.Procedures.MyProc<DataTable>();
|
||||||
|
List<int> ids = db.Procedures.MyProc<List<int>>();
|
||||||
|
List<User> users = db.Procedures.MyProc<List<User>>();
|
||||||
|
User user = db.Procedures.MyProc<User>();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output and Return Value Aggregation
|
||||||
|
|
||||||
|
When output and/or return values are used, DynamORM aggregates:
|
||||||
|
|
||||||
|
- main result
|
||||||
|
- output parameters
|
||||||
|
- return value
|
||||||
|
|
||||||
|
into a dynamic object or mapped class (if a target type is provided).
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Enable `DynamicDatabaseOptions.SupportStoredProcedures` in options.
|
||||||
|
- Prefix stripping is automatic in result keys (`out_Result` becomes `Result`).
|
||||||
|
- Behavior is implemented in `DynamORM/DynamicProcedureInvoker.cs` and XML examples in `DynamORM/DynamicDatabase.cs`.
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
# 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<dynamic>).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<Users>("u");
|
|
||||||
```
|
|
||||||
|
|
||||||
Composable select:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using (var query = db.From<Users>("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<T>()`, `ScalarAs<T>()`).
|
|
||||||
|
|
||||||
## 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<int>();
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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<string, MyProcResult>(
|
|
||||||
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<object>`: dynamic row enumeration.
|
|
||||||
- `T == IEnumerable<primitive>`: scalar conversion per row.
|
|
||||||
- `T == IEnumerable<class>`: 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).
|
|
||||||
Reference in New Issue
Block a user