Regira-Packages

Normalizing Entity Properties

Saving normalized properties

A normalized property is usually just a joined string (using a space), built by normalizing one or more source properties.

Automated

Customized

// base class
public abstract class EntityNormalizerBase<T>(INormalizer? normalizer = null) : IEntityNormalizer<T>
{
    public virtual bool IsExclusive => false;

    public abstract Task HandleNormalize(T item, CancellationToken token = default);
    public virtual async Task HandleNormalizeMany(IEnumerable<T> items, CancellationToken token = default) {...;
}

When IsExclusive is true, only this normalizer is executed for the entity type. Otherwise, other compatible normalizers are also executed.

NormalizedContent is computed when the entity is saved. When a parent folds in text from related entities (e.g. a ticket whose searchable content includes its replies), re-normalize the parent whenever that related data changes.

The catch: a child added in the same SaveChanges as the parent isn’t committed yet, so a normalizer that queries the database for it won’t see it — the searchable text lags by one save. Three ways to handle it, cheapest first:

These fit simple-to-moderate denormalization. When the cross-entity logic gets genuinely complex, don’t force it into a normalizer — move it to a dedicated service that owns building the searchable text; it’s clearer and easier to test.

Filtering using normalized properties

Sample from FilterHasNormalizedContentQueryBuilder

    public IQueryable<IHasNormalizedContent> Build(IQueryable<IHasNormalizedContent> query, ISearchObject<TKey>? so)
    {
        if (!string.IsNullOrWhiteSpace(so?.Q))
        {
            var keywords = qHelper.Parse(so.Q);
            foreach (var q in keywords)
            {
                query = query.Where(x => EF.Functions.Like(x.NormalizedContent, q.QW));
            }
        }

        return query;
    }

Architecture

Services

  1. INormalizer - Property-level normalization (string transformation)
  2. IObjectNormalizer - Object-level normalization (processes properties with [Normalized] attribute)
  3. IEntityNormalizer - Entity-level normalization (custom business logic)

Normalized attribute

// Normalize from multiple properties (concatenated with space)
[Normalized(SourceProperties = [nameof(Title), nameof(Description)])]
public string? NormalizedContent { get; set; }

Dependency Injection

Auto retrieve normalizers

Normalizers run as SaveChanges interceptors. UseEntities<TContext>(e => e.UseDefaults()) wires the EntityNormalizerContainerInterceptor into the DbContext options automatically; without UseDefaults(), select it explicitly:

services.UseEntities<MyDbContext>(e => e.WireDbContext(DbContextWiring.NormalizerInterceptors));

The interceptor resolves all matching normalizers when saving entities.

Default services

Interface Implementation
INormalizer DefaultNormalizer
IQKeywordHelper QKeywordHelper
IObjectNormalizer ObjectNormalizer
IEntityNormalizer DefaultEntityNormalizer<IEntity>
services.UseEntities<DbContext>(e =>
{
    // Registers all default (normalizing) services
    e.AddDefaultEntityNormalizer();
    // or e.UseDefaults(); to also register other default helper services
});

Globally

services.UseEntities<DbContext>(e =>
{
    e.AddNormalizer<IEntityInterface, MyGlobalEntityNormalizer>();
});

Per Entity

services
    .UseEntities<DbContext>(/*...*/)
    .For<Entity>(entity =>
    {
        entity.AddNormalizer<MyEntityNormalizer>();
    });

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