Decouple the designer from Visual Studio and ship a headless EDMX toolchain - #3
Merged
Conversation
- Add Microsoft.Data.Entity.Design.Renderer with the exporters moved out of EntityDesigner, plus a headless host that builds and routes a diagram with no Visual Studio shell - Add Microsoft.Data.Entity.Tools: edmx render, writing SVG and Mermaid and rasterising the SVG for image formats - Load EDMX without the shell: own artifact factory that picks up a sibling .edmx.diagram, and a render-safety check that does not require the model's ADO.NET provider to be installed - Split raster export behind IRasterExporter so Visual Studio keeps Diagram.CreateBitmap and other hosts supply their own - Skip the package model manager subscription when no shell is loaded, via PackageManager.IsLoaded rather than a null check that asserts - Add Microsoft.Data.Entity.Tests.Design.Renderer covering the loader, the exporters and headless routing Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Documentation and structure: - Document every public type and member, with worked examples on the interfaces and MEF attributes an extension author implements - One type per file, formatted to the repo's conventions, and rename two files that never contained the type they were named after - Add a README covering the extension points, MEF discovery, a complete worked example, and the known gaps that remain - Retarget to netstandard2.0 and net10.0, dropping the unused WPF dependency; the VSIX now resolves it through the ProjectReference Fixes, none of which change a public signature: - Throw ArgumentNullException rather than ArgumentException for null EntityDesignerCommand arguments - Reject a null command name at the setter, so GetHashCode can no longer throw once the command is in a hash-based collection - Require a message on ExtensionError, and clamp the -1 "no position" sentinel where it reaches the Visual Studio TextSpan instead of navigating the Error List to a negative offset - Only an Error severity discards a model transform; warnings and messages are reported and the extension's work is kept - Log to the activity log when malformed XML skips the extension pipeline, instead of silently running nothing - Detect duplicate conversion extensions before running any of them, so a conflict fails cleanly and named rather than silently picking whichever MEF enumerated first Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd net10.0 - Move 22 view files to Microsoft.VisualStudio.Data.Tools.Design.XmlCore, where every consumer of them already had a reference: the Explorer views, the WPF commands and value converters, the editable content controls, LoadingUI, and the UITypeEditor that was sitting among the view models - Repoint the three XAML files that named the old assembly on their clr-namespace declarations - Drop UseWPF and UseWindowsForms: no file in XmlCore references System.Windows any more - Target netstandard2.0 and net10.0, with per-framework output so the two do not overwrite each other. netstandard2.0 covers the .NET Framework consumers and stops the layer quietly reacquiring a desktop dependency - Let the VSIX resolve XmlCore through its ProjectReference rather than a hardcoded flat path, which no longer exists What is left behind is the object model, the editing context and the Explorer and property window view models - no UI framework of any kind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… GAC DbContext is what people use, so the designer no longer generates ObjectContext classes. The single file generator emits no classes for any strategy now: DbContext generation produces a context plus a file per entity type, which a single file generator cannot express, so it is delivered by the EF 6.x DbContext Generator item template that already ships here - the same way Entity Framework shipped it. - Delete VersioningFacade/LegacyCodegen and LegacyCodeGenerationDriver, which wrapped System.Data.Entity.Design from the .NET Framework's in-box assembly, along with their tests - Drop all four GAC references from VersioningFacade - Keep LanguageOption, which was only filed under LegacyCodegen by accident, with explicit values instead of ones aliased to the GAC enum - Point the provider services probe at Entity Framework 6's DbProviderServices rather than the pre-EF6 type of the same name. This changes which providers it recognises, which is the intent - Target net48, netstandard2.1 and net10.0. netstandard2.x cannot stand alone: EF 6.5 ships no netstandard2.0 asset, and .NET Framework has never been able to consume netstandard2.1 - Move CopyPasteUtils to the package. Wrapping the Windows clipboard, it was the only file in Model with a UI dependency - Raise System.Text.Json to 9.0.0, which is what let the provider packages resolve for the non net48 targets Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Target net48, netstandard2.1 and net10.0, and drop UseWindowsForms. The clipboard helper that needed it moved to the package earlier, so nothing in Model referenced a UI framework any more - Delete HostContext, the mutable static that let Model call up into the package to log an Update Model from Database warning. The callback is now passed to UpdateModelFromDatabaseCommand and through to UpdateConceptualAndMappingModelsCommand, so the dependency is visible to the compiler at every hop and a host that does not supply one is making a choice rather than forgetting a static - Let the VSIX resolve Model through its ProjectReference rather than a hardcoded flat path, which no longer exists Behaviour is unchanged: the same ErrorListHelper method receives the same warning and writes to the same error list. Both new parameters are optional, so no other caller of these commands needed to change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
System.Activities has no supported .NET Core successor, so it pinned both DatabaseGeneration and Design to net48. The workflow it bought was a two-step Sequence deserialized from TablePerTypeStrategy.xaml - infer SSDL and MSL from the CSDL, then render DDL from the SSDL - which a method expresses directly. - Add DatabaseScriptGenerator, replacing the XAML activity graph, and DatabaseScript to carry its SSDL/MSL/DDL output. - Replace IGenerateActivityOutput with ISchemaGenerator, dropping the NativeActivityContext and inputs dictionary the generators only ever used for parameter lookup. CsdlToSsdl and CsdlToMsl now take their inputs directly; both had a dead _activity field. - Add IDdlGenerator so the VS-hosted T4 step is injected rather than referenced, keeping DatabaseGeneration free of any Visual Studio dependency. TemplateActivity and SsdlToDdlActivity become TemplateProcessor and SsdlToDdlGenerator in Design. - Run generation on the thread pool in WizardPageDbGenSummary, which the workflow runtime used to do implicitly, and marshal results back through the existing SynchronizationContext. - Retarget DatabaseGeneration to net48;netstandard2.1;net10.0. The DatabaseGenerationWorkflow designer property and its file-list converter are removed, since they selected a .xaml graph that no longer exists. Existing .edmx files carrying the property still round-trip: SafeGetDesignerProperty ignores properties nothing reads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"EntityDesigner" read like the designer feature as a whole, when the project is specifically the DSL surface: DslDefinition.dsl, the 14 files generated from it, and the Modeling SDK Store that mirrors the EDMX for display. Renames the project and test project folders and .csproj files. The shipping assembly keeps AssemblyName and RootNamespace of Microsoft.Data.Entity.Design.EntityDesigner - the DomainModel registration, VSIX, and generated code all bind to that name. The test assembly has no such constraint, so it is renamed to Microsoft.Data.Entity.Tests.Design.Dsl along with its namespaces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Completes the project rename by changing AssemblyName, so the shipped binary matches what the project is. - AssemblyName, Title, and Description on the Dsl project. - Four InternalsVisibleTo grants naming the old assembly. - ExportDiagramDialog.xaml, which resolves the Properties namespace with an explicit assembly= qualifier. - Eight PkgDefData entries registering toolbox tabs, items, and bitmaps by DLL filename, plus the VSIXSourceItem flat path in the Package project. That path is not derived from the ProjectReference, so it would have kept packaging a file that no longer exists. - The .lci and ten .lcl localization assets, which are named for the DLL. RootNamespace stays Microsoft.Data.Entity.Design.EntityDesigner. Manifest resource names derive from it, and the generated DSL code addresses DomainModelResx by that name, so changing it would break resource lookup for no benefit. Renaming the namespaces is a separate change across 148 files. Verified by wiping bin/obj and confirming the rebuilt VSIX contains Microsoft.Data.Entity.Design.Dsl.dll. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Completes the rename so namespace, assembly, and project all agree. Replaces the fully-qualified namespace across 178 files, including the 20 Namespace attributes in DslDefinition.dsl and the 14 .tt templates. Those are the generation inputs, so leaving them would have reverted the .cs files on the next regeneration. Also fixes ten relative references in the Package project, which wrote bare EntityDesigner.ViewModel and EntityDesigner.Utils resolved against the enclosing Microsoft.Data.Entity.Design namespace. A fully-qualified find/replace cannot see those; the compiler caught them. EntityDesigner.ctmenu is left alone. It is a VS menu resource name paired with an entry in the pkgdef, not a namespace. Verified beyond the compiler, which cannot catch a broken resource lookup: reflected over the rebuilt assembly to confirm the manifest resource names match what the generated DisplayNameResource and DescriptionResource attributes reference, and that every non-empty key in DomainModelResx resolves. The 17 that return empty are empty in the .resx by design. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Puts Visual Studio at the top of the name, matching the existing Microsoft.VisualStudio.Data.Tools.Design.XmlCore sibling. This assembly carries 24 Visual Studio package references - more than the VSIX itself - so the old name read like a core layer while being the most shell-coupled assembly in the tree. Renames the project folder, .csproj, and AssemblyName, and updates the six ProjectReference paths, six InternalsVisibleTo grants, the pkgdef CodeBase entry, the VSIXSourceItem flat path, ResourcesHelper's VS-install probe, and eleven localization assets. Namespaces are deliberately left alone; see the follow-up note in the commit body of any future namespace change. Attempting the namespace rename produced 2268 compiler errors, because namespaces in this repo are organised by feature while assemblies are organised by dependency layer, and the two cross-cut on purpose. Microsoft.Data.Entity.Design.UI.Views.Explorer is declared by three assemblies; Model, Model.Commands, Model.Validation, Model.Integrity and others are split between Model and XmlCore. Moving one assembly's namespaces is not a rename but a decision to abandon that scheme, and it touches four assemblies at once. Verified from a wiped bin/obj: the rebuilt VSIX contains Microsoft.VisualStudio.Data.Entity.Design.dll. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The headless rendering path no longer needs to be .NET Framework only. Both projects now produce net48 and net10.0 assemblies. Also maps system.diagnostics.performancecounter to nuget.org. It arrives as a transitive dependency only on the net10.0 graph, and PackageSourceMapping was refusing to restore it. Note that the Renderer test project still targets net48 only, so the net10.0 assemblies compile and lay down their dependencies but are not yet exercised at runtime. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…build The in-flight rename left the tree unbuildable. Every failure traced to one of three causes, all fallout from moving types out of Microsoft.Data.Entity.Design.*: - Stale using directives kept alongside their replacements (CS0234). - Folder namespaces named after types used in the same subtree (CS0118). C# resolves members of an enclosing namespace, including child namespaces, before any using directive or alias, so such a namespace shadows the type for the whole subtree and its parent and no alias can fix it. Pluralized the colliding leaves - NavigationProperties, Associations, Functions, FunctionImports - plus Properties, Types and Tables, which had not fired yet but carry the same trap. Folders and namespaces still agree everywhere. - Names that used to bind through the enclosing namespace and no longer do (CS0104/CS0103): Resources, ModelChangeEventArgs, Services.ServiceProvider, Design.Resources. Made those bindings explicit. The T4 templates under CodeGeneration/Generators/GeneratedCode still imported the pre-rename namespace. Their import directives are what become the usings in the generated output, so the .tt files and their .cs are updated together and a regeneration will not undo the fix. Also fixes two pre-existing runtime failures, both found by running the tool rather than by building it: - edmx render was dead on net48. With no PlatformTarget, an AnyCPU exe defaults to Prefer32Bit, launches 32 bit, and cannot load the GraphObject layout engine's x64 native dependency. Set PlatformTarget=x64. - Renderer and Tools targeted net10.0 rather than net10.0-windows, leaving WinForms out of the framework reference set. NuGet.config carried no package source mapping for the .NET 10 targeting and runtime packs, so the net10.0 target could not restore an apphost. Verified: the solution builds clean from scratch, and `edmx render Northwind.edmx` produces byte-identical SVG - 51 rects, 36 connector paths, 152 text elements. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds specs/dsl-shell-decoupling.md: the target layering, the naming scheme, the three mechanisms that do the decoupling, an inventory of what moves, and a sequence where each step keeps the tree green. The end state is a single check: Microsoft.Data.Entity.Design.Dsl.csproj holds no ProjectReference to Microsoft.VisualStudio.Data.Entity.Design.csproj. That edge exists today and points the wrong way. Also corrects headless-edmx-rendering.md, which claimed the net472-only Modeling SDK pinned the renderer and everything downstream to net48, and that a real dotnet tool would need a net10 front end shelling out to a net48 worker. Both are false: .NET 10 references and calls .NET Framework assemblies on Windows. Records the two things that do bite - the desktop TFM requirement and the x64 native dependency in GraphObject - and the one genuinely open problem, a VS SDK dependency resolving to a reference assembly under net10. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Regenerating GeneratedCode\*.cs from DslDefinition.dsl no longer requires saving each .tt inside Visual Studio. Wires up the four things the transform needs outside the IDE: - the Microsoft.TextTemplating.targets import, which supplies TransformAll - a DirectiveProcessor item naming DslDirectiveProcessor, which resolves the `Dsl` directive that reads DslDefinition.dsl. It is registered in Visual Studio's private registry hive, so a command line transform has to name the type and assembly explicitly - IncludeFolders, so the nested Dsl\*.tt includes resolve - VsIdePath, without which the targets do not add PrivateAssemblies to the transform's reference path and the directive processor cannot load the Modeling SDK TransformOnBuild is deliberately left off. The generated code is checked in and a plain build must not depend on the DSL SDK being present. DslTemplatesSrc has to be a real environment variable: the template host expands it inside the include directives, and MSBuild's property function allowlist blocks System.Environment::SetEnvironmentVariable. A guard target fails fast with the expected path when it is unset, because the transform task overwrites its outputs before it discovers the includes are unresolvable. Records in the spec that regeneration currently produces code that does not compile - the checked-in output predates VS 18's DSL SDK, and the new templates no longer mark InternalSaveModel virtual. Verified: the guard fires and touches nothing, and a normal build is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EntityDesignerDiagram was never a diagram. It is the layout, routing and hit-testing surface; the object representing an <edmx:Designer><Diagrams> <Diagram/> entry is Model.Designer.Diagram, which every consumer imports under the alias ModelDiagram. Renames the class and everything derived from it - EntityDesignerSurfaceBase, EntityDesignerSurfaceSerializer, EntityDesignerSurfaceMoniker, EntityDesignerSurfaceSelectionRules, EntityDesignerSurface_AddRule, EntityDesignerSurfaceAdd, EntityDesignerSurfaceModelChange - across 49 files, and renames the five files that carried the old name. DslDefinition.dsl is updated in the same commit so the definition and the generated code cannot disagree. Regenerating would otherwise silently undo this: the code is hand-edited because the installed DSL SDK is 18.0 while the project builds against 17.10 packages, and regeneration currently produces code that does not compile. See specs/dsl-toolchain-alignment.md. Three strings are deliberately left as EntityDesignerDiagram because they are XML wire format, not type names: ElementName in DslDefinition.dsl, the serializer's XmlTagName, and the element named in CannotMonikerizeElement. MonikerElementName keeps its lower camel case spelling for the same reason. Model.Designer.EntityDesignerDiagramConstant is a different concept and is untouched. Also retargets Microsoft.Data.Entity.Tests.Design.Renderer to net10.0-windows to match the renderer. It was still on plain net10.0, so every test that built a Store failed to load System.Windows.Forms - 18 failures, now 3. Verified: solution builds clean, `edmx render Northwind.edmx` is byte-identical to the baseline (51 rects, 36 connector paths, 152 text elements), DSL tests pass, renderer tests pass 138/138 on net48 and 135/138 on net10. The 3 remaining net10 failures are all one call chain - GetGlobalService resolving Microsoft.VisualStudio.Shell to a reference assembly - reached from the surface's theming code, which the decoupling moves out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every mutation on EntityDesignerSurface repeated the same four steps: open a store transaction, tag its context with TransactionOriginatorDiagramId, enlist a view model change, commit. Five call sites, and forgetting the tag is silent - the model layer simply stops knowing which diagram a change came from. Adds three helpers and converts all five sites: - InDiagramTransaction(name, Action) runs work and commits. - InDiagramTransaction(name, Func<bool>) commits only when the work reports a change. SetEntityShapesExpanded needs this: committing a transaction that changed nothing still pushes an entry onto the undo stack, so it returns false when no shape was expanded or collapsed. - ApplyViewModelChange(name, change) for the three dialog driven adds. Behaviour is unchanged; the raw BeginTransaction and the context tag now appear exactly once in the file rather than five times. Adds EntityDesignerSurfaceTransactionTests covering commit, conditional rollback, the diagram id tag and the null guard. No mocks; the tests build a real Store. The tests are marked [DoNotParallelize]. Without it they failed intermittently - 0, 1 and 2 failures across five runs - because constructing a Store mutates process wide serializer state through DomainXmlSerializerDirectory .InternalAddBehavior, so concurrent stores race. Every Store building test class in this solution already carries that attribute; this one was missing it. Six consecutive runs are now clean. Mutation checked: making the wrapper commit unconditionally and drop the context tag fails exactly the rollback and diagram id tests, with assertion messages rather than incidental exceptions. Verified: solution builds clean from scratch, `edmx render Northwind.edmx` is byte-identical to the baseline, and the suite is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six WinForms wizard pages threw MissingManifestResourceException at runtime, which surfaced as four failing tests once the solution started compiling again. Renaming the project in 6608e52 changed its RootNamespace from Microsoft.Data.Entity.Design to Microsoft.VisualStudio.Data.Entity.Design. Manifest resource names are derived from RootNamespace plus folder path, so the resx files moved to the new prefix while the form classes kept the old namespace. ComponentResourceManager looks its resources up by the type's full name, so the two no longer met: embedded Microsoft.VisualStudio.Data.Entity.Design.VisualStudio .ModelWizard.gui.WizardPageDbConfig.resources requested Microsoft.Data.Entity.Design.VisualStudio .ModelWizard.Gui.WizardPageDbConfig.resources Wrong prefix and wrong casing, because the folders are lower case while the namespaces were Pascal case. Finishes that rename for the ModelWizard tree and makes folders and namespaces agree: gui and engine are renamed to Gui and Engine, and every namespace under them moves to the Microsoft.VisualStudio.* prefix. The embedded names now match the type names exactly, so lookup resolves without any LogicalName overrides. Moving those files out of Microsoft.Data.Entity.Design.* cost them their enclosing namespace bindings, the same way the earlier CS0104 wave did, so VSHelpers, NativeMethods, VSFileFinder and Resources are now imported or aliased explicitly where they were previously found by walking outwards. Verified: solution builds clean from scratch, `edmx render Northwind.edmx` is byte-identical, and the Package suite goes from 14 failures to 10. The remaining 10 are a different problem and predate this: they construct real WinForms wizard pages, and VS's DpiHelper needs IVsSettingsManager from a live shell. Fixing the resources only moved their failure later, from InitializeComponent to DpiHelper. They need a VS host to run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The solution compiles with 0 errors, but 14 tests fail, 203 never run, and the build emits 270 warnings including four packages with known vulnerabilities. specs/known-issues.md records each one with its evidence, cause where known, and a suggested order of attack. Two findings worth calling out. 17 tests are declared static, which MSTest silently refuses to run - they are not even reported as skipped, only as an MSTEST0003 warning. And 186 tests are disabled with [Ignore], almost all of them saying the same thing in thirteen different spellings: the locally built EF6 assemblies expose different member visibility than the shipped ones. That is 39% of the VersioningFacade suite. Two entries are expected to close as a side effect of the DSL/shell decoupling rather than needing their own work: the three net10 renderer failures and the net10 edmx render tool, which share one call chain through the surface's theming code into Microsoft.VisualStudio.Shell. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
16 advisories across 4 packages, two of them high severity. Restore and build now emit zero NU1901, NU1902 and NU1903, and total build warnings drop from 270 to 192. MessagePack 2.5.187 -> 2.5.302 11 advisories, 2 high Npgsql 4.1.3 -> 4.1.14 1 advisory, 1 high Azure.Identity 1.10.3 -> 1.16.0 2 advisories Microsoft.Identity.Client 4.56.0 -> 4.87.0 2 advisories All four are transitive, so these are PackageVersion entries relying on CentralPackageTransitivePinningEnabled, which was already on. Each is held inside the major line its consumer binds to, so no API surface moves: - MessagePack stays on 2.x. StreamJsonRpc and the rest of the VS SDK bind to 2.x and 3.x is a breaking change. - Npgsql stays on 4.1.x. EntityFramework6.Npgsql 6.4.3 depends on Npgsql 4.1.3 and 5.x would break the EF6 provider. 4.1.14 is the last of the line. - Azure.Identity is held at 1.16.0 rather than the latest 1.x. 1.17 and later pull Azure.Core 1.53, which needs System.Text.Json 10; that pin is shared with the VSIX, and raising it was more churn than this fix warrants. 1.16.0 lands on Azure.Core 1.47.3, needing only System.Text.Json 8, satisfied by the existing 9.0.0 pin. Verified: restore clean, build clean from scratch, `edmx render Northwind.edmx` byte-identical, and the test suite unchanged at the same 14 failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The type was defined twice, in Microsoft.Data.Entity.Tests.Design and again in Microsoft.Data.Entity.Tests.Design.Package, in the same namespace, with the projects referencing each other. The two copies were identical apart from a BOM, using order and a trailing newline. CS0436 drops from 26 to 4. Deleting the Package copy is all that was needed: Tests.Design.Package already referenced Tests.Design, which already exposed its internals to it, and the consuming test classes are in the same namespace. Not moved to Microsoft.Data.Entity.Tests.Shared as the issue list first suggested. It depends on MockDTE, which lives in Tests.Design, and Tests.Design already references Tests.Shared, so moving it would have inverted that edge. It also cannot be made public, because it returns the internal ModelBuilderWizardForm. The 4 remaining CS0436 are Krafs.Publicizer injecting IgnoresAccessChecksToAttribute into more than one assembly. Left alone on purpose: blanket-suppressing CS0436 would also hide genuine duplicate type mistakes like this one. Verified: build clean from scratch, 181 warnings down from 192, and the test suite unchanged at the same 14 failures with the Package suite still passing 49. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AssemblyLoader_passed_WebsiteProject_can_find_correct_paths_to_DLLs threw a NullReferenceException at the foreach over vsWebSite.References. The production code was correct. The defect was in MockDTE.CreateVsWebsite: foreach binds to the strongly typed AssemblyReferences.GetEnumerator(), not the IEnumerable one, and the helper set up only the latter, so the former returned null and the loop threw. CreateVsProject2 sets up both, which is why the project reference test sitting next to it always passed. Also fixes a latent second defect in the same helper. It passed Returns(references.GetEnumerator()), evaluating the enumerator once at setup rather than Returns(() => ...), so any second enumeration would have received an enumerator that was already exhausted. Microsoft.Data.Entity.Tests.Design now passes 455 of 481, stable across four runs. Solution wide failures drop from 14 to 13. Also records in specs/known-issues.md why the 17 static test methods are static: they were already static at 721bc51 in 2021, inherited from the EF6 repository as xUnit [Fact] methods. xUnit runs static test methods and MSTest does not, so the January 2026 conversion to [TestMethod] turned a legal signature into one MSTest silently skips. They were not made static to hide failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Modeling SDK cannot be driven from more than one thread in a process. It assumes a single threaded host and keeps unsynchronized process wide state in at least two places: - DomainXmlSerializerDirectory.InternalAddBehavior, reached from CoreDesignSurfaceSerializationHelperBase.InitializeSerialization during store construction. - StoreDiagramMappingData.GetInstance(Store), a static Dictionary keyed by Store that DiagramCommittingRule.TransactionCommitting writes to on every transaction commit. Locking store construction is not enough. That was tried and measured: with a lock around new Store(...) and parallelism otherwise on, 12 runs gave 7 clean, one with a single failure, one with two, and one torn dictionary that crashed the test host. The commit path still raced, because the state is mutated inside SDK rules that no care at our call sites can guard. So the constraint is declared where it actually applies - the whole assembly. A test project sets UsesDslStore to opt out of the blanket [Parallelize(MethodLevel)] in Directory.Build.props and receive [DoNotParallelize] instead. Tests.Design.Dsl and Tests.Design.Renderer set it. This replaces [DoNotParallelize] on four individual classes. Per class only works if every future author remembers, and these failures are intermittent enough to pass review - which is how the hazard survived this long. Everything that does not touch a Store keeps method level parallelism. Verified: 12 consecutive runs of the Dsl suite and 8 of the Renderer suite, all deterministic; build clean from scratch; Northwind SVG byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The DSL SDK assumes one thread and keeps unsynchronized process wide state. That is not a test-only quirk, it shapes what the whole rendering path can do, so it is written down rather than left as tribal knowledge in a commit message. Adds specs/threading-model.md: the two known static hazards with the stack traces that identify them, why locking store construction is not sufficient, why per class [DoNotParallelize] is not sufficient, and how the constraint is enforced per assembly through UsesDslStore. Adds a threading section to src/README.md listing which projects are single threaded and which are free threaded, plus the one instruction that matters for anyone adding a test project: set UsesDslStore if it builds a Store. Also documents regenerating the DSL from the command line, with a pointer to the caveats. Annotates the three types that carry the constraint - HeadlessDiagramStore, EntityDesignerSurface and EntityDesignerViewModel - so it is visible at the point of use and not only in a spec. Note the practical consequence for the command line tool: rendering several diagrams must be sequential within a process. Parallel rendering needs one process per diagram. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The designer used to ask whether it was running inside Visual Studio, by resolving SVsUIShell through Package.GetGlobalService. That is the wrong question for a designer to ask, and under .NET 10 merely touching Microsoft.VisualStudio.Shell throws "This is a reference assembly", so the command line renderer died before drawing anything. Colors are now pushed in rather than pulled out. The Dsl declares DiagramPalette - five semantic colors with defaults - and DiagramTheme, which holds the palette in force and re-themes registered style sets when it changes. Microsoft.Data.Entity.Design.Package owns VsDiagramTheme, the only place that maps Visual Studio color keys onto those slots. IsThemeServiceAvailable and EntityDesignerSurface.SetColorTheme are deleted. Fixes the leak in the same move. InitializeResources subscribed a closure to the static VSColorTheme.ThemeChanged and could never unsubscribe, because a shape class has no teardown point - it was the only unpaired += in the solution. The subscription now lives in the package, which has a lifetime, and is removed on dispose. Results: - Microsoft.Data.Entity.Tests.Design.Renderer passes 138/138 on net10, up from 135/138. Solution wide failures drop from 13 to 10. - `edmx render` runs on .NET 10 for the first time, and its SVG is byte identical to the net48 build: 51 rects, 36 connector paths, 152 text elements. Shipping Microsoft.Data.Entity.Tools as a real dotnet tool is now a packaging decision rather than a technical obstacle. - The Dsl project no longer references VSColorTheme, EnvironmentColors or GetGlobalService for anything on the diagram, shape or connector paths. What remains is the three WPF controls - the context menu, its panel, and the floating zoom control - which move wholesale in work item 3. CompartmentFill is deliberately left at its default rather than themed: compartments are painted with the fill color declared in DslDefinition.dsl, which is not themed, so colorizing the property icons against anything else would make them disagree with what is drawn behind them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records how one setting can drive both surfaces. Not implemented; for review. The palette added when theming was inverted covers designer chrome and has no effect on SVG output at all, because the exporter reads no StyleSet and emits its own CSS from 14 hardcoded hex literals. Entity fill is a third system again, stored per shape in the EDMX. Changing "the colors entities render in" today means editing three things in three different ways. The plan promotes DiagramPalette into the vocabulary both renderers consume and has each translate it into its own medium: StyleSet overrides and GDI icon colorization for the designer, CSS custom properties for SVG. Two things get fixed on the way, both behaviour preserving and both landable first: the WCAG luminance formula that decides black-or-white text on a fill is currently implemented twice, in EntityTypeShape and SvgStylesheetManager; and DiagramImageHelper calls ThemeUtils in the Visual Studio project at ten sites for GDI bitmap work with nothing VS specific in it, which is both a theming concern and one of the dependencies blocking the decoupling fitness function. Leaves one question open for decision, because it is a product question rather than a technical one: whether modernizing entity fill overrides at render time, rewrites FillColor in the EDMX, or maps stored colors through the palette as a hint. Nothing else in the plan depends on the answer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records the principle governing where code goes: the core owns decisions, the shell owns the platform. Anything the core needs is either pushed in, with a working default so it runs headless, or requested by an event the shell answers. Nothing is pulled, and the core never asks whether a host is present. The point that was being missed: Visual Studio dependence and Windows dependence are the same problem. The test is not "does this reference a Visual Studio assembly", it is "could this run somewhere with no user interface at all". WinForms, WPF and GDI all fail that test the same way VSColorTheme does. Corrects specs/unified-theming.md accordingly. Work item 2 previously proposed moving ThemeUtils - GDI bitmap colorization - into the Dsl in order to remove a project reference. That is backwards: it trades a Visual Studio dependency for a Windows one and makes the harder problem worse. The item now moves DiagramImageHelper and the icon rasterization out to the package instead, which is where the palette already went. All of it is paint time, and painting only happens inside Visual Studio - the SVG exporter has its own vector icons. Also records the corollary that has been violated most: decisions are functions. Validating a path, building a connection string, choosing a default - these belong in the core taking values and returning values, not on a UserControl. Ten wizard tests currently fail because the logic under test can only be reached by constructing a WinForms control, which needs a live shell. A test that needs Visual Studio running is reporting an architecture problem, not a test problem. States honestly what the rule cannot deliver: while the designer is built on the Modeling SDK it cannot be fully platform free, because the SDK is WinForms and GDI to its foundations. The achievable target is that the designer contains no platform code of ours, leaving one known replaceable dependency instead of a hundred scattered ones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Folds the UI consolidation into step 8, because the two cannot be separated. The UI still in the Dsl needs Dsl types - the context menu holds an EntityDesignerSurface, CustomZoomDialog uses the Dsl's own resources - so its destination has to be able to reference the Dsl, which cannot happen while the Dsl references it. Moving first produces a circular reference. This was tried and reverted. Records the measured dependency inventory rather than an estimate. Thirteen files, and several apparent hits are a false positive: the Resources matches are the Dsl's own Properties.Resources behind the EntityDesignerRes alias. EntityTypeShape turns out to need nothing at all - its using is stale. Two findings worth having written down: IViewDiagram is declared in the VS project but implemented by EntityDesignerSurface, so it has to move into the Dsl. A base type cannot point back at the shell. The CustomZoomDialog resx needs care on the way across. Its manifest name comes from root namespace plus folder path, while ComponentResourceManager looks it up by the type's full name; if those disagree the dialog throws MissingManifestResourceException at runtime with nothing failing at compile time. That is exactly what broke the ModelWizard pages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two of the smaller items from step 8, both verified against the measured inventory rather than assumed. IViewDiagram was declared in the Visual Studio project but implemented by EntityDesignerSurface in the Dsl - a base type pointing at the layer above it. It moves to Microsoft.Data.Entity.Design.Model, which both projects already reference, and which is where its only dependency (EFElement) lives. Note it does not move into the Dsl, which was what the spec said. IDiagramManager in the Visual Studio project also consumes it, and the Dsl cannot host a type the Visual Studio project needs until the project reference is reversed. Model is visible to both today and stays correct afterwards, so it avoids a second move later. EntityTypeShape's using of Microsoft.Data.Entity.Design.VisualStudio was stale. The only apparent usage was Resources, which is the Dsl's own Properties.Resources behind the EntityDesignerRes alias. MicrosoftDataEntityDesignCommandSet takes a targeted alias rather than importing Model.Designer wholesale: that namespace declares EntityTypeShape, AssociationConnector and InheritanceConnector, which collide with the Dsl's identically named view types. That collision is what step 6's Edmx prefix rename exists to remove. Verified: clean build, Northwind SVG byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The designer used to construct WPF dialogs, and three ViewModelChange classes held a live dialog and read its controls from inside the transaction. That made "add an entity type" impossible to express without a dialog existing, which is the single biggest reason the designer could not run headless. Each request is now its own event with its own EventArgs carrying what it needs and what it wants back: NewEntityTypeRequested NewEntityTypeRequestedEventArgs NewAssociationRequested NewAssociationRequestedEventArgs NewInheritanceRequested NewInheritanceRequestedEventArgs UnmappedStorageEntitySetsDeletionRequested ...DeletionRequestedEventArgs ReferentialConstraintRequested ReferentialConstraintRequestedEventArgs CircularInheritanceDetected CircularInheritanceDetectedEventArgs Cancelled starts true, so a host that answers nothing creates nothing - which is exactly what the command line renderer wants and needs no special case. VsDiagramRequestHandler in the package is the only place those events meet a dialog. It is attached per document view and disposed with it, so handlers are removed rather than accumulating. The three *_AddFromDialog classes are replaced by EntityTypeAddFromRequest, AssociationAddFromRequest and InheritanceAddFromRequest, which take values. ViewUtils.SetBaseEntityType was mostly model logic wrapped around one error dialog. The logic moves to InheritanceHelper.TrySetBaseEntityType in the model, returning false for circular inheritance instead of telling the user about it. Both shells then decide how to surface it: the designer raises CircularInheritanceDetected carrying the two entity types rather than a message, so the host words it in its own language; the properties window keeps showing the dialog directly through a thin ViewUtils wrapper. It went to the model, not the Dsl, because EFEntityTypeDescriptor in the Visual Studio project calls it too. Verified: clean build, and Northwind renders byte-identical on both net48 and net10.0-windows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ogic The file was two jobs sharing a name. Choosing which document view to show is shell work; resolving an EFObject to a shape and setting the selection is designer work. They are now DesignerNavigator (Package) and DiagramNavigator (Dsl). Both of the Dsl side's Visual Studio touches came from telling the mapping details window to follow along, and both became one MappingDetailsNavigationRequested event. The designer used to write EntityMappingModes directly into the shell's editing context; it now reports bool? UsesFunctionMapping instead -- a fact about the model rather than a display mode. null preserves the existing behaviour of leaving the mode alone on the association-set-mapping path. Also fixes a latent NullReferenceException: the doc view loop null-checked docView but then dereferenced singleDiagramDocView, and a null diagram was passed into code that dereferenced it. Verified: solution builds clean, test results unchanged (same 10 known wizard failures), and edmx render Northwind.edmx is byte identical on net48 and net10.0-windows at 51 rects / 36 paths / 152 text elements. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Escher was Microsoft's internal codename for the designer and carries no meaning for anyone maintaining this now. The four types take the Edmx prefix their assemblies already use: EscherAttributeContentValidator -> EdmxAttributeContentValidator EscherModelValidator -> EdmxModelValidator EscherModelValidatorVisitor -> EdmxModelValidatorVisitor EscherExtensionPointManager -> EdmxExtensionPointManager Files renamed to match. The codename still appears in comments and, more substantially, in the Escher_CSDL / Escher_SSDL / Escher_MSL / Escher_UpdateModelFromDB members of ErrorClass, which distinguish designer-level errors from the runtime's. Those are a separate decision - they are an enum consumers switch on, not an internal type name. Build green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Split, across six waves of parallel agents: - 89 of 91 multi-type files now hold exactly one top-level type. The two survivors are generic/non-generic arity pairs (SubscribeContextCallback, IValueProperty), which conventionally share a file. - git mv where the leftover type no longer matched its filename, including IXmlDesignePackage.cs -> IXmlDesignerPackage.cs, fixing a long-standing typo. - Extension containers renamed to [LastNamespace]_[ClassExtending]Extensions: FeatureSupportedStateExtensions -> Edmx_FeatureStateExtensions, IExplorerViewModelExtensions -> Explorer_IExplorerViewModelExtensions. - tools/type-catalog.cs now counts delegates. It walked only BaseTypeDeclarationSyntax, so delegate-only violations were invisible and every earlier count was a floor, not a total. Namespaces: - Microsoft.Data.Tools.VSXmlDesignerBase is gone. 24 declarations plus every consumer fold onto the folder-derived names already present alongside them. - Fixed three manifest resource LogicalName entries still pinned to the dead namespace. TypeEditorHost had already been migrated, so new Icon(typeof(TypeEditorHost), "arrow.ico") resolved against a name nothing embedded - a runtime failure no build could surface. The .resources and .ico entries require different prefixes; both are commented so the asymmetry is not "corrected" later. Verified against the built assembly's manifest table. Dead code removed: - Both SwitchConverter/SwitchCase copies and the SwitchConverterErrorMessage key. Neither had a XAML consumer, and a WPF IValueConverter without one is unreachable. - Seven unused .resx under UI/Views/Dialogs: six belonged to WPF DialogWindows and held only Name1/Color1/Bitmap1/Icon1 boilerplate, two also carrying orphaned WinForms designer data from before they were ported to XAML. The assembly drops from 22 embedded resources to 15. Also: - HeaderIconSet implements IDisposable; its public Dispose() was unreachable from a using block. - File nesting for UI/Views/Dialogs: one wildcard plus its two real exceptions. - Five dialog view models moved to UI/ViewModels/Dialog alongside the EnumTypeDialog view models they match. They are not controls. - known-issues.md: Generate_returns_code reproduces 2-of-5 at ff6422f, so it predates this work. It fails far more often filtered than in a full run, which points at an ordering hazard rather than thread contention. Added Tests.Package as a fourth assembly exhibiting the flake. Build clean; 1,732 tests pass across 14 assemblies on net48 and net10.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PackageResources.Designer.cs asks for Microsoft.VisualStudio.Data.Entity.Package.PackageResources, but the csproj pinned ManifestResourceName to ...Package.Resources - the pre-rename name. The resx and its generated class were renamed in e85d22d; this override was not, so every lookup through PackageResources threw at runtime. Opening a diagram hit it via UpdateWindowFrameCaption -> EditorCaptionFormat. No build could catch this: manifest names are only checked when a ResourceManager groveling for them fails. Dropping the element outright does NOT work here - this project's VSSDK resx pipeline then emits an empty manifest name (".resources"), which is why all three EmbeddedResource entries pin their name explicitly. Comment added so the next reader does not repeat that. Swept the rest of the tree against the built assemblies rather than assuming this was the only one: - 16 ResourceManager("literal") call sites across 38 project outputs: all resolve. Note the generator splits long names as "abc" + "def", so a per-line grep silently under-reads them. - 8 type-derived lookups - 6 ComponentResourceManager(typeof(T)) WinForms designers plus the 2 new Icon(typeof(TypeEditorHost), ...) calls: all resolve. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ExportDiagramDialog.xaml threw XamlParseException on load: 'PackageResources.ExportImage_DialogTitle' StaticExtension value cannot be resolved to an enumeration, static field, or static property. Caused by e85d22d. That pass moved single-use strings to their consumer, and ExportImage_* moved out of the public DiagramsResources into PackageResources - which is internal. The XAML prefix was repointed with it: before props -> Microsoft.Data.Entity.Design.Diagrams.Properties (Resources, public) after props -> Microsoft.VisualStudio.Data.Entity.Package (PackageResources, internal) The move was right; the visibility was not. SDK-style WPF projects no longer emit GeneratedInternalTypeHelper, so BAML cannot reach an internal type at all. The two other XAML-consumed resource classes, EdmxDesignerResources and DialogsResource, are already public for exactly this reason - PackageResources now matches them: public class, public statics, internal ctor, and PublicResXFileCodeGenerator so a regeneration does not revert it. Verified against built metadata rather than the build log, since x:Static is resolved at runtime and compiles clean either way: - PackageResources is Public, and all 10 members the XAML names are public static properties. - Swept every x:Static in our XAML: 157 targets on our types, 0 on non-public ones. 58 target external types and were not checked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ck.cs
Context menu parenting:
DiagramSurfaceContextMenu hosts itself in a Popup with AllowsTransparency set,
so WPF gives it its own top-level window instead of drawing inside the parent.
Nothing owned that window, so another application could be activated into the
z-order between the menu and Visual Studio, leaving the menu floating over an
unrelated window.
Show() now takes the owner hwnd and applies it on Popup.Opened via
SetWindowLongPtr(GWLP_HWNDPARENT). It has to run per-open, not once at
construction, because WPF destroys and recreates the popup's window between
opens. The service supplies IVsUIShell.GetDialogOwnerHwnd, which is what
ExportDiagramDialog already used; that duplicated lookup is now the shared
GetDialogOwnerHwnd helper. IntPtr.Zero is tolerated and simply leaves the popup
unowned, so a missing shell cannot break the menu.
SetWindowLongPtrW exists only in the 64-bit user32, so the 32-bit entry point is
used when the process is 32-bit.
tools/resource-check.cs:
Covers the three lookups the compiler cannot see, reading built assemblies
rather than the build log, and exits with the problem count so it can gate a
build:
- ResourceManager("Name") literals, joining the "abc" + "def" fragments the resx
generator emits for long names.
- ComponentResourceManager(typeof(T)) and new Icon(typeof(T), "file"), which
resolve against T's full name and so break silently when a type moves.
- x:Static, which needs a public type with a public static member.
Reads each project's OWN output; a stale copy in a consumer's bin would
otherwise vouch for a name that is no longer embedded. Mutation-checked in both
directions: a bad literal and a nonexistent x:Static member are each reported,
and the exit code goes 0 -> 1.
Currently: 16 literals, 8 type-derived lookups, 157 x:Static, no problems.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The built-in layout is three calls to the SDK's AutoLayoutShapeElements with workarounds still carrying Microsoft's original bug numbers, and it produces diagrams that ignore the grouping already recorded in the file. This designs an opt-in MSAGL engine behind a floating-toolbar toggle, with grouping detection that persists its guess to a new GroupName attribute so a human can correct it. Records two things found while investigating, both of which reverse earlier assumptions: GraphObject cannot be dropped because it is the geometry substrate under every DSL shape, not the auto-layout engine behind one call; and the EDMX connector-route persistence path already exists end to end, so recording routes needs no new plumbing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rendering must not mutate what it renders. An earlier draft proposed a --layout flag on edmx render, which would have turned a read-only command into one that rewrites its input. Layout gets its own subcommand instead, which also makes algorithm comparison a matter of running layout then render over copies. Names the placement algorithms by what they do rather than by their papers, and records that swapping them is one constructor argument, since every settings type derives from LayoutAlgorithmSettings and CalculateLayout dispatches on it. Tuned defaults per algorithm live on MsAglConstants. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LayoutEngineManager holds one keyed instance of each engine for the life of the designer. Taking the algorithm at construction would force an instance per algorithm or a rebuild of the manager to change it. A settable property lets the CLI set it before invoking and leaves room for a designer-side picker later. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AutoLayoutDiagram was 160 lines of Modeling SDK workarounds sitting on the surface, which left no room for a second way to lay a diagram out. It moves verbatim into DslLayoutEngine behind LayoutEngineBase, with LayoutEngineManager holding the keyed set and tracking which one is current. The surface now delegates, so all five existing callers are untouched and behaviour is unchanged. SaveLayoutFlags comes out of its nested private home into its own file, since the MSAGL engine will need the same freeze-and-restore. The surface no longer names a GraphObject type, so that using goes too. Layout takes a weakly typed IList rather than IList<ShapeElement>, matching the SDK's own AutoLayoutShapeElements and what every caller already holds; the spec is corrected to match. Verified: solution builds with 0 errors, and all 14 test assemblies pass with 0 failures under --logger trx. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The engines were constructed inline where they were needed, which is the
opposite of dependency injection. Each host now composes them once at its own
composition root and the surface is handed the result:
- the VS package registers a LayoutEngineManager as a package service during
Initialize, and the doc data pulls it out at the first point it holds a
surface
- the CLI registers LayoutEngineBase and LayoutEngineManager with the host
builder it already had, and RenderCommand takes the manager and threads it
into EdmxDiagramLoader.Load
Registration order is the priority order in both: LayoutEngineManager takes the
first engine as its default, and MS.DI resolves IEnumerable<T> in registration
order, so the two hosts agree without a second mechanism.
Names corrected: the surface holds a LayoutManager, and the manager's own
collection of engines is LayoutEngines. The surface starts with none and
AutoLayoutDiagram no-ops until a host supplies one, so the layout request that
arrives during deserialization is dropped rather than served by a default
nobody chose.
Verified: solution builds with 0 errors, all 14 test assemblies pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AddService with a pre-constructed LayoutEngineManager was a service locator holding a singleton - nothing was injecting anything, and the manager's constructor parameter was being satisfied by hand at the registration site. The package now builds a ServiceCollection, registers the engine and the manager as types, and bridges the result to the VS service container through the ServiceCreatorCallback overload. Nothing is constructed until something asks, and what comes back was built by the container. Registration order stays the priority order, which this container guarantees for IEnumerable<T>. MEF was considered and rejected for this. The VSIX declares only a VsPackage asset, so VS's MEF catalog never sees these types, and [ImportMany] ordering is undefined - determinism there needs ExportMetadata and an explicit sort, which contradicts registration-order-is-priority. MEF becomes the right answer only if layout engines ever become a third-party extension point, and then as a discovery mechanism feeding these registrations. Verified: solution builds with 0 errors, all 14 test assemblies pass, and both Microsoft.Extensions.DependencyInjection assemblies ship inside the VSIX. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The spec said Advanced mode "does not fall back to" DSL routing, which was weak enough to keep inviting proposals that MSAGL place the shapes while DSL draws the lines - as a first version, or to evaluate placement on its own. It is not an option at any stage. The DSL routing is what this work exists to replace, so shipping on top of it would measure the new thing through the defect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Places shapes and routes connectors with MSAGL, writing both back to the
diagram. Self-contained: it calls no Modeling SDK layout or routing at any
stage, and every connector it touches comes back ManuallyRouted with explicit
EdgePoints, which the existing change rules persist to the EDMX.
Three things the API forced, each recorded where it bites:
- Work at 96 units per inch, not in inches. SugiyamaLayoutSettings clamps
LayerSeparation to Math.Max(10, value), so every separation under ten
inches would silently become ten.
- Run RectilinearEdgeRouter explicitly. For a graph with no clusters
LayoutHelpers.CalculateLayout hands straight to the layered engine, which
emits its own splines and never consults EdgeRoutingMode.
- Read routes from either a Polyline or a Curve of segments. Corner fitting
always runs, even at radius zero, where it returns plain lines.
MSAGL measures upward from the bottom left and the designer downward from the
top left, so coordinates are mirrored about the topmost node on the way back.
Also adds EntityTypeShape.ConnectedLinks, which both engines use instead of
copying FromRoleLinkShapes and ToRoleLinkShapes into an ArrayList, and maps the
Msagl package for PackageSourceMapping.
Verified: solution builds with 0 errors, all 14 test assemblies pass, and three
new tests assert placement without overlap, a route on every connector, and
that shapes actually move off the positions the EDMX supplied.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sits beside the Auto Layout button rather than with the grid toggles, because it changes what that button does rather than what the surface looks like. Checked selects MsAglLayoutEngine, cleared selects DslLayoutEngine. Toggling only moves LayoutEngineManager.Current; it does not lay the diagram out. Re-arranging everything the moment a toggle is clicked would discard positions the user may have spent time on, so the Layout button stays the thing that moves shapes. If the requested engine is not registered the toggle reverts and logs, so the button never claims a mode the designer is not in. State is synced from whichever engine is actually current once the diagram has loaded, with the handler unhooked while loading, matching the grid toggles. Verified: solution builds with 0 errors. Test suite passes apart from Generate_returns_code, the pre-existing flake recorded as known-issues 1.4. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
GeneratorTestBase.Model was an unguarded lazy static. Four test classes derive from that base and MSTest runs at MethodLevel parallelism, so two threads could both find the field null, both build a DbModel, and the second assignment would replace the first. The replacement is the defect, not the duplicated work. Every test there reads the property twice - once for an entity set, once for the model passed beside it - so a replacement between those reads hands the generator an entity set belonging to a different DbModel. TableDiscoverer.Discover then looks that set up in the other model's mappings, matches nothing, and First throws "Sequence contains no matching element". That also explains the inversion recorded in known-issues 1.4, which had looked like an ordering hazard: a small filtered run starts all four classes at once with the field still null, the widest possible window for the race. Fixed with Lazy<DbModel> and LazyThreadSafetyMode.ExecutionAndPublication, so the model is built once and never replaced. Not [DoNotParallelize], which would have hidden a real defect behind slower tests. Known-issues 1.4 and 1.5 are updated. The failing test was DefaultVB, not DefaultCSharp as every prior entry claimed - all four classes have a method of that name and the console summary never said which. 1.5's other two rows stay open; their names were never captured. Verified: the reproducing filter failed 3 of 3 before and passes 10 of 10 after; the assembly passes 455/481 three runs running; the full solution passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
known-issues.md had grown to a third fixed entries, which buries what is still open. Anything resolved now moves to fixed-bugs.md instead of staying in place marked FIXED. The entries are kept rather than deleted because several describe traps that are easy to walk back into. Numbers are not reused across the two files, so the gaps in known-issues.md are deliberate and every existing reference still resolves - rename-catalog.md and Package.cs point at 4.2 and 6.1, both of which are still open and unmoved. Moved: 1.1, 1.2, 1.3, 1.4, 2.3, 3.1, 3.2, 4.1. Issue 1.5 is split rather than moved, since only one of its three rows was ever identified; the two unnamed ones stay open and now carry the suspicion that solved 1.4. Also records a third sighting under 1.5 - Tests.Design.EntityFramework on net10, seen once during this work, not reproduced across three full-solution runs with trx enabled from the start, so the name escaped again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MSTest silently never runs a static test method. All 17 were already static in 2021, inherited from the EF6 repository where they were xUnit [Fact] methods - xUnit runs static tests, MSTest does not - so the January 2026 conversion to [TestMethod] turned a legal signature into one that is skipped without report. Measured on Microsoft.Data.Entity.Tests.Design.EntityFramework: before 390 total, 295 passed, 95 skipped after 407 total, 304 passed, 103 skipped +17 collected, exactly the expected count. Nine of them run and all nine pass; the other eight carry [Ignore]. No new failures on either target. Fixing this uncovered a second, unrelated cause of MSTEST0003 that it had been masking: seven private helper methods carrying a stray [TestMethod, Ignore] from the same conversion. All seven are live - called by real tests, between 1 and 11 call sites each - so the attribute is the defect, not the method. Logged as known-issues 2.4 rather than fixed here, since it is a different bug. The issue moves to fixed-bugs.md. Its file paths were wrong there too: the tests are in Tests.Design.EntityFramework, not VersioningFacade. Also makes trx logging a standing rule for 1.5 rather than a remedy. Capturing reactively has now lost the name on four separate sightings, because the rerun that captures is the run that passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
None of the eight could ever have been collected as a test - all are private, take parameters, and most return a value. They picked up the attribute during the same [Fact] to [TestMethod] conversion that caused 2.2, which was masking them. Seven are called by live tests; CreateDbModel's call sites are commented out along with the tests that used them, so the method stays. The eighth only became visible once the other seven were cleared, and is in an assembly the original 2.2 survey never covered. MSTEST0003 is now 0 across the solution, down from 98. Also names the 1.5 intermittent, on the first run after trx logging became unconditional. Create_creates_valid_EntityConnection and its sibling, net10 only, and the cause is production code: StoreSchemaConnectionFactory.EnsureSqlClientRegistered sets its guard flag before doing the registration the flag guards, so a second thread returns early and calls GetFactory against an unpopulated registry. Same shape as the GeneratorTestBase defect one layer down. Logged as 2.5 rather than fixed here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The flag was set before the registration it guarded, so a second thread saw it, returned early, and called DbProviderFactories.GetFactory against a registry nothing had populated yet - "The specified invariant name 'System.Data.SqlClient' wasn't found in the list of registered .NET Data Providers". net10 only, since .NET Framework resolves the provider from machine.config and never enters this path. This was the named cause of one row of known-issues 1.5. Replaced with Lazy<bool> and LazyThreadSafetyMode.ExecutionAndPublication. This makes concurrent callers wait, which is the requirement rather than a side effect: the caller's next statement is GetFactory, which throws if registration has not finished, so returning early was the bug. The wait is bounded to the first call and only for threads arriving mid-registration; afterwards Lazy nulls its factory and the getter is a plain field read. AddSingleton blocks in the same place when a singleton is first resolved. Verified: 12 runs of the affected assembly on net10, trx on every run, zero recurrences. The two failures seen during verification were a different registry - DependencyResolver's static ProviderServicesResolver - and one of them was on pre-fix code across 32 runs, so that family is independent of this change and is logged as known-issues 2.6. 2.6 also records why this is a patch: three traced intermittents are now the same shape, the real fix is container-managed registration, and it is deferred because a container cannot own a BCL static registry and because the package's phased load makes placement a design question. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The engine ran RectilinearEdgeRouter itself after LayoutHelpers.CalculateLayout returned. Two things were wrong with that. The comment justifying it was false: LayeredLayoutEngine does read EdgeRoutingMode and has a Rectilinear case that builds the same router. The claim that a cluster-free graph ignores the mode was simply wrong. Worse, routing separately discards the crossing reduction. The layered algorithm orders shapes so edges flowing through its channels cross as little as possible; a router run afterwards re-paths every edge independently as a shortest obstacle-avoiding route and knows nothing about those channels. Only EdgeRoutingMode.SugiyamaSplines consumes that work. So the pipeline minimised crossings and then threw the result away, which is what the diagram showed. Routing is now MSAGL's, selected by a ConnectorRouting property alongside Algorithm: Orthogonal, Layered, Curved, Straight. RouteConnectors is kept but commented out, with both errors recorded next to it. Curve flattening had to improve to go with it. Taking each segment's endpoints turns a spline into the polygon through its corners - correct only for the orthogonal mode, where every segment is a line. Non-line segments are now sampled. The toolbar gains a temporary dropdown to switch routing on a live diagram, which required a DropDownTemplate on the existing command template selector. Both are marked TEMPORARY and are meant to go once a mode is chosen; selecting a mode re-runs the layout immediately, unlike the Advanced toggle. Verified: solution builds with 0 errors, all 14 test assemblies pass with trx logging on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On solution restore, frame activation ran ReloadArtifactIfNecessary, which called GetNewOrExistingContext and thereby initiated the artifact's first load. That load's OpenXmlModel pumps the message loop, the shell's deferred doc-data load runs inside the pump, and it read IsDesignerSafe on the VSArtifact still suspended inside its own Init - published to the model manager but with no XLinq node yet. Validating that half-built artifact computed a line number for an artifact-level error and asserted in EFObject.GetTextSpan, six times, then opened the designer blank; a second, proper load followed. The document loaded twice, the first time broken. ReloadArtifactIfNecessary is a reload hook and must never start the first load: it now returns unless ModelManager.GetArtifact (a pure lookup that never loads) shows the artifact is already loaded. The normal doc-data path loads and validates it properly. Supporting robustness on the same path: - EFArtifact.Init throws on a null model/document instead of registering an artifact with no node, and subscribes to transaction events only after the node is set. - ModelManager.RegisterArtifact rolls a throwing Init back out of the manager's lookup tables. - VSXmlModelProvider.GetXmlModel re-checks its cache after OpenXmlModel (which can re-enter during its pump) and disposes the redundant model. - EntityDesignDocumentFrameMgr null-guards LayerManager, which is only assigned once Init completes. - EFObject.GetTextSpan's null-node assert now names the element type, its pretty string, the artifact URI, and IsDisposed. Co-Authored-By: Claude <noreply@anthropic.com>
Surfaces the diagram's layout choices in the Properties window and adds an MSAGL-based grouping layout alongside the legacy DSL engine. - Diagram carries LayoutMode (Legacy|Modern) and ConnectorMode; each shape carries GroupName. All three persist as attributes and are editable in the Properties window, with GroupName browsable only in Modern mode. - Any layout-affecting property change (DiagramLayoutInput) re-runs the layout, so editing mode, connector routing, or a group name rearranges the diagram on its own. - GroupingLayout/GroupingStrategy detect groups (explicit, fill colour, hub affinity, structural) and name each group after the entity it centres on; the modern engine writes its guesses back only for shapes that had none. - A clear-groups button appears in the floating toolbar in Modern mode. - LayoutEngineManager resolves a stateless engine per LayoutMode; the DSL engine reports Legacy and ignores connector mode. ConnectorRouting is removed. - Rendering tests: TestEdmxBuilder plus GroupingLayoutTests. Co-Authored-By: Claude <noreply@anthropic.com>
Sweeps the codebase so a firing assert shows its condition (and stack) instead of a blank "Assertion Failed" banner: 344 single-line, single-arg Debug.Assert(cond) calls across the product now read Debug.Assert(cond, "cond"), matching the codebase's existing convention. Applied by a char-accurate transform that skips anything it cannot parse cleanly, so multi-line and ambiguous cases are left untouched. Also adds the standard license header to source files that were missing it. Co-Authored-By: Claude <noreply@anthropic.com>
…edraw, grouping opt-in Property window - Expose ManuallyRouted on a selected association's active-diagram connector (new Routing category, EFAssociationDescriptor). Off clears the points and lets the engine re-route; On pins the connector's current route. No selection redirect, so Association stays the selected object and Delete/Rename are intact. Per-connector redraw - LayoutEngineBase.RouteConnectors: route-only, moves no shape. MSAGL uses RectilinearEdgeRouter with all shapes as fixed obstacles; DSL freezes shapes and reroutes the links. EntityDesignerSurface.RerouteConnectors is the entry point. - "Redraw Route" context-menu item, shown only in Modern mode; leaves the ManuallyRouted flag untouched (mutation-checked test). Provenance fix - EntityTypeShape_ChangeRule no longer clears ManuallyRouted on shape move/expand, so a hand-routed connector survives reopen instead of reverting to auto. Grouping opt-in - EnableGrouping / GenerateGroupNames Diagram settings (Modern only, default off), a Modern Layout Options property category, and GroupingLayout.GroupByExisting. - RebakePolicy enum + ManualRouteRebakePolicy attribute (forward plumbing) + XSD. Tests - ConnectorRoutingTests (provenance across move/resize/load + malformed XML), MsAglLayoutEngine route-only tests, grouping opt-in tests, TestEdmxBuilder Route/Grouping. Spec - Two-model round-trip writeup, connector routing/provenance, single-connector redraw, and grouping opt-in rules in specs/diagram-layout-engines.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…icit Package updates. The .NET runtime libraries move to a single 10.0.10 baseline shared by the net48 and net10.0 targets, via a new DotNetLibrariesPackageVersion: System.Text.Json, System.Resources.Extensions, System.Security.Permissions, System.Drawing.Common, System.ComponentModel.Composition and the three Microsoft.Extensions packages. 10.0.10 rather than 10.0.11 because its assembly version, 10.0.0.10, is exactly the ceiling of the bindingRedirect in devenv.exe.config, so the VSIX unifies on the copy Visual Studio already loads instead of running a second one side by side. Also: Azure.Identity to 1.21.0, the first release not deprecated for depending on a deprecated MSAL; the Microsoft.Identity.Client pin removed so it resolves to the 4.83.1 Azure.Core asks for, under the host's 4.84.1.0 ceiling; EntityFramework to 6.5.2; MySql.Data.EntityFramework to 9.7.0; Oracle.ManagedDataAccess.EntityFramework to 23.26.300; Microsoft.IO.Redist to 6.1.3; Microsoft.Data.SqlClient.SNI to 6.0.3; ILRepack to 2.0.46; Krafs.Publicizer to 2.3.2; McMaster to 5.1.0; Svg.Skia to 5.2.3; MSTest to 4.3.3; FluentAssertions to 7.2.2, held off the 8.x line because it ships under a commercial license. Two pins are gone. Microsoft.NET.Test.Sdk is carried by MSTest, and any pin below its 18.4.0 floor fails restore with NU1109. Microsoft.SourceLink.GitHub is built into the .NET 8 and later SDKs, so its package reference goes too. Microsoft.ServiceHub.Framework and Nerdbank.Streams stay where they are: their newer releases require Microsoft.VisualStudio.Threading 17.13, above the 17.10 line the VSIX targets. Svg.Skia 5.x splits Svg.Animation and Svg.SceneGraph into separate packages, which needed new packageSourceMapping entries. Target frameworks. Every project now lists its own TargetFrameworks and Directory.Build.props sets neither TargetFramework nor RuntimeIdentifier. The RID was doing nothing: native assets land identically without it, verified by diffing the whole bin/Release tree. The VSIX project keeps a singular TargetFramework on purpose, since even a single valued TargetFrameworks triggers cross targeting and breaks the VSSDK import. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PackAsTool rejects any TargetFrameworkIdentifier with a TargetPlatformIdentifier set (NETSDK1146), so net10.0-windows made the CLI unshippable as a tool. Both the CLI and the renderer it depends on now target plain net10.0 and pick up the Windows Desktop assemblies through a FrameworkReference instead of the -windows moniker. The renderer had to move too: a net10.0 project cannot reference a net10.0-windows one (NU1201). PrivateAssets="all" on the renderer's FrameworkReference is load bearing. _CheckForTransitiveWindowsDesktopDependencies fails any non -windows consumer with NETSDK1136, but it only inspects TransitiveFrameworkReference, so keeping the reference from flowing lets the CLI reference the renderer and declare its own. UseWindowsForms and UseWPF are gone from both. Neither project needs designer or XAML build support; they only construct WinForms and WPF objects in memory, which the framework reference alone supports. Windows only at runtime either way. Verified by packing, installing with dotnet tool install --global, and rendering Northwind to Mermaid and SVG through the installed dotnet-edmx command. Full suite still passes, including the renderer's net10.0 tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restore failed on a clean runner with NU1100 for Microsoft.DiaSymReader, System.Data.Common, System.Security.Cryptography.Pkcs and System.Text.RegularExpressions. With source mapping enabled behind a <clear />, a package matching no pattern matches no source at all, so restore cannot even pick a feed to try. This was latent rather than new. All four resolved locally out of the warm global package cache, which is why it only surfaced on CI. Verified by clearing ~/.nuget/packages and restoring cold: the same four before, none after, followed by a clean build and a full test run against the cold tree. Co-Authored-By: Claude Opus 5 (1M context) <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.
Takes the EF6 entity designer from a Visual Studio–only extension to a layered codebase whose designer runs without a shell, and ships that capability as a cross-target library and a standalone
dotnetglobal tool.Headless rendering and the
dotnet edmxCLIEasyAF.Edmx.DiagramToolspackage installs as a global tool:dotnet tool install --global EasyAF.Edmx.DiagramTools, invoked asdotnet edmx render.erDiagramwith entities, cardinality and foreign key names — a text form of the model suited to diffing, docs and AI consumption.The designer no longer needs the shell
Microsoft.Data.Entity.Design.Dslhas no project reference to the Visual Studio layer, and no file in it touchesPackageManager,VsUtils,VSColorTheme,DiagramViewor any dialog type.specs/layer-map.mdand encoded in the solution folders, so every platform dependency now has one legitimate home.Diagram layout
Modernization
TargetFrameworks.VersioningFacadefreed from the GAC.Naming and structure
EntityDesigner→Dsl,XmlCore→XmlEngine, andDesign→Microsoft.VisualStudio.Data.Entity.Design, with namespaces, generated code and string references carried through.Packages and security
dotnet list package --vulnerable --include-transitiveis clean across the solution.devenv.exe.configbinding-redirect ceiling so the VSIX unifies on the assemblies Visual Studio already loads.Azure.Identitymoved off the deprecated line;MSTest4,Microsoft.NET.Test.SdkandMicrosoft.SourceLink.GitHubpins removed as redundant.PackageSourceMappingcompleted for every previously unroutable package.Build and release
Fixes
ModelWizardandPackageResourcesresource lookups corrected (MissingManifestResourceException).EnsureSqlClientRegisteredno longer publishes its guard before completing the work.static) and 8 stray[TestMethod]attributes on private helpers corrected; aGenerate_returns_codeflake fixed.🤖 Generated with Claude Code