Skip to content

Add dotnet-ef JSON config defaults and validation features#37966

Open
IMZihad21 wants to merge 2 commits intodotnet:mainfrom
IMZihad21:tooling/dotnet-ef-json-config-defaults
Open

Add dotnet-ef JSON config defaults and validation features#37966
IMZihad21 wants to merge 2 commits intodotnet:mainfrom
IMZihad21:tooling/dotnet-ef-json-config-defaults

Conversation

@IMZihad21
Copy link
Copy Markdown

@IMZihad21 IMZihad21 commented Mar 22, 2026

Summary

This PR adds support for loading default dotnet ef options from .config/dotnet-ef.json.

Config is discovered by walking up from the current working directory. When a matching CLI option is not provided, values from config are used.

Supported config properties:

  • project
  • startupProject
  • framework
  • configuration
  • context

CLI options continue to take precedence.

Changes

  • Added config discovery/loading in src/dotnet-ef/DotNetEfConfigLoader.cs.
  • Updated src/dotnet-ef/RootCommand.cs to apply config defaults for root options and context forwarding behavior.
  • Added resource-backed user-facing errors in:
    • src/dotnet-ef/Properties/Resources.resx
    • src/dotnet-ef/Properties/Resources.Designer.cs
  • Added tests in test/dotnet-ef.Tests/DotNetEfConfigTest.cs for:
    • discovery and nearest-config behavior
    • CLI-over-config precedence
    • relative/absolute path handling
    • argument propagation
    • invalid/unreadable config handling

Config snapshot

{
  "project": "src/App.Infrastructure",
  "startupProject": "src/App.Api",
  "framework": "net9.0",
  "configuration": "Debug",
  "context": "AppDbContext"
}

Validation

  • .\restore.cmd
  • . .\activate.ps1
  • dotnet test test/dotnet-ef.Tests/dotnet-ef.Tests.csproj

Result: passed locally.

Impact

  • Reduces repeated dotnet ef arguments in repo workflows.
  • Preserves existing behavior by keeping explicit CLI options authoritative.
  • Improves failure diagnostics for malformed/unsupported config content.

Review updates

  • Fixed dbcontext optimize so config-injected --context no longer changes skip-optimization behavior.
  • Fixed explicit context detection to handle both = and : forms, such as --context:Foo and -c:Foo.
  • Fixed config validation order so forbidden/unknown properties report the right error even when the JSON value is non-string.
  • Added tests covering the new option forms, the optimize behavior, and non-string unsupported properties.

Add .config/dotnet-ef.json discovery from current directory up to repo root and apply config values for project, startup project, framework, configuration, and context when CLI options are not explicitly provided.

Introduce resource-backed validation and error handling for invalid JSON, unsupported/unknown properties, invalid value types, and unreadable files.

Add dotnet-ef tests for config discovery/precedence, path resolution, argument propagation, and failure scenarios.

Impact: users can set consistent dotnet-ef defaults in source-controlled config with existing CLI precedence preserved, reducing repetitive command arguments without breaking current behavior.
@IMZihad21 IMZihad21 requested a review from a team as a code owner March 22, 2026 17:41
@IMZihad21
Copy link
Copy Markdown
Author

@IMZihad21 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@dotnet-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@dotnet-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@dotnet-policy-service agree company="Microsoft"

Contributor License Agreement

@dotnet-policy-service agree

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for loading default dotnet ef options from a repo-local JSON config file (.config/dotnet-ef.json), applying those defaults when corresponding CLI options aren’t provided, while keeping explicit CLI arguments authoritative.

Changes:

  • Introduces config discovery/loading + validation via DotNetEfConfigLoader (walk-up discovery, path resolution, strict property validation).
  • Applies config defaults in dotnet-ef execution flow (project/startup project/framework/configuration) and conditionally forwards a default --context for select commands.
  • Adds resource-backed user-facing errors and new unit tests covering discovery/precedence/path behavior and invalid/unreadable config handling.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/dotnet-ef.Tests/DotNetEfConfigTest.cs Adds unit tests for config discovery, precedence, path resolution, and invalid/unreadable config scenarios.
src/dotnet-ef/RootCommand.cs Loads config defaults and conditionally appends --context to forwarded args; adds helpers for context applicability/option detection.
src/dotnet-ef/Properties/Resources.resx Adds new localized strings for config read/validation errors.
src/dotnet-ef/Properties/Resources.Designer.cs Adds strongly-typed accessors for the new config error strings.
src/dotnet-ef/DotNetEfConfigLoader.cs Implements config discovery, JSON parsing, validation, and relative path resolution.
Files not reviewed (1)
  • src/dotnet-ef/Properties/Resources.Designer.cs: Language not supported

Comment on lines 91 to 95
var skipOptimization = remainingArguments.Count > 2
&& remainingArguments[0] == "dbcontext"
&& remainingArguments[1] == "optimize"
&& !remainingArguments.Any(a => a == "--no-scaffold");
startupProject.Build(skipOptimization ? ["/p:EFScaffoldModelStage=none", "/p:EFPrecompileQueriesStage=none"] : null);
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

skipOptimization is now computed from remainingArguments, which may include a config-injected --context. That changes behavior for dbcontext optimize when the user didn't pass any extra args: previously _args.Count > 2 would be false, but with config defaults it can become true and pass /p:EFScaffoldModelStage=none and /p:EFPrecompileQueriesStage=none unexpectedly. Consider basing this check on the original user args (or removing the Count > 2 gate if it's not needed) so config defaults don't affect build behavior.

Copilot uses AI. Check for mistakes.
=> args.Any(
argument => names.Any(
name => string.Equals(argument, name, StringComparison.Ordinal)
|| argument.StartsWith(name + "=", StringComparison.Ordinal)));
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

ContainsOption only treats name= as an inline option form, but this CLI parser also supports name:value (it splits on both ':' and '='). If a user passes --context:ExplicitContext (or -c:ExplicitContext), ResolveContext won't detect it and may append a second --context from config. Update the detection to treat both name= and name: as inline forms (and add/adjust a test case to cover this).

Suggested change
|| argument.StartsWith(name + "=", StringComparison.Ordinal)));
|| argument.StartsWith(name + "=", StringComparison.Ordinal)
|| argument.StartsWith(name + ":", StringComparison.Ordinal)));

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +98
var value = ValidateValue(fullPath, property);

switch (property.Name)
{
case "project":
project = ResolvePath(configDirectory, value);
break;
case "startupProject":
startupProject = ResolvePath(configDirectory, value);
break;
case "context":
context = value;
break;
case "framework":
framework = value;
break;
case "configuration":
configuration = value;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

ValidateValue is called before switching on the property name, so forbidden/unknown properties with non-string values (e.g. { "connection": 1 }) will currently throw the "must be a non-empty JSON string" error instead of the more accurate "property isn't supported" / "remove unsupported property" message. Consider switching on property.Name first and only validating the value for supported properties, so error reporting remains accurate regardless of the JSON value kind.

Suggested change
var value = ValidateValue(fullPath, property);
switch (property.Name)
{
case "project":
project = ResolvePath(configDirectory, value);
break;
case "startupProject":
startupProject = ResolvePath(configDirectory, value);
break;
case "context":
context = value;
break;
case "framework":
framework = value;
break;
case "configuration":
configuration = value;
switch (property.Name)
{
case "project":
project = ResolvePath(configDirectory, ValidateValue(fullPath, property));
break;
case "startupProject":
startupProject = ResolvePath(configDirectory, ValidateValue(fullPath, property));
break;
case "context":
context = ValidateValue(fullPath, property);
break;
case "framework":
framework = ValidateValue(fullPath, property);
break;
case "configuration":
configuration = ValidateValue(fullPath, property);

Copilot uses AI. Check for mistakes.
Keep dbcontext optimize skip behavior based on original user arguments so config-injected defaults do not change build behavior.

Recognize both '=' and ':' inline context option forms when detecting explicit CLI input, and validate config values only after dispatching on supported property names so forbidden or unknown properties report the correct error category.

Add tests covering explicit inline context options, optimize skip behavior, and forbidden or unknown non-string config properties to lock in the reviewed behavior.
@IMZihad21
Copy link
Copy Markdown
Author

IMZihad21 commented Mar 29, 2026

@AndriySvyryd
@copilot

Addressed all review points in the latest update.

  • dbcontext optimize now bases skip-optimization on the original user args, so a config-injected --context no longer changes that behavior.
  • Explicit context detection now handles both = and : forms, including --context:... and -c:....
  • Config validation now switches on property name before validating value, so forbidden/unknown properties report the correct error even for non-string JSON values.
  • Added/updated tests for the new option forms, optimize behavior, and non-string unsupported properties.

Please re-check the updated changes.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants