4.3 KiB
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
[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
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:
IsKeyAllowNullIsNoInsertIsNoUpdateType,Size,Precision,Scale
These influence generated parameters and update/insert behavior.
Advanced Mapping Example
[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.
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=>InsertExisting+ modified =>UpdateToBeDeleted=>DeleteDeleted=> throws for write operationsUnknown=> throws (explicit state needed)
Example entity with property change tracking:
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:
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 = truewhen 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.