Upgrade to .NET 11 and EF Core 11 (preview 7), and xunit v3 - #294
Open
ChrisJollyAU wants to merge 48 commits into
Open
Upgrade to .NET 11 and EF Core 11 (preview 7), and xunit v3#294ChrisJollyAU wants to merge 48 commits into
ChrisJollyAU wants to merge 48 commits into
Conversation
Three references carried their own version and so bypassed Dependencies.targets: LibRed.EFCore pinned Microsoft.EntityFrameworkCore.Relational at 10.0.0-rc.2.*, and EFCore.Jet.Tests pinned Design, Relational and Relational.Specification.Tests at 10.0.*. A central version bump would have silently left all three behind on the old major. Dropping the Version attribute lets the `Update=` items in Dependencies.targets supply it, which is how every other reference in the repo already works. No functional change — verified building against EF 10 before the bump. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SDK 11.0.100-preview.6.26359.118, TFMs net10.0 -> net11.0, and the three central version properties pinned exactly at 11.0.0-preview.6.26359.118. Pinned rather than a floating range as elsewhere: while migrating, a silent float to preview 7 would move results underneath the pre-upgrade baseline and make failure attribution meaningless. The move is not optional in parts. Microsoft.EntityFrameworkCore.Relational.Specification.Tests 11 ships net11.0 ONLY and depends on Microsoft.DotNet.XUnitV3Extensions, so the framework bump, the EF bump and the xunit v2->v3 migration are one atomic change; none can land alone. Four EF 11 API breaks in EFCore.Jet, 18 errors: - JsonTypeMapping -> StructuralJsonTypeMapping. Worth eight of the errors on its own: EF 11 reduced JsonTypeMapping to an obsolete stub that no longer derives from RelationalTypeMapping, which took GetDataReaderMethod, CustomizeDataReaderExpression, GenerateNonNullSqlLiteral and the nested RelationalTypeMappingParameters out of scope together. The members themselves are unchanged. - ValidateValueGeneration moved from ModelValidator to RelationalModelValidator and dropped its IEntityType parameter, which the key already carries. - SqlExpressionVisitor was removed outright, with no replacement base. Both visitors now derive from ExpressionVisitor with a VisitExtension that dispatches to the same 36 per-node methods, kept verbatim so no query-translation behaviour moves. Unrecognised nodes fall through to the base, which visits children — the default the removed class applied. - IReadOnlyIndex.Properties widened to IReadOnlyPropertyBase. JetIndexConvention now gives up on the index filter for a non-property member, which is how it already treats an unmapped column. All five LibRed projects — Core, Sql, Engine, Ado, EFCore — build against .NET 11 and EF 11 with no changes at all. The engine is EF-version-agnostic as intended; only the Jet provider it borrows plumbing from needed work. The test projects do NOT build yet: ~5,700 errors dominated by 2,068 CS0433 "type exists in both" and 492 CS0104 ambiguous references, i.e. xunit v2 and v3 both in the graph. That is the next commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Forced rather than chosen: EF 11's Relational.Specification.Tests depends on Microsoft.DotNet.XUnitV3Extensions, so inheriting from its test bases requires v3. It was already in the graph transitively alongside the explicit v2 references, which is what produced 2,068 CS0433 "type exists in both" and 492 CS0104 ambiguity errors. Swapping the packages removes the collision. xunit.v3.core / .assert / .extensibility.core pinned at 4.0.0-pre.108 — the same build EF resolves, so the two cannot disagree about which xunit is present. Test projects become OutputType=Exe, as v3 requires; xunit.runner.console (v2-only) is dropped; xunit.runner.visualstudio stays and already supports v3. ConditionalFact/ConditionalTheory become plain Fact/Theory — 1,378 attributes across 277 files. The obsolescence message suggests adding a typeof() argument, but that would be wrong here: NOT ONE call site in the repo passes a condition member name. Every one is either bare or `Skip = "reason"`, both of which route through the same obsolete params-string constructor. A conditional fact with no condition is a fact, so the attribute was buying nothing. That took CS0618 from 2,610 to zero. Three spellings had to be covered, found only by re-measuring after each pass: `[ConditionalFact]` and `[ConditionalFact(Skip = ...)]`, the namespace-qualified `[Xunit.ConditionalFact]`, and combined lists like `[ConditionalTheory, MemberData(...)]` where a comma follows the name. Xunit.Abstractions is deleted in v3, so its using is removed from 243 files and `using Xunit;` added to the 26 that had been relying on it to reach ITestOutputHelper, which now lives in Xunit itself. Errors across the solution: ~5,700 -> ~740. What remains is two unrelated jobs, neither of them package work: the 12 shared framework files under test/Shared/TestUtilities/Xunit still target v2's deleted reflection model (IMessageBus, ExceptionAggregator, RunSummary, IXunitTestCase, ITypeInfo), and separately EF 11 changed its test bases (TestStoreFactory no longer overridable, AssertSql and TestSqlLoggerFactory moved onto the base, IsAsyncData gone). The tree does not build yet. Note for the framework rewrite: the 4 JetConditional* files exist to discover ConditionalFact/Theory and now have nothing to discover, but the 20 JetCondition/LibRedCondition usages they evaluate still need a home — v3's native SkipUnless/SkipWhen/SkipType is the likely replacement. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two of the three jobs in test/Shared/TestUtilities/Xunit. The crash-detection runner chain is untouched and still the last thing standing between the tree and a build. Orderers. v3 takes and returns IReadOnlyCollection rather than IEnumerable, and the Xunit.Abstractions reflection wrappers it used to navigate — TestMethod.TestClass.Class.Name, TestMethod.Method.Name, DisplayName — are gone in favour of flat metadata on the test case itself: TestClassName, TestMethodName, TestCaseDisplayName, and TestCollectionDisplayName for collections. Behaviour is unchanged; only the route to the sort keys is. Conditions. Every one of the 20 usages is class-level and every one is the same condition, IsNotCI, so they move to the .NET team's ConditionalClass, which is v3-native and already in the graph through EF's spec-tests package. It runs a class when its named member is TRUE, hence the new TestEnvironment.IsNotCI beside the existing IsCI rather than inverting at each call site. That leaves the four JetConditional* discoverers and test cases with nothing to discover — the previous commit turned every [ConditionalFact]/[ConditionalTheory] into a plain [Fact]/[Theory] — and the four JetCondition/LibRedCondition attributes and enums with no one to evaluate them. Eight files deleted, 275 lines, none of it replaced by anything I had to write. One case has no v3 equivalent and is deliberately kept rather than quietly dropped: each provider applies an assembly-level condition declaring that nothing can run without a configured test database. v3 covers per-method (SkipWhen/SkipUnless) and per-class (ConditionalClass) but not per-assembly, and EF 11 deleted the ITestCondition those attributes implement, so a local one now lives in test/Shared. It is currently inert — the rewritten framework is where it gets evaluated. Framework files still failing: 140 errors across JetXunitTestCaseRunner, JetXunitTheoryTestCaseRunner, JetXunitTestFrameworkDiscoverer, JetXunitTestRunner, JetXunitTestFramework and TestRunnerCrashAttribute. Both features they carry now have a v3 landing point: Assert.Skip(reason) is the dynamic-skip primitive, and TestRunner<,> still exposes InvokeTest/PreInvoke/PostInvoke to trigger it from. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The last of test/Shared/TestUtilities/Xunit. Five v2 classes replaced by two, 473 lines deleted against
207 added, because most of what was there existed only to reach a point either side of the test body —
and v3 offers that directly.
The v2 arrangement needed a custom test framework, a framework discoverer, a test-case runner and a
theory-case runner purely to interpose on each test. All four are gone: BeforeAfterTestAttribute's
Before/After, applied once at assembly level, is the same interception point. No custom test framework
remains, so the [assembly: TestFramework(...)] declarations go too.
The crash protocol itself is unchanged, because it has to be: it survives a process that never gets to
run any more of its own code. A marker naming the in-flight test is written before the test and deleted
after; a marker still present at startup can only mean the runner died mid-test, so it is promoted to
TestsKnownToCrashTestRunner.txt and skipped from then on — now via Assert.Skip, which is v3's dynamic-skip
primitive. Markers are keyed per test rather than by "most recent", since tests run concurrently and each
must delete its own.
UnsupportedExpressionSkipPolicy lifts out the other half — the message matching that reports a test as
skipped when it failed only because the provider has no SQL for APPLY, row skipping or sequences, rather
than as a failure. That logic is provider knowledge, not xunit plumbing, so it survives verbatim.
NOT YET WIRED, and it will show up in the first baseline diff: that policy has nowhere to hang. In v2 it
sat on the custom test runner; v3 exposes only Run on XunitTestCaseRunner, so reaching it means either
reimplementing the runner chain or intercepting at the message sink via XunitTestFramework.CreateExecutor.
Until then those tests report as failures, not skips, and the count will read high against the 223-name
pre-upgrade baseline. The assembly-level ITestCondition ("no test database configured") is inert for the
same reason.
Also: v3 made the collection orderer generic and constrains it to ITestCollectionMetadata rather than
ITestCollection — the case and collection orderers differ there — and its orderer attributes take a Type
instead of a type-name/assembly-name pair.
Remaining errors are EF 11 test-base churn, not xunit: TestStoreFactory no longer overridable, AssertSql
and TestSqlLoggerFactory moved onto the base, IsAsyncData gone.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EF 11 renamed NonSharedModelTestBase's hook from TestStoreFactory to NonSharedTestStoreFactory. Both still exist — the shared-store bases keep TestStoreFactory — so this is a rename on one family, not a general one, which is why the 44 sites were patched by compiler-reported file and line rather than by matching the text everywhere it appears. Every affected class is a NonSharedModelTestBase derivative: the AdHoc* query suites plus CompiledModel and EntitySplitting on both providers. Clears 88 CS0115 and, with them, 88 of the 92 CS0534 "does not implement abstract member" errors, which were cascades from the same cause. Solution errors: ~460 -> ~332. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EF 11 moved its inheritance test bases out of the flat namespaces into feature sub-namespaces — Microsoft.EntityFrameworkCore.BulkUpdates.Inheritance and Microsoft.EntityFrameworkCore.Query.Inheritance. The types were never removed, which is why the errors were "could not be found" rather than anything about changed members. 95 files: 55 needed the BulkUpdates namespace, 40 the Query one. Clears all 208 CS0246 and the CS0103 for IsAsyncData, which came from the same reorganisation. Worth recording how this was found, because it was not by me: the XML docs shipped with these packages document almost nothing — not these types, not TestStoreFactory — so I had been reading type names out of assembly strings, which gives names but not namespaces. The user pointed VS at one file and took its "add missing using" suggestion. The IDE resolves against the real compilation, which is exactly the lookup being reconstructed by hand. Worth reaching for first next time an EF major moves things. Solution errors: ~5,700 at the start of the upgrade -> 184. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The errors that needed no judgement, 184 -> 128. IAsyncLifetime and IAsyncDisposable return ValueTask in xunit v3, not Task — 14 methods across 8 files. The `new` keyword had gone wrong in both directions, because EF 11 moved members onto some bases and off others: 8 removed where nothing is hidden any more, 4 added where the base now declares what we redeclare. ExecuteSqlInterpolated is obsolete in favour of ExecuteSql — 4 call sites. AscendingTestCollectionOrderer becomes an explicit interface implementation. Restating a generic constraint requires it to match the interface's exactly, and the interface's constraint is not visible in the shipped XML docs, so ITestCollection and ITestCollectionMetadata were both guesses and both wrong. Explicit implementation inherits the constraint instead of restating it, which is what CS0425 suggests. Also correcting an earlier claim: this file was reported as compiling when the framework rewrite landed. It was not. That check filtered the build output with a pattern that did not match the path format, and an empty result was read as success rather than as a grep that matched nothing. Remaining 128 all need judgement: 124 CS0115 across roughly 30 base methods that EF 11 renamed or removed — each one a choice between fixing a signature and dropping an override, where dropping loses a provider-specific assertion — plus 4 CS0534 for SetParameterizedCollectionMode, a genuinely NEW abstract member on PrimitiveCollectionsQueryRelationalTestBase that needs a real decision about which parameter translation modes Jet and LibRed support. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EF 11 deleted a swathe of BuiltInDataTypesTestBase tests and changed several test-infrastructure signatures, leaving both provider suites unbuildable. A solution build stops at the first failing project and a wave of CS0115 masks everything behind it, so this was swept per project and rebuilt until the list stopped changing rather than driven off one build's output. Remove the overrides EF no longer declares, including Columns_have_expected_data_types, whose entity types (NullableBackedDataTypes, BuiltInNullableDataTypes and friends) no longer exist. Where a member changed shape rather than going away, follow it instead of deleting: CleanAsync now takes createTables, PrimitiveCollectionsQuery gains SetParameterizedCollectionMode, and ExecuteSqlInterpolated* is now ExecuteSql*. AddOptions genuinely was removed, so that one goes. The Jet suite is a near-mirror of LibRed, so its half was derived from the LibRed fixes and then compared member by member for parity. That comparison is what caught QueryBuiltInDataTypesTest, which survives a clean build because an uncalled private method is not an error. Test projects drop Microsoft.NET.Test.Sdk and MSTest.TestFramework. xunit v3 is self-executing under Microsoft.Testing.Platform, and the .NET 10+ SDK refuses the VSTest target those brought in, which is why no tests were discoverable at all. Finally, the two mechanisms the xunit v2 removal left inert are wired up again. UnsupportedExpressionSkipPolicy gains a string-based core, since a failure is intercepted as a reported message carrying exception type names rather than live exceptions; that also drops its OleDb and Odbc references, which had no business in a folder shared with the cross-platform LibRed tests. UnsupportedExpressionTestFramework wraps the executor's message sink so a query the provider has declared it cannot translate is reported skipped rather than failed - a BeforeAfterTestAttribute cannot do this, as it never learns why a test failed. TestConditions evaluates the assembly-level ITestCondition, which v3 has no native equivalent for. Both compile and a Query run shows the expected skip count, but the failure-to-skip rewrite has not yet been exercised against a real unsupported expression. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two independent breaks, both fallout from the xunit v3 / EF 11 move.
The visible one: every suite now fails before a test runs with "Testing with VSTest target is no
longer supported by Microsoft.Testing.Platform". xunit v3 runs on Microsoft.Testing.Platform, and
from the .NET 10 SDK on, routing an MTP project through dotnet test's old VSTest path is a hard
error rather than a fallback. The opt-in goes in global.json ("test": {"runner": ...}); this SDK
build does not read the dotnet.config form, and says so in its own help text. That message names
".NET 10 SDK and later" as a fixed string in the targets file - it is not reporting the SDK in use,
and the same error reproduces on the 11.0.100-preview.6 the workflows install.
That leaves the VSTest-only flags with nowhere to go. --logger trx becomes --report-xunit-trx, same
filename convention and same TRX schema, so the green-tests XPath still parses it unchanged. The two
non-shard suites gain an explicit --results-directory: VSTest defaulted the trx to the project's
TestResults, MTP defaults it to the build output's, which would have quietly emptied the upload globs
and the comparison that reads them. --filter, --results-directory and -p:FixedTestOrder pass through
untouched - xunit v3 accepts the compound VSTest filter syntax the shards rely on.
--blame-hang-timeout is dropped rather than translated. It is a per-test budget; MTP's built-in
--timeout is a per-run one, so carrying the number across would kill a shard three minutes in with
everything passing. Honest per-test behaviour needs the HangDump extension package, which is a bigger
change than this one. A hung test is now bounded only by the job timeout; a crashed one is still
caught by the TestRunnerCrashDetection markers.
Crash detection in the shard retry loops keyed off Sequence_* blame files, which are a VSTest
artifact and will never appear again - the loop would have silently stopped retrying. The trx is
written in-process at the end of a run, so its absence is the crash signal, and that now retries
instead of exiting 3. Ordinary failures still write a trx and fall through to the comparison.
The second break is restore, and it only hit the three suites that inherit EF's test bases:
Microsoft.DotNet.XUnitV3Extensions comes in transitively with Relational.Specification.Tests and is
published to dotnet-eng alone, never to nuget.org, so NU1101. Added that feed.
While in there: dotnet9 carried the right package ids but only 9.x of them, nothing this repo pins
since the EF 11 move, so dotnet11 takes its place. The two myget.org feeds are publish targets - the
NuGet job pushes to them by URL, not by source key - and nothing restores from them, but they were
still probed on every restore and were the source of the connection resets in the log. Five sources
down to three, each with a reason to be there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fallout from the runner switch, found by the first red run rather than by reading. EFCore.Jet.Data.Tests is the repo's only MSTest project, and global.json's opt-in is all-or-nothing: "All projects must use that test runner". MSTest reaches MTP through its own runner rather than the xunit one, which needs EnableMSTestRunner and an executable entry point. It had a second break behind that one. --report-xunit-trx is an xunit extension option, and MSTest under MTP ships no trx reporter at all, so even once the project was on the right runner the step would have died on an unknown option. Microsoft.Testing.Extensions.TrxReport supplies it, pinned to the 2.2.3 the project already resolves for the platform itself; the flag there is --report-trx. Verified locally: 97 tests, trx written to the expected directory. EFCore.Jet.Tests is dropped from the run rather than fixed. The assembly contains no tests - its only two test files have been <Compile Remove>d since 45bae42, the initial EF 9 update in July 2024 - so the step had spent two years reporting green over an empty run. VSTest tolerated that; MTP exits 8. The step and its now-dead upload glob go, with a comment recording why and what would bring it back. The path filter in Changes stays, since rebuilding on a change there is still right. Much of what is in those two files appears to be covered by EFCore.Jet.FunctionalTests now, so whether the project earns its place at all is worth a look before restoring it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SGN sat with the double-precision VBA math group and went through UnaryDouble, whose Func<double, double> widened Math.Sign's int straight back to a double. VBA's Sgn returns Variant/Integer and .NET's Math.Sign returns int, so the boxed type was wrong the whole time - it just never mattered while the value was only ever compared. EF 11 rephrases the Math translation tests to project the scalar instead of filtering on it, so it now reaches GetInt32, which throws on a boxed Double. Moved to Convert1, which boxes the int. Null propagation is unchanged - both helpers return null for a null argument. Covers the whole family: Sign, Sign_float, Sign_decimal and Sign_int all execute, the latter two passing outright since they carry no baseline. The other functions in that group (SIN/COS/TAN/ATN/EXP/LOG/SQR) genuinely return doubles and stay where they are. Sign and Sign_float still fail on AssertSql: their baselines are the EF 10 filter shape and need the rewrite pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Access has two concatenation operators and neither is what EF assumes. '&' always concatenates but
coerces a NULL operand to a zero-length string; '+' propagates NULL but re-dispatches on operand
TYPE. Verified against ACE (IntA = 0, StringA = 'Foo'):
NULL & 'x' -> 'x' NULL + 'x' -> NULL
IntA & '5' -> '05' IntA + '5' -> '5' numeric ADDITION
IntA & 'x' -> '0x' IntA + 'x' -> '' empty, silently, no error
IntA & StringA -> '0Foo' IntA + StringA -> ''
So '+' is not the fix, despite its NULL behaviour being the one EF wants - that propagation is a side
effect of VBA arithmetic propagating Null through Variants, and the same arithmetic nature is what
makes it choose between two different operations based on operand type. Worse here than in general:
the Convert visitor emits no CAST, so a Convert(number -> string) arrives as a bare numeric and '+'
would return a wrong value rather than an error. That shortcut is safe only because '&' is
type-insensitive - a coupling between the two methods that was nowhere written down, and now is.
Keep '&' and emulate the propagation around it, the same way SKIP/OFFSET, @@rowcount and
parameterised TOP are emulated: IIF(<operand> IS NOT NULL [AND ...], a & b, NULL). MayBeNull skips
the guard for non-null constants and non-nullable columns, so a concat that cannot produce NULL
generates byte-identical SQL to before, and identical operands (x + x) are checked once rather than
twice. Guard shape follows EF's own - IS NOT NULL checks ANDed, value in the THEN.
Verified on LibRed and on real ACE. Across NullSemantics, StringTranslations and NorthwindFunctions:
438 tests, 15 failures, every one of them AssertBaseline and not one AssertResults. The six
Is_not_null_optimizes_binary_op_with_{partial,mixed,nested}_checks tests are the ones this fixes;
the nine Concat_*/Join_non_aggregate baselines are churn awaiting the rewrite pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mechanical follow-on from the EF 11 move, across the Jet and LibRed functional suites: - [Theory] -> [Fact] where the base method lost its async parameter, so the skip attributes match the shape of what they are skipping again. - Overrides deleted where the inherited baseline now matches what the provider generates, leaving nothing for the override to say. - AssertSql text updated where the EF 11 pipeline emits a different but equivalent shape. Authored outside this session; committed unrun, so the suites are the check rather than review. Does NOT include the rewrite pass for the SGN and string-concat fixes in cb0a497 and ab0566c - the 15 known AssertSql failures there are still outstanding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dropping --blame-hang-timeout in eea4702 was the cheap option and it cost six hours of runner time on the first hang: two BuildAndTest shards sat wedged past three hours with nothing to stop them short of GitHub's 360-minute default. The crash-retry loop cannot help there either - it keys off a missing trx after the process exits, and a process that never exits never reaches the check. Hangs were always the blame flag's job. Microsoft.Testing.Extensions.HangDump supplies the MTP equivalent. The open question at the time was whether its timer is per-test like VSTest's blame timeout or per-run like MTP's own global timeout, which would have made the old values wrong by orders of magnitude. Measured rather than assumed: the Engine suite runs 895 tests in ~72s and passes clean under --hangdump-timeout 30s, so the timer is per-test and the original numbers carry over unchanged - 3m for the Jet suites and Engine, 5m for the LibRed ACE cross-checks, 10m for the LibRed functional suite. Unlike blame, the option only exists where the package is referenced, so all six suites CI runs get it. It composes with MSTest as well as xunit v3. timeout-minutes on every job is the backstop under that, sized well above observed runtimes so only a hang trips it: 120m for BuildAndTest (three sharded ACE suites, each retryable three times), 90m for LibRedFunctional, 60m for LibRedAccess, 30m for LibRed and NuGet, 10-20m for the rest. A hang should now be caught per-test, named, and dumped long before any of these fire - they exist so that a hang the dump extension somehow misses costs minutes rather than a working day. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The switch to Microsoft.Testing.Platform quietly reversed a default. xunit v3's --show-stdout and --show-stderr both default to 'All', so captured ITestOutputHelper output is printed for every test that runs, not just failures - VSTest only surfaced it on failure. EF logs the compiled shaper expression tree per query under the Query category, so a functional run now emits tens of thousands of lines of lifted expression trees and GitHub truncates the step. This is the same cost the comment above the Data.Tests step already describes for --verbosity detailed, arriving by a different route: nothing consumes that output, the green-tests extraction reads the trx, and crash detection keys off a missing trx. Set both to 'Failed' on the xunit suites, which keeps the diagnostics where they are actually read. Not applied to EFCore.Jet.Data.Tests - these are xunit extension options and the MSTest suite has no equivalent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AceRenameTests.Access_reads_a_libred_renamed_table_and_column_and_still_applies_the_default kills LibRed.Core.Tests on the runner with 0xC0000005 inside System.Data.OleDb's ICommandText.Execute. Two runs stopped at exactly +329 in 45.7s and 45.4s, so it is deterministic there, not a race. It passes locally - alone and across the full 471 - under the same MTP runner, with the same ACE 16 provider preference and collection parallelism already disabled, so the difference is the environment rather than the runner. Worth being precise about what changed, because three things landed together and the suite could not run at all in between: the test was added 2026-07-23 and was green on .NET 10 + xunit v2 + VSTest; 2026-07-28 brought .NET 11, EF 11 and xunit v3; the VSTest path then hard-errored until the MTP opt-in in eea4702. So today is the first execution since, and MTP, xunit v3 and the .NET 11 runtime are all untested variables. EF 11 is not among them - LibRed.Core.Tests has no EF dependency. A native AV in OleDb's COM marshalling is at least as plausible from the runtime as from the test host. Not guessing further without evidence. Microsoft.Testing.Extensions.CrashDump turns the AV into a dump naming the faulting frame, on every ACE-touching suite, and the dumps upload as artifacts - the Jet job folds them into its existing test-results artifact, LibRedAccess gets its own step. The three LibRedAccess steps also gain the retry the Jet shards have always had. Crash is distinguished from failure by exit code: MTP returns 0 for success, 2 for failed tests and 8 for no tests ran, so anything else means the host died rather than reported, and only that is retried. A genuine test failure still fails the step on the first attempt. Worth noting where it dies. The three assertions before it pass - ACE resolves both renamed names and honours the DEFAULT - and the crash is on the stale column name, which should raise a clean OleDbException. An AV there means ACE followed something it should not have, which is either its own fragile error path or a dangling reference left by the rename. The dump should say which. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
EF 11 renamed a family of spec tests, and the green lists still name the old methods. The check compares names that passed, so an entry naming a method that no longer exists can never match and reads as a regression forever - and it fails the step before the list can ever be updated, so the staleness is self-perpetuating. 73 entries in each list, the same 73 in both, which is what a removed method should look like: ConvertToProviderTypes 12, EverythingIsBytes 12, EverythingIsStrings 12, CustomConverters 10, BuiltInDataTypes 8, JsonTypes 7, ComplexNavigations / TPC / TPH / TPT / Transaction 2 each, AdHocComplexTypeQuery and PrimitiveCollections 1 each. Determined by matching every entry against the 38,336 names the assembly currently discovers, not by running anything - an entry was dropped only when nothing with that exact name exists, so a test that merely fails is untouched. The renames are real: Can_insert_and_read_back_all_non_nullable_data_types is gone and the class now has Can_insert_and_read_back_all_mapped_data_types and its variants. 8 of the 73 are not removals but display-name changes - the JsonTypes emoji escaping (\ud83d\udc4d where the assembly now renders the character), the TimeOnly/TimeSpan collection theory data, and Column_collection_of_strings_contains_null becoming Contains_null. The old strings can never match either, so they go too; the auto-update will re-add them under their new names once they pass. Discovery was taken from an x64 build while the lists are x86. Nothing in the removed set looks architecture-conditional, but that was not verified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The repo has to match EF's own SQL Server functional tests, and those run on VSTest. MTP was never a deliberate choice here - xunit v3 pulls in xunit.v3.core.mtp-v2, whose props default IsTestingPlatformApplication to true, and the .NET 10+ SDK then hard-errors on the VSTest target. The opt-in in eea4702 was the path of least resistance out of that error, not a decision. xunit v3 itself stays: EF 11's Relational.Specification.Tests depends on Microsoft.DotNet.XUnitV3Extensions, so inheriting its test bases requires v3. Only the runner changes; xunit.runner.visualstudio is the VSTest adapter for v3 and was already referenced everywhere. Two properties in test/Directory.Build.props do it, and both are needed. IsTestingPlatformApplication stops the SDK erroring. TestingPlatformDotnetTestSupport stops MTP importing its VSTest.targets, which REPLACES the VSTest target with one that calls InvokeTestingPlatform only when the first property is true - so setting only the first leaves a target that builds and exits, reporting success having run nothing. That silent no-op cost an hour to spot and is worth knowing about. EFCore.Jet.FunctionalTests and EFCore.LibRed.FunctionalTests also had to gain Microsoft.NET.Test.Sdk. Neither referenced it: under MTP they were self-executing test applications and did not need it, so under VSTest there was nothing to run them. Same silent-success symptom, different cause. Everything MTP-specific goes with it: the global.json runner opt-in, the TrxReport / HangDump / CrashDump extension packages and their pins, EnableMSTestRunner and OutputType=Exe on the MSTest suite, and the workflow flags. The workflows return to --logger trx and --blame-hang-timeout, the crash detection returns to looking for VSTest blame Sequence_* files, and the job caps and dump-upload steps that only existed to cover MTP gaps go away. Kept from the MTP detour, because they were never about the runner: the dotnet-eng NuGet feed that fixes the XUnitV3Extensions restore, and dropping the EFCore.Jet.Tests step whose project contains no tests at all. Verified per suite type: LibRed.Engine 895 passed, EFCore.Jet.FunctionalTests 237 run (173/20/44, matching what MTP reported for the same filter), EFCore.LibRed.FunctionalTests 69 passed, and the MSTest suite 97 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The NULL guard added in ab0566c wrapped far more than it needed to. MayBeNull treated anything that was not a bare column or constant as nullable, so an operand EF had already made non-null got wrapped anyway. In the precompiled-query baselines that produced SET `b`.`Name` = IIF(IIF(`b`.`Name` IS NULL, '', `b`.`Name`) IS NOT NULL AND @suffix IS NOT NULL, IIF(`b`.`Name` IS NULL, '', `b`.`Name`) & @suffix, NULL) - a NULL check on a COALESCE, which cannot be NULL, with the operand written out three times. Only a bare nullable ColumnExpression needs the guard now. Anything composed - a COALESCE, a CASE, a function, a parameter - is left alone, because EF has already expressed whatever null handling it wants there, and where it has not, the provider has no better information than EF does. The example above goes back to exactly the SQL it generated before the guard existed. The tests the guard exists for are unaffected, because the operand that has to propagate in them is a bare column: SELECT IIF(`e`.`BoolA`, IIF(`e`.`NullableStringA` IS NOT NULL, `e`.`NullableStringA` & IIF(`e`.`NullableStringB` IS NULL, '', `e`.`NullableStringB`), NULL), NULL) which mirrors SQL Server's [NullableStringA] + ISNULL([NullableStringB], N'') - only A propagates. NullSemantics still runs 332 with the same 6 baseline-only failures it had under the broad guard, so the narrowing cost no coverage. Baselines swept for both providers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
EF 11 adds Group37310, whose FK to GroupMember37310 is the optional-composite shape the fixture
already handles four times over: Id is required, GroupOwnerId is not, so a row can carry a partially
null FK. SQL Server's MATCH SIMPLE treats any null column as satisfying the constraint; ACE and LibRed
both apply MATCH FULL and reject it - verified today on real ACE ("a related record is required in
table 'GroupMember37310'") and on LibRed ("no matching row in 'GroupMember37310'"), same constraint,
same statement.
So it joins the four already dropped in SeedAsync. The engine semantics are untouched: MATCH FULL was
established deliberately in 4c31ac0 against ACE ground truth, and the tests that actually target it -
CompositeFkMatchFullTests, CompositeFkNullAccessTests - still enforce it. The constraint is only
scenery for these tests, which are about updating a many-to-many alongside a reference with a
composite key, not about how the FK behaves; leaving it in place blocked the write before the test
could reach what it exists to check.
One difference from the other four: Group37310 is absent from the owned model, where EF's own SQL
Server test stubs this scenario out with "No owned types". An unconditional drop therefore threw in
SeedAsync and took the entire owned fixture down through InitializeAsync - every test in the class,
not just this one - so this drop is guarded on the table being present in the model.
Can_update_many_to_many_and_reference_with_composite_key now passes 10/10 on both providers, up from
8 failures each (Identity, TptIdentity, ClientCascade, ClientNoAction, both async modes).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The x86 legs truncate where x64 does not. On ACE 2010 the same driver, same code and same tests get through all 23,509 of shard 3 on x64 and die at 3,828 on x86 - which is the shape of a 2GB user address space rather than a driver fault, ACE being the one native component in that process. Confirmed there is headroom to gain rather than assuming it: the x86 testhost the test platform ships (microsoft.testplatform.testhost, build/net8.0/x86/testhost.x86.exe) reports machine 0x014C with the LARGE_ADDRESS_AWARE bit CLEAR, so it is capped at 2GB today. Setting it lifts the ceiling to 4GB. Marks both dotnet.exe and the apphosts under test/**/bin, because which process ends up holding ACE depends on whether VSTest launches the output-directory apphost or runs 'dotnet exec testhost.dll', and the whole toolchain on these legs is the per-architecture SDK installed into .dotnet_x86. Cheaper to mark both than to depend on that detail. The flag is one COFF Characteristics bit (0x0020), patched directly instead of via editbin so the step needs no vcvars environment; non-x86 images are skipped by machine type. It invalidates Authenticode signatures, which does not matter on an ephemeral runner. The step reports patched/already/skipped counts, so if a future SDK ships these already marked the log says so instead of leaving us to assume the change did something. Expected to help 2010 x86 only. ACE 2016 dies at 1,025-1,427 of 23,509 on BOTH architectures, so its failure is the driver rather than address space, and this cannot fix it - that redistributable (16.0.5044.1000) is also out of support since 2025-10-14 and will not be fixed upstream. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6cd0dde marked the x86 hosts large-address-aware in a single step after Build Solution, and it failed: Exception calling "WriteAllBytes" ... The process cannot access the file '...\.dotnet_x86\dotnet.exe' because it is being used by another process. MSBuild keeps worker nodes and the VB/C# compiler server alive after a build for reuse, and those hold dotnet.exe open, so nothing can rewrite it afterwards. Split in two along what each target needs. dotnet.exe is patched immediately after the SDK install, before anything has run it. The apphosts under test/**/bin are patched after the build, since they do not exist until then, preceded by 'dotnet build-server shutdown' so the same node reuse cannot hold those open either. No change to what gets marked or why: both remain candidates because which process ends up holding ACE depends on whether VSTest launches the output-directory apphost or 'dotnet exec testhost.dll'. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Added DOTNET_CI env var to CI workflows. Marked flaky test classes with [SkipOnCI] to skip on CI. Updated expected SQL in many-to-many load tests to match actual queries. Combined [ConditionalClass] and [SkipOnCI] for migration tests. Fixed attribute typo in MigrationsInfrastructureLibRedTest.cs.
Shard 2 was doing almost nothing. Measured on the 2010 x86 ODBC leg: shard 1 Query minus Associations/Translations 14,029 tests 12m13s shard 2 Associations + Translations 773 tests 36s shard 3 everything non-Query 23,509 tests ~15m Shard 2 held 2% of the suite and finished in half a minute while shard 1 ran for twelve. Note the axis is wall clock, not test count: shard 3 carries the most tests by far and is both the fastest per test (~38ms vs ~52ms) and the more stable of the two big shards, so it is left exactly as it was. The Query work now splits down a meaningful line rather than an arbitrary one - Northwind and GearsOfWar together, everything else in Query on the other side: shard 1 Query, excluding Northwind and GearsOfWar 6,381 shard 2 Query Northwind* + GearsOfWar* 8,422 shard 3 non-Query, unchanged 23,533 Verified against the assembly's 38,336 discovered tests that the three filters are a partition: the counts sum exactly, no test matches two shards, and none is left uncovered. The Query/non-Query separation is preserved - shard 3 remains purely non-Query rather than a mix. Projected at the measured per-test rates this puts shards 1 and 2 at roughly 5.5 and 7.3 minutes, both comfortably inside shard 3's 15, so shard 3 stays the critical path and nothing gets slower. Per-test cost is assumed constant when tests move between shards, which will not hold exactly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
577acc8 left an empty slot in the attribute list: [ConditionalClass(typeof(TestEnvironment), nameof(TestEnvironment.IsNotCI)), , SkipOnCI("Flaky on CI")] which is a compile error, so the whole EFCore.LibRed.FunctionalTests assembly failed to build rather than any single test failing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Test assertions in ManyToManyLoadJetTest and ManyToManyLoadLibRedTest now conditionally check the expected SQL based on the EntityState. If the state is Detached, one SQL variant is asserted; otherwise, a different SQL shape is used. The expected SQL strings are formatted with indentation and line breaks for clarity, and the selected columns in the joined subqueries differ between the two cases.
Dependencies - Bump DotNetVersion/EFCoreVersion/MSLibVersion to 11.0.0-preview.7.26381.103. - Drop references that are redundant or fiction: Microsoft.Extensions.Caching.Memory (supplied by EF Core, pinned 8.0.0 but always resolved 11.x), Newtonsoft.Json (no source uses it; still present transitively), and System.Diagnostics.DiagnosticSource, System.Collections.Immutable and System.ComponentModel.TypeConverter, which are in-box on net10/net11 and were already being pruned before restore. - NetTopologySuite declared 2.6.0, which is what Relational.Specification.Tests requires and what always resolved. Microsoft.Build.Tasks.Core centralised at 18.7.1; the three test projects carried inline 18.7.1/18.7.1/18.4.0. - EFCore.Jet.Odbc no longer pins System.Data.Odbc to 4.5.2 inline, so it follows MSLibVersion like every other project. This changes the shipped package's declared dependency floor. - Add LibRed.Benchmarks to the solution; being outside it, nothing restored or built it, and it had drifted a month behind. Guard row-independent projections across a LEFT JOIN (Jet/ACE) Jet/ACE evaluates a derived table's projected expression after the join, against the all-NULL row, rather than treating the absent row as NULL wholesale. A bare literal has no NULL input, so it survives the join and comes back non-NULL for a row that matched nothing. EF 11 (dotnet/efcore#30915, PR #38479) fixes whole-object materialization off the nullable side of a group join by injecting "1 AS marker" and gating on it, so on ACE the gate concludes "matched" and the shaper throws "Nullable object must have a value". JetOuterJoinProjectionGuardExpressionVisitor rewrites the marker to CASE WHEN anchor IS NULL THEN NULL ELSE 1 END. It runs in JetParameterBasedSqlProcessor after base.Process: the anchor is non-nullable within the subquery, so applied earlier SqlNullabilityProcessor proves the test false and folds the CASE back to its ELSE branch. It matches only EF's injected fragment, not user constants, which are never read for null-ness. AdHocMiscellaneousQuery goes from 43 to 15 failures; NorthwindJoinQuery is unchanged either way. Migration lock Take the lock with a conditional INSERT ... SELECT ... WHERE NOT EXISTS reporting @@rowcount, the Jet analogue of SQLite's INSERT OR IGNORE + SELECT changes(), so losing the race is an ordinary result rather than an exception recognised by message text. Also fix the async retry sleeping on the constant instead of the backing-off local, switch its ConfigureAwait to false, and add DefaultLockTableName for parity with SQLite. LibRed duplicate keys RowInserter throws ConstraintViolationException, which LibRedCommand translates at the ADO boundary into LibRedException with number 2627, so callers can recognise a duplicate key without matching on message text. Tests Port ~140 missing overrides per provider from upstream SQL Server with their baselines, and order every member of the 24 affected classes to match upstream. SQL Server version guards are dropped and IsJsonTypeSupported branches collapsed, as neither applies here. Baselines are still SQL Server dialect and will be swept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tion global.json pinned the preview 6 SDK while the packages moved to preview 7, which broke every job in CI. setup-dotnet installs the exact version from global.json and does not apply rollForward when choosing what to download, so CI built and ran on preview 6. Microsoft.Extensions.Primitives is in-box, so package pruning drops the package reference and the app binds to the shared framework's copy - preview 6's, which lacks the ChangeToken.OnChange overload that preview 7's Configuration calls. Every fixture failed in its static constructor with MissingMethodException. Locally rollForward: latestFeature picked up an rc.1 SDK, so none of this showed. The SDK build number matches the package build exactly; they ship together. CI: run the two ACE-free LibRed jobs on ubuntu-24.04-arm and windows-11-arm as well. LibRed is fully managed with no Access dependency, so ARM is the leg that actually exercises the cross-platform claim off x64. LibRedAccess is unchanged - it cross-checks against the real engine over OLE DB and needs Windows x64 with ACE. ConstraintViolationException was only thrown on the insert path; the update path threw a plain InvalidOperationException for the same violation, so an update that hit a duplicate key reached ADO callers untranslated while an insert became a DbException. Both paths now throw it and LibRedCommand translates both. Deriving from InvalidOperationException keeps catch blocks working but does not keep Assert.Throws<InvalidOperationException> passing - xUnit matches the type exactly, and ThrowsAny is the one that accepts derived types. The four affected assertions in UniqueIndexEnforcementTests now name the type directly, and the doc comment says so rather than claiming otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Jet's ROUND is the VBA function, so it widens a Currency column to a Double: SUM(ROUND(UnitPrice, 2)) comes back from ACE as a Double even though EF asked for a decimal. Up to .NET 10, Convert.ToDecimal rounded that Double at 15 significant digits -- the runtime's VarDecFromR8 did so deliberately, to keep garbage digits out of the decimal it was making. dotnet/runtime#130566 (.NET 11 preview 7) replaced it with a correctly-rounded full-precision conversion, so 58.6 became 58.600000000000001421085471520 and Sum_over_round_works_correctly_in_projection started failing. Nothing in EF, the SQL, the model or the provider had changed. That 15-digit rounding came from OLE Automation, which is also where Jet's Currency type and its VBA ROUND come from, so the old behaviour was tuned for exactly this case and we were relying on it without knowing. Port the pre-change algorithm into JetDecimalConverter (15 significant digits for double, 7 for float) and call it from JetDataReader.GetDecimal ahead of the existing Convert.ToDecimal fallback, leaving genuine decimal/Currency values untouched. A port rather than a G15 round-trip: it reproduces the old overflow and flush-to-zero boundaries exactly and allocates nothing on a hot read path, and G15/G7 formatting is what we already had to abandon for the double and float literal mappings over rounding and out-of-range values. Also document the OLE Automation heritage in CLAUDE.md, including the rule this cost us: when a long-stable conversion misbehaves with no code change on our side, suspect the runtime's OA-era compatibility behaviour before the provider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Correct the range error to name the bound it actually tests. It reported JetConfiguration.TimeSpanOffset (1899-12-30) while the check is against 0100-01-01, so anyone hitting it was told the floor was 1899 -- particularly confusing given the OLE epoch's part in date handling elsewhere. JetDateOnlyTypeMapping already reported 0100-01-01; this was the outlier. Drop the unreachable branch in the same method. Both callers substitute the OLE epoch for a Ticks-0 value before calling it, so the value can never be default by then; the guard and the commented-out assignment beside it are leftovers from before JetDateTimeRangeConverter took over correcting default for ordering. Remove the unused MaxDateTimeDoublePrecision constant, and the now-unneeded EntityFrameworkCore.Jet.Data using with it. Document why ProcessStoreType passes storeTypeNameBase in place of storeType: Jet/ACE has no scaled datetime, so "datetime(3)" must collapse to the bare "datetime". It reads like a slip and is not one. No behaviour change. BuiltInDataTypesJetTest 55/55. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Probing ACE (AceVbaConversionProbeTest, which pins each result) found three
places where .NET's Convert.* stood in for OLE Automation's and had drifted:
CStr formats a Double at 15 significant digits and a Single at 7 -- the OA/VB
convention. ACE returns "0.3" for 0.1+0.2 and "0.333333333333333" for 1/3, while
Convert.ToString gives .NET Core 3.0+'s shortest round-trippable form,
"0.30000000000000004". The same 15-digit convention behind the decimal
regression, surfacing in string form.
Booleans convert as VARIANT_BOOL: CInt/CLng/CDbl/CSng/CCur(True) are all -1 in
ACE, where Convert.ToInt16(true) is 1, and CByte(True) overflows because a byte
cannot hold -1. The evaluator already had a Numeric() helper applying Jet's
-1/0 convention, and the arithmetic path used it -- the C-functions simply
bypassed it and called Convert.To* directly. Route them through it.
CBool accepts numeric strings ("-1") and non-integral numbers; Convert.ToBoolean
rejects the former outright, so LibRed refused input ACE accepts.
Two things the probe corrected rather than confirmed. CDec does not exist in the
Jet Expression Service at all -- ACE rejects it for a column or a literal alike
-- so LibRed's CDec has no parity contract to honour, and CCur is ACE's route to
a decimal. And CStr(True) is "-1", not "True" as the VBA runtime proper renders
it: the expression service is not VBA, so its behaviour is a question to probe
rather than one to reason out from VBA documentation. An existing test asserted
"True" on that basis and was wrong.
LibRed.Engine.Tests 895/895.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An OA date is a double: integer part days from 1899-12-30, fraction the time of day. Below the epoch the day count goes negative while the time fraction stays positive, so 1899-12-29 06:00 is -1.25 and 18:00 is -1.75 -- later in the day is the smaller serial. Microsoft documents the consequence: midnight 1899-12-30 "is not the minimum value, it is the middle when negative values are considered". ACE compares and orders on that raw serial, so it places later pre-epoch times first: `06:00 < 18:00` is False and ORDER BY returns 1,3,2,4,5,6 over rows spanning the epoch (AcePreEpochDateProbeTest pins both). Its date functions are unaffected -- DateAdd/DateDiff work in date space -- which is why the existing DateAdd/DateDiff tests never surfaced any of this. The evaluator compared CLR DateTime values, i.e. chronologically, while IndexKeyEncoder writes that same raw serial as the index key. So the two paths disagreed: a pre-epoch range predicate returned rows 2,3,4 on a scan but only row 4 through an index seek, the rows at -1.25 and -1.75 falling below a -1.0 lower bound in serial space despite being chronologically later. Which rows a query returned depended on the planner's choice of access path. The key encoding cannot change -- ACE writes those keys too -- so matching ACE in the evaluator is the only way both paths agree. Compare via ToOADate in Compare, which CompareForSort delegates to, so ordering and comparison move together. From the epoch onward the two orders are identical; only pre-1899 dates are affected. Not hypothetical: the GearsOfWar model stores DateTimes in the year 102 (raised from the original year 2, which is unusable as years below 100 do not round-trip). GearsOfWar 3300 passed / 0 failed after the change, and LibRed.Engine.Tests 898/898. PreEpochDateOrderingTests guards index-vs-scan agreement for ORDER BY and for a straddling range, comparison consistent with ordering, and pins the sequence to 1,3,2,4,5,6 -- the same sequence ACE produced for the same rows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Revised SQL assertions in test files to use Jet/ACE-compliant syntax: replaced T-SQL constructs (e.g., ISNULL, COALESCE, CASE, CHARINDEX, VALUES, OPENJSON, NVARCHAR, N'', NOCOUNT) with Jet/ACE equivalents (IIF, INSTR, CLNG, CVar, TRUE/FALSE), switched identifier quoting from [] to backticks, adjusted join/subquery syntax, and updated parameter/literal formatting. Ensured null handling and conditional logic use IIF. Removed unsupported features and updated test expectations to match Jet/ACE SQL output per project guidelines.
The converter restores the pre-.NET-11 double->decimal behaviour that dotnet/runtime#130566 replaced, and anyone reading doubles out of ACE has the same problem, so it is API rather than an internal detail. That also lets the test reference it directly. EF Core hit this in the Cosmos provider (JSON numbers are doubles) and fixed it by round-tripping through a "G15" string. The two are NOT equivalent: Convert.ToDecimal(-0.9892735183189034) returned -0.989273518318904 on .NET 10, which this port reproduces, while the round-trip yields ...903 — the correctly rounded 15-digit answer, but not the one that shipped. The old algorithm scaled by a power of ten in double arithmetic before rounding, and that error is part of the behaviour being restored. The port was checked against the real .NET 10 runtime over 3.5 million values, including the overflow and flush-to-zero boundaries, with no disagreement. JetDecimalConverterTest keeps the regression values, the float 7-digit path, the boundaries, and the pinned divergence from the G15 approach. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Working through the tests that passed under EF 10 and no longer do. The green list is an EF 10 artefact — no EF 11 run has been clean enough to update it — so these are the EF 10 -> EF 11 delta rather than recent regressions. Simple_decimal_literals_are_parsed_for_HasDefaultValue built its expectation with (decimal)-1.1111, a runtime double->decimal cast, so preview 7 moved the EXPECTATION to -1.1110999999999999765520897199 while scaffolding kept parsing the literal exactly. Now a decimal literal. Upstream's SQL Server test has the same latent bug and will hit it when they take the preview 7 SDK; they are pinned to preview 6. JsonTypes collection tests passed SQL Server's store types (nchar(32), varchar(max)); Jet text is always Unicode and 255 is the varchar ceiling, so the element facets asserted a type the mapping had already resolved. LibRed already used the Jet values. CompiledModel needed ManyTypes.Decimal's DecimalTypeDefaultWarning suppressed, exactly as the SQL Server test does — same validator, same warning. Select_DTO_constructor_distinct_..._after_client_eval no longer throws on Jet, so the Assert.ThrowsAsync<TrueException> wrapper (which upstream keeps, because it still fails on SQL Server) had to go, and its ORDER BY gained the collection key. ToQueryString_for_include_reference_and_collection lost a now-redundant ordering term. Column_collection_of_nullable_strings_contains_null was overwritten during the preview 7 sync with SQL Server's OPENJSON body. Jet has never been able to query into a JSON collection: the EF 10 override asserted the translation failure, and that is restored — calling its own base rather than the non-nullable one, which the EF 10 version got wrong. Also removed StringTranslationsJetTest.Equals and six ModelBuilding entries from the green lists. Those tests are discovered but never execute — name collisions with object.Equals and with protected generic helpers — so they can never pass again and would gate the list forever. Fixed upstream by dotnet/efcore#38766 for rc1, at which point the ModelBuilding ones return under the same names while Equals becomes Instance_Equals. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The generator now emits a ProviderName on DbContextModelAttribute and uses file-scoped namespaces, so every baseline file differs. Codegen formatting only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
723 files carried a BOM and 1,170 did not, because .editorconfig set end_of_line = crlf but said nothing about charset — endings were governed and held, encoding was not and drifted. Visual Studio writes new C# files without a BOM, so that is the convention: strip the 723 and add charset = utf-8 alongside the existing end_of_line, in both sections that set it. Encoding only. Verified no file differs by anything but its BOM, the solution builds with no warnings, and LibRed.Engine.Tests is 898/898. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Redundant null checks in LEFT JOIN conditions are removed by overriding VisitLeftJoin in JetQuerySqlGenerator. Join predicates now use simple equality (ON a = b) instead of complex null checks for composite keys and nullable columns. Updated test baselines reflect the simplified SQL output, ensuring more idiomatic and Jet/Access-compliant queries.
Both write outside a transaction and reseed as their last statement, so a failed assertion skips the cleanup and leaves its rows behind. Deadlock_on_deletes_with_dependents_is_handled_correctly has never passed on ACE, so it left two owners every run, and Inserts_when_database_type_is_different - which runs after it and counts owners - then saw 4 instead of 2. That is the intermittent failure: deterministic contagion, appearing only when the two land in the same shard. Reseeding in a finally makes a failure stop at the test that caused it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MID raises "Invalid use of Null" when its start or length is NULL rather than propagating it, so Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex failed once EF 11 stopped emitting the conversion whose null check happened to protect it. The split is by argument position, not by function: a NULL in a string or value position propagates, a NULL in a numeric one - length, start, count, code - raises, because VBA cannot coerce Null into a numeric parameter. Verified in LibRed.Core.Tests.AceNullArgumentProbeTest, which also records that LEFT, RIGHT, INSTR, SPACE, STRING, CHR and DATEADD behave the same; only MID has been hit so far, because numeric arguments are almost always literals. Emitted at generation and not as a CASE in the query tree, because dotnet/efcore#34127 removes a CASE that merely replicates SQL's native null propagation - which is exactly what this looks like to a dialect where these functions do propagate. Same reason the outer-join projection guard runs late. IIF short-circuits, so no inner placeholder is needed. Nullability originating in the value argument is not guarded: ACE returns NULL for the whole call when the value is NULL, before coercing the numerics, so MID(note, 1, LEN(note)) needs nothing and only MID(note, 1, LEN(other.Name)) does. Without that the guard also fired on every Substring over a nullable column, changing SQL that was already correct. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the boundary behind the MID guard: string and value positions propagate NULL, numeric positions - length, start, count, code, date increment - raise. Also pins that IIF short-circuits, so a guard needs no inner placeholder, and that a NULL value argument short-circuits the call before its numeric arguments are coerced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Converting a bool to a number has to yield .NET's 1/0, but the column holds VARIANT_BOOL's -1/0. The generator did that flip when it saw a Convert node, which stopped working once EF elided the conversion: bool maps to smallint, so Convert.ToInt16(bool) looked like a no-op and reached SQL as a bare column, making `WHERE Bool = 1` match nothing. Convert.ToInt32(bool) was unaffected only because int and smallint differ. JetSqlExpressionFactory.Convert is the single funnel - Convert.ToXxx through JetConvertTranslator, and casts built by EF - so one flip covers both, and a plain cast whose conversion is also elided, which neither of the other places could reach. What it emits is a multiplication rather than a cast, so nothing downstream classifies it as redundant. Only numeric targets are flipped. Excluding bool and string was not enough: a bool concatenated into a string arrives with some other target type and came out as `HasSoulPatch * -1 & ''`. That makes the generator's bool branch dead, and with the flip gone both of its arms were identical, so the special case goes entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First clean run since the upgrade began, so the lists could finally be published: OLE DB 34,912 -> 35,581 and ODBC 31,245 -> 32,055, with nothing removed from either. Committed by hand because the AutoCommit workflow runs the copy of its file on the default branch, so its fix cannot take effect for this PR. The lists had been frozen far longer than the EF 11 delta: 135 MigrationsJetTest entries appear in both providers, along with the complex-type model builder tests, all of which have been passing without being recorded. Until now the gate was only protecting what it had managed to capture before the freeze. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
actions/checkout refuses to check out the head repository from a workflow_run job, which holds the base repo's token and secrets - the "pwn request" shape. The green-test commit step never ran because of it. Check out the base repository at the PR's branch instead of naming the head repo, which is the same code for a same-repository branch and needs no opt-in, and skip the job entirely for forks: GITHUB_TOKEN could not push to a fork anyway, so there was nothing to gain and a fork's code would have been placed in a trusted context. test_results.yml already dropped its checkout for the same reason. Takes effect once on the default branch; workflow_run always runs the file from there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves src to net11.0 and EF Core 11 preview 7, the test projects to xunit v3, and works through the resulting behaviour changes in both providers.
Provider changes
Decimal conversion. dotnet/runtime#130566 replaced Convert.ToDecimal(double)'s 15-significant-digit rounding — inherited from OLE Automation's VarDecFromR8 — with a correctly-rounded full-precision conversion. Jet's ROUND is the VBA function, so it widens Currency to Double and SUM(ROUND(UnitPrice, 2)) came back as 58.600000000000001421085471520. JetDecimalConverter restores the old algorithm; verified against the .NET 10 runtime over 3.5 million values.
VARIANT_BOOL conversions. bool maps to smallint, so EF now elides Convert.ToInt16(bool) as a no-op, taking with it the * -1 flip that turns Jet's -1 into .NET's 1 — WHERE Bool = 1 matched nothing. The flip moved to JetSqlExpressionFactory.Convert, the single funnel for both Convert.ToXxx and plain casts.
NULL arguments to VBA functions. MID raises "Invalid use of Null" on a NULL length rather than propagating, and the conversion that used to guard it is likewise gone. Guarded at SQL generation, because dotnet/efcore#34127 removes such a check from the query tree — correctly, for any dialect where these functions do propagate.
LEFT JOIN predicates. dotnet/efcore#38449 keeps outer-key null checks on compound join predicates; Jet's ON accepts only comparisons between the joined tables, so the redundant ones are dropped at generation.
LibRed matches ACE's VBA conversion semantics (CStr at 15/7 significant digits, VARIANT_BOOL for CInt/CLng/CDbl, CBool on numeric strings) and compares dates by OLE Automation serial, which keeps its evaluator and index ordering consistent.
Infrastructure
ARM64 CI legs for the LibRed jobs; large-address-aware x86 test hosts; shard rebalancing and crash retry; encoding normalised to UTF-8 without BOM with charset now stated in .editorconfig.
Notes
ACE behaviour is recorded as executable probes under LibRed.Core.Tests rather than prose — VBA conversions, pre-epoch dates, join null guards, and which function arguments reject NULL. Green-list entries were removed for tests that are discovered but never execute (dotnet/efcore#38766, fixed for rc1).