Files
DynamORM/docs/mapping-and-entities.md

191 lines
4.3 KiB
Markdown

# Mapping and Entities
DynamORM mapping is a core feature, not just a convenience layer.
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`
```csharp
[Table(Name = "users", Owner = "dbo", Override = true)]
public class User
{
// ...
}
```
Fields:
- `Name`: table name override.
- `Owner`: schema/owner segment.
- `Override`: prefer attribute schema over database schema (important when provider schema is limited).
## `ColumnAttribute`
```csharp
public class User
{
[Column("id", isKey: true)]
public int Id { get; set; }
[Column("email")]
public string Email { get; set; }
[Column("created_at", false, DbType.DateTime)]
public DateTime CreatedAt { get; set; }
}
```
Important flags:
- `IsKey`
- `AllowNull`
- `IsNoInsert`
- `IsNoUpdate`
- `Type`, `Size`, `Precision`, `Scale`
These influence generated parameters and update/insert behavior.
## Advanced Mapping Example
```csharp
[Table(Name = "users", Override = true)]
public class UserEntity : DynamicEntityBase
{
[Column("id", true, DbType.Int32)]
public int Id { get; set; }
[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
{
[Required(1f, 10f)]
public int Rank { get; set; }
[Required(2, true)]
[Required(7, 18, ElementRequirement = true)]
public decimal[] Scores { get; set; }
}
var issues = DynamicMapperCache.GetMapper<Profile>().ValidateObject(profile);
```
Validation scenarios are verified in `DynamORM.Tests/Helpers/Validation/ObjectValidationTest.cs`.
## `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 => _first;
set
{
OnPropertyChanging(nameof(First), _first, value);
_first = value;
}
}
}
```
Typical flow:
```csharp
var user = db.From<UserEntity>()
.Where(x => x.id == 19)
.Execute<UserEntity>()
.First();
user.SetDynamicEntityState(DynamicEntityState.Existing);
user.First = "Yuri";
user.Save(db); // updates only changed fields when possible
```
## 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(...)`
- `Insert(...)`
- `Update(...)`
- `Delete(...)`
- `Save(...)`
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.