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

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:

  • IsKey
  • AllowNull
  • IsNoInsert
  • IsNoUpdate
  • Type, 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 => Insert
  • Existing + modified => Update
  • ToBeDeleted => Delete
  • Deleted => throws for write operations
  • Unknown => 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 = 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.