Integrate deep-dive content into API docs and expand mapping
This commit is contained in:
@@ -1,8 +1,26 @@
|
||||
# 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`
|
||||
|
||||
@@ -14,9 +32,11 @@ public class User
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
- `Name`: table name override.
|
||||
- `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`
|
||||
|
||||
@@ -28,6 +48,9 @@ public class User
|
||||
|
||||
[Column("email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Column("created_at", false, DbType.DateTime)]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -39,13 +62,40 @@ Important flags:
|
||||
- `IsNoUpdate`
|
||||
- `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
|
||||
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`.
|
||||
|
||||
## `DynamicEntityBase`
|
||||
## `DynamicEntityBase` Lifecycle
|
||||
|
||||
`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
|
||||
public class UserEntity : DynamicEntityBase
|
||||
{
|
||||
private string _first;
|
||||
|
||||
[Column("id", true)]
|
||||
public int Id { get; set; }
|
||||
|
||||
[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()`
|
||||
- `Insert(db)`
|
||||
- `Update(db)`
|
||||
- `Delete(db)`
|
||||
- `Refresh(db)`
|
||||
- `Save(db)` driven by `DynamicEntityState`
|
||||
```csharp
|
||||
var user = db.From<UserEntity>()
|
||||
.Where(x => x.id == 19)
|
||||
.Execute<UserEntity>()
|
||||
.First();
|
||||
|
||||
## 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()`
|
||||
- `GetByQuery(...)`
|
||||
@@ -98,4 +180,11 @@ Available behaviors:
|
||||
- `Delete(...)`
|
||||
- `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.
|
||||
|
||||
Reference in New Issue
Block a user