Mark a field with [Encrypted] to have values automatically encrypted before
INSERT/UPDATE and decrypted after SELECT.
public class SecureData : IdentityRecord
{
[Column("SSN")]
[Encrypted(typeof(AesEncryption), EncryptionMethodType.AllDataEncrypted)]
public TString SSN = new TString();
}Implement EncryptionAlgorithm:
public class AesEncryption : EncryptionAlgorithm
{
public override int GetMaxFieldLength() => 256;
public override object Encrypt(object plaintext)
{
// ... encrypt plaintext string, return Base64 string ...
}
public override object Decrypt(object ciphertext)
{
// ... decrypt Base64 string, return plaintext ...
}
public override EncryptionMethodType GetEncryptionMethodType()
=> EncryptionMethodType.AllDataEncrypted;
}
AllDataEncryptedmeans the entire field value is encrypted (safe to query withEqualTermif you encrypt the search value too).PartialEncryptionmeans only part of the value is encrypted — this type cannot be used in query terms (the ORM will throw aPersistenceException).
Implement IDBFieldMapper to handle non-standard CLR ↔ DB type conversions:
public class JsonMapper : IDBFieldMapper
{
private Record _container;
public void SetContainingDataObject(Record obj) => _container = obj;
public object ConvertToDBValue(object value)
=> value is MyStruct s ? System.Text.Json.JsonSerializer.Serialize(s) : null;
public object ConvertFromDBValue(object value)
=> value is string json ? System.Text.Json.JsonSerializer.Deserialize<MyStruct>(json) : default;
}
// Usage on a field:
[Column("Metadata")]
[FieldMapping(typeof(JsonMapper))]
public TString Metadata = new TString();When you have an abstract base entity and multiple concrete subtypes (table-per-hierarchy
or table-per-type), register the mapping in your BaseFactory:
public class AppFactory : BaseFactory
{
protected override void CreateTypeMap()
{
// "When the ORM needs to create a PaymentMethod, use CreditCardPayment instead"
AddTypeMapping(typeof(PaymentMethod), typeof(CreditCardPayment));
}
}Pass AppFactory to the connection constructor.
LookupRecord is a base class for reference / code-table entities whose rows are
cached in memory after the first load.
[Table("Statuses")]
public class Status : LookupRecord
{
[Column("Code")] public TString Code = new TString();
[Column("Label")] public TString Label = new TString();
public override void PrimeAndQueryCache(QueryTerm term, SortOrder sort, int version)
{
// Load all rows once into a static dictionary here
}
}When the ORM resolves a foreign-key relationship to a LookupRecord, it reads from
the in-memory cache rather than issuing a JOIN, reducing round trips for high-read
reference tables.
Accumulate operations and flush them in a single database round-trip:
foreach (var item in itemsToInsert)
{
var p = new Product(conn);
p.Name.SetValue(item.Name);
p.Price.SetValue(item.Price);
p.QueueForInsert(); // deferred
}
conn.ProcessActionQueue(); // all inserts in one transaction
conn.ClearActionQueue(); // remove any leftover pending operationsSimilarly: QueueForUpdate(), QueueForDelete().
var result = conn.ExecStoredProcedure(
template, "usp_GetProductsByCategory",
0 /*start*/, 0 /*count (0=all)*/,
new Record.SPInputParameter("@CategoryID", 2));// Returns an RecordCollection populated from the query
var results = conn.ExecSQL(template,
"SELECT * FROM Products WHERE CreatedAt > @since",
new Dictionary<string, object> { { "@since", DateTime.Today.AddDays(-7) } });
// Returns a BaseReader for low-level access
using var reader = conn.ExecSQL("SELECT COUNT(*) FROM Products");
reader.Read();
int count = (int)reader.GetValue(0);When IsObjectTemplatingEnabled is true on the connection, objects passed to Create()
are pre-populated from a cached template rather than being default-constructed.
Override in a custom connection subclass to enable:
public override bool IsObjectTemplatingEnabled => true;The ORM inspects the database schema on first use of each table:
- Reads column names, types, nullability, precision, and scale from
INFORMATION_SCHEMA - Caches the schema per connection string
- Uses
sp_pkeysto discover primary key columns
You can inspect the discovered metadata:
List<TargetFieldInfo> columns = conn.GetTargetFieldInfo("Products");
foreach (var col in columns)
Console.WriteLine($"{col.TargetName}: {col.TargetType.Name}, PK={col.IsInPK}");