Add OptimizeContext MSBuild target that runs on build (#33049)
Fixes #24894
This commit is contained in:
Родитель
4341961f47
Коммит
52f37b3826
|
@ -10,6 +10,7 @@
|
|||
( $(MSBuildProjectName.EndsWith('.Tests')) OR
|
||||
$(MSBuildProjectName.EndsWith('.FunctionalTests'))) ">true</IsUnitTestProject>
|
||||
<IsUnitTestProject Condition=" '$(IsUnitTestProject)' == '' ">false</IsUnitTestProject>
|
||||
<SolutionRoot>$(MSBuildThisFileDirectory)</SolutionRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<ItemGroup Condition="'$(PackageReadmeFile)' != ''">
|
||||
<None Include="$(PackageReadmeFile)" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
|
||||
<Import Project="eng\testing\linker\trimmingTests.targets" Condition="'$(IsPublishedAppTestProject)' == 'true'" />
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -31,10 +31,11 @@
|
|||
<MicrosoftDotNetBuildTasksTemplatingVersion>9.0.0-beta.24114.1</MicrosoftDotNetBuildTasksTemplatingVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Other dependencies">
|
||||
<MicrosoftBuildFrameworkVersion>17.0.0</MicrosoftBuildFrameworkVersion>
|
||||
<MicrosoftBuildTasksCoreVersion>17.0.0</MicrosoftBuildTasksCoreVersion>
|
||||
<MicrosoftBuildUtilitiesCoreVersion>17.0.0</MicrosoftBuildUtilitiesCoreVersion>
|
||||
<!-- NB: This version affects Visual Studio compatibility. See https://learn.microsoft.com/visualstudio/extensibility/roslyn-version-support -->
|
||||
<MicrosoftCodeAnalysisVersion>4.8.0</MicrosoftCodeAnalysisVersion>
|
||||
<MicrosoftCodeAnalysisTestingVersion>1.1.2-beta1.23578.3</MicrosoftCodeAnalysisTestingVersion>
|
||||
<XUnitVersion>2.6.1</XUnitVersion>
|
||||
<XUnitRunnerVisualstudioVersion>2.5.3</XUnitRunnerVisualstudioVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<TrimmingTestDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'trimmingTests'))</TrimmingTestDir>
|
||||
<TrimmingTestProjectsDir>$([MSBuild]::NormalizeDirectory('$(TrimmingTestDir)', 'projects'))</TrimmingTestProjectsDir>
|
||||
<TrimmingTestProjectsDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)'))</TrimmingTestProjectsDir>
|
||||
<ProjectTemplate>$(MSBuildThisFileDirectory)project.csproj.template</ProjectTemplate>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
<ItemGroup Condition="'$(AdditionalProjectReferences)' != ''">
|
||||
<_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" />" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
|
@ -47,10 +47,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="build\**\*">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>build</PackagePath>
|
||||
</None>
|
||||
<None Include="build\**\*" PackagePath="build\" Pack="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>MSBuild tasks for Entity Framework Core projects.</Description>
|
||||
<TargetFrameworks>$(DefaultNetCoreTargetFramework);net472</TargetFrameworks>
|
||||
<AssemblyName>Microsoft.EntityFrameworkCore.Tasks</AssemblyName>
|
||||
<RootNamespace>Microsoft.EntityFrameworkCore</RootNamespace>
|
||||
<IncludeSymbols>false</IncludeSymbols>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<GenerateDependencyFile>true</GenerateDependencyFile>
|
||||
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
|
||||
<NoWarn>NU5100;NU5128</NoWarn>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)..\..\rulesets\EFCore.noxmldocs.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\dotnet-ef\Exe.cs" Link="Tools\Exe.cs" />
|
||||
<Compile Include="..\ef\AnsiConsole.cs" Link="Tools\AnsiConsole.cs"/>
|
||||
<Compile Include="..\ef\AnsiConstants.cs" Link="Tools\AnsiConstants.cs"/>
|
||||
<Compile Include="..\ef\AnsiTextWriter.cs" Link="Tools\AnsiTextWriter.cs"/>
|
||||
<Compile Include="..\ef\Reporter.cs" Link="Tools\Reporter.cs"/>
|
||||
<Compile Include="..\ef\NotNullIfNotNullAttribute.cs" Link="Tools\NotNullIfNotNullAttribute.cs"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Properties\Resources.Designer.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<CustomToolNamespace>Microsoft.EntityFrameworkCore.Tools.Properties</CustomToolNamespace>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.Designer.tt</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Microsoft.Build" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ef\ef.csproj" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net472'">
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildFrameworkVersion)" PrivateAssets="all" ExcludeAssets="Runtime" IsImplicitlyDefined="true" />
|
||||
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(MicrosoftBuildTasksCoreVersion)" PrivateAssets="all" ExcludeAssets="Runtime" IsImplicitlyDefined="true" />
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCoreVersion)" PrivateAssets="all" ExcludeAssets="Runtime" IsImplicitlyDefined="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
|
||||
<PackageReference Include="System.Text.Json" Version="$(SystemTextJsonVersion)" PrivateAssets="All" />
|
||||
<Reference Include="Microsoft.Build" />
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
<Reference Include="Microsoft.Build.Tasks.v4.0" />
|
||||
<Reference Include="Microsoft.Build.Utilities.v4.0" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Configuration" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Workaround for insufficient support for task packages by NuGet Pack: https://github.com/NuGet/Home/issues/6321
|
||||
and bugs with ProjectReference: https://github.com/NuGet/Home/issues/10907, https://github.com/NuGet/Home/issues/10312
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<NuspecFile>$(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec</NuspecFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="SetPackageProperties" BeforeTargets="InitializeStandardNuspecProperties" DependsOnTargets="Build">
|
||||
<ItemGroup>
|
||||
<NuspecProperty Include="AssemblyName=$(AssemblyName)" />
|
||||
<NuspecProperty Include="OutputPath=$(OutputPath)" />
|
||||
<NuspecProperty Include="DefaultNetCoreTargetFramework=$(DefaultNetCoreTargetFramework)" />
|
||||
<NuspecProperty Include="Configuration=$(Configuration)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
$CommonMetadataElements$
|
||||
<dependencies>
|
||||
<group targetFramework=".NET8.0">
|
||||
<dependency id="Microsoft.EntityFrameworkCore.Design" version="$Version$" />
|
||||
</group>
|
||||
</dependencies>
|
||||
<readme>docs\PACKAGE.md</readme>
|
||||
</metadata>
|
||||
<files>
|
||||
$CommonFileElements$
|
||||
<file src="PACKAGE.md" target="docs\" />
|
||||
<file src="$OutputPath$$DefaultNetCoreTargetFramework$\$AssemblyName$.dll" target="tasks\$DefaultNetCoreTargetFramework$\" />
|
||||
<file src="$OutputPath$$DefaultNetCoreTargetFramework$\$AssemblyName$.pdb" target="tasks\$DefaultNetCoreTargetFramework$\" />
|
||||
<file src="$OutputPath$$DefaultNetCoreTargetFramework$\$AssemblyName$.deps.json" target="tasks\$DefaultNetCoreTargetFramework$\" />
|
||||
<file src="$OutputPath$net472\*" target="tasks\net472\" />
|
||||
<file src="..\..\artifacts\bin\ef\$Configuration$\netcoreapp2.0\*" target="tools\netcoreapp2.0\" />
|
||||
<file src="buildTransitive\*" target="buildTransitive\" />
|
||||
</files>
|
||||
</package>
|
|
@ -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 `<EFOptimizeContext Condition="'$(Configuration)'=='Release'">true</EFOptimizeContext>` and then run build normally.
|
||||
|
||||
If the startup project is different from the current project it needs to be specified: `<EFStartupProject>..\Startup\Startup.csproj</EFStartupProject>`
|
||||
|
||||
## 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).
|
|
@ -0,0 +1,58 @@
|
|||
// <auto-generated />
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.EntityFrameworkCore.Tools.Properties
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.EntityFrameworkCore.Properties.Resources", typeof(Resources).Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public static string NETCoreApp1StartupProject(object? startupProject, object? targetFrameworkVersion)
|
||||
=> string.Format(
|
||||
GetString("NETCoreApp1StartupProject", nameof(startupProject), nameof(targetFrameworkVersion)),
|
||||
startupProject, targetFrameworkVersion);
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public static string NETStandardStartupProject(object? startupProject)
|
||||
=> string.Format(
|
||||
GetString("NETStandardStartupProject", nameof(startupProject)),
|
||||
startupProject);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<#
|
||||
Session["ResourceFile"] = "Resources.resx";
|
||||
Session["ResourceNamespace"] = "Microsoft.EntityFrameworkCore.Properties";
|
||||
Session["AccessModifier"] = "internal";
|
||||
Session["NoDiagnostics"] = true;
|
||||
#>
|
||||
<#@ include file="..\..\..\tools\Resources.tt" #>
|
|
@ -0,0 +1,129 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="NETCoreApp1StartupProject" xml:space="preserve">
|
||||
<value>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</value>
|
||||
</data>
|
||||
<data name="NETStandardStartupProject" xml:space="preserve">
|
||||
<value>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</value>
|
||||
</data>
|
||||
<data name="UnsupportedFramework" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<string>()
|
||||
.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(',', ';');
|
||||
}
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public abstract class OperationTaskBase : Build.Utilities.Task
|
||||
{
|
||||
/// <summary>
|
||||
/// The assembly to use.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public ITaskItem Assembly { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The startup assembly to use.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public ITaskItem StartupAssembly { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The target framework moniker.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string TargetFrameworkMoniker { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The target runtime framework version.
|
||||
/// </summary>
|
||||
public string? RuntimeFrameworkVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The project assets file.
|
||||
/// </summary>
|
||||
public string? ProjectAssetsFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The directory containing the database files.
|
||||
/// </summary>
|
||||
public ITaskItem? DataDir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The project directory.
|
||||
/// </summary>
|
||||
public ITaskItem? ProjectDir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The root namespace to use.
|
||||
/// </summary>
|
||||
public string? RootNamespace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The language to use. Defaults to C#.
|
||||
/// </summary>
|
||||
public string? Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A flag indicating whether nullable reference types are enabled.
|
||||
/// </summary>
|
||||
public bool Nullable { get; set; }
|
||||
|
||||
protected virtual bool Execute(IEnumerable<string> additionalArguments, out string? result)
|
||||
{
|
||||
var args = new List<string>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Generates files that contain tailored code for some runtime services.
|
||||
/// </summary>
|
||||
public class OptimizeContext : OperationTaskBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the target DbContext.
|
||||
/// </summary>
|
||||
public string? DbContextName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The namespace to use for the generated classes.
|
||||
/// </summary>
|
||||
public string? TargetNamespace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The output directory. Usually, relative to the project directory.
|
||||
/// </summary>
|
||||
public ITaskItem? OutputDir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generated files that should be include in the build.
|
||||
/// </summary>
|
||||
[Output]
|
||||
public ITaskItem[] GeneratedFiles { get; private set; } = null!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Execute()
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.LogMessage(MessageImportance.High, "Optimizing DbContext...");
|
||||
|
||||
var additionalArguments = new List<string> { "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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Project ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<_TaskTargetFramework Condition="'$(MSBuildRuntimeType)' == 'core'">net8.0</_TaskTargetFramework>
|
||||
<_TaskTargetFramework Condition="'$(MSBuildRuntimeType)' != 'core'">net472</_TaskTargetFramework>
|
||||
<_EFCustomTasksAssembly>$([MSBuild]::NormalizePath($(MSBuildThisFileDirectory), '..\tasks\$(_TaskTargetFramework)\$(MSBuildThisFileName).dll'))</_EFCustomTasksAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<UsingTask TaskName="$(MSBuildThisFileName).OptimizeContext" AssemblyFile="$(_EFCustomTasksAssembly)"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<EFOptimizeContext Condition="'$(EFOptimizeContext)' == ''">false</EFOptimizeContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(EFTargetLanguage)' == ''">
|
||||
<EFTargetLanguage Condition="'$(MSBuildProjectExtension)' == '.csproj'">C#</EFTargetLanguage>
|
||||
<EFTargetLanguage Condition="'$(MSBuildProjectExtension)' == '.vbproj'">VB</EFTargetLanguage>
|
||||
<EFTargetLanguage Condition="'$(MSBuildProjectExtension)' == '.fsproj'">F#</EFTargetLanguage>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -0,0 +1,116 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Project ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<PropertyGroup>
|
||||
<EFGeneratedFilesList Condition="'$(EFGeneratedFilesList)' == ''">$([MSBuild]::NormalizePath($(MSBuildProjectDirectory), '$(IntermediateOutputPath)$(AssemblyName).EFGeneratedFiles.txt'))</EFGeneratedFilesList>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Invokes OptimizeContext on the startup project to generate files if needed -->
|
||||
<Target Name="_GenerateFiles"
|
||||
BeforeTargets="_ReadGeneratedFilesList"
|
||||
Condition="'$(EFOptimizeContext)'=='true'">
|
||||
<PropertyGroup>
|
||||
<EFStartupProject Condition="'$(EFStartupProject)' ==''">$(MSBuildProjectFullPath)</EFStartupProject>
|
||||
<EFRootNamespace Condition="'$(EFRootNamespace)' == ''">$(RootNamespace)</EFRootNamespace>
|
||||
<EFRootNamespace Condition="'$(EFRootNamespace)' == ''">$(AssemblyName)</EFRootNamespace>
|
||||
<EFTargetNamespace Condition="'$(EFTargetNamespace)' == ''">$(EFRootNamespace)</EFTargetNamespace>
|
||||
<_FullOutputPath>$([MSBuild]::NormalizePath($(MSBuildProjectDirectory), '$(OutputPath)'))</_FullOutputPath>
|
||||
<_FullIntermediateOutputPath>$([MSBuild]::NormalizePath($(MSBuildProjectDirectory), '$(IntermediateOutputPath)'))</_FullIntermediateOutputPath>
|
||||
<EFNullable>false</EFNullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Nullable)'=='enable' Or '$(Nullable)'=='annotations'">
|
||||
<EFNullable>true</EFNullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(OutputType)'=='Library'">
|
||||
<_AssemblyFullName>$(_FullOutputPath)$(AssemblyName).dll</_AssemblyFullName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(OutputType)'=='Exe'">
|
||||
<_AssemblyFullName>$(_FullOutputPath)$(AssemblyName).exe</_AssemblyFullName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(OutputType)'=='WinExe'">
|
||||
<_AssemblyFullName>$(_FullOutputPath)$(AssemblyName).exe</_AssemblyFullName>
|
||||
</PropertyGroup>
|
||||
|
||||
<MSBuild Projects="$(EFStartupProject)"
|
||||
Targets="OptimizeContext"
|
||||
BuildInParallel="true"
|
||||
Properties="Configuration=$(Configuration);Platform=$(Platform);PublishAot=false;EFOptimizeContext=false;EFGeneratedFilesListToWrite=$(EFGeneratedFilesList);DbContextAssembly=$(_AssemblyFullName);DbContextName=$(DbContextName);EFRootNamespace=$(EFRootNamespace);EFTargetNamespace=$(EFTargetNamespace);EFTargetLanguage=$(EFTargetLanguage);EFNullable=$(EFNullable);EFOutputDir=$(_FullIntermediateOutputPath);EFProjectDir=$(MSBuildProjectDirectory)">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="_GeneratedFiles" />
|
||||
</MSBuild>
|
||||
|
||||
<Message Text="Generated files: @(_GeneratedFiles)" Importance="low" />
|
||||
</Target>
|
||||
|
||||
<Target Name="OptimizeContext"
|
||||
DependsOnTargets="Build"
|
||||
Inputs="$(DbContextAssembly)"
|
||||
Outputs="$(EFGeneratedFilesListToWrite)"
|
||||
Returns="$(_GeneratedFiles)">
|
||||
<PropertyGroup>
|
||||
<_FullOutputPath>$([MSBuild]::NormalizePath($(MSBuildProjectDirectory), '$(OutputPath)'))</_FullOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(OutputType)'=='Library'">
|
||||
<_StartupAssemblyFullName>$(_FullOutputPath)$(AssemblyName).dll</_StartupAssemblyFullName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(OutputType)'=='Exe'">
|
||||
<_StartupAssemblyFullName>$(_FullOutputPath)$(AssemblyName).exe</_StartupAssemblyFullName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(OutputType)'=='WinExe'">
|
||||
<_StartupAssemblyFullName>$(_FullOutputPath)$(AssemblyName).exe</_StartupAssemblyFullName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MyItemGroup Remove="@(_GeneratedFiles)"/>
|
||||
</ItemGroup>
|
||||
|
||||
<OptimizeContext Assembly="$(DbContextAssembly)"
|
||||
StartupAssembly="$(_StartupAssemblyFullName)"
|
||||
ProjectAssetsFile="$(ProjectAssetsFile)"
|
||||
RuntimeFrameworkVersion="$(RuntimeFrameworkVersion)"
|
||||
TargetFrameworkMoniker="$(TargetFrameworkMoniker)"
|
||||
DbContextName="$(DbContextName)"
|
||||
RootNamespace="$(EFRootNamespace)"
|
||||
TargetNamespace="$(EFTargetNamespace)"
|
||||
Language="$(EFTargetLanguage)"
|
||||
Nullable="$(EFNullable)"
|
||||
OutputDir="$(EFOutputDir)"
|
||||
ProjectDir="$(EFProjectDir)">
|
||||
<Output TaskParameter="GeneratedFiles" PropertyName="_GeneratedFiles" />
|
||||
</OptimizeContext>
|
||||
</Target>
|
||||
|
||||
<!-- Read the previously generated files if the files weren't regenerated -->
|
||||
<Target Name="_ReadGeneratedFilesList"
|
||||
BeforeTargets="_CompileGeneratedFiles"
|
||||
Condition="Exists($(EFGeneratedFilesList))">
|
||||
<ReadLinesFromFile File="$(EFGeneratedFilesList)" Condition="@(_GeneratedFiles->Count()) == 0">
|
||||
<Output TaskParameter="Lines" ItemName="_ReadGeneratedFiles"/>
|
||||
</ReadLinesFromFile>
|
||||
<Message Text="Found previously generated files: @(_ReadGeneratedFiles)" Importance="low" Condition="@(_GeneratedFiles->Count()) == 0" />
|
||||
|
||||
<ItemGroup>
|
||||
<_GeneratedFiles Include="@(_ReadGeneratedFiles)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!-- Adds the generated files to compilation -->
|
||||
<Target Name="_CompileGeneratedFiles"
|
||||
BeforeTargets="CoreCompile">
|
||||
<ItemGroup>
|
||||
<Compile Include="@(_GeneratedFiles)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!-- Writes the list of generated files with a newer timestamp than the assembly compiled with them -->
|
||||
<Target Name="_WriteGeneratedFilesList"
|
||||
AfterTargets="Build"
|
||||
Condition="'$(EFOptimizeContext)'=='true' And @(_GeneratedFiles->Count()) > 0">
|
||||
<Message Text="Writing generated files list to: $(EFGeneratedFilesList)" Importance="low" />
|
||||
|
||||
<WriteLinesToFile File="$(EFGeneratedFilesList)" Lines="@(_GeneratedFiles)" Overwrite="true"/>
|
||||
</Target>
|
||||
|
||||
</Project>
|
|
@ -1,4 +1,4 @@
|
|||
<Project>
|
||||
<Project>
|
||||
|
||||
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
|
||||
|
||||
|
@ -7,9 +7,12 @@
|
|||
<PackageId>Microsoft.EntityFrameworkCore.Tools</PackageId>
|
||||
<NuspecFile>$(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec</NuspecFile>
|
||||
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
|
||||
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<DevelopmentDependency>true</DevelopmentDependency>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<IncludeSource>false</IncludeSource>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<Description>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
|
||||
</Description>
|
||||
<CheckEolTargetFramework>False</CheckEolTargetFramework>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="System.Diagnostics" />
|
||||
<Using Include="System.Linq.Expressions" />
|
||||
<Using Include="System.Reflection" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.ChangeTracking" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Diagnostics" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Design" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Infrastructure" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Metadata" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Metadata.Builders" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Metadata.Conventions" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Migrations" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Migrations.Design" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Migrations.Operations" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Query" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Scaffolding" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Storage" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Storage.ValueConversion" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Update" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.ValueGeneration" />
|
||||
<Using Include="Microsoft.EntityFrameworkCore.Utilities" />
|
||||
<Using Include="Microsoft.Extensions.Logging" />
|
||||
<Using Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<ProjectReference Include="..\ef\ef.csproj" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ef\ef.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.DotNet.Build.Tasks.Templating" Version="$(MicrosoftDotNetBuildTasksTemplatingVersion)" AllowExplicitReference="true" PrivateAssets="All" IsImplicitlyDefined="true" />
|
||||
<PackageReference Include="Microsoft.DotNet.Build.Tasks.Templating" Version="$(MicrosoftDotNetBuildTasksTemplatingVersion)" PrivateAssets="All" IsImplicitlyDefined="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<GeneratedContent Include="*.psd1.in" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Workaround for insufficient support for tools packages by NuGet Pack: https://github.com/NuGet/Home/issues/6321.
|
||||
-->
|
||||
<Target Name="SetPackageProperties" BeforeTargets="InitializeStandardNuspecProperties" DependsOnTargets="Build">
|
||||
<PropertyGroup>
|
||||
<DevelopmentDependency>true</DevelopmentDependency>
|
||||
|
||||
<!-- Make sure we create a symbols.nupkg -->
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<NuspecProperty Include="version=$(PackageVersion)" />
|
||||
<NuspecProperty Include="configuration=$(Configuration)" />
|
||||
<NuspecProperty Include="intermediateOutputPath=$(IntermediateOutputPath)" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata minClientVersion="3.6">
|
||||
<metadata>
|
||||
$CommonMetadataElements$
|
||||
<minClientVersion>3.6</minClientVersion>
|
||||
<dependencies>
|
||||
<group targetFramework=".NET8.0">
|
||||
<dependency id="Microsoft.EntityFrameworkCore.Design" version="$version$" />
|
||||
<dependency id="Microsoft.EntityFrameworkCore.Design" version="$Version$" />
|
||||
</group>
|
||||
</dependencies>
|
||||
<readme>docs\PACKAGE.md</readme>
|
||||
|
@ -14,7 +13,6 @@
|
|||
<files>
|
||||
$CommonFileElements$
|
||||
<file src="PACKAGE.md" target="docs\" />
|
||||
<file src="lib\**\*" target="lib/" />
|
||||
<file src="tools\**\*" target="tools/" />
|
||||
<file src="$intermediateOutputPath$*.psd1" target="tools/" />
|
||||
<file src="../../artifacts/bin/ef/$configuration$/net472/ef.exe" target="tools/net472/any/" />
|
||||
|
|
|
@ -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<string> args,
|
||||
string? workingDirectory = null,
|
||||
bool interceptOutput = false)
|
||||
Action<string?>? handleOutput = null,
|
||||
Action<string?>? handleError = null,
|
||||
Action<string>? 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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<Match>())
|
||||
|
|
|
@ -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<string> generatedFiles)
|
||||
{
|
||||
foreach (var file in generatedFiles)
|
||||
{
|
||||
Reporter.WriteData(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\EFCore\EFCore.csproj" />
|
||||
<ProjectReference Include="..\..\src\EFCore.Analyzers\EFCore.Analyzers.csproj" />
|
||||
<ProjectReference Include="..\..\src\EFCore.Design\EFCore.Design.csproj" />
|
||||
<ProjectReference Include="..\..\src\EFCore.Proxies\EFCore.Proxies.csproj" />
|
||||
<ProjectReference Include="..\..\src\EFCore.Abstractions\EFCore.Abstractions.csproj" />
|
||||
|
|
|
@ -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) =>
|
||||
|
|
Загрузка…
Ссылка в новой задаче