Skip to content

Latest commit

 

History

History
1132 lines (833 loc) · 41.1 KB

File metadata and controls

1132 lines (833 loc) · 41.1 KB

Model Building in EfCore.Boost

Consistent Modeling Across SQL Server, PostgreSQL, and MySQL

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.


What Should Be Explicitly Marked?

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.


Applying EfCore.Boost Conventions

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.


Cross-Provider Modeling

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.

General Recommendations

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.

Cross-Provider Problem Areas

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]

Example: Cross-Provider Concurrency

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.


Model Configuration

EfCore.Boost supports model configuration through both custom attributes and a fluent API. Both approaches provide identical behavior and can be used interchangeably.

Discoverability and Persistence

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.

  1. Fluent methods do not add CLR attributes (which is impossible at runtime).
  2. They apply equivalent EF Core annotations to the property metadata.
  3. The resulting model is semantically equivalent between attribute and fluent styles.

Attributes vs Fluent API

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.

Naming Conventions in Fluent API

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 the Has- prefix (e.g., HasStrShort()).
  • Semantic attributes (like Name, Email, AddressCity) use the HasPurpose- prefix (e.g., HasPurposeName()).

Side-by-Side Comparison

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");

Detailed Examples

Using Attributes

[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; }
}

Using Fluent API

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();
    });
}

Naming & Schema Conventions

EfCore.Boost abstracts the differences between engines that support schemas and engines that do not.

SQL Server & PostgreSQL

Schemas behave normally:

cms.Routes
dbo.Users

MySQL

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

Views and the ViewKey Attribute

Views Are First-Class Citizens

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; }
}

Using Fluent API

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.


Date & Timestamp Behavior

Strategy

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 DateTime with UTC semantics.
  • Application code should generate these values using DateTime.UtcNow and 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.

SQL Server

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.

PostgreSQL

EfCore.Boost standardizes to:

timestamp with time zone

and expects the application to store UTC explicitly.

MySQL — Session Timezone Enforcement

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

Recommendation

EfCore.Boost strongly encourages:

  • Store point-in-time values as UTC using DateTime.
  • Normalize any DateTimeOffset values 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.


String & Text Handling

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.

Provider Baseline

SQL Server:

nvarchar(x)

MySQL:

varchar(x)

PostgreSQL:

  • defaults to citext when appropriate
  • requires citext extension enabled

Why citext?

  • case-insensitive matching
  • indexable
  • stable search semantics
  • reduces logic complexity

Recommended Collations & Database Generation

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).

SQL Server

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 A and a.
  • AS (Accent Sensitive): Distinguishes a from á.
  • 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:

  1. Latin1_General_100_CI_AS_SC_UTF8 (Modern)
  2. Icelandic_100_CI_AS (Regional fallback)
  3. Latin1_General_100_CI_AS (Legacy fallback)

MySQL

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

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.


String Size Considerations

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

Primary Keys & Identity Behavior

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

Concurrency Columns

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.


Automatic Concurrency Handling

[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.


Automatic Versioning Without Exceptions

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();

Design Philosophy

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

Cascade Delete Policy

Pain Avoided on Purpose

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.

Explicit Control with Attributes

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();
}

Fluent API Configuration

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 relation

Policy:

Delete semantics must be intentional, not accidental.

Benefits:

  • safer data lifecycle
  • clearer reasoning
  • fewer schema traps
  • improved portability
  • lowered operational risk

Shared Read Models: Views & Routines

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.


About Migration Execution

Model building defines the database logically.
Deployment builds it physically.

EfCore.Boost supports disciplined cross‑database migration strategies. These are documented separately in:

📄 EfMigrationsCMD.md

That document covers:

  • generating migrations per provider
  • consistent schema deployment
  • handling EF migration snapshot limitations
  • automation using PowerShell / command scripts

Examples of Applying EfCore.Boost Attributes

This section demonstrates typical attribute usage and how design intent becomes explicit in the model.

Logging Domain Example

ErrorLog

[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.

LogContext

[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

View Example with Composite ViewKey

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.


Custom Attributes

EfCore.Boost custom attributes fall into two clearly separated groups:

  1. 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.

  2. 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.


Views, schema, and identity

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.


Relationship override attributes

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()

String length & text intent

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].

Semantic string attributes

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()

Notes

  • Only one string intent attribute should be applied to a property.

Decimal precision intent

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.


Date and time types

Boost standardizes timestamp handling to avoid provider-specific ambiguity.

Recommended usage:

  • Use DateTime for persisted timestamps.
  • Store all timestamps in UTC.
  • Normalize DateTimeOffset to 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

Semantic marker attributes

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()

Notes on usage

  • 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.

Example usage

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;
}

Summary

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.