Skip to content

Add an immutable typed filter builder for DTO selection - #207

Closed
jogibear9988 wants to merge 3 commits into
codex/dto-select-predicatesfrom
codex/dto-select-filter-builder
Closed

jogibear9988 wants to merge 3 commits into
codex/dto-select-predicatesfrom
codex/dto-select-filter-builder

Conversation

@jogibear9988

@jogibear9988 jogibear9988 commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Add an explicit immutable Filter<T> alternative to the lambda API in #206. Callers identify DTO properties with typed selectors and compose named comparison operations.

Public API

The Select<T> extension and Filter<T> are in DotNetProjects.Migrator.Framework. Public signatures:

public static IReadOnlyList<T> Select<T>(
    this ITransformationProvider provider,
    string table,
    Filter<T> filter)
    where T : class, new();

// Filter<T> is immutable and requires T : class, new().
public static Filter<T> Equal<TValue>(
    Expression<Func<T, TValue>> column, TValue value);
public static Filter<T> NotEqual<TValue>(
    Expression<Func<T, TValue>> column, TValue value);
public static Filter<T> LessThan<TValue>(
    Expression<Func<T, TValue>> column, TValue value);
public static Filter<T> LessThanOrEqual<TValue>(
    Expression<Func<T, TValue>> column, TValue value);
public static Filter<T> GreaterThan<TValue>(
    Expression<Func<T, TValue>> column, TValue value);
public static Filter<T> GreaterThanOrEqual<TValue>(
    Expression<Func<T, TValue>> column, TValue value);
public static Filter<T> IsNull<TValue>(
    Expression<Func<T, TValue>> column);
public static Filter<T> IsNotNull<TValue>(
    Expression<Func<T, TValue>> column);

public Filter<T> And(Filter<T> other);
public Filter<T> Or(Filter<T> other);
public Filter<T> Not();

DTO used in the examples

using DotNetProjects.Migrator.Framework;
using DtoColumn = System.ComponentModel.DataAnnotations.Schema.ColumnAttribute;

public sealed class ScreenDto
{
    public int Id { get; set; }
    public bool Archive { get; set; }

    [DtoColumn("GenericDefinition")]
    public string Definition { get; set; }
}

This uses the DTO mapping introduced in #206, including inherited properties and standard column/not-mapped attributes.

Usage

Assuming provider is the migration's ITransformationProvider:

var minimumId = 10;

// Type-safe property selectors and comparison values.
var filter = Filter<ScreenDto>.Equal(x => x.Archive, false)
    .And(Filter<ScreenDto>.GreaterThan(x => x.Id, minimumId));

IReadOnlyList<ScreenDto> screens =
    provider.Select<ScreenDto>("Common_Screens", filter);

// T can also be inferred from Filter<ScreenDto>.
var sameScreens = provider.Select("Common_Screens", filter);

// Grouped OR, with explicit inclusion of NULL rows.
var otherDefinitions = Filter<ScreenDto>.IsNull(x => x.Definition)
    .Or(Filter<ScreenDto>.NotEqual(x => x.Definition, "obsolete"));

var activeOtherScreens = provider.Select(
    "Common_Screens",
    Filter<ScreenDto>.Equal(x => x.Archive, false)
        .And(otherDefinitions));

// Negation creates a new filter.
var archivedScreens = provider.Select(
    "Common_Screens",
    Filter<ScreenDto>.Equal(x => x.Archive, false).Not());

// Filters can be reused. Composition never modifies the original.
var aboveMinimum = Filter<ScreenDto>.GreaterThan(x => x.Id, minimumId);
var activeAboveMinimum = aboveMinimum.And(
    Filter<ScreenDto>.Equal(x => x.Archive, false));

Inside a Classic migration, use Database.Select<ScreenDto>(table, filter); in a FluentMigration, use Context.Select<ScreenDto>(table, filter). Reads execute immediately and return materialized DTOs.

Supported behavior and limits

  • TValue connects the selected property type to the comparison value type. Selectors must name direct mapped properties in their declared type; casts, calculations, nested properties and widened/boxed selectors are rejected.
  • Filters capture values when built. Changing a local variable later does not change an existing filter. And, Or and Not return new immutable filters and preserve grouping.
  • Null checks require nullable value or reference properties. Ordering on strings, booleans, GUIDs and enums, and byte-array comparisons, are unsupported.
  • Both APIs share DTO mapping, quoting, native parameter binding, SQL null/collation semantics, transactions and command/reader disposal with Add typed DTO selection, updates and deletes #206.
  • A null filter throws; use Select<T>(table) to read all rows.

Part 2 of the implementation for #202. Depends on #206; merge #206 first, then retarget this PR to master. The current base is codex/dto-select-predicates, so this diff contains only the builder additions.

Validation:

  • Solution build passed.
  • 877 unit tests and 489 SQLite tests passed; 27 builder tests cover composition, immutable reuse, null semantics, parameter safety, transactions and invalid selectors.
  • Oracle/HANA/Informix binding tests now exercise both APIs; the native-driver integration fixture exercises both APIs across the existing eleven-database CI matrix.
  • Documentation tests/link checks passed; all 110 published C# examples compiled and 8 SQLite parity checks passed.
  • Non-SQLite live databases were not run locally.

Copilot AI balanced review requested due to automatic review settings October 14, 2026 19:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI balanced review requested due to automatic review settings October 14, 2026 19:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants