191 lines
4.3 KiB
Markdown
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.
|