diff --git a/Directory.Build.props b/Directory.Build.props
index 922eddb183..c5555f06dd 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -10,6 +10,7 @@
( $(MSBuildProjectName.EndsWith('.Tests')) OR
$(MSBuildProjectName.EndsWith('.FunctionalTests'))) ">true
false
+ $(MSBuildThisFileDirectory)
diff --git a/Directory.Build.targets b/Directory.Build.targets
index bca11ece4c..1ecc794c90 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -8,7 +8,7 @@
-
+
diff --git a/EFCore.sln b/EFCore.sln
index d45bf85cdf..9af5fa087f 100644
--- a/EFCore.sln
+++ b/EFCore.sln
@@ -138,6 +138,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.SqlServer.Abstractio
EndProject
Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "EFCore.VisualBasic.FunctionalTests", "test\EFCore.VisualBasic.FunctionalTests\EFCore.VisualBasic.FunctionalTests.vbproj", "{2AC6A8AC-5C0A-422A-B21A-CDC8D75F20A3}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Tasks", "src\EFCore.Tasks\EFCore.Tasks.csproj", "{711EE8F3-F92D-4470-8B0B-25D8B13EF282}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -152,6 +154,10 @@ Global
{4F7C93F3-A30F-4061-804C-32293DC256A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F7C93F3-A30F-4061-804C-32293DC256A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F7C93F3-A30F-4061-804C-32293DC256A1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {711EE8F3-F92D-4470-8B0B-25D8B13EF282}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {711EE8F3-F92D-4470-8B0B-25D8B13EF282}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {711EE8F3-F92D-4470-8B0B-25D8B13EF282}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {711EE8F3-F92D-4470-8B0B-25D8B13EF282}.Release|Any CPU.Build.0 = Release|Any CPU
{715C38E9-B2F5-4DB2-8025-0C6492DEBDD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{715C38E9-B2F5-4DB2-8025-0C6492DEBDD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{715C38E9-B2F5-4DB2-8025-0C6492DEBDD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -371,6 +377,7 @@ Global
GlobalSection(NestedProjects) = preSolution
{2D66A1DA-D102-4DD9-960B-7D863BBB53DE} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{4F7C93F3-A30F-4061-804C-32293DC256A1} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
+ {711EE8F3-F92D-4470-8B0B-25D8B13EF282} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{715C38E9-B2F5-4DB2-8025-0C6492DEBDD4} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{11B51A41-47CB-4EDB-9D8A-17095A65034A} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
{D3D0A8E8-EC2F-4E01-8650-8554E186A66F} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
diff --git a/eng/Versions.props b/eng/Versions.props
index 1067ab3366..f38f48d693 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -31,10 +31,11 @@
9.0.0-beta.24114.1
+ 17.0.0
+ 17.0.0
+ 17.0.0
4.8.0
1.1.2-beta1.23578.3
- 2.6.1
- 2.5.3
diff --git a/eng/testing/linker/trimmingTests.props b/eng/testing/linker/trimmingTests.props
index f1928b5afd..3945aa10e8 100644
--- a/eng/testing/linker/trimmingTests.props
+++ b/eng/testing/linker/trimmingTests.props
@@ -1,7 +1,6 @@
- $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'trimmingTests'))
- $([MSBuild]::NormalizeDirectory('$(TrimmingTestDir)', 'projects'))
+ $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)'))
$(MSBuildThisFileDirectory)project.csproj.template
enable
diff --git a/eng/testing/linker/trimmingTests.targets b/eng/testing/linker/trimmingTests.targets
index 2174c5ab2a..b8b04229e0 100644
--- a/eng/testing/linker/trimmingTests.targets
+++ b/eng/testing/linker/trimmingTests.targets
@@ -64,7 +64,7 @@
<_additionalProjectReferenceTemp Include="$(AdditionalProjectReferences)" />
- <_additionalProjectReference Include="<ProjectReference Include="$(LibrariesProjectRoot)%(_additionalProjectReferenceTemp.Identity)\src\%(_additionalProjectReferenceTemp.Identity).csproj" SkipUseReferenceAssembly="true" />" />
+ <_additionalProjectReference Include="<ProjectReference Include="$(SolutionRoot)%(_additionalProjectReferenceTemp.Identity)\src\%(_additionalProjectReferenceTemp.Identity).csproj" SkipUseReferenceAssembly="true" />" />
diff --git a/src/EFCore.Design/EFCore.Design.csproj b/src/EFCore.Design/EFCore.Design.csproj
index c91aa89bdd..5dd5be8a41 100644
--- a/src/EFCore.Design/EFCore.Design.csproj
+++ b/src/EFCore.Design/EFCore.Design.csproj
@@ -47,10 +47,7 @@
-
- True
- build
-
+
diff --git a/src/EFCore.Tasks/EFCore.Tasks.csproj b/src/EFCore.Tasks/EFCore.Tasks.csproj
new file mode 100644
index 0000000000..6f52fa837c
--- /dev/null
+++ b/src/EFCore.Tasks/EFCore.Tasks.csproj
@@ -0,0 +1,88 @@
+
+
+
+ MSBuild tasks for Entity Framework Core projects.
+ $(DefaultNetCoreTargetFramework);net472
+ Microsoft.EntityFrameworkCore.Tasks
+ Microsoft.EntityFrameworkCore
+ false
+ true
+ true
+ true
+ NU5100;NU5128
+ true
+ $(MSBuildThisFileDirectory)..\..\rulesets\EFCore.noxmldocs.ruleset
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TextTemplatingFileGenerator
+ Resources.Designer.cs
+ Microsoft.EntityFrameworkCore.Tools.Properties
+
+
+
+
+
+ True
+ True
+ Resources.Designer.tt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/EFCore.Tasks/EFCore.Tasks.nuspec b/src/EFCore.Tasks/EFCore.Tasks.nuspec
new file mode 100644
index 0000000000..12df70caf8
--- /dev/null
+++ b/src/EFCore.Tasks/EFCore.Tasks.nuspec
@@ -0,0 +1,23 @@
+
+
+
+
+ $CommonMetadataElements$
+
+
+
+
+
+ docs\PACKAGE.md
+
+
+ $CommonFileElements$
+
+
+
+
+
+
+
+
+
diff --git a/src/EFCore.Tasks/PACKAGE.md b/src/EFCore.Tasks/PACKAGE.md
new file mode 100644
index 0000000000..d073d54d1f
--- /dev/null
+++ b/src/EFCore.Tasks/PACKAGE.md
@@ -0,0 +1,17 @@
+The Entity Framework Core MSBuild tasks integrate EF design-time tools into the build process. They're primarily used to generate the compiled model.
+
+This package should be referenced by the project containing the derived `DbContext`.
+
+## Usage
+
+Install the package into your project, set `true` and then run build normally.
+
+If the startup project is different from the current project it needs to be specified: `..\Startup\Startup.csproj`
+
+## Getting started with EF Core
+
+See [Getting started with EF Core](https://learn.microsoft.com/ef/core/get-started/overview/install) for more information about EF NuGet packages, including which to install when getting started.
+
+## Feedback
+
+If you encounter a bug or issues with this package,you can [open an Github issue](https://github.com/dotnet/efcore/issues/new/choose). For more details, see [getting support](https://github.com/dotnet/efcore/blob/main/.github/SUPPORT.md).
diff --git a/src/EFCore.Tasks/Properties/Resources.Designer.cs b/src/EFCore.Tasks/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..137060329e
--- /dev/null
+++ b/src/EFCore.Tasks/Properties/Resources.Designer.cs
@@ -0,0 +1,58 @@
+//
+
+using System;
+using System.Reflection;
+using System.Resources;
+
+#nullable enable
+
+namespace Microsoft.EntityFrameworkCore.Tools.Properties
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.EntityFrameworkCore.Properties.Resources", typeof(Resources).Assembly);
+
+ ///
+ /// Startup project '{startupProject}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the Entity Framework Core .NET Command-line Tools only supports version 2.0 or higher. For information on using older versions of the tools, see https://go.microsoft.com/fwlink/?linkid=871254
+ ///
+ public static string NETCoreApp1StartupProject(object? startupProject, object? targetFrameworkVersion)
+ => string.Format(
+ GetString("NETCoreApp1StartupProject", nameof(startupProject), nameof(targetFrameworkVersion)),
+ startupProject, targetFrameworkVersion);
+
+ ///
+ /// Startup project '{startupProject}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the Entity Framework Core .NET Command-line Tools with this project, add an executable project targeting .NET Core or .NET Framework that references this project, and set it as the startup project using --startup-project; or, update this project to cross-target .NET Core or .NET Framework. For more information on using the Entity Framework Tools with .NET Standard projects, see https://go.microsoft.com/fwlink/?linkid=2034781
+ ///
+ public static string NETStandardStartupProject(object? startupProject)
+ => string.Format(
+ GetString("NETStandardStartupProject", nameof(startupProject)),
+ startupProject);
+
+ ///
+ /// Startup project '{startupProject}' targets framework '{targetFramework}'. The Entity Framework Core .NET Command-line Tools don't support this framework. See https://aka.ms/efcore-docs-cli-tfms for more information.
+ ///
+ public static string UnsupportedFramework(object? startupProject, object? targetFramework)
+ => string.Format(
+ GetString("UnsupportedFramework", nameof(startupProject), nameof(targetFramework)),
+ startupProject, targetFramework);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name)!;
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+
+ return value;
+ }
+ }
+}
+
diff --git a/src/EFCore.Tasks/Properties/Resources.Designer.tt b/src/EFCore.Tasks/Properties/Resources.Designer.tt
new file mode 100644
index 0000000000..9e9ef701f7
--- /dev/null
+++ b/src/EFCore.Tasks/Properties/Resources.Designer.tt
@@ -0,0 +1,7 @@
+<#
+ Session["ResourceFile"] = "Resources.resx";
+ Session["ResourceNamespace"] = "Microsoft.EntityFrameworkCore.Properties";
+ Session["AccessModifier"] = "internal";
+ Session["NoDiagnostics"] = true;
+#>
+<#@ include file="..\..\..\tools\Resources.tt" #>
\ No newline at end of file
diff --git a/src/EFCore.Tasks/Properties/Resources.resx b/src/EFCore.Tasks/Properties/Resources.resx
new file mode 100644
index 0000000000..5d2fa92187
--- /dev/null
+++ b/src/EFCore.Tasks/Properties/Resources.resx
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Startup project '{startupProject}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the Entity Framework Core .NET Command-line Tools only supports version 2.0 or higher. For information on using older versions of the tools, see https://go.microsoft.com/fwlink/?linkid=871254
+
+
+ Startup project '{startupProject}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the Entity Framework Core .NET Command-line Tools with this project, add an executable project targeting .NET Core or .NET Framework that references this project, and set it as the startup project using --startup-project; or, update this project to cross-target .NET Core or .NET Framework. For more information on using the Entity Framework Tools with .NET Standard projects, see https://go.microsoft.com/fwlink/?linkid=2034781
+
+
+ Startup project '{startupProject}' targets framework '{targetFramework}'. The Entity Framework Core .NET Command-line Tools don't support this framework. See https://aka.ms/efcore-docs-cli-tfms for more information.
+
+
\ No newline at end of file
diff --git a/src/EFCore.Tasks/Tasks/Internal/MsBuildUtilities.cs b/src/EFCore.Tasks/Tasks/Internal/MsBuildUtilities.cs
new file mode 100644
index 0000000000..5fbaa229ef
--- /dev/null
+++ b/src/EFCore.Tasks/Tasks/Internal/MsBuildUtilities.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if NET472
+using System.Configuration;
+#endif
+
+namespace Microsoft.EntityFrameworkCore.Tasks.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+internal class MsBuildUtilities
+{
+ public static string[] Split(string s)
+ => !string.IsNullOrEmpty(s)
+ ? s.Split(';')
+ .Select(entry => entry.Trim())
+ .Where(entry => entry.Length != 0)
+ .ToArray()
+ : [];
+
+ public static string? TrimAndGetNullForEmpty(string? s)
+ {
+ if (s == null)
+ {
+ return null;
+ }
+
+ s = s.Trim();
+
+ return s.Length == 0 ? null : s;
+ }
+
+ public static string[] TrimAndExcludeNullOrEmpty(string?[]? strings)
+ => strings == null
+ ? []
+ : strings
+ .Select(TrimAndGetNullForEmpty)
+ .Where(s => s != null)
+ .Cast()
+ .ToArray();
+
+ public static bool IsTrue(string? value) => bool.TrueString.Equals(TrimAndGetNullForEmpty(value), StringComparison.OrdinalIgnoreCase);
+
+ public static bool IsTrueOrEmpty(string? value) => TrimAndGetNullForEmpty(value) == null || IsTrue(value);
+
+ public static bool? GetBooleanOrNull(string? value) => bool.TryParse(value, out var result) ? result : null;
+
+ public static string? ToMsBuild(string? value) => value?.Replace(',', ';');
+}
diff --git a/src/EFCore.Tasks/Tasks/Internal/OperationTaskBase.cs b/src/EFCore.Tasks/Tasks/Internal/OperationTaskBase.cs
new file mode 100644
index 0000000000..9d82495a61
--- /dev/null
+++ b/src/EFCore.Tasks/Tasks/Internal/OperationTaskBase.cs
@@ -0,0 +1,253 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.Versioning;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Build.Framework;
+using Microsoft.EntityFrameworkCore.Tools;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tasks.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public abstract class OperationTaskBase : Build.Utilities.Task
+{
+ ///
+ /// The assembly to use.
+ ///
+ [Required]
+ public ITaskItem Assembly { get; set; } = null!;
+
+ ///
+ /// The startup assembly to use.
+ ///
+ [Required]
+ public ITaskItem StartupAssembly { get; set; } = null!;
+
+ ///
+ /// The target framework moniker.
+ ///
+ [Required]
+ public string TargetFrameworkMoniker { get; set; } = null!;
+
+ ///
+ /// The target runtime framework version.
+ ///
+ public string? RuntimeFrameworkVersion { get; set; }
+
+ ///
+ /// The project assets file.
+ ///
+ public string? ProjectAssetsFile { get; set; }
+
+ ///
+ /// The directory containing the database files.
+ ///
+ public ITaskItem? DataDir { get; set; }
+
+ ///
+ /// The project directory.
+ ///
+ public ITaskItem? ProjectDir { get; set; }
+
+ ///
+ /// The root namespace to use.
+ ///
+ public string? RootNamespace { get; set; }
+
+ ///
+ /// The language to use. Defaults to C#.
+ ///
+ public string? Language { get; set; }
+
+ ///
+ /// A flag indicating whether nullable reference types are enabled.
+ ///
+ public bool Nullable { get; set; }
+
+ protected virtual bool Execute(IEnumerable additionalArguments, out string? result)
+ {
+ var args = new List();
+
+ var startupAssemblyName = Path.GetFileNameWithoutExtension(StartupAssembly.ItemSpec);
+ var targetDir = Path.GetDirectoryName(Path.GetFullPath(StartupAssembly.ItemSpec))!;
+ var depsFile = Path.Combine(
+ targetDir,
+ startupAssemblyName + ".deps.json");
+ var runtimeConfig = Path.Combine(
+ targetDir,
+ startupAssemblyName + ".runtimeconfig.json");
+ var projectAssetsFile = MsBuildUtilities.TrimAndGetNullForEmpty(ProjectAssetsFile);
+
+ string executable;
+ var targetFramework = new FrameworkName(TargetFrameworkMoniker);
+ if (targetFramework.Identifier == ".NETCoreApp")
+ {
+ if (targetFramework.Version < new Version(2, 0))
+ {
+ throw new InvalidOperationException(
+ Resources.NETCoreApp1StartupProject(startupAssemblyName, targetFramework.Version));
+ }
+
+ executable = "dotnet";
+ args.Add("exec");
+
+ if (File.Exists(depsFile))
+ {
+ args.Add("--depsfile");
+ args.Add(depsFile);
+ }
+
+ if (projectAssetsFile != null
+ && File.Exists(projectAssetsFile))
+ {
+ using var file = File.OpenRead(projectAssetsFile);
+ using var reader = JsonDocument.Parse(file);
+ var projectAssets = reader.RootElement;
+ var packageFolders = projectAssets.GetProperty("packageFolders").EnumerateObject().Select(p => p.Name);
+
+ foreach (var packageFolder in packageFolders)
+ {
+ args.Add("--additionalprobingpath");
+ args.Add(packageFolder.TrimEnd(Path.DirectorySeparatorChar));
+ }
+ }
+
+ var runtimeFrameworkVersion = MsBuildUtilities.TrimAndGetNullForEmpty(RuntimeFrameworkVersion);
+ if (File.Exists(runtimeConfig))
+ {
+ args.Add("--runtimeconfig");
+ args.Add(runtimeConfig);
+ }
+ else if (runtimeFrameworkVersion != null)
+ {
+ args.Add("--fx-version");
+ args.Add(runtimeFrameworkVersion);
+ }
+
+ args.Add(Path.Combine(
+ Path.GetDirectoryName(typeof(OperationTaskBase).Assembly.Location)!,
+ "..",
+ "..",
+ "tools",
+ "netcoreapp2.0",
+ "ef.dll"));
+ }
+ else if (targetFramework.Identifier == ".NETStandard")
+ {
+ throw new InvalidOperationException(Resources.NETStandardStartupProject(startupAssemblyName));
+ }
+ else
+ {
+ throw new InvalidOperationException(
+ Resources.UnsupportedFramework(startupAssemblyName, targetFramework.Identifier));
+ }
+
+ args.AddRange(additionalArguments);
+ args.Add("--assembly");
+ args.Add(Assembly.ItemSpec);
+
+ if (StartupAssembly != null)
+ {
+ args.Add("--startup-assembly");
+ args.Add(StartupAssembly.ItemSpec);
+ }
+
+ if (ProjectDir != null)
+ {
+ args.Add("--project-dir");
+ args.Add(ProjectDir.ItemSpec);
+ }
+
+ if (DataDir != null) {
+ args.Add("--data-dir");
+ args.Add(DataDir.ItemSpec);
+ }
+
+ var rootNamespace = MsBuildUtilities.TrimAndGetNullForEmpty(RootNamespace);
+ if (rootNamespace != null) {
+ args.Add("--root-namespace");
+ args.Add(rootNamespace);
+ }
+
+ var language = MsBuildUtilities.TrimAndGetNullForEmpty(Language);
+ if (language != null) {
+ args.Add("--language");
+ args.Add(language);
+ }
+
+ if (Nullable)
+ {
+ args.Add("--nullable");
+ }
+
+ args.Add("--working-dir");
+ args.Add(Directory.GetCurrentDirectory());
+
+ args.Add("--verbose");
+ args.Add("--no-color");
+ args.Add("--prefix-output");
+
+ var resultBuilder = new StringBuilder();
+ var exitCode = Exe.Run(executable, args, ProjectDir?.ItemSpec, HandleOutput, processCommandLine: Log.LogCommandLine);
+ result = resultBuilder.Length > 0 ? resultBuilder.ToString() : null;
+
+ return exitCode == 0;
+
+ void HandleOutput(string? output)
+ {
+ if (output == null)
+ {
+ return;
+ }
+
+ if (output.StartsWith(Reporter.ErrorPrefix, StringComparison.InvariantCulture))
+ {
+ Log.LogError(output.Substring(Reporter.ErrorPrefix.Length));
+ }
+ else if (output.StartsWith(Reporter.WarningPrefix, StringComparison.InvariantCulture))
+ {
+ Log.LogWarning(output.Substring(Reporter.WarningPrefix.Length));
+ }
+ else if (output.StartsWith(Reporter.InfoPrefix, StringComparison.InvariantCulture))
+ {
+ Log.LogMessage(output.Substring(Reporter.InfoPrefix.Length));
+ }
+ else if (output.StartsWith(Reporter.VerbosePrefix, StringComparison.InvariantCulture))
+ {
+ Log.LogMessage(MessageImportance.Low, output.Substring(Reporter.VerbosePrefix.Length));
+ }
+ else if (output.StartsWith(Reporter.DataPrefix, StringComparison.InvariantCulture))
+ {
+ resultBuilder.AppendLine(output.Substring(Reporter.DataPrefix.Length));
+ }
+ else if(output.StartsWith("fail: ", StringComparison.InvariantCulture))
+ {
+ Log.LogError(output.Substring(6));
+ }
+ else if (output.StartsWith("warn: ", StringComparison.InvariantCulture))
+ {
+ Log.LogWarning(output.Substring(6));
+ }
+ else if (output.StartsWith("info: ", StringComparison.InvariantCulture))
+ {
+ Log.LogMessage(output.Substring(6));
+ }
+ else if (output.StartsWith("dbug: ", StringComparison.InvariantCulture)
+ || output.StartsWith("trce: ", StringComparison.InvariantCulture))
+ {
+ Log.LogMessage(MessageImportance.Low, output.Substring(6));
+ }
+ else
+ {
+ Log.LogError(output);
+ }
+ }
+ }
+}
diff --git a/src/EFCore.Tasks/Tasks/OptimizeContext.cs b/src/EFCore.Tasks/Tasks/OptimizeContext.cs
new file mode 100644
index 0000000000..27524d13e2
--- /dev/null
+++ b/src/EFCore.Tasks/Tasks/OptimizeContext.cs
@@ -0,0 +1,84 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Xml.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Microsoft.EntityFrameworkCore.Tasks.Internal;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tasks;
+
+///
+/// Generates files that contain tailored code for some runtime services.
+///
+public class OptimizeContext : OperationTaskBase
+{
+ ///
+ /// The name of the target DbContext.
+ ///
+ public string? DbContextName { get; set; }
+
+ ///
+ /// The namespace to use for the generated classes.
+ ///
+ public string? TargetNamespace { get; set; }
+
+ ///
+ /// The output directory. Usually, relative to the project directory.
+ ///
+ public ITaskItem? OutputDir { get; set; }
+
+ ///
+ /// Generated files that should be include in the build.
+ ///
+ [Output]
+ public ITaskItem[] GeneratedFiles { get; private set; } = null!;
+
+ ///
+ public override bool Execute()
+ {
+ try
+ {
+ Log.LogMessage(MessageImportance.High, "Optimizing DbContext...");
+
+ var additionalArguments = new List { "dbcontext", "optimize" };
+ if (OutputDir != null)
+ {
+ additionalArguments.Add("--output-dir");
+ additionalArguments.Add(OutputDir.ItemSpec);
+ }
+
+ var targetNamespace = MsBuildUtilities.TrimAndGetNullForEmpty(TargetNamespace);
+ if (targetNamespace != null)
+ {
+ additionalArguments.Add("--namespace");
+ additionalArguments.Add(targetNamespace);
+ }
+
+ var dbContextName = MsBuildUtilities.TrimAndGetNullForEmpty(DbContextName);
+ if(dbContextName != null)
+ {
+ additionalArguments.Add("--context");
+ additionalArguments.Add(dbContextName);
+ }
+
+ var success = Execute(additionalArguments, out var result);
+ if (!success
+ || result == null)
+ {
+ return false;
+ }
+
+ GeneratedFiles = result.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)
+ .Select(f => new TaskItem(f)).ToArray();
+ }
+ catch (Exception e)
+ {
+ Log.LogErrorFromException(e);
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+}
diff --git a/src/EFCore.Tasks/buildTransitive/Microsoft.EntityFrameworkCore.Tasks.props b/src/EFCore.Tasks/buildTransitive/Microsoft.EntityFrameworkCore.Tasks.props
new file mode 100644
index 0000000000..0046e1a412
--- /dev/null
+++ b/src/EFCore.Tasks/buildTransitive/Microsoft.EntityFrameworkCore.Tasks.props
@@ -0,0 +1,20 @@
+
+
+
+ <_TaskTargetFramework Condition="'$(MSBuildRuntimeType)' == 'core'">net8.0
+ <_TaskTargetFramework Condition="'$(MSBuildRuntimeType)' != 'core'">net472
+ <_EFCustomTasksAssembly>$([MSBuild]::NormalizePath($(MSBuildThisFileDirectory), '..\tasks\$(_TaskTargetFramework)\$(MSBuildThisFileName).dll'))
+
+
+
+
+
+ false
+
+
+
+ C#
+ VB
+ F#
+
+
\ No newline at end of file
diff --git a/src/EFCore.Tasks/buildTransitive/Microsoft.EntityFrameworkCore.Tasks.targets b/src/EFCore.Tasks/buildTransitive/Microsoft.EntityFrameworkCore.Tasks.targets
new file mode 100644
index 0000000000..ebd85bdabf
--- /dev/null
+++ b/src/EFCore.Tasks/buildTransitive/Microsoft.EntityFrameworkCore.Tasks.targets
@@ -0,0 +1,116 @@
+
+
+
+
+ $([MSBuild]::NormalizePath($(MSBuildProjectDirectory), '$(IntermediateOutputPath)$(AssemblyName).EFGeneratedFiles.txt'))
+
+
+
+
+
+ $(MSBuildProjectFullPath)
+ $(RootNamespace)
+ $(AssemblyName)
+ $(EFRootNamespace)
+ <_FullOutputPath>$([MSBuild]::NormalizePath($(MSBuildProjectDirectory), '$(OutputPath)'))
+ <_FullIntermediateOutputPath>$([MSBuild]::NormalizePath($(MSBuildProjectDirectory), '$(IntermediateOutputPath)'))
+ false
+
+
+
+ true
+
+
+
+ <_AssemblyFullName>$(_FullOutputPath)$(AssemblyName).dll
+
+
+ <_AssemblyFullName>$(_FullOutputPath)$(AssemblyName).exe
+
+
+ <_AssemblyFullName>$(_FullOutputPath)$(AssemblyName).exe
+
+
+
+
+
+
+
+
+
+
+
+ <_FullOutputPath>$([MSBuild]::NormalizePath($(MSBuildProjectDirectory), '$(OutputPath)'))
+
+
+
+ <_StartupAssemblyFullName>$(_FullOutputPath)$(AssemblyName).dll
+
+
+ <_StartupAssemblyFullName>$(_FullOutputPath)$(AssemblyName).exe
+
+
+ <_StartupAssemblyFullName>$(_FullOutputPath)$(AssemblyName).exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_GeneratedFiles Include="@(_ReadGeneratedFiles)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/EFCore.Tools/EFCore.Tools.csproj b/src/EFCore.Tools/EFCore.Tools.csproj
index 91e375fae5..779f77df29 100644
--- a/src/EFCore.Tools/EFCore.Tools.csproj
+++ b/src/EFCore.Tools/EFCore.Tools.csproj
@@ -1,4 +1,4 @@
-
+
@@ -7,9 +7,12 @@
Microsoft.EntityFrameworkCore.Tools
$(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec
true
+ true
true
+ true
false
false
+ true
Entity Framework Core Tools for the NuGet Package Manager Console in Visual Studio.
Enables these commonly used commands:
@@ -25,58 +28,25 @@ Script-Migration
Update-Database
False
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
+
+
-
- true
-
-
- true
-
-
-
diff --git a/src/EFCore.Tools/EFCore.Tools.nuspec b/src/EFCore.Tools/EFCore.Tools.nuspec
index 49db60b954..fa956e5db9 100644
--- a/src/EFCore.Tools/EFCore.Tools.nuspec
+++ b/src/EFCore.Tools/EFCore.Tools.nuspec
@@ -1,12 +1,11 @@
-
+
$CommonMetadataElements$
- 3.6
-
+
docs\PACKAGE.md
@@ -14,7 +13,6 @@
$CommonFileElements$
-
diff --git a/src/EFCore.Tools/lib/net8.0/_._ b/src/EFCore.Tools/lib/net8.0/_._
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/src/dotnet-ef/Exe.cs b/src/dotnet-ef/Exe.cs
index bcac989a39..1501de4660 100644
--- a/src/dotnet-ef/Exe.cs
+++ b/src/dotnet-ef/Exe.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
@@ -13,33 +12,53 @@ internal static class Exe
string executable,
IReadOnlyList args,
string? workingDirectory = null,
- bool interceptOutput = false)
+ Action? handleOutput = null,
+ Action? handleError = null,
+ Action? processCommandLine = null)
{
var arguments = ToArguments(args);
- Reporter.WriteVerbose(executable + " " + arguments);
+ processCommandLine ??= Reporter.WriteVerbose;
+ processCommandLine(executable + " " + arguments);
var startInfo = new ProcessStartInfo
{
FileName = executable,
Arguments = arguments,
UseShellExecute = false,
- RedirectStandardOutput = interceptOutput
+ RedirectStandardOutput = handleOutput != null,
+ RedirectStandardError = handleError != null
};
if (workingDirectory != null)
{
startInfo.WorkingDirectory = workingDirectory;
}
- var process = Process.Start(startInfo)!;
-
- if (interceptOutput)
+ var process = new Process
{
- string? line;
- while ((line = process.StandardOutput.ReadLine()) != null)
- {
- Reporter.WriteVerbose(line);
- }
+ StartInfo = startInfo
+ };
+
+ if (handleOutput != null)
+ {
+ process.OutputDataReceived += (sender, args) => handleOutput(args.Data);
+ }
+
+ if (handleError != null)
+ {
+ process.ErrorDataReceived += (sender, args) => handleError(args.Data);
+ }
+
+ process.Start();
+
+ if (handleOutput != null)
+ {
+ process.BeginOutputReadLine();
+ }
+
+ if (handleError != null)
+ {
+ process.BeginErrorReadLine();
}
process.WaitForExit();
diff --git a/src/dotnet-ef/Project.cs b/src/dotnet-ef/Project.cs
index a37a869dae..1296a2d1c4 100644
--- a/src/dotnet-ef/Project.cs
+++ b/src/dotnet-ef/Project.cs
@@ -185,7 +185,7 @@ internal class Project
args.Add("/nologo");
args.Add("/p:PublishAot=false"); // Avoid NativeAOT warnings
- var exitCode = Exe.Run("dotnet", args, interceptOutput: true);
+ var exitCode = Exe.Run("dotnet", args, handleOutput: Reporter.WriteVerbose);
if (exitCode != 0)
{
throw new CommandException(Resources.BuildFailed);
diff --git a/src/ef/AnsiTextWriter.cs b/src/ef/AnsiTextWriter.cs
index 689d3ccb74..41239c4c1f 100644
--- a/src/ef/AnsiTextWriter.cs
+++ b/src/ef/AnsiTextWriter.cs
@@ -1,10 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
using System.Diagnostics;
-using System.IO;
-using System.Linq;
using System.Text.RegularExpressions;
namespace Microsoft.EntityFrameworkCore.Tools;
@@ -30,7 +27,7 @@ internal class AnsiTextWriter
private void Interpret(string value)
{
- var matches = Regex.Matches(value, "\x1b\\[([0-9]+)?m");
+ var matches = Regex.Matches(value, "\x1b\\[([0-9]+)?m", RegexOptions.None, TimeSpan.FromSeconds(10));
var start = 0;
foreach (var match in matches.Cast())
diff --git a/src/ef/Commands/DbContextOptimizeCommand.cs b/src/ef/Commands/DbContextOptimizeCommand.cs
index 38f6602ff4..9b9bcc4afc 100644
--- a/src/ef/Commands/DbContextOptimizeCommand.cs
+++ b/src/ef/Commands/DbContextOptimizeCommand.cs
@@ -17,11 +17,21 @@ internal partial class DbContextOptimizeCommand
}
using var executor = CreateExecutor(args);
- executor.OptimizeContext(
+ var result = executor.OptimizeContext(
_outputDir!.Value(),
_namespace!.Value(),
Context!.Value());
+ ReportResults(result);
+
return base.Execute(args);
}
+
+ private static void ReportResults(IEnumerable generatedFiles)
+ {
+ foreach (var file in generatedFiles)
+ {
+ Reporter.WriteData(file);
+ }
+ }
}
diff --git a/src/ef/Commands/MigrationsBundleCommand.cs b/src/ef/Commands/MigrationsBundleCommand.cs
index 845b68bc4a..5e8dd3b660 100644
--- a/src/ef/Commands/MigrationsBundleCommand.cs
+++ b/src/ef/Commands/MigrationsBundleCommand.cs
@@ -177,7 +177,7 @@ internal partial class MigrationsBundleCommand
publishArgs.Add("--disable-build-servers");
- var exitCode = Exe.Run("dotnet", publishArgs, directory, interceptOutput: true);
+ var exitCode = Exe.Run("dotnet", publishArgs, directory, handleOutput: Reporter.WriteVerbose);
if (exitCode != 0)
{
throw new CommandException(Resources.BuildBundleFailed);
diff --git a/src/ef/Reporter.cs b/src/ef/Reporter.cs
index 11469e4b6b..446fe1fb6c 100644
--- a/src/ef/Reporter.cs
+++ b/src/ef/Reporter.cs
@@ -8,6 +8,12 @@ namespace Microsoft.EntityFrameworkCore.Tools;
internal static class Reporter
{
+ public const string ErrorPrefix = "error: ";
+ public const string WarningPrefix = "warn: ";
+ public const string InfoPrefix = "info: ";
+ public const string DataPrefix = "data: ";
+ public const string VerbosePrefix = "verbose: ";
+
public static bool IsVerbose { get; set; }
public static bool NoColor { get; set; }
public static bool PrefixOutput { get; set; }
@@ -17,22 +23,22 @@ internal static class Reporter
=> NoColor ? value : colorizeFunc(value);
public static void WriteError(string? message)
- => WriteLine(Prefix("error: ", Colorize(message, x => Bold + Red + x + Reset)));
+ => WriteLine(Prefix(ErrorPrefix, Colorize(message, x => Bold + Red + x + Reset)));
public static void WriteWarning(string? message)
- => WriteLine(Prefix("warn: ", Colorize(message, x => Bold + Yellow + x + Reset)));
+ => WriteLine(Prefix(WarningPrefix, Colorize(message, x => Bold + Yellow + x + Reset)));
public static void WriteInformation(string? message)
- => WriteLine(Prefix("info: ", message));
+ => WriteLine(Prefix(InfoPrefix, message));
public static void WriteData(string? message)
- => WriteLine(Prefix("data: ", Colorize(message, x => Bold + Gray + x + Reset)));
+ => WriteLine(Prefix(DataPrefix, Colorize(message, x => Bold + Gray + x + Reset)));
public static void WriteVerbose(string? message)
{
if (IsVerbose)
{
- WriteLine(Prefix("verbose: ", Colorize(message, x => Bold + Black + x + Reset)));
+ WriteLine(Prefix(VerbosePrefix, Colorize(message, x => Bold + Black + x + Reset)));
}
}
diff --git a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj
index 37019a1aa2..865de57807 100644
--- a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj
+++ b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj
@@ -48,7 +48,6 @@
-
diff --git a/test/EFCore.Specification.Tests/TestUtilities/ModelAsserter.cs b/test/EFCore.Specification.Tests/TestUtilities/ModelAsserter.cs
index e7493ce3c4..e6b2b0c1ab 100644
--- a/test/EFCore.Specification.Tests/TestUtilities/ModelAsserter.cs
+++ b/test/EFCore.Specification.Tests/TestUtilities/ModelAsserter.cs
@@ -72,6 +72,10 @@ public class ModelAsserter
expectedEntityTypes = expectedEntityTypes.OrderBy(p => p.Name);
actualEntityTypes = actualEntityTypes.OrderBy(p => p.Name);
}
+ else
+ {
+ expectedEntityTypes = expectedEntityTypes.Select(x => x);
+ }
Assert.Equal(expectedEntityTypes, actualEntityTypes,
(expected, actual) =>
@@ -214,6 +218,10 @@ public class ModelAsserter
expectedProperties = expectedProperties.OrderBy(p => p.Name);
actualProperties = actualProperties.OrderBy(p => p.Name);
}
+ else
+ {
+ expectedProperties = expectedProperties.Select(x => x);
+ }
Assert.Equal(expectedProperties, actualProperties,
(expected, actual) =>
@@ -280,6 +288,10 @@ public class ModelAsserter
expectedProperties = expectedProperties.OrderBy(p => p.Name);
actualProperties = actualProperties.OrderBy(p => p.Name);
}
+ else
+ {
+ expectedProperties = expectedProperties.Select(x => x);
+ }
Assert.Equal(expectedProperties, actualProperties,
(expected, actual) =>
@@ -407,6 +419,10 @@ public class ModelAsserter
expectedProperties = expectedProperties.OrderBy(p => p.Name);
actualProperties = actualProperties.OrderBy(p => p.Name);
}
+ else
+ {
+ expectedProperties = expectedProperties.Select(x => x);
+ }
Assert.Equal(expectedProperties, actualProperties,
(expected, actual) =>
@@ -465,6 +481,10 @@ public class ModelAsserter
expectedNavigations = expectedNavigations.OrderBy(p => p.Name);
actualNavigations = actualNavigations.OrderBy(p => p.Name);
}
+ else
+ {
+ expectedNavigations = expectedNavigations.Select(x => x);
+ }
Assert.Equal(expectedNavigations, actualNavigations,
(expected, actual) =>
@@ -540,6 +560,10 @@ public class ModelAsserter
expectedNavigations = expectedNavigations.OrderBy(p => p.Name);
actualNavigations = actualNavigations.OrderBy(p => p.Name);
}
+ else
+ {
+ expectedNavigations = expectedNavigations.Select(x => x);
+ }
Assert.Equal(expectedNavigations, actualNavigations,
(expected, actual) =>
@@ -615,6 +639,10 @@ public class ModelAsserter
expectedKeys = expectedKeys.Order(KeyComparer.Instance);
actualKeys = actualKeys.Order(KeyComparer.Instance);
}
+ else
+ {
+ expectedKeys = expectedKeys.Select(x => x);
+ }
Assert.Equal(expectedKeys, actualKeys,
(expected, actual) =>
@@ -689,6 +717,10 @@ public class ModelAsserter
expectedForeignKey = expectedForeignKey.Order(ForeignKeyComparer.Instance);
actualForeignKey = actualForeignKey.Order(ForeignKeyComparer.Instance);
}
+ else
+ {
+ expectedForeignKey = expectedForeignKey.Select(x => x);
+ }
Assert.Equal(expectedForeignKey, actualForeignKey,
(expected, actual) =>
@@ -777,6 +809,10 @@ public class ModelAsserter
expectedIndex = expectedIndex.Order(IndexComparer.Instance);
actualIndex = actualIndex.Order(IndexComparer.Instance);
}
+ else
+ {
+ expectedIndex = expectedIndex.Select(x => x);
+ }
Assert.Equal(expectedIndex, actualIndex,
(expected, actual) =>