Skip to content

9.35.0

Choose a tag to compare

@jeremydmiller jeremydmiller released this 26 Sep 20:53
· 52 commits to master since this release
494126b

Full upgrade guide: https://weasel.jasperfx.net/release-9-35

Two independent fixes, plus CI hygiene.

SQL Server: generated scripts run under sqlcmd, and run twice

#593 — PRs #620, #624, thanks to @jovball

A rendered migration script (db-patch, db-dump, WriteMigrationFileAsync, ToDatabaseScript) now runs as one file under sqlcmd or SSMS, and runs a second time against the same database without failing.

  • stored procedures emit CREATE OR ALTER PROCEDURE between GO lines, because T-SQL requires a procedure definition to be the first statement of its batch
  • existence guards on index, foreign key, table type and sequence creation, plus DROP INDEX IF EXISTS
  • generated script files head with SET QUOTED_IDENTIFIER ON;, so no sqlcmd flags are needed
  • a procedure body containing a line whose entire content is GO is refused at render time — neither sqlcmd nor Weasel's splitter parses string literals, so such a line would split the batch inside the definition

DROP INDEX IF EXISTS and CREATE OR ALTER make SQL Server 2016 SP1 the effective floor.

⚠️ Breaking change, SQL Server only

Rendered stored procedure DDL now carries GO lines. A consumer that executes that text through its own SqlCommand must split it first with the new SqlServerBatchSplitter.Split, or switch to CreateAsync. Everything Weasel executes itself already splits.

EF Core: batched queries return what EF Core returns

#621 — PRs #622, #625, reported and fixed by @ayuksekkaya

BatchedQuery.Query<T>() and QuerySingle<T>() built entities by reflecting over the entity type's scalar properties. Anything that was not flat came back incomplete and nothing reported an error: owned types, owned JSON columns, complex properties and complex collections left null or empty, Included navigations empty, a collection Include returning one parent per child row, and nothing tracked. Saving one of those entities wrote the empty members to the database.

EF Core now runs every batched query and materializes every result, so a batched query returns exactly what the same query returns on its own — tracking, identity resolution, owned/complex/JSON members, Includes and projections.

Batching into a single round trip requires the new BatchedQueryInterceptor:

services.AddDbContext<MyDbContext>(opts =>
{
    opts.UseNpgsql(connectionString);
    opts.UseWeaselBatchedQueries();
});

Without it — and where a batch cannot faithfully stand in for separate execution — every queued query runs on its own round trip with the same results. Correctness does not depend on registering anything; only the round trip count does.

Wolverine users: Wolverine batches query plans automatically as soon as a handler has two of them against the same DbContext, so this defect could appear in a working handler when an unrelated second plan was added. Wolverine registers the interceptor from the release that takes this version of Weasel — JasperFx/wolverine#4626.

CI

#611 — PR #623. The seven CI workflows have concurrency groups, so a re-pushed branch stops stacking 18 jobs behind the ones it supersedes.

New public API

  • SqlServerBatchSplitter.Split(string) — sqlcmd's GO semantics
  • Migrator.SplitIntoBatches(string) — public virtual, returns the whole string by default; every provider but SQL Server keeps that
  • IndexDefinition.WriteCreateStatement(Table, TextWriter) — the guarded emission method
  • BatchQueryExtensions.UseWeaselBatchedQueries(...) and BatchedQueryInterceptor

Verification

Every PR was 18/18 green, and master was packed locally and run against both downstream consumers before this release: Wolverine.SqlServerTests 483/0, Wolverine.EfCoreTests 233/0, Polecat.Tests 2809/0, Weasel.SqlServer.Tests 642/0, Weasel.EntityFrameworkCore.Tests 130/0.