EfCore.Boost builds directly on Entity Framework Core’s model-building foundations. You still define entities, relationships, views, and read models using normal EF Core techniques.
What EfCore.Boost adds is:
- higher-level intent attributes or equivalent fluent API options
- uniform conventions across database engines
- predictable naming and schema behavior
- correct timestamp handling
- strong string and text semantics
- safe and explicit foreign-key behavior
- first-class support for views and read models
The result is a model that behaves consistently across SQL Server, PostgreSQL, and MySQL — without scattering provider–specific decisions through your code.
EfCore.Boost works best when the model clearly communicates intent. In practice, this means you should be deliberate about attributes or equivalent fluent configuration on the properties where database providers commonly differ or where EF Core defaults can be ambiguous.
As a general rule, explicitly configure:
-
unique numeric identifiers
Use Boost identity attributes such as[DbAutoUid]/[DbUid]for integer or long identifiers that should be generated by the database. -
GUID values that require database generation semantics
Use[DbGuid]when the database should generate GUID values consistently across providers. -
strings
Use string intent attributes such as[StrCode],[StrShort],[StrMed],[StrLong],[Text], or semantic string attributes such as[Name],[Email],[Title],[Url], etc. -
decimals
Use decimal intent attributes such as[Money],[Price],[Qty],[Rate],[Percentage],[Latitude], or[Longitude]when the numeric meaning matters.
This is especially important for strings, because length and text semantics are part of the database contract. A string without an explicit intent is treated as unbounded text/provider-default text. That may be correct for descriptions or large content, but it is usually not ideal for names, codes, searchable fields, indexed columns, or user-facing labels.
For decimals, EfCore.Boost applies a consistent default precision across providers when no attribute is specified. However, adding a decimal intent attribute is still preferred when the property represents a known concept such as money, price, quantity, rate, percentage, or coordinates. The attribute documents the domain meaning and allows Boost, tooling, and future maintainers to understand why that precision is appropriate.
In short:
| Property kind | Recommended approach |
|---|---|
| Integer/long database-generated ID | Mark with [DbAutoUid] / [DbUid] |
| Database-generated GUID | Mark with [DbGuid] |
| Short, indexed, searchable, or meaningful string | Mark with a Str* or semantic string attribute |
| Free-form large text | Mark with [Text] when you want to document that intent |
| Decimal with business meaning | Prefer a semantic decimal attribute such as [Money], [Qty], [Rate], etc. |
| Decimal without attribute | Uses Boost’s provider-consistent default precision |
float / double |
Not standardized by Boost; use only when approximate numeric behavior is intended |
The goal is not to decorate every property unnecessarily. The goal is to make the model explicit wherever storage semantics, generation behavior, indexing, precision, or cross-provider portability matter.
EfCore.Boost conventions are enabled with a single call inside your DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyEfBoostConventions(this);
}This activates:
- provider-aware schema and naming
- EF view support
- Boost string attributes (StrShort, StrMed, etc.)
- UTC & timestamp normalization
- cascade delete policy
- Postgres-specific fixes (citext, timestamp)
- MySQL timezone handling
- view key support
EF continues to work normally — EfCore.Boost simply aligns environments and removes ambiguity.
Entity Framework Core already provides excellent relational modeling support, and most standard EF Core attributes and fluent configuration work perfectly across SQL Server, PostgreSQL, and MySQL.
Normal relational concepts such as:
- Keys
- Relationships
- Foreign keys
- Required fields
- Max length
- Precision
- Indexes
- Navigation properties
- Table mapping
are naturally portable and should continue to use normal EF Core configuration.
However, some EF Core configuration patterns expose provider-specific SQL types, SQL functions, or database generation strategies directly into the model. Those areas become problematic when the same application targets multiple relational database providers.
EfCore.Boost recommends expressing database intent instead of provider-specific storage details wherever possible.
Avoid embedding provider-specific details directly into the EF model when building cross-provider applications.
Common areas to avoid:
HasColumnType(...)[Column(TypeName = ...)]HasDefaultValueSql(...)HasComputedColumnSql(...)- Provider-specific identity configuration
- Provider-specific GUID generation
- Provider-specific sequence usage
- SQL Server
rowversion
Instead, prefer Boost conventions and intent-based attributes/fluent APIs where available.
| Native EF / common usage | Cross-provider issue | Boost recommendation |
|---|---|---|
[Column(TypeName = "nvarchar(50)")] |
SQL Server-specific type name | [StrShort], [StrCode] |
.HasColumnType("varchar(50)") |
Type semantics vary between providers | Boost string intent attributes |
[Column(TypeName = "text")] |
text differs between providers |
[Text] |
.HasColumnType("money") |
SQL Server-specific money type | [Money] |
.HasColumnType("decimal(19,4)") |
Hard-coded provider SQL type syntax | [Money] or precision intent convention |
.HasDefaultValueSql("getutcdate()") |
SQL Server-specific SQL function | [DbDefaultCurrentUtc] |
.HasDefaultValueSql("now()") |
PostgreSQL/MySQL-specific SQL function | [DbDefaultCurrentUtc] |
.HasComputedColumnSql(...) |
SQL syntax differs heavily by provider | Prefer provider-specific migration scripts |
.UseIdentityColumn() |
SQL Server identity strategy | [DbAutoUid] |
.UseIdentityAlwaysColumn() |
PostgreSQL-specific identity strategy | [DbAutoUid] |
.UseSerialColumn() |
PostgreSQL legacy sequence strategy | [DbAutoUid] |
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] |
Provider behavior differs internally | [DbAutoUid] |
SQL Server newsequentialid() |
SQL Server-specific GUID generation | [DbGuid] |
PostgreSQL gen_random_uuid() |
PostgreSQL-specific GUID generation | [DbGuid] |
.HasSequence(...) |
Sequence support and behavior differ by provider | Prefer Boost ID allocation strategies |
[Timestamp] / .IsRowVersion() |
SQL Server-specific concurrency mechanism | [AutoIncrementConcurrency] |
SQL Server commonly uses rowversion:
[Timestamp]
public byte[] RowVersion { get; set; } = [];
//or fluently:
entity.Property(x => x.RowVersion).IsRowVersion();This works well on SQL Server but does not translate naturally across providers.
EfCore.Boost instead recommends provider-neutral numeric concurrency tokens:
[AutoIncrementConcurrency]
public long Version { get; set; }
//or fluently:
entity.Property(x => x.Version).HasAutoIncrementConcurrency();This allows Boost to apply consistent optimistic concurrency behavior across SQL Server, PostgreSQL, and MySQL.
EfCore.Boost supports model configuration through both custom attributes and a fluent API. Both approaches provide identical behavior and can be used interchangeably.
Unlike many fluent configurations that only exist during the model-building phase, EfCore.Boost fluent methods mark the EF Core model with the same metadata annotations as attribute-based configuration. This ensures that the model remains discoverable by other conventions, tooling, or application code regardless of how it was configured.
- Fluent methods do not add CLR attributes (which is impossible at runtime).
- They apply equivalent EF Core annotations to the property metadata.
- The resulting model is semantically equivalent between attribute and fluent styles.
While both styles are supported, attributes are often simpler to visualize and maintain as they keep the metadata directly on the entity classes. The Fluent API provides a powerful alternative for cases where entities should remain clean of dependencies or when configuring models from an external library.
To avoid naming collisions with common domain properties (like Name or Email), fluent methods for semantic attributes use the HasPurpose- prefix.
- Standard technical attributes (like
DbAutoUid,StrShort,Money) use theHas-prefix (e.g.,HasStrShort()). - Semantic attributes (like
Name,Email,AddressCity) use theHasPurpose-prefix (e.g.,HasPurposeName()).
| Intent | Attribute Style | Fluent Style |
|---|---|---|
| Identity | [DbAutoUid] public long Id { get; set; } |
builder.Property(x => x.Id).HasDbAutoUid(); |
| Short String | [StrShort] public string Name { get; set; } |
builder.Property(x => x.Name).HasStrShort(); |
| Purpose: Name | [Name] public string FullName { get; set; } |
builder.Property(x => x.FullName).HasPurposeName(); |
| Money | [Money] public decimal Price { get; set; } |
builder.Property(x => x.Price).HasPurposeMoney(); |
| Qty | [Qty] public decimal Amount { get; set; } |
builder.Property(x => x.Amount).HasPurposeQty(); |
| Concurrency | [AutoIncrementConcurrency] public int Version { get; set; } |
builder.Property(x => x.Version).HasAutoIncrementConcurrency(); |
| No Cascade | [NoCascadeDelete] on navigation |
builder.HasOne(...).WithMany(...).HasNoCascadeDelete(); |
| Schema | [DbSchema("sales")] on class |
builder.Entity<T>().HasDbSchema("sales"); |
[DbSchema("sales")]
public class Customer
{
[DbAutoUid]
public long Id { get; set; }
[StrShort]
public string Name { get; set; }
[Money]
public decimal Balance { get; set; }
[AutoIncrementConcurrency]
public int Version { get; set; }
}protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Customer>(entity =>
{
entity.HasDbSchema("sales");
entity.Property(x => x.Id).HasDbAutoUid();
entity.Property(x => x.Name).HasStrShort();
entity.Property(x => x.Balance).HasPurposeMoney();
entity.Property(x => x.Version).HasAutoIncrementConcurrency();
});
}EfCore.Boost abstracts the differences between engines that support schemas and engines that do not.
Schemas behave normally:
cms.Routes
dbo.Users
Since MySQL lacks schema namespaces in the same form, EfCore.Boost maps schema and table as:
cms_Routes
core_Users
This keeps naming meaningful while remaining portable.
EfCore.Boost ensures:
- consistent logical grouping
- readable migration SQL
- fewer conditional mappings
- identical entity definitions across providers
A view in EfCore.Boost is a supported, intentional read model.
[ViewKey(nameof(Id))]
[DbSchema("cms", Table="CurrentMenuItemsV")]
public class CurrentMenuItemsV
{
public long Id { get; set; }
}modelBuilder.Entity<CurrentMenuItemsV>(entity =>
{
entity.HasDbSchema("cms", "CurrentMenuItemsV");
entity.HasViewKey(x => x.Id);
});EfCore.Boost will:
- treat it as a view, not a table
- handle schema naming correctly
- declare the key EF requires
- ensure the model is read-oriented
Views:
- can be queried normally
- can act as OData sources
- can share types with routine result sets
EfCore.Boost treats views as proper data access surfaces, not awkward EF hacks.
EfCore.Boost standardizes timestamp behavior across engines:
- Normalizes EF mappings so semantics match.
- Encourages UTC everywhere for cross-provider consistency.
- Persisted point-in-time values should normally use
DateTimewith UTC semantics. - Application code should generate these values using
DateTime.UtcNowand avoid storing local server time. - Prevents “server timezone surprise” and produces stable auditing and logging.
DateTimeOffset may be useful at API boundaries where incoming values include an offset, but it is not a fully portable persistence abstraction. For this reason, EfCore.Boost recommends normalizing persisted DateTimeOffset values to UTC as well.
Uses datetime2, which works correctly with UTC. SQL Server can preserve original offsets using datetimeoffset, but other providers do not preserve offset information in the same way.
EfCore.Boost standardizes to:
timestamp with time zone
and expects the application to store UTC explicitly.
EfCore.Boost does not assume MySQL runs in UTC.
Instead:
At the start of every MySQL database session, EfCore.Boost explicitly sets the session timezone to UTC.
This guarantees:
- no drift across environments
- stable timestamps in distributed systems
- predictable data movement
- repeatable queries
EfCore.Boost strongly encourages:
- Store point-in-time values as UTC using
DateTime. - Normalize any
DateTimeOffsetvalues to UTC before persistence. - Convert to local time or specific timezones only at UI boundaries.
- If the original offset must be preserved for business reasons, store it explicitly in a separate column.
This produces deterministic results everywhere.
Cross–database string rules are not uniform. EfCore.Boost smooths differences while preserving power.
EfCore.Boost strongly encourages the use of Case-Insensitive collations to avoid inconsistencies in string comparison or key lookups. While this is not strictly enforced for MySQL and SQL Server, it is strongly recommended for consistent cross-provider behavior. PostgreSQL maps to citext which naturally avoids this problem.
SQL Server:
nvarchar(x)
MySQL:
varchar(x)
PostgreSQL:
- defaults to citext when appropriate
- requires citext extension enabled
- case-insensitive matching
- indexable
- stable search semantics
- reduces logic complexity
For optimal cross-provider compatibility, EfCore.Boost project templates use database-level collation strategies that ensure Case-Insensitivity (CI) and support for modern character sets (UTF-8).
The recommended strategy for SQL Server is to use a modern, UTF-8 enabled, Case-Insensitive, Accent-Sensitive collation.
Preferred Collation: Latin1_General_100_CI_AS_SC_UTF8
- CI (Case Insensitive): Matches
Aanda. - AS (Accent Sensitive): Distinguishes
afromá. - SC (Supplementary Characters): Full support for emoji and supplementary characters.
- UTF8: Native UTF-8 storage (SQL Server 2019+), excellent for cross-language data.
Our templates typically use a fallback strategy during database creation:
Latin1_General_100_CI_AS_SC_UTF8(Modern)Icelandic_100_CI_AS(Regional fallback)Latin1_General_100_CI_AS(Legacy fallback)
The recommended collation for MySQL is the modern Unicode 9.0+ based case-insensitive collation.
Preferred Collation: utf8mb4_0900_sci
- utf8mb4: Full Unicode support (including emoji).
- 0900: Based on Unicode 9.0.0 collation algorithm.
- ai (Accent Sensitive):
- ci (Case Insensitive): Consistent with Boost's case-insensitive design.
PostgreSQL achieves this naturally by mapping strings to citext and dates to timestamp with time zone. This provides case-insensitive behavior and correct UTC handling.
EfCore.Boost encourages case-insensitive designs, especially for:
- indexed columns
- searchable text
- identifiers
- user-facing strings
Preferred settings:
ENCODING = 'UTF8'
LOCALE_PROVIDER = icu
ICU_LOCALE = 'en-US';
Applications requiring language-specific sorting behavior may choose provider-specific collations or ICU locales appropriate for their region or language.
EfCore.Boost’s StrShort, StrMed, StrLong, etc. define intent. They are chosen to be:
- portable across providers
- safely indexable
- practical for application use
- clear to reason about
A frequent fear is: “Does nvarchar length harm performance?”
Reality:
- SQL Server: size rarely matters unless massively abused
- MySQL: performs correctly within EfCore.Boost ranges
- PostgreSQL: handles text types efficiently
Performance issues appear when giant text columns are indexed incorrectly or misused. EfCore.Boost’s sizing avoids most traps.
Anything beyond StrLong essentially behaves like free-text and normally should not be indexed.
So sizing provides:
- clarity
- portability
- safe indexing
- predictable migrations
EfCore.Boost provides attributes that clarify identity intent.
[DbAutoUid]
- declares a property as a key
- aligns generation strategy per provider
- prevents multiple PK definitions
- stabilizes migrations
Guid identities are equally supported and consistently mapped.
EfCore.Boost ensures identity behavior remains predictable during:
- migrations
- bulk insert
- replication
- restore/import scenarios
Handling row concurrency in EF Core is not consistent across database providers.
Different databases rely on different mechanisms:
| Provider | Typical mechanism |
|---|---|
| SQL Server | rowversion / timestamp |
| PostgreSQL | xmin system column |
| MySQL | triggers or manual version columns |
Because these approaches differ in column type, behavior, and EF configuration, writing portable applications becomes difficult.
EfCore.Boost provides a uniform cross-provider approach to concurrency using a simple integer version column.
[AutoIncrementConcurrency]
Applied to an int or long property.
Behavior:
- the column is automatically incremented whenever the row changes
- EF Core treats the column as a concurrency token
- conflicting updates raise the standard EF Core
DbUpdateConcurrencyException - behavior is consistent across SQL Server, PostgreSQL, and MySQL
Benefits:
- predictable cross-database behavior
- no provider-specific column types
- migration friendly
- easy debugging and inspection
Example:
public class Product
{
[DbAutoUid]
public long Id { get; set; }
[StrShort]
public string Name { get; set; }
[AutoIncrementConcurrency]
public long Version { get; set; }
}Whenever the row changes, the version value increments automatically.
If another process updates the row first, EF Core detects the mismatch and throws a concurrency exception.
EF Core's concurrency system is strict by design.
If a concurrency token is present, EF Core always enforces it, which can cause problems in scenarios where the data layer is used for more than user edits, such as:
- data replication
- background synchronization
- import pipelines
- system maintenance tasks
In these scenarios concurrency exceptions are often undesirable.
EfCore.Boost therefore provides an alternative attribute.
[AutoIncrement]
Behavior:
- the column automatically increments whenever the row changes
- no EF concurrency exception is triggered
- the column can be used for manual concurrency checks
Example:
public class Product
{
[DbAutoUid]
public long Id { get; set; }
[StrShort]
public string Name { get; set; }
[AutoIncrement]
public long Version { get; set; }
}The version column still tracks row changes, but the application can decide when and how to verify concurrency.
Typical pattern:
// retrieve current server version
var serverVersion = await uow.Products.QueryUnTracked()
.Where(p => p.Id == rowId).Select(p => p.Version).FirstAsync();
// manual concurrency validation
if (serverVersion != incoming.Version) throw new ConcurrencyConflictException();EfCore.Boost separates version tracking from conflict enforcement.
| Attribute | Behavior |
|---|---|
[AutoIncrementConcurrency] |
Automatically increments the version and enables EF Core concurrency enforcement. |
[AutoIncrement] |
Automatically increments the version but leaves concurrency checks to the application. |
This gives developers control to choose the appropriate model for each workload.
Benefits:
- portable across database providers
- predictable migration behavior
- safer replication scenarios
- flexible concurrency strategies
EF Core and many databases default to cascade deletes.
Enterprise reality: cascade deletes create more harm than good.
They often cause:
- unintended mass deletion
- unpredictable cascade chains
- circular dependency failures
- migration errors
- operational risk
EfCore.Boost disables cascade deletes globally unless explicitly chosen.
You can enable or disable cascade delete for specific relationships using purpose-built attributes on navigation properties.
public class Order
{
[DbAutoUid]
public long Id { get; set; }
// Explicitly enable cascade delete: when Order is deleted, Items are also deleted
[CascadeDelete]
public List<OrderItem> Items { get; set; } = new();
}Alternatively, you can configure the cascade behavior using the Fluent API during model building.
modelBuilder.Entity<Order>()
.HasMany(x => x.Items)
.WithOne(x => x.Order)
.HasForeignKey(x => x.OrderId)
.HasCascadeDelete(); // Enables cascade delete for this relationPolicy:
Delete semantics must be intentional, not accidental.
Benefits:
- safer data lifecycle
- clearer reasoning
- fewer schema traps
- improved portability
- lowered operational risk
EfCore.Boost supports a unified view-model concept.
A model class may represent:
- a database view
- a recordset returned from a routine
This allows:
- OData to operate over the view
- routines to return the same view structure
- consistency without duplication
Recommended usage pattern:
- expose views as repositories when appropriate
- treat routine results as read-only sources
- avoid mapping routines as repositories directly
Application stays consistent, predictable, and easier to maintain.
Model building defines the database logically.
Deployment builds it physically.
EfCore.Boost supports disciplined cross‑database migration strategies. These are documented separately in:
That document covers:
- generating migrations per provider
- consistent schema deployment
- handling EF migration snapshot limitations
- automation using PowerShell / command scripts
This section demonstrates typical attribute usage and how design intent becomes explicit in the model.
[Index(nameof(LastChangedUtc), IsUnique = false, AllDescending = true)]
[Index(nameof(SessionId), IsUnique = false)]
[Index(nameof(Context), nameof(LastChangedUtc), nameof(SessionId), IsUnique = false)]
public class ErrorLog
{
[DbAutoUid]
public long Id { get; set; }
public DateTime LastChangedUtc { get; set; } = DateTime.UtcNow;
public long? SessionId { get; set; }
public int Context { get; set; }
[StrMed]
public string? ErrorMsg { get; set; }
[Text]
public string? ErrorDetails { get; set; }
[ForeignKey(nameof(Context))]
public LogContext? LogContext { get; set; }
public int Tenant { get; set; } = 1;
}Why this matters
[DbAutoUid]ensures consistent identity handling across databases[StrMed]defines intent and keeps size/indexing portable[Text]communicates “large text, not for indexing.”
Note: This is not a strictly necessary attribute since the result is the same as if we skip this.
However, it is good documentation about our intention and that we did not simply forget to mark the string length.
[Index(nameof(Ctx), nameof(Tenant), IsUnique = true)]
public class LogContext
{
[DbAutoUid]
public int Id { get; set; }
public int Ctx { get; set; }
[StrMed]
public string Name { get; set; } = string.Empty;
[Text]
public string? Description { get; set; }
public int LogTypeId { get; set; } = 1;
public int Tenant { get; set; } = 1;
}Clear model intent:
- Explicit uniqueness rules
- Case-insensitive + index-friendly naming
EfCore.Boost treats views as first-class read models.
ViewKey declares an EF identity for tracking and correctness.
[ViewKey(nameof(LoginId), nameof(CustId), nameof(Code))]
public class LoginPermissionsV
{
public long LoginId { get; set; }
public long CustId { get; set; }
[StrCode]
public string Code { get; set; } = string.Empty;
public int ModuleId { get; set; }
public bool IsExternal { get; set; } = false;
public bool IsInternal { get; set; } = false;
}Key advantages:
- Stable composite key without artificial surrogate IDs
[StrCode]expresses “short identifier, case-insensitive, index-safe”- Pure read model with clear semantics
- Works equally well whether backed by a real DB view or routine result
Note on View Mapping: For read-only views, string length (
[StrCode],[StrShort], etc.) and decimal precision ([Money],[Percent], etc.) attributes are technically optional. EF Core mapping is more forgiving for reads than for writes. While these attributes are not strictly required for data retrieval, they are still recommended for semantic clarity and to maintain consistency with the rest of your domain model.
EfCore.Boost custom attributes fall into two clearly separated groups:
-
Model & relationship attributes
These influence how EF Core reasons about the model: schema placement, keys, identity strategy, and relationship behavior.
They do not define column size or numeric precision. -
Column intent attributes (string & decimal)
These describe the semantic intent of a column (code, text, money, quantity, etc.).
Boost translates that intent into consistent, provider-correct column definitions, ensuring data is stored uniformly across SQL Server, PostgreSQL, and MySQL.
Defaults when no attribute is specified:
- string: EF Core default mapping (
text/ provider equivalent), equivalent to[Text] - decimal:
decimal(19,4)(previously provider-specific, now unified)
Boost intentionally does not enforce precision conventions for float or double.
In application-level data modeling, decimal is the natural choice for fixed-precision values such as money, rates, and quantities.
It provides deterministic precision across providers and avoids rounding artifacts inherent in binary floating-point types.
float and double are designed for scientific and computational scenarios where approximate values and wide dynamic range are more important than exact decimal representation.
For this reason, Boost standardizes precision for decimal only.
| Attribute | Applies to | Purpose |
|---|---|---|
ViewKey |
Class (view/read model) | Declares the EF key for a view or read-model so it can be queried correctly. |
DbSchema |
Class | Declares the logical schema (and optionally object name) in a provider-aware way. |
DbUid |
Property | Marks a numeric identity column (identity / sequence / auto-increment depending on provider). |
DbGuid |
Property | Marks a GUID column with provider-correct generation semantics. The value is unique but not necessarily a primary key or identifier. |
DbUid expresses identity semantics.
DbGuid expresses GUID generation, independent of key or identity usage.
These override the default cascade policy documented earlier and are intended for local exceptions only.
| Attribute | Applies to | Purpose | Fluent Style |
|---|---|---|---|
NoCascadeDelete |
Navigation / relationship | Explicitly disables cascade delete for a specific relationship. | .HasNoCascadeDelete() |
CascadeDelete |
Navigation / relationship | Explicitly enables cascade delete for a specific relationship. | .HasCascadeDelete() |
These attributes express string intent, not raw database types.
Boost applies consistent provider-specific column definitions and safe defaults.
| Attribute | Length applied | Purpose |
|---|---|---|
StrCode |
30 | Short identifiers or codes (index-friendly). |
StrShort |
50 | Short application strings (names, labels). |
StrMed |
256 | Medium-length application strings. |
StrLong |
512 | Long application strings. |
Text |
unbounded | Free or unbounded text intent (not index-oriented). |
Default (no attribute): EF Core default string mapping (text / provider equivalent), equivalent to [Text].
However, if you want more expressive intent on your columns, EfCore.Boost
also provides a set of semantic string attributes. These can be used
instead of the raw Str* attributes. They communicate the purpose of
the data rather than just the maximum length of the column.
Internally, Boost maps these semantic attributes to one of the standard string buckets:
StrCode, StrShort, StrMed, StrLong, or Text.
This keeps database schemas consistent while still allowing the model to
clearly express what the data represents, and for tooling to recognize
standard column meanings.
For example, the model could be scanned for country codes automatically, by some tool.
| Attribute | Maps to | Typical usage | Fluent Style |
|---|---|---|---|
CountryCode |
StrCode |
ISO country codes (IS, US, DE). |
.HasPurposeCountryCode() |
CurrencyCode |
StrCode |
ISO currency codes (ISK, EUR, USD). |
.HasPurposeCurrencyCode() |
LanguageCode |
StrCode |
ISO language codes (en, is). |
.HasPurposeLanguageCode() |
CultureCode |
StrCode |
Culture identifiers such as locale codes (en-GB, is-IS). |
.HasPurposeCultureCode() |
MimeType |
StrShort |
MIME content type identifiers. | .HasPurposeMimeType() |
AddressPostalCode |
StrShort |
Postal or ZIP codes. | .HasPurposeAddressPostalCode() |
AddressStreetNumber |
StrShort |
Street number part of an address. | .HasPurposeAddressStreetNumber() |
AddressBuildingUnit |
StrShort |
Apartment, suite, or building unit identifiers. | .HasPurposeAddressBuildingUnit() |
Phone |
StrShort |
Telephone numbers. | .HasPurposePhoneNumber() |
UserName |
StrShort |
Login or account user names. | .HasPurposeUserName() |
Name |
StrMed |
Names of people, entities, or objects. | .HasPurposeName() |
Title |
StrMed |
Titles or headings. | .HasPurposeTitle() |
ExternalRef |
StrMed |
Identifiers referencing external systems. | .HasPurposeExternalRef() |
AddressStreetName |
StrMed |
Street names in addresses. | .HasPurposeAddressStreetName() |
AddressCity |
StrMed |
City or town names. | .HasPurposeAddressCity() |
AddressAdminArea |
StrMed |
Administrative region, state, or province. | .HasPurposeAddressAdminArea() |
AddressRecipientName |
StrMed |
Recipient name used in address fields. | .HasPurposeAddressRecipientName() |
Email |
StrLong |
Email addresses. | .HasPurposeEmail() |
Url |
StrLong |
Web addresses or resource URLs. | .HasPurposeSiteUrl() |
FileName |
StrLong |
File names including extensions. | .HasPurposeFileName() |
Html |
Text |
Stored HTML markup content. | .HasPurposeHtml() |
Markdown |
Text |
Markdown formatted content. | .HasPurposeMarkdown() |
RichText |
Text |
Rich text editor output. | .HasPurposeRichText() |
Json |
Text |
JSON structured content. | .HasPurposeJson() |
Editor |
Text |
Editor JSON payloads such as TipTap content. | .HasPurposeEditor() |
- Only one string intent attribute should be applied to a property.
These attributes express numeric intent rather than raw precision.
Boost enforces uniform precision and scale across providers for decimal only.
They also do express **common domain semantics ** and tooling can use them to recognize standard column meanings.
| Attribute | Precision / Scale | Typical meaning | Fluent Style |
|---|---|---|---|
Percentage |
18,8 | Percentages and ratios. | .HasPurposePercentage() |
Qty |
18,8 | Quantities. | .HasPurposeQty() |
Rate |
18,8 | Rates and factors. | .HasPurposeRate() |
Price |
19,4 | Prices. | .HasPurposePrice() |
Money |
19,4 | Monetary values. | .HasPurposeMoney() |
SortRank |
38,19 | Ranking or scoring values. | .HasPurposeSortRank() |
Scientific |
38,19 | High-precision scientific values. | .HasPurposeScientific() |
Longitude |
9,6 | Geocraphic longitude | .HasPurposeLongitude() |
Latitude |
9,6 | Geocraphic latitude. | .HasPurposeLatitude() |
Default (no attribute): decimal(19,4).
Of course, you may specify the standard EF Core precision attribute instead, for example:
[Precision(16, 3)]Explicit precision configuration always overrides Boost conventions.
Boost standardizes timestamp handling to avoid provider-specific ambiguity.
Recommended usage:
- Use
DateTimefor persisted timestamps. - Store all timestamps in UTC.
- Normalize
DateTimeOffsetto UTC if used for persistence. - Convert to local time only at UI boundaries.
Boost ensures:
- SQL Server uses
datetime2 - PostgreSQL uses
timestamp with time zone - MySQL sessions are forced to UTC
Boost does not rely on database-local timezone interpretation. Application code is responsible for consistent UTC storage.
This prevents:
- server timezone drift
- daylight saving surprises
- cross-environment inconsistencies
- replication confusion
These attributes do not change the database schema.
They exist to express common domain semantics and allow conventions, tooling, or libraries to recognize standard column meanings.
| Attribute | Applies to | Typical usage | Fluent Style |
|---|---|---|---|
Media |
byte[] |
Binary media content such as images, files, or attachments stored in the database. | .HasPurposeMedia() |
Hash |
byte[] |
Cryptographic hashes such as password hashes or integrity hashes. | .HasPurposeHash() |
Salt |
byte[] |
Salt values used together with hashed secrets. | .HasPurposeSalt() |
Encrypted |
byte[] |
Encrypted binary payloads or encrypted field storage. | .HasPurposeEncrypted() |
SigningKey |
byte[] |
Binary signing keys or cryptographic key material used for token signing or verification. | .HasPurposeSigningKey() |
SoftDelete |
bool or DateTime |
Marks a column used for soft-delete state or deletion timestamp (for example IsDeleted). |
.HasPurposeSoftDelete() |
LastChangedUtc |
DateTime |
Timestamp for the last modification of the row. | .HasPurposeLastChangedUtc() |
CreatedUtc |
DateTime |
Timestamp when the record was created. | .HasPurposeCreatedUtc() |
ValidFromUtc |
DateTime |
Start of a validity period for temporal data. | .HasPurposeValidFromUtc() |
ValidToUtc |
DateTime |
End of a validity period for temporal data. | .HasPurposeValidToUtc() |
ExpiresUtc |
DateTime |
Expiration timestamp for temporary or expiring data. | .HasPurposeExpiresUtc() |
Tenant |
long, int, or Guid |
Tenant identifier used for multi-tenant data partitioning. | .HasPurposeTenant() |
Status |
int, short, or string |
Application-defined status or state indicator. | .HasPurposeStatus() |
SoftRef |
Any key type | Lightweight reference to another entity (not a formal foreign key). | .HasPurposeSoftRef() |
BirthDate |
DateTime or DateOnly |
Birth date field, often subject to specific privacy or validation rules. | .HasPurposeBirthDate() |
- Only one string or one decimal intent attribute should be applied per property.
- Explicit fluent configuration always overrides conventions.
- Floating-point types (
float,double) are intentionally left untouched. - These attributes exist to keep models portable, predictable, and consistent across database providers.
For a bulk insert demo we displayd this example model:
[Index(nameof(LastChangedUtc), IsUnique = false, AllDescending = true)]
[Index(nameof(SessionId), IsUnique = false)]
[Index(nameof(Context), nameof(LastChangedUtc), nameof(SessionId), IsUnique = false)]
public class ErrorLog
{
[DbAutoUid]
public long Id { get; set; }
public DateTime LastChangedUtc { get; set; } = DateTime.UtcNow;
public long? SessionId { get; set; }
public int Context { get; set; }
[Title]
public string? ErrorMsg { get; set; }
[Text]
public string? ErrorDetails { get; set; }
[ForeignKey(nameof(Context))]
public LogContext? LogContext { get; set; }
[Tenant]
public int Tenant { get; set; } = 1;
}
[Index(nameof(Ctx), nameof(Tenant), IsUnique = true)]
public class LogContext
{
[DbAutoUid]
public int Id { get; set; }
public int Ctx { get; set; }
[Name]
public string Name { get; set; } = string.Empty;
[Text]
public string? Description { get; set; }
public int LogTypeId { get; set; } = 1;
[Tenant]
public int Tenant { get; set; } = 1;
}We could make the model more expressive by using the semantic attributes provided by EfCore.Boost.
These attributes describe the purpose of the data, enabling clearer domain models while also allowing conventions and tooling to infer appropriate database mappings.
Using these attributes is entirely optional. Projects may choose to rely only on the basic Str* attributes, or define their own semantic attributes and conventions following the same pattern.
[Index(nameof(LastChangedUtc), IsUnique = false, AllDescending = true)]
[Index(nameof(SessionId), IsUnique = false)]
[Index(nameof(Context), nameof(LastChangedUtc), nameof(SessionId), IsUnique = false)]
public class ErrorLog
{
[DbAutoUid]
public long Id { get; set; }
[LastChangedUtc]
public DateTime LastChangedUtc { get; set; } = DateTime.UtcNow;
[ExternalRef]
public long? SessionId { get; set; } //Note: this point s to sessio-info record not current session record
[ExternalRef]
public int Context { get; set; }
[Title]
public string? ErrorMsg { get; set; }
[Text]
public string? ErrorDetails { get; set; }
[ForeignKey(nameof(Context))]
public LogContext? LogContext { get; set; }
[Tenant]
public int Tenant { get; set; } = 1;
}
[Index(nameof(Ctx), nameof(Tenant), IsUnique = true)]
public class LogContext
{
[DbAutoUid]
public int Id { get; set; }
[ExternalRef]
public int Ctx { get; set; }
[Name]
public string Name { get; set; } = string.Empty;
[Text]
public string? Description { get; set; }
[ExternalRef]
public int LogTypeId { get; set; } = 1;
[Tenant]
public int Tenant { get; set; } = 1;
}EfCore.Boost Model Building provides:
✔ Consistent cross‑database modeling
✔ Stable and safe timestamp handling
✔ Practical string & collation strategy
✔ Strong identity conventions
✔ Explicit cascade delete control
✔ First‑class views and read models
✔ MySQL timezone safety
✔ PostgreSQL citext normalization
EfCore.Boost doesn’t replace EF Core philosophy — it stabilizes it for multi‑database, enterprise‑scale systems, so you focus on architecture instead of vendor quirks.