Skip to content

v3.1.0 — DCMP's change request, and two fixes every user needs

Choose a tag to compare

@Sajadh92 Sajadh92 released this 18 Sep 01:53
8a8d7fd

DynamicWhere.ex 3.1 answers the change request DCMP raised after moving onto 3.0, and closes two defects in the core that affect every user, guarded or not. If you are on 3.0.0, upgrade.

dotnet add package DynamicWhere.ex --version 3.1.0

Packages

Package Version
DynamicWhere.ex 3.1.0
DynamicWhere.ex.Policies.EntityFrameworkCore 3.1.0
DynamicWhere.ex.Policies.Redis 3.1.0
DynamicWhere.ex.Policies.AspNetCore 3.1.0

Why to upgrade

  • A member named Root, It or Parent bypassed field policies. System.Linq.Dynamic.Core read those names as its own root / it / parent keywords, so Root.Name addressed the row's own Name. Under ApplyPolicy, a dynamic projection of Root.Name returned a [DwDenied] column, and a [DwForceWhere] scope reached through such a navigation filtered the wrong column. Expressions are now parsed with those keywords off, and ParsingConfig.Default is no longer read.
  • One In or NotIn condition with about 700 values ended the process. The values were joined as one flat chain, one level of nesting per value, and EF Core and the expression compiler walk that tree recursively. The request thread's stack overflowed, and no catch can stop that. A list longer than 32 values is now nested as a balanced tree; a list of 32 or fewer is written exactly as before. This affected guarded and unguarded queries alike.
  • Invented field names grew the process without limit. The reflection cache tracked a path before validating it, so every failed name stayed forever. Only valid paths are tracked now.
  • The strict tier's refusals leaked which columns exist. A denied field and a missing name gave different answers, padding a name with dots changed the refusal, a cost-weighted hidden field broke the budget first, and MissingContextValue named the scope column and the key it reads. Every one of these now answers alike.
  • [DwForceWhere(AllowNull = true)] on IsNull or IsNotNull scoped nothing, rendering (field IS NOT NULL OR field IS NULL). It is refused now, at construction and when a stored rule is read.
  • Refusal audit events hold caller text, which could forge a log line. It is cut to 256 characters, and control, format, line separator and paragraph separator characters are escaped.

Read before upgrading

Eleven behaviours changed. The full list is breaking changes, points 14 to 24. The ones most likely to reach you:

  • Dates read the member's type. DateTimeOffset members, nullable dates and DateOnly members now filter correctly, where 3.0.0 threw.
  • A date value is ISO 8601, year-first, or a format your deployment declares. 01/09/2026 is refused as ambiguous until you call DwDates.Configure.
  • Segment set operations run in the database. They were combined in memory by object reference, so Intersect returned nothing and Except removed nothing whenever the query was untracked, projected or guarded.
  • A field path may not start with one of the parser's own wordsnew, iif, np, isnull, is, as, cast, true, false, null. Seven of them used to throw from the parser, and Null returned no rows and no error. The library refuses them by name now, in every clause.
  • PageCount on an unpaged result is 1.
  • ApplyPolicy(ctx) refuses a context that never went through PrepareAsync, with or without a store.
  • Four new caps refuse guarded requests 3.0.0 ran: MaxConditionDepth and MaxConditionSets (10), MaxConditionValues (1000) and MaxAggregates (50).
  • Under DwTier.Strict the policy trace stays off results unless you ask for it; LastTrace still holds it.

What's new

  • [DwEntity(DefaultOrder = "CreatedAt desc, Id")] — the order a guarded query takes when its caller sends none.
  • DwCaps.DefaultPageSize — a page for a guarded query that sends none.
  • [DwForceWhere(..., AllowNull = true)] — the scope for a record that belongs to one tenant or to none.
  • DwPolicyOptions.AuditRefusals — every refused guarded query in the audit, with the real field. DwAuditEvent gains ErrorCode.
  • DwDates.Configure — declare the date formats your deployment accepts, once, at startup.
  • DwPolicyContext.IsPrepared and LogicException.Subject are public.

The public API only gains members. Nothing was removed or changed.

Also fixed

  • An idle rule store refused every guarded query fifteen minutes after its last write.
  • The composable Group and Summary returned groups the k-anonymity floor suppresses.
  • A forced null check built from a context key failed every guarded query on its type.
  • A DateTime whose Kind is Local, compared against a DateTimeOffset member, filtered hours away from the moment it held.
  • DwDates.Configure refuses a format ISO 8601 already reads, which read a value one way on a server in UTC and another way outside it.

Verified

  • 2,067 tests on EF Core 8 and 1,382 on the declared EF Core 6.0.22 floor. Release builds with zero warnings.
  • Every fix was mutation-checked: broken on purpose, and a test turned red.
  • Four review passes over the branch — security, QA, dates, and the docs against the code.
  • 232 of 232 demo API endpoints against a seeded PostgreSQL 16.
  • The docs reference names all 156 public types and 931 members, checked mechanically.

Compatibility

Targets net6.0, runs on .NET 6 through 10, with EF Core 6.0.22 or later. A project with no policy attributes and no DwPolicy.Configure call is affected only by the behaviour changes above.