Regira-Packages

Entity Models

Creating Entity Models

public interface IEntity;
public interface IEntity<TKey> : IEntity
{
    public TKey Id { get; set; }
}

Referencing one of your own children

An owner with an optional foreign key to one of its own child rows — while the child’s foreign key back is required, and therefore cascades — makes the two tables reference each other. Prefer marking the child instead: a flag or a rank column identifies the same row with no foreign key. Startup validation warns about the shape.

Where the reference has to stay, two things need handling:

SaveChangesBreakingDeleteCycles / SaveChangesBreakingDeleteCyclesAsync (Regira.Entities.EFcore.Extensions) do that. Call them from the context’s own overrides — both of them, or synchronous callers stay broken:

public override int SaveChanges(bool acceptAllChangesOnSuccess)
    => this.SaveChangesBreakingDeleteCycles(base.SaveChanges, acceptAllChangesOnSuccess);

public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken token = default)
    => this.SaveChangesBreakingDeleteCyclesAsync(base.SaveChangesAsync, acceptAllChangesOnSuccess, token);

Pass acceptAllChangesOnSuccess to the extension and base.SaveChanges itself as the delegate. The extension nulls the optional side with a direct UPDATE, tells the change tracker the database no longer holds the reference, and then runs the save exactly once with your flag, all inside one transaction. Nothing is accepted before that save returns: a save the database rejects leaves every change pending, and the retry (EF’s own or yours) drops the reference again. SaveChanges(false) + AcceptAllChanges() therefore behaves exactly as it does without the extension, and the count returned is the save’s own — the UPDATE is not counted.

A save without such a pair is a single round trip and opens no transaction. The change tracker is read only when the model has two entity types referencing each other and the provider is relational; any other context pays a cached lookup and nothing else, and the in-memory provider, which orders no deletes, saves the pair on its own. Already inside a transaction of your own — or a TransactionScope — the extension joins it rather than opening a second one, which is what lets the pattern work under EnableRetryOnFailure() inside EF’s own CreateExecutionStrategy().Execute(...) recipe. A bare BeginTransaction() under a retrying strategy is refused by EF’s own SaveChanges, extension or not.

SearchObject

Use SearchObject for filtering entities.

public record SearchObject : SearchObject<int>;
public record SearchObject<TKey> : ISearchObject<TKey>
{
    public TKey? Id { get; set; }
    public ICollection<TKey>? Ids { get; set; }
    public ICollection<TKey>? Exclude { get; set; }
    public string? Q { get; set; }

    public DateTime? MinCreated { get; set; }
    public DateTime? MaxCreated { get; set; }
    public DateTime? MinLastModified { get; set; }
    public DateTime? MaxLastModified { get; set; }

    public ArchivedFilter? Archived { get; set; }
}

Archived Property (soft delete):

Q Property (General Text Search):

SortBy Enum

public enum EntitySortBy
{
    Default,
    Id,
    IdDesc,
    Created,
    CreatedDesc,
    LastModified,
    LastModifiedDesc,
}

Includes Enum

[Flags]
public enum MyEntityIncludes
{
    Default = 0,
    // Add custom options here
    Option1 = 1 << 0,
    Option2 = 1 << 1,
    All = Option1 | Option2
}

Overview

  1. Index — Overview of Regira Entities
  2. Entity Models — Creating and structuring entity models
  3. Services — Implementing entity services and repositories
  4. Mapping — Mapping Entities to and from DTOs
  5. Web Endpoints — Exposing entity operations as HTTP endpoints
  6. Normalizing — Data normalization techniques
  7. Attachments — Managing file attachments
  8. Built-in Features — Ready to use components
  9. Checklist — Step-by-step guide for common tasks
  10. Practical Examples — Complete implementation examples