diff --git a/EntityFramework.sln b/EntityFramework.sln index 169d9d6d88b..ae9238dff31 100644 --- a/EntityFramework.sln +++ b/EntityFramework.sln @@ -69,6 +69,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.EntityFrameworkCo EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.EntityFrameworkCore.Sqlite.Design.Tests", "test\Microsoft.EntityFrameworkCore.Sqlite.Design.Tests\Microsoft.EntityFrameworkCore.Sqlite.Design.Tests.xproj", "{BFF1BBE8-C6CE-4103-9C75-6184C8B8D936}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "dotnet-ef", "src\dotnet-ef\dotnet-ef.xproj", "{5C1B3280-F11D-4E4E-81B3-BCDA4942E097}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "dotnet-ef.Tests", "test\dotnet-ef.Tests\dotnet-ef.Tests.xproj", "{93D7AB4E-2E04-4217-BBE5-43EC0D54344F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -199,6 +203,14 @@ Global {BFF1BBE8-C6CE-4103-9C75-6184C8B8D936}.Debug|Any CPU.Build.0 = Debug|Any CPU {BFF1BBE8-C6CE-4103-9C75-6184C8B8D936}.Release|Any CPU.ActiveCfg = Release|Any CPU {BFF1BBE8-C6CE-4103-9C75-6184C8B8D936}.Release|Any CPU.Build.0 = Release|Any CPU + {5C1B3280-F11D-4E4E-81B3-BCDA4942E097}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C1B3280-F11D-4E4E-81B3-BCDA4942E097}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C1B3280-F11D-4E4E-81B3-BCDA4942E097}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C1B3280-F11D-4E4E-81B3-BCDA4942E097}.Release|Any CPU.Build.0 = Release|Any CPU + {93D7AB4E-2E04-4217-BBE5-43EC0D54344F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93D7AB4E-2E04-4217-BBE5-43EC0D54344F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93D7AB4E-2E04-4217-BBE5-43EC0D54344F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93D7AB4E-2E04-4217-BBE5-43EC0D54344F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -235,5 +247,7 @@ Global {D4A29871-788D-4EA3-B637-1F8D14DA23B4} = {C605BAB7-B06E-4BDF-80B2-E661B7670E25} {000FF28B-AA1E-4DD2-8582-0259CF07B025} = {E7D68B03-3A5D-4710-9A1F-20D198E41F78} {BFF1BBE8-C6CE-4103-9C75-6184C8B8D936} = {C605BAB7-B06E-4BDF-80B2-E661B7670E25} + {5C1B3280-F11D-4E4E-81B3-BCDA4942E097} = {E7D68B03-3A5D-4710-9A1F-20D198E41F78} + {93D7AB4E-2E04-4217-BBE5-43EC0D54344F} = {C605BAB7-B06E-4BDF-80B2-E661B7670E25} EndGlobalSection EndGlobal diff --git a/src/Microsoft.EntityFrameworkCore.Commands/Design/DatabaseOperations.cs b/src/Microsoft.EntityFrameworkCore.Commands/Design/DatabaseOperations.cs index 820650c4431..998e4ad89e8 100644 --- a/src/Microsoft.EntityFrameworkCore.Commands/Design/DatabaseOperations.cs +++ b/src/Microsoft.EntityFrameworkCore.Commands/Design/DatabaseOperations.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -24,15 +25,15 @@ public class DatabaseOperations public DatabaseOperations( [NotNull] ILoggerProvider loggerProvider, - [NotNull] string assemblyName, - [NotNull] string startupAssemblyName, + [NotNull] Assembly assembly, + [NotNull] Assembly startupAssembly, [CanBeNull] string environment, [NotNull] string projectDir, [NotNull] string rootNamespace) { Check.NotNull(loggerProvider, nameof(loggerProvider)); - Check.NotEmpty(assemblyName, nameof(assemblyName)); - Check.NotEmpty(startupAssemblyName, nameof(startupAssemblyName)); + Check.NotNull(assembly, nameof(assembly)); + Check.NotNull(startupAssembly, nameof(startupAssembly)); Check.NotNull(projectDir, nameof(projectDir)); Check.NotNull(rootNamespace, nameof(rootNamespace)); @@ -40,7 +41,7 @@ public DatabaseOperations( _projectDir = projectDir; _rootNamespace = rootNamespace; - var startup = new StartupInvoker(startupAssemblyName, environment); + var startup = new StartupInvoker(startupAssembly, environment); _servicesBuilder = new DesignTimeServicesBuilder(startup); } diff --git a/src/Microsoft.EntityFrameworkCore.Commands/Design/DbContextOperations.cs b/src/Microsoft.EntityFrameworkCore.Commands/Design/DbContextOperations.cs index ebb1457048d..7493600f1f1 100644 --- a/src/Microsoft.EntityFrameworkCore.Commands/Design/DbContextOperations.cs +++ b/src/Microsoft.EntityFrameworkCore.Commands/Design/DbContextOperations.cs @@ -24,8 +24,8 @@ namespace Microsoft.EntityFrameworkCore.Design public class DbContextOperations { private readonly ILoggerProvider _loggerProvider; - private readonly string _assemblyName; - private readonly string _startupAssemblyName; + private readonly Assembly _assembly; + private readonly Assembly _startupAssembly; private readonly string _projectDir; private readonly LazyRef _logger; private readonly IServiceProvider _runtimeServices; @@ -33,23 +33,23 @@ public class DbContextOperations public DbContextOperations( [NotNull] ILoggerProvider loggerProvider, - [NotNull] string assemblyName, - [NotNull] string startupAssemblyName, + [NotNull] Assembly assembly, + [NotNull] Assembly startupAssembly, [NotNull] string projectDir, [CanBeNull] string environment) { Check.NotNull(loggerProvider, nameof(loggerProvider)); - Check.NotEmpty(assemblyName, nameof(assemblyName)); - Check.NotEmpty(startupAssemblyName, nameof(startupAssemblyName)); + Check.NotNull(assembly, nameof(assembly)); + Check.NotNull(startupAssembly, nameof(startupAssembly)); Check.NotEmpty(projectDir, nameof(projectDir)); _loggerProvider = loggerProvider; - _assemblyName = assemblyName; - _startupAssemblyName = startupAssemblyName; + _assembly = assembly; + _startupAssembly = startupAssembly; _projectDir = projectDir; _logger = new LazyRef(() => _loggerProvider.CreateCommandsLogger()); - var startup = new StartupInvoker(startupAssemblyName, environment); + var startup = new StartupInvoker(startupAssembly, environment); _runtimeServices = startup.ConfigureServices(); _servicesBuilder = new DesignTimeServicesBuilder(startup); @@ -125,11 +125,10 @@ private IDictionary> FindContextTypes() { _logger.Value.LogDebug(CommandsStrings.LogFindingContexts); - var startupAssembly = Assembly.Load(new AssemblyName(_startupAssemblyName)); var contexts = new Dictionary>(); // Look for IDbContextFactory implementations - var contextFactories = startupAssembly.GetConstructibleTypes() + var contextFactories = _startupAssembly.GetConstructibleTypes() .Where(t => typeof(IDbContextFactory).GetTypeInfo().IsAssignableFrom(t)); foreach (var factory in contextFactories) { @@ -157,18 +156,8 @@ where i.GetTypeInfo().IsGenericType } // Look for DbContext classes in assemblies - Assembly assembly; - try - { - assembly = Assembly.Load(new AssemblyName(_assemblyName)); - } - catch (Exception ex) - { - throw new OperationException(CommandsStrings.UnreferencedAssembly(_assemblyName, _startupAssemblyName), ex); - } - - var types = startupAssembly.GetConstructibleTypes() - .Concat(assembly.GetConstructibleTypes()) + var types = _startupAssembly.GetConstructibleTypes() + .Concat(_assembly.GetConstructibleTypes()) .Select(i => i.AsType()); var contextTypes = types.Where(t => typeof(DbContext).IsAssignableFrom(t)) .Concat( diff --git a/src/Microsoft.EntityFrameworkCore.Commands/Design/Internal/StartupInvoker.cs b/src/Microsoft.EntityFrameworkCore.Commands/Design/Internal/StartupInvoker.cs index 1a47be61b4a..f295164d99a 100644 --- a/src/Microsoft.EntityFrameworkCore.Commands/Design/Internal/StartupInvoker.cs +++ b/src/Microsoft.EntityFrameworkCore.Commands/Design/Internal/StartupInvoker.cs @@ -16,16 +16,15 @@ public class StartupInvoker private readonly string _environment; public StartupInvoker( - [NotNull] string startupAssemblyName, + [NotNull] Assembly startupAssembly, [CanBeNull] string environment) { - Check.NotEmpty(startupAssemblyName, nameof(startupAssemblyName)); + Check.NotNull(startupAssembly, nameof(startupAssembly)); _environment = !string.IsNullOrEmpty(environment) ? environment : "Development"; - var startupAssembly = Assembly.Load(new AssemblyName(startupAssemblyName)); _startupType = startupAssembly.DefinedTypes.Where(t => t.Name == "Startup" + _environment) .Concat(startupAssembly.DefinedTypes.Where(t => t.Name == "Startup")) .Concat(startupAssembly.DefinedTypes.Where(t => t.Name == "Program")) diff --git a/src/Microsoft.EntityFrameworkCore.Commands/Design/MigrationsOperations.cs b/src/Microsoft.EntityFrameworkCore.Commands/Design/MigrationsOperations.cs index af7497584d7..c38d2650925 100644 --- a/src/Microsoft.EntityFrameworkCore.Commands/Design/MigrationsOperations.cs +++ b/src/Microsoft.EntityFrameworkCore.Commands/Design/MigrationsOperations.cs @@ -23,7 +23,7 @@ public class MigrationsOperations { private readonly ILoggerProvider _loggerProvider; private readonly LazyRef _logger; - private readonly string _assemblyName; + private readonly Assembly _assembly; private readonly string _projectDir; private readonly string _rootNamespace; private readonly DesignTimeServicesBuilder _servicesBuilder; @@ -31,15 +31,15 @@ public class MigrationsOperations public MigrationsOperations( [NotNull] ILoggerProvider loggerProvider, - [NotNull] string assemblyName, - [NotNull] string startupAssemblyName, + [NotNull] Assembly assembly, + [NotNull] Assembly startupAssembly, [CanBeNull] string environment, [NotNull] string projectDir, [NotNull] string rootNamespace) { Check.NotNull(loggerProvider, nameof(loggerProvider)); - Check.NotEmpty(assemblyName, nameof(assemblyName)); - Check.NotEmpty(startupAssemblyName, nameof(startupAssemblyName)); + Check.NotNull(assembly, nameof(assembly)); + Check.NotNull(startupAssembly, nameof(startupAssembly)); Check.NotNull(projectDir, nameof(projectDir)); Check.NotNull(rootNamespace, nameof(rootNamespace)); @@ -48,17 +48,17 @@ public MigrationsOperations( _loggerProvider = loggerProvider; _logger = new LazyRef(() => loggerFactory.CreateCommandsLogger()); - _assemblyName = assemblyName; + _assembly = assembly; _projectDir = projectDir; _rootNamespace = rootNamespace; _contextOperations = new DbContextOperations( loggerProvider, - assemblyName, - startupAssemblyName, + assembly, + startupAssembly, projectDir, environment); - var startup = new StartupInvoker(startupAssemblyName, environment); + var startup = new StartupInvoker(startupAssembly, environment); _servicesBuilder = new DesignTimeServicesBuilder(startup); } @@ -162,13 +162,16 @@ private void EnsureServices(IServiceProvider services) throw new OperationException(CommandsStrings.NonRelationalProvider(providerServices.InvariantName)); } + var assemblyName = _assembly.GetName(); var options = services.GetRequiredService(); var contextType = services.GetRequiredService().GetType(); - var assemblyName = RelationalOptionsExtension.Extract(options).MigrationsAssembly + var migrationsAssemblyName = RelationalOptionsExtension.Extract(options).MigrationsAssembly ?? contextType.GetTypeInfo().Assembly.GetName().Name; - if (_assemblyName != assemblyName) + if (assemblyName.Name != migrationsAssemblyName + && assemblyName.FullName != migrationsAssemblyName) { - throw new OperationException(CommandsStrings.MigrationsAssemblyMismatch(_assemblyName, assemblyName)); + throw new OperationException( + CommandsStrings.MigrationsAssemblyMismatch(assemblyName.Name, migrationsAssemblyName)); } } } diff --git a/src/Microsoft.EntityFrameworkCore.Commands/Design/OperationExecutor.cs b/src/Microsoft.EntityFrameworkCore.Commands/Design/OperationExecutor.cs index 026b8af9df2..0cfb5ac83eb 100644 --- a/src/Microsoft.EntityFrameworkCore.Commands/Design/OperationExecutor.cs +++ b/src/Microsoft.EntityFrameworkCore.Commands/Design/OperationExecutor.cs @@ -7,6 +7,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Internal; @@ -34,25 +35,37 @@ public OperationExecutor([NotNull] object logHandler, [NotNull] IDictionary args var projectDir = (string)args["projectDir"]; var rootNamespace = (string)args["rootNamespace"]; + var startupAssembly = Assembly.Load(new AssemblyName(startupTargetName)); + + Assembly assembly; + try + { + assembly = Assembly.Load(new AssemblyName(targetName)); + } + catch (Exception ex) + { + throw new OperationException(CommandsStrings.UnreferencedAssembly(targetName, startupTargetName), ex); + } + _contextOperations = new LazyRef( () => new DbContextOperations( loggerProvider, - targetName, - startupTargetName, + assembly, + startupAssembly, projectDir, environment)); _databaseOperations = new LazyRef( () => new DatabaseOperations( loggerProvider, - targetName, - startupTargetName, + assembly, + startupAssembly, environment, projectDir, rootNamespace)); _migrationsOperations = new LazyRef( () => new MigrationsOperations( loggerProvider, - targetName, - startupTargetName, + assembly, + startupAssembly, environment, projectDir, rootNamespace)); @@ -318,7 +331,7 @@ private IEnumerable ReverseEngineerImpl( public class ScaffoldRuntimeDirectives : OperationBase { - public ScaffoldRuntimeDirectives([NotNull] OperationExecutor executor, [NotNull] object resultHandler, [NotNull] IDictionary args) + public ScaffoldRuntimeDirectives([NotNull] OperationExecutor executor, [NotNull] object resultHandler, [NotNull] IDictionary args) : base(resultHandler) { Check.NotNull(executor, nameof(executor)); diff --git a/src/dotnet-ef/ConsoleCommandLogger.cs b/src/dotnet-ef/ConsoleCommandLogger.cs new file mode 100644 index 00000000000..8ee5b0fb656 --- /dev/null +++ b/src/dotnet-ef/ConsoleCommandLogger.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.Design.Internal; +using Microsoft.DotNet.Cli.Utils; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class ConsoleCommandLogger : CommandLogger + { + public ConsoleCommandLogger([NotNull] string name) + : base(name) + { + } + + protected override void WriteDebug(string message) + => Reporter.Verbose.WriteLine(message.Bold().Black()); + + protected override void WriteError(string message) + => Reporter.Error.WriteLine(message.Bold().Red()); + + protected override void WriteInformation(string message) + => Reporter.Error.WriteLine(message); + + protected override void WriteTrace(string message) + => Reporter.Verbose.WriteLine(message.Bold().Black()); + + protected override void WriteWarning(string message) + => Reporter.Error.WriteLine(message.Bold().Yellow()); + } +} diff --git a/src/dotnet-ef/DatabaseCommand.cs b/src/dotnet-ef/DatabaseCommand.cs new file mode 100644 index 00000000000..822c5193e78 --- /dev/null +++ b/src/dotnet-ef/DatabaseCommand.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class DatabaseCommand + { + public static void Configure([NotNull] CommandLineApplication command) + { + command.Description = "Commands to manage your database"; + + command.HelpOption("-h|--help"); + + command.Command("update", DatabaseUpdateCommand.Configure); + + command.OnExecute(() => command.ShowHelp()); + } + } +} diff --git a/src/dotnet-ef/DatabaseUpdateCommand.cs b/src/dotnet-ef/DatabaseUpdateCommand.cs new file mode 100644 index 00000000000..1451bc86f7d --- /dev/null +++ b/src/dotnet-ef/DatabaseUpdateCommand.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class DatabaseUpdateCommand + { + public static void Configure([NotNull] CommandLineApplication command) + { + command.Description = "Updates the database to a specified migration"; + + var migration = command.Argument( + "[migration]", + "The target migration. If '0', all migrations will be reverted. If omitted, all pending migrations will be applied"); + + var context = command.Option( + "-c|--context ", + "The DbContext to use. If omitted, the default DbContext is used"); + var startupProject = command.Option( + "-s|--startupProject ", + "The startup project to use. If omitted, the current project is used."); + var environment = command.Option( + "-e|--environment ", + "The environment to use. If omitted, \"Development\" is used."); + command.HelpOption("-h|--help"); + + command.OnExecute( + () => Execute(migration.Value, context.Value(), startupProject.Value(), environment.Value())); + } + + private static int Execute(string migration, string context, string startupProject, string environment) + { + new OperationsFactory(startupProject, environment) + .CreateMigrationsOperations() + .UpdateDatabase(migration, context); + + return 0; + } + } +} \ No newline at end of file diff --git a/src/dotnet-ef/DbContextCommand.cs b/src/dotnet-ef/DbContextCommand.cs new file mode 100644 index 00000000000..bca63a3d984 --- /dev/null +++ b/src/dotnet-ef/DbContextCommand.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class DbContextCommand + { + public static void Configure([NotNull] CommandLineApplication command) + { + command.Description = "Commands to manage your DbContext types"; + + command.HelpOption("-h|--help"); + + command.Command("list", DbContextListCommand.Configure); + command.Command("scaffold", DbContextScaffoldCommand.Configure); + + command.OnExecute(() => command.ShowHelp()); + } + } +} diff --git a/src/dotnet-ef/DbContextListCommand.cs b/src/dotnet-ef/DbContextListCommand.cs new file mode 100644 index 00000000000..c042d56bb18 --- /dev/null +++ b/src/dotnet-ef/DbContextListCommand.cs @@ -0,0 +1,92 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class DbContextListCommand + { + public static void Configure([NotNull] CommandLineApplication command) + { + command.Description = "List your DbContext types"; + + var startupProject = command.Option( + "-s|--startupProject ", + "The startup project to use. If omitted, the current project is used."); + var environment = command.Option( + "-e|--environment ", + "The environment to use. If omitted, \"Development\" is used."); + var json = command.Option( + "--json", + "Use json output"); + command.HelpOption("-h|--help"); + + command.OnExecute( + () => Execute( + startupProject.Value(), + environment.Value(), + json.HasValue() + ? (Action>)ReportJsonResults + : ReportResults) + ); + } + + private static int Execute( + string startupProject, + string environment, + Action> reportResultsAction) + { + var contextTypes = new OperationsFactory(startupProject, environment) + .CreateDbContextOperations() + .GetContextTypes(); + + reportResultsAction(contextTypes); + + return 0; + } + + private static void ReportJsonResults(IEnumerable contextTypes) + { + Reporter.Output.Write("["); + + var first = true; + foreach (var contextType in contextTypes) + { + if (first) + { + first = false; + } + else + { + Reporter.Output.Write(","); + } + + Reporter.Output.WriteLine(); + Reporter.Output.Write(" { \"fullName\": \"" + contextType.FullName + "\" }"); + } + + Reporter.Output.WriteLine(); + Reporter.Output.WriteLine("]"); + } + + private static void ReportResults(IEnumerable contextTypes) + { + var any = false; + foreach (var contextType in contextTypes) + { + Reporter.Output.WriteLine(contextType.FullName); + any = true; + } + + if (!any) + { + Reporter.Error.WriteLine("No DbContext was found"); + } + } + } +} \ No newline at end of file diff --git a/src/dotnet-ef/DbContextScaffoldCommand.cs b/src/dotnet-ef/DbContextScaffoldCommand.cs new file mode 100644 index 00000000000..8e41ae3b8eb --- /dev/null +++ b/src/dotnet-ef/DbContextScaffoldCommand.cs @@ -0,0 +1,116 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class DbContextScaffoldCommand + { + public static void Configure([NotNull] CommandLineApplication command) + { + command.Description = "Scaffolds a DbContext and entity type classes for a specified database"; + + var connection = command.Argument( + "[connection]", + "The connection string of the database"); + var provider = command.Argument( + "[provider]", + "The provider to use. For example, EntityFramework.MicrosoftSqlServer"); + + var dataAnnotations = command.Option( + "-a|--data-annotations", + "Use DataAnnotation attributes to configure the model where possible. If omitted, the output code will use only the fluent API.", + CommandOptionType.NoValue); + var context = command.Option( + "-c|--context ", + "Name of the generated DbContext class."); + var force = command.Option( + "-f|--force", + "Force scaffolding to overwrite existing files. Otherwise, the code will only proceed if no output files would be overwritten.", + CommandOptionType.NoValue); + var outputDir = command.Option( + "-o|--output-dir ", + "Directory of the project where the classes should be output. If omitted, the top-level project directory is used."); + var schemas = command.Option( + "-s|--schema ", + "Selects a schema for which to generate classes.", + CommandOptionType.MultipleValue); + var tables = command.Option( + "-t|--table ", + "Selects a table for which to generate classes.", + CommandOptionType.MultipleValue); + var startupProject = command.Option( + "-s|--startupProject ", + "The startup project to use. If omitted, the current project is used."); + var environment = command.Option( + "-e|--environment ", + "The environment to use. If omitted, \"Development\" is used."); + command.HelpOption("-h|--help"); + + command.OnExecute( + () => + { + if (string.IsNullOrEmpty(connection.Value)) + { + Reporter.Error.WriteLine(("Missing required argument '" + connection.Name + "'").Bold().Red()); + command.ShowHelp(); + + return Task.FromResult(1); + } + if (string.IsNullOrEmpty(provider.Value)) + { + Reporter.Error.WriteLine(("Missing required argument '" + provider.Name + "'").Bold().Red()); + command.ShowHelp(); + + return Task.FromResult(1); + } + + return ExecuteAsync( + connection.Value, + provider.Value, + dataAnnotations.HasValue(), + context.Value(), + force.HasValue(), + outputDir.Value(), + schemas.Values, + tables.Values, + startupProject.Value(), + environment.Value()); + }); + } + + private static async Task ExecuteAsync( + string connection, + string provider, + bool dataAnnotations, + string context, + bool force, + string outputDir, + List schemas, + List tables, + string startupProject, + string environment) + { + await new OperationsFactory(startupProject, environment) + .CreateDatabaseOperations() + .ReverseEngineerAsync( + provider, + connection, + outputDir, + context, + schemas, + tables, + dataAnnotations, + force); + + Reporter.Error.WriteLine("Done"); + + return 0; + } + } +} \ No newline at end of file diff --git a/src/dotnet-ef/Extensions/CommandLineApplicationExtensions.cs b/src/dotnet-ef/Extensions/CommandLineApplicationExtensions.cs new file mode 100644 index 00000000000..ee678f56272 --- /dev/null +++ b/src/dotnet-ef/Extensions/CommandLineApplicationExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal static class CommandLineApplicationExtensions + { + public static void OnExecute(this CommandLineApplication command, Action invoke) + => command.OnExecute( + () => + { + invoke(); + + return 0; + }); + + public static CommandOption Option(this CommandLineApplication command, string template, string description) + => command.Option( + template, + description, + template.IndexOf('<') != -1 + ? CommandOptionType.SingleValue + : CommandOptionType.NoValue); + } +} diff --git a/src/dotnet-ef/MigrationsAddCommand.cs b/src/dotnet-ef/MigrationsAddCommand.cs new file mode 100644 index 00000000000..8db5f71677d --- /dev/null +++ b/src/dotnet-ef/MigrationsAddCommand.cs @@ -0,0 +1,68 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class MigrationsAddCommand + { + public static void Configure([NotNull] CommandLineApplication command) + { + command.Description = "Add a new migration"; + + var name = command.Argument("[name]", "The name of the migration"); + + var outputDir = command.Option( + "-o|--output-dir ", + "The directory (and sub-namespace) to use. If omitted, \"Migrations\" is used."); + var context = command.Option( + "-c|--context ", + "The DbContext to use. If omitted, the default DbContext is used"); + var startupProject = command.Option( + "-s|--startupProject ", + "The startup project to use. If omitted, the current project is used."); + var environment = command.Option( + "-e|--environment ", + "The environment to use. If omitted, \"Development\" is used."); + command.HelpOption("-h|--help"); + + command.OnExecute( + () => + { + if (string.IsNullOrEmpty(name.Value)) + { + Reporter.Error.WriteLine(("Missing required argument '" + name.Name + "'").Bold().Red()); + command.ShowHelp(); + + return 1; + } + + return Execute( + name.Value, + outputDir.Value(), + context.Value(), + startupProject.Value(), + environment.Value()); + }); + } + + private static int Execute( + string name, + string outputDir, + string context, + string startupProject, + string environment) + { + new OperationsFactory(startupProject, environment) + .CreateMigrationsOperations() + .AddMigration(name, outputDir, context); + + Reporter.Error.WriteLine("Done. To undo this action, use 'ef migrations remove'"); + + return 0; + } + } +} diff --git a/src/dotnet-ef/MigrationsCommand.cs b/src/dotnet-ef/MigrationsCommand.cs new file mode 100644 index 00000000000..2ac08e90980 --- /dev/null +++ b/src/dotnet-ef/MigrationsCommand.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class MigrationsCommand + { + public static void Configure([NotNull] CommandLineApplication command) + { + command.Description = "Commands to manage your migrations"; + + command.HelpOption("-h|--help"); + + command.Command("add", MigrationsAddCommand.Configure); + command.Command("list", MigrationsListCommand.Configure); + command.Command("remove", MigrationsRemoveCommand.Configure); + command.Command("script", MigrationsScriptCommand.Configure); + + command.OnExecute(() => command.ShowHelp()); + } + } +} diff --git a/src/dotnet-ef/MigrationsListCommand.cs b/src/dotnet-ef/MigrationsListCommand.cs new file mode 100644 index 00000000000..c7c1c2ab082 --- /dev/null +++ b/src/dotnet-ef/MigrationsListCommand.cs @@ -0,0 +1,100 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.CommandLineUtils; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class MigrationsListCommand + { + public static void Configure([NotNull] CommandLineApplication command) + { + command.Description = "List the migrations"; + + var context = command.Option( + "-c|--context ", + "The DbContext to use. If omitted, the default DbContext is used"); + var startupProject = command.Option( + "-s|--startupProject ", + "The startup project to use. If omitted, the current project is used."); + var environment = command.Option( + "-e|--environment ", + "The environment to use. If omitted, \"Development\" is used."); + var json = command.Option( + "--json", + "Use json output"); + command.HelpOption("-h|--help"); + + command.OnExecute( + () => Execute( + context.Value(), + startupProject.Value(), + environment.Value(), + json.HasValue() + ? (Action>)ReportJsonResults + : ReportResults)); + } + + private static int Execute( + string context, + string startupProject, + string environment, + Action> reportResultsAction) + { + var migrations = new OperationsFactory(startupProject, environment) + .CreateMigrationsOperations() + .GetMigrations(context); + + reportResultsAction(migrations); + + return 0; + } + + private static void ReportJsonResults(IEnumerable migrations) + { + Reporter.Output.Write("["); + + var first = true; + foreach (var migration in migrations) + { + if (first) + { + first = false; + } + else + { + Reporter.Output.Write(","); + } + + Reporter.Output.WriteLine(); + Reporter.Output.WriteLine(" {"); + Reporter.Output.WriteLine(" \"id\": \"" + migration.Id + "\","); + Reporter.Output.WriteLine(" \"name\": \"" + migration.Name + "\""); + Reporter.Output.Write(" }"); + } + + Reporter.Output.WriteLine(); + Reporter.Output.WriteLine("]"); + } + + private static void ReportResults(IEnumerable migrations) + { + var any = false; + foreach (var migration in migrations) + { + Reporter.Output.WriteLine(migration.Id); + any = true; + } + + if (!any) + { + Reporter.Error.WriteLine("No migrations were found"); + } + } + } +} \ No newline at end of file diff --git a/src/dotnet-ef/MigrationsRemoveCommand.cs b/src/dotnet-ef/MigrationsRemoveCommand.cs new file mode 100644 index 00000000000..d52804f609c --- /dev/null +++ b/src/dotnet-ef/MigrationsRemoveCommand.cs @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class MigrationsRemoveCommand + { + public static void Configure([NotNull] CommandLineApplication command) + { + command.Description = "Remove the last migration"; + + var context = command.Option( + "-c|--context ", + "The DbContext to use. If omitted, the default DbContext is used"); + var startupProject = command.Option( + "-s|--startupProject ", + "The startup project to use. If omitted, the current project is used."); + var environment = command.Option( + "-e|--environment ", + "The environment to use. If omitted, \"Development\" is used."); + command.HelpOption("-h|--help"); + + command.OnExecute( + () => Execute( + context.Value(), + startupProject.Value(), + environment.Value())); + } + + private static int Execute(string context, string startupProject, string environment) + { + new OperationsFactory(startupProject, environment) + .CreateMigrationsOperations() + .RemoveMigration(context); + + return 0; + } + } +} \ No newline at end of file diff --git a/src/dotnet-ef/MigrationsScriptCommand.cs b/src/dotnet-ef/MigrationsScriptCommand.cs new file mode 100644 index 00000000000..577db31a3c5 --- /dev/null +++ b/src/dotnet-ef/MigrationsScriptCommand.cs @@ -0,0 +1,80 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using JetBrains.Annotations; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.CommandLineUtils; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class MigrationsScriptCommand + { + public static void Configure([NotNull] CommandLineApplication command) + { + command.Description = "Generate a SQL script from migrations"; + + var from = command.Argument( + "[from]", + "The starting migration. If omitted, '0' (the initial database) is used"); + var to = command.Argument( + "[to]", + "The ending migration. If omitted, the last migration is used"); + + var output = command.Option( + "-o|--output ", + "The file to write the script to instead of stdout"); + var idempotent = command.Option( + "-i|--idempotent", + "Generates an idempotent script that can used on a database at any migration"); + var context = command.Option( + "-c|--context ", + "The DbContext to use. If omitted, the default DbContext is used"); + var startupProject = command.Option( + "-s|--startupProject ", + "The startup project to use. If omitted, the current project is used."); + var environment = command.Option( + "-e|--environment ", + "The environment to use. If omitted, \"Development\" is used."); + command.HelpOption("-h|--help"); + + command.OnExecute( + () => Execute( + from.Value, + to.Value, + output.Value(), + idempotent.HasValue(), + context.Value(), + startupProject.Value(), + environment.Value())); + } + + private static int Execute( + string from, + string to, + string output, + bool idempotent, + string context, + string startupProject, + string environment) + { + var sql = new OperationsFactory(startupProject, environment) + .CreateMigrationsOperations() + .ScriptMigration(from, to, idempotent, context); + + if (string.IsNullOrEmpty(output)) + { + Reporter.Output.WriteLine(sql); + } + else + { + Reporter.Verbose.WriteLine("Writing SQL script to '" + output + "'".Bold().Black()); + File.WriteAllText(output, sql); + + Reporter.Error.WriteLine("Done"); + } + + return 0; + } + } +} \ No newline at end of file diff --git a/src/dotnet-ef/OperationsFactory.cs b/src/dotnet-ef/OperationsFactory.cs new file mode 100644 index 00000000000..b4d61e4797e --- /dev/null +++ b/src/dotnet-ef/OperationsFactory.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Design.Internal; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Loader; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class OperationsFactory + { + private readonly Assembly _assembly; + private readonly Assembly _startupAssembly; + private readonly string _environment; + private readonly string _projectDir; + private readonly string _rootNamespace; + + public OperationsFactory([CanBeNull] string startupProject, [CanBeNull] string environment) + { + var project = Directory.GetCurrentDirectory(); + startupProject = startupProject ?? project; + + var startupProjectContext = ProjectContext.CreateContextForEachFramework(startupProject).First(); + var projectContext = ProjectContext.CreateContextForEachFramework(project).First(); + var startupAssemblyName = new AssemblyName(startupProjectContext.ProjectFile.Name); + var assemblyName = new AssemblyName(projectContext.ProjectFile.Name); + var assemblyLoadContext = startupProjectContext.CreateLoadContext(); + + _startupAssembly = assemblyLoadContext.LoadFromAssemblyName(startupAssemblyName); + + try + { + _assembly = assemblyLoadContext.LoadFromAssemblyName(assemblyName); + } + catch (Exception ex) + { + throw new OperationException( + CommandsStrings.UnreferencedAssembly( + projectContext.ProjectFile.Name, + startupProjectContext.ProjectFile.Name), + ex); + } + _environment = environment; + _projectDir = projectContext.ProjectDirectory; + _rootNamespace = projectContext.ProjectFile.Name; + } + + public virtual DatabaseOperations CreateDatabaseOperations() + => new DatabaseOperations( + new LoggerProvider(name => new ConsoleCommandLogger(name)), + _assembly, + _startupAssembly, + _environment, + _projectDir, + _rootNamespace); + + public virtual DbContextOperations CreateDbContextOperations() + => new DbContextOperations( + new LoggerProvider(name => new ConsoleCommandLogger(name)), + _assembly, + _startupAssembly, + _projectDir, + _environment); + + public virtual MigrationsOperations CreateMigrationsOperations() + => new MigrationsOperations( + new LoggerProvider(name => new ConsoleCommandLogger(name)), + _assembly, + _startupAssembly, + _environment, + _projectDir, + _rootNamespace); + } + + +} diff --git a/src/dotnet-ef/Program.cs b/src/dotnet-ef/Program.cs new file mode 100644 index 00000000000..8735583a2af --- /dev/null +++ b/src/dotnet-ef/Program.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.CommandLineUtils; +using JetBrains.Annotations; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class Program + { + public static int Main([NotNull] string[] args) + { + DebugHelper.HandleDebugSwitch(ref args); + + var app = new CommandLineApplication + { + Name = "dotnet ef", + FullName = "Entity Framework Core Commands" + }; + + app.HelpOption("-h|--help"); + app.VersionOption("--version", GetVersion); + + app.Command("database", DatabaseCommand.Configure); + app.Command("dbcontext", DbContextCommand.Configure); + app.Command("migrations", MigrationsCommand.Configure); + + app.OnExecute( + () => + { + WriteLogo(); + app.ShowHelp(); + }); + + + int result; + try + { + result = app.Execute(args); + } + catch (Exception ex) + { + if (ex is TargetInvocationException) + { + ex = ex.InnerException; + } + + if (!(ex is OperationException)) + { + Reporter.Verbose.WriteLine(ex.ToString().Bold().Black()); + } + + Reporter.Error.WriteLine(ex.Message.Bold().Red()); + result = 1; + } + + return result; + } + + private static string GetVersion() + => typeof(Program).GetTypeInfo().Assembly.GetCustomAttribute() + .InformationalVersion; + + private static void WriteLogo() + { + const string Bold = "\x1b[1m"; + const string Normal = "\x1b[22m"; + const string Magenta = "\x1b[35m"; + const string White = "\x1b[37m"; + const string Default = "\x1b[39m"; + + Reporter.Output.WriteLine(); + Reporter.Output.WriteLine(@" _/\__ ".Insert(21, Bold + White)); + Reporter.Output.WriteLine(@" ---==/ \\ "); + Reporter.Output.WriteLine(@" ___ ___ |. \|\ ".Insert(26, Bold).Insert(21, Normal).Insert(20, Bold + White).Insert(9, Normal + Magenta)); + Reporter.Output.WriteLine(@" | __|| __| | ) \\\ ".Insert(20, Bold + White).Insert(8, Normal + Magenta)); + Reporter.Output.WriteLine(@" | _| | _| \_/ | //|\\ ".Insert(20, Bold + White).Insert(8, Normal + Magenta)); + Reporter.Output.WriteLine(@" |___||_| / \\\/\\".Insert(33, Normal + Default).Insert(23, Bold + White).Insert(8, Normal + Magenta)); + Reporter.Output.WriteLine(); + } + } +} diff --git a/src/dotnet-ef/Properties/AssemblyInfo.cs b/src/dotnet-ef/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..381ac229e82 --- /dev/null +++ b/src/dotnet-ef/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; +using System.Resources; + +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: AssemblyMetadata("Serviceable", "True")] diff --git a/src/dotnet-ef/Properties/InternalsVisibleTo.cs b/src/dotnet-ef/Properties/InternalsVisibleTo.cs new file mode 100644 index 00000000000..321648d206b --- /dev/null +++ b/src/dotnet-ef/Properties/InternalsVisibleTo.cs @@ -0,0 +1,6 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("dotnet-ef.Tests")] diff --git a/src/dotnet-ef/dotnet-ef.xproj b/src/dotnet-ef/dotnet-ef.xproj new file mode 100644 index 00000000000..38dfdf4b8e7 --- /dev/null +++ b/src/dotnet-ef/dotnet-ef.xproj @@ -0,0 +1,18 @@ + + + + 14.0.24720 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 5c1b3280-f11d-4e4e-81b3-bcda4942e097 + Microsoft.EntityFrameworkCore.Commands + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + \ No newline at end of file diff --git a/src/dotnet-ef/project.json b/src/dotnet-ef/project.json new file mode 100644 index 00000000000..b2672aa1781 --- /dev/null +++ b/src/dotnet-ef/project.json @@ -0,0 +1,24 @@ +{ + "version": "1.0.0-*", + "description": "Entity Framework Core Commands for .NET Core CLI.", + "compilationOptions": { + "warningsAsErrors": true, + "emitEntryPoint": true + }, + "compile": "..\\Shared\\*.cs", + "dependencies": { + "Microsoft.DotNet.Cli.Utils": "1.0.0-*", + "Microsoft.DotNet.ProjectModel.Loader": "1.0.0-*", + "Microsoft.EntityFrameworkCore.Commands": "1.0.0-*", + "Microsoft.Extensions.CommandLineUtils": "1.0.0-*" + }, + "frameworks": { + "dnxcore50": { + "imports": "portable-net452+win81", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1-*", + "System.Security.Cryptography.Algorithms": "4.0.0-*" + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.EntityFrameworkCore.Commands.Tests/Design/Internal/StartupInvokerTest.cs b/test/Microsoft.EntityFrameworkCore.Commands.Tests/Design/Internal/StartupInvokerTest.cs index df58e3a2009..14103cac0f6 100644 --- a/test/Microsoft.EntityFrameworkCore.Commands.Tests/Design/Internal/StartupInvokerTest.cs +++ b/test/Microsoft.EntityFrameworkCore.Commands.Tests/Design/Internal/StartupInvokerTest.cs @@ -15,7 +15,7 @@ public void ConfigureDesignTimeServices_uses_Development_environment_when_unspec { var services = new ServiceCollection(); var startup = new StartupInvoker( - typeof(StartupInvokerTest).GetTypeInfo().Assembly.FullName, + typeof(StartupInvokerTest).GetTypeInfo().Assembly, environment: null); startup.ConfigureDesignTimeServices(services); @@ -29,7 +29,7 @@ public void ConfigureDesignTimeServices_invokes_static_methods() { var services = new ServiceCollection(); var startup = new StartupInvoker( - typeof(StartupInvokerTest).GetTypeInfo().Assembly.FullName, + typeof(StartupInvokerTest).GetTypeInfo().Assembly, "Static"); startup.ConfigureDesignTimeServices(services); @@ -42,7 +42,7 @@ public void ConfigureDesignTimeServices_invokes_static_methods() public void ConfigureDesignTimeServices_is_noop_when_not_found() { var startup = new StartupInvoker( - typeof(StartupInvokerTest).GetTypeInfo().Assembly.FullName, + typeof(StartupInvokerTest).GetTypeInfo().Assembly, environment: "Unknown"); startup.ConfigureDesignTimeServices(new ServiceCollection()); @@ -52,7 +52,7 @@ public void ConfigureDesignTimeServices_is_noop_when_not_found() public void ConfigureServices_uses_Development_environment_when_unspecified() { var startup = new StartupInvoker( - typeof(StartupInvokerTest).GetTypeInfo().Assembly.FullName, + typeof(StartupInvokerTest).GetTypeInfo().Assembly, environment: null); var services = startup.ConfigureServices(); @@ -65,7 +65,7 @@ public void ConfigureServices_uses_Development_environment_when_unspecified() public void ConfigureServices_is_noop_when_not_found() { var startup = new StartupInvoker( - typeof(StartupInvokerTest).GetTypeInfo().Assembly.FullName, + typeof(StartupInvokerTest).GetTypeInfo().Assembly, environment: "Unknown"); var services = startup.ConfigureServices(); @@ -77,7 +77,7 @@ public void ConfigureServices_is_noop_when_not_found() public void ConfigureServices_invokes_static_methods() { var startup = new StartupInvoker( - typeof(StartupInvokerTest).GetTypeInfo().Assembly.FullName, + typeof(StartupInvokerTest).GetTypeInfo().Assembly, "Static"); var services = startup.ConfigureServices(); @@ -90,7 +90,7 @@ public void ConfigureServices_invokes_static_methods() public void ConfigureServices_invokes_method_with_alternative_signature() { var startup = new StartupInvoker( - typeof(StartupInvokerTest).GetTypeInfo().Assembly.FullName, + typeof(StartupInvokerTest).GetTypeInfo().Assembly, "Alternative"); var services = startup.ConfigureServices(); @@ -104,7 +104,7 @@ public void ConfigureDesignTimeServices_works_on_other_types() { var services = new ServiceCollection(); var startup = new StartupInvoker( - typeof(StartupInvokerTest).GetTypeInfo().Assembly.FullName, + typeof(StartupInvokerTest).GetTypeInfo().Assembly, environment: null); startup.ConfigureDesignTimeServices(typeof(NotStartup), services); diff --git a/test/dotnet-ef.Tests/ApiConsistencyTest.cs b/test/dotnet-ef.Tests/ApiConsistencyTest.cs new file mode 100644 index 00000000000..f5688ce3772 --- /dev/null +++ b/test/dotnet-ef.Tests/ApiConsistencyTest.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; + +namespace Microsoft.EntityFrameworkCore.Commands +{ + public class ApiConsistencyTest : ApiConsistencyTestBase + { + protected override Assembly TargetAssembly => typeof(Program).GetTypeInfo().Assembly; + } +} diff --git a/test/dotnet-ef.Tests/dotnet-ef.Tests.xproj b/test/dotnet-ef.Tests/dotnet-ef.Tests.xproj new file mode 100644 index 00000000000..13eab538961 --- /dev/null +++ b/test/dotnet-ef.Tests/dotnet-ef.Tests.xproj @@ -0,0 +1,18 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 93d7ab4e-2e04-4217-bbe5-43ec0d54344f + Microsoft.EntityFrameworkCore.Commands + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + \ No newline at end of file diff --git a/test/dotnet-ef.Tests/project.json b/test/dotnet-ef.Tests/project.json new file mode 100644 index 00000000000..6838ddbeba8 --- /dev/null +++ b/test/dotnet-ef.Tests/project.json @@ -0,0 +1,18 @@ +{ + "dependencies": { + "dotnet-ef": "1.0.0-*", + "Microsoft.EntityFrameworkCore.Commands.Tests": "1.0.0" + }, + "testRunner": "xunit", + "commands": { + "test": "xunit.runner.aspnet" + }, + "frameworks": { + "dnxcore50": { + "imports": "portable-net452+win81", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1-*" + } + } + } +}