Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tools: Flow arguments into IDesignTimeDbContextFactory #8332

Closed
bricelam opened this issue Apr 29, 2017 · 74 comments
Closed

Tools: Flow arguments into IDesignTimeDbContextFactory #8332

bricelam opened this issue Apr 29, 2017 · 74 comments

Comments

@bricelam
Copy link
Member

@bricelam bricelam commented Apr 29, 2017

We've enabled the ability for arguments to be passed into the Create method (via a string[] args parameter), but we need to think through the design of how they get specified on the command line.

For dotnet ef and ef, I think it's obvious: any unknown argument will be passed verbatim.

dotnet ef database update --connection-string "Data Source=prod.db"

It gets a little more complicated on PowerShell where the paradigm is so different. Right now, the easiest way I can think of is to use a string array parameter.

Update-Database -AppArgs '--connection-string', 'Data Source=prod.db'
@bricelam
Copy link
Member Author

@bricelam bricelam commented Apr 29, 2017

Another harebrained idea I had was to apply a convention to the unknown arguments. For example, -ConnectionString would magically be translated to --connection-string, but this falls down if someone tries to take advantage of PowerShell's case-insensitivity -connectionstring or ability to under-specify -Conn.

@ajcvickers ajcvickers added this to the 2.0.0 milestone May 1, 2017
@divega
Copy link

@divega divega commented May 1, 2017

@bricelam would something like this work for PowerShell?

Update-Database -CommandLineArgs "--connection-string 'Data Source=prod.db'"

Ideally I want to be able to copy and paste command line arguments I use for invoking the application into one argument of the PowerShell command and only make minimal edits.

@bricelam
Copy link
Member Author

@bricelam bricelam commented May 1, 2017

Yep. That would work too.

@bricelam
Copy link
Member Author

@bricelam bricelam commented May 1, 2017

(assuming you swapped the apostrophes and quotes)

@bricelam
Copy link
Member Author

@bricelam bricelam commented May 1, 2017

You'd also need to handle argument quoting and escaping yourself. Whereas with the array, we could handle that for you.

@bricelam bricelam modified the milestones: 2.0.0, 2.0.0-preview2 May 16, 2017
@bricelam bricelam modified the milestones: Backlog, 2.0.0 Jun 2, 2017
@pjmagee
Copy link

@pjmagee pjmagee commented Oct 6, 2017

Need this now :) Kind of relates directly to my question on SO: https://stackoverflow.com/questions/46583136/how-to-run-dotnet-ef-commands-to-target-a-specific-appsettings-environment-j#comment80159150_46583136

@bricelam , can I pass arguments and have this work with the current released version?

@bricelam
Copy link
Member Author

@bricelam bricelam commented Oct 6, 2017

No, this has not been implemented. You could read environment variables, a file, etc. inside IDesignTimeDbContextFactory.

@bricelam
Copy link
Member Author

@bricelam bricelam commented Jan 30, 2018

Note to implementer, we should allow them to be specified with DbContextActivator.CreateInstance() too.

@dkehring
Copy link

@dkehring dkehring commented Apr 24, 2018

Let me give you a use case that I think highlights why tackling this issue is important. When developing databases, I typically have a "development" database that my code-in-development uses. I also have a complementary "test" database that is used by my integration tests. This is separate from the "development" database so as not to interfere with that atomic nature of the integration tests (I run all tests in an ambient transaction, ensuring any database changes are rolled back after each test - that's another issue). In the past, I've used Fluent Migrations to keep all of my databases in sync (dev, test, staging, production) and was able to execute the database update by passing in an argument that specifies the connection string to pull from the app.config file in the migration project. Now that I'm trying to use EF Core 2.0 and code-first migrations, I can't find a way to specify the connection string to use when executing "dotnet ef database update". I created an implementation of the IDesignTimeDbContextFactory interface, but the args in the constructor are not used. Would be nice to be able to pass in the argument in the "update-database" command to indicate the connection string.

@Korigoth
Copy link

@Korigoth Korigoth commented May 27, 2018

For my use case i need to specify the connection string name to the command.

Do we have an ETA on when it will be implemented?

@ajcvickers
Copy link
Member

@ajcvickers ajcvickers commented May 29, 2018

@dkehring @Korigoth The typical way to do this is to read a file, an environment variable, or similar inside IDesignTimeDbContextFactory. Does this work for you? If not, can you explain a bit more why?

@Korigoth
Copy link

@Korigoth Korigoth commented May 29, 2018

@ajcvickers i did it with an environment variable for now and it's working as expected, so i've created different batch file for all the jobs we need.

but thanks for the reply, may be it's not documented enough on the document websites?

@dkehring
Copy link

@dkehring dkehring commented May 29, 2018

@ajcvickers I need to be able to target a specific database on the command line. Reading from a file or environment variable won't work. Let's say I have a file that contains the connection strings for the dev and test databases. If I read from this file, how do I select the connection string I need? What I'd like to be able to do is something like this:

C:> dotnet ef database update "test"
... or ...
C:> dotnet ef database update "dev"

Then in my implementation of IDesignTimeDbContextFactory, I can read a local config file and use the argument string passed in ("test", "dev", etc.) to select the correct connection string based on that key. I need something in IDesignTimeDbContextFactory that can affect the logic of the connection string selection. Hope that clarifies. I'm not sure what you're doing in your suggestion about environment variables. Does your batch job change the environment variable before the database update is executed?

@Korigoth
Copy link

@Korigoth Korigoth commented May 29, 2018

@dkehring Exactly, i do set ConnStringName=YourConnectionStringNameNeededForTheJob then i do dotnet ef database update or anything required. (in cmd)

Here is the piece of code i use:

    public EspaceBiereContext CreateDbContext(string[] args)
    {
        var appSettingsFinder = new DefaultAppSettingsFinder();
        var path = appSettingsFinder.FindPath<DbContextFactory>("EspaceBiere.sln", "EspaceBiere.Web");
        var config = new ConfigBuilder()
            .SetBasePath(path)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables()
            .Build();

        var connectionStringName = GetEnvironmentVariable("ConnName", ConnNameError);
        var connectionString = config.GetConnectionString(connectionStringName);
        var builder = new DbContextOptionsBuilder<EspaceBiereContext>();
        builder.UseSqlServer(connectionString);

        return new EspaceBiereContext(builder.Options);
    }

    private string GetEnvironmentVariable(string name, string errorMessage)
    {
        var connectionStringName = Environment.GetEnvironmentVariable(name);

        if (string.IsNullOrWhiteSpace(connectionStringName))
        {
            throw new Exception(errorMessage);
        }

        return connectionStringName;
    }
@dkehring
Copy link

@dkehring dkehring commented May 30, 2018

@ajcvickers Thanks for sharing. This is an interesting hack, but hardly an optimal solution. In my opinion, environment variables define the local machine environment and are - for the most part - fairly static. Sort of like the old machine.config file. Your solution works, but it's a work-around due to an incomplete implementation of IDesignTimeDbContextFactory. In fact, it's interesting that the input paramater "args" on IDesignTimeDbContextFactory.CreateDbContext is marked with the NotNull attribute.

image

@lajones
Copy link
Contributor

@lajones lajones commented Jun 10, 2020

you're saying that a -Connection appearing after the -- would still be interpreted by Scaffold-DbContext, right?

Right. Basically if you pass in a mixture of known and unknown args/options, the known ones will be assigned by Powershell to the matching parameter, the unknown ones, wherever they were on the command-line, will be captured using a [ValueFromRemainingArguments]-parameter which is a string[]. We can then look at that string[] and pass on whatever we feel is appropriate, or error.

@lajones
Copy link
Contributor

@lajones lajones commented Jun 10, 2020

Decisions from Design Meeting 6/10/2020.

Command-Line
We will use update the command-line code to use -- and fix the bugs as outlined above.

Powershell
We will use the -Arguments approach outlined above except it will be called -Args and will take a single string parameter - which means multiple arguments would need to be quoted:

Scaffold-DbContext -Connection xyz -Provider abc -Args 'FirstArg SecondArg ThirdArg'

For Powershell, the CmdLet just collects all the arguments and calls the command-line equivalent. Whatever is in the string argument to -Args will be passed in as the argument of the -- command-line option without interpretation.

@lajones
Copy link
Contributor

@lajones lajones commented Jun 16, 2020

The approach above was checked in on June 16, 2020.

For the purposes of the explanation below "normal arguments" are arguments or options that a particular command understands, which influence the way that command does its work, e.g. the name of the migration to create when calling migrations add or Add-Migration is a "normal argument"; so is --output-dir or -OutputDir.

"Application arguments", on the other hand, are any arguments which the command should pass on to whatever application is involved (usually one that creates and/or uses a DbContext class referred to by the command).

Command-Line
The commands will no longer interpret any extraneous arguments as application arguments. Instead, if you want to call a dotnet ef command with application arguments, you must now put -- after you pass in all the normal arguments, followed by whatever you want to the application arguments to be. All arguments after the -- will be interpreted as application arguments. If you need to pass in an application argument which, for instance, contains a space you will need to quote that argument e.g.

dotnet ef migrations add InitialCreate -- FirstAppArg "This is all the second application argument" ThirdAppArg

Powershell
Similarly the Powershell commands will no longer interpret any extraneous arguments as arguments to be passed to the application. Instead you need to add a -Args argument to the command. This will take a single string parameter which can represent as many application arguments as you wish.

The single string parameter means a couple of differences compared to the command-line.

  1. If you wish to pass multiple application arguments you must quote the whole set of application arguments so that it looks like a single string argument to Powershell (see here for a description of quoting in Powershell). E.g.:
Add-Migration InitialCreate -Args 'FirstAppArg SecondAppArg'
  1. Once Powershell has interpreted the quotes (if any), the single string argument is appended (after --) to the underlying dotnet ef command including any remaining quotes. At that point the single string will be interpreted as if you had passed it in on the command-line, so if you e.g. want to pass in an argument containing spaces you will need to ensure that what is passed to the dotnet ef command contains the second set of quotes necessary for it to understand that the argument containing the space is one argument. E.g.:
Add-Migration InitialCreate -Args 'FirstAppArg "This is all the second application argument" ThirdAppArg'
  1. In distinction to the dotnet ef command, passing further arguments after the single string argument which -Args uses will cause an error: E.g. this will cause an error:
Add-Migration InitialCreate -Args FirstAppArg MistakenAppArg

because the Add-Migration command interprets FirstAppArg as all of the application arguments and will try to interpret MistakenAppArg as a normal argument. This will fail because Add-Migration has no MistakenAppArg argument.

@HugoPeters1024
Copy link

@HugoPeters1024 HugoPeters1024 commented Jun 22, 2020

@lajones Have these changes been deployed yet? I've just updated to 3.1.5 and the following command dotnet ef database update -- production is producing the following error:

Build started...
Build succeeded.
Specify --help for a list of available options and commands.
Unrecognized option '--'

For completeness sake, I am using a context factory and was hoping to swap out connection strings based on the command line args.

@lajones
Copy link
Contributor

@lajones lajones commented Jun 22, 2020

@HugoPeters1024 I'm sorry but these changes were checked in to the 5.0 release rather than the 3.1 release, specifically they will be available in 5.0 preview 7.

@HugoPeters1024
Copy link

@HugoPeters1024 HugoPeters1024 commented Jun 22, 2020

@lajones Oh that's my bad. Not sure how but I somehow failed to see that. I'll be looking forward to it then, cheers ;)

@lajones
Copy link
Contributor

@lajones lajones commented Jun 22, 2020

@HugoPeters1024 No problem. When it is available let us know if there are any problems. Cheers.

@ismailkocacan
Copy link

@ismailkocacan ismailkocacan commented Jul 19, 2020

I can pass -AppArgs parameters with Add-Migration command. It Works. Nice.
Add-Migration InitialCreate -Context MasterDbContext -OutputDir Migrations\Master -AppArgs 'P1' 'P2'

I also check it, with this -Migration parameter it works.
Update-Database -Migration 20200718203431_InitialCreate -Context MasterDbContext -AppArgs 'P1' 'P2'

But I can't pass with Update-Database command.
Update-Database -Context MasterDbContext -AppArgs 'P1' 'P2'

I don't see all args parameters.
Have you any suggestion ?

@ajcvickers ajcvickers removed this from the 5.0.0-preview7 milestone Jul 20, 2020
@ajcvickers ajcvickers reopened this Jul 22, 2020
@ajcvickers
Copy link
Member

@ajcvickers ajcvickers commented Jul 24, 2020

@ismailkocacan I tried this with the preview 7 tools. The syntax to use is: -Args P1 or if passing multiple args, then -Args 'P1 P2' . This worked for me for both Add-Migration and Update-Database commands.

Can you check that you have the preview 7 Tools package installed?

@ismailkocacan
Copy link

@ismailkocacan ismailkocacan commented Jul 25, 2020

@ismailkocacan I tried this with the preview 7 tools. The syntax to use is: -Args P1 or if passing multiple args, then -Args 'P1 P2' . This worked for me for both Add-Migration and Update-Database commands.

Can you check that you have the preview 7 Tools package installed?

@ajcvickers Yes, I checked. It works. Thanks.

I installed dotnet ef 5.0.0-preview.7.20365.15.
How can I specifiy args parameters with dotnet ef ?

I execute below command and I don't see -args parameters ?

dotnet ef database update --help

Usage: dotnet ef database update [arguments] [options]

Arguments:
  <MIGRATION>  The target migration. If '0', all migrations will be reverted. Defaults to the last migration.

Options:
  --connection <CONNECTION>              The connection string to the database. Defaults to the one specified in AddDbContext or OnConfiguring.
  -c|--context <DBCONTEXT>               The DbContext to use.
  -p|--project <PROJECT>                 The project to use.
  -s|--startup-project <PROJECT>         The startup project to use.
  --framework <FRAMEWORK>                The target framework.
  --configuration <CONFIGURATION>        The configuration to use.
  --runtime <RUNTIME_IDENTIFIER>         The runtime to use.
  --msbuildprojectextensionspath <PATH>  The MSBuild project extensions path. Defaults to "obj".
  --no-build                             Don't build the project. Only use this when the build is up-to-date.
  -h|--help                              Show help information
  -v|--verbose                           Show verbose output.
  --no-color                             Don't colorize output.
  --prefix-output                        Prefix output with level.

@ajcvickers
Copy link
Member

@ajcvickers ajcvickers commented Jul 31, 2020

@ismailkocacan From the comment above, use this syntax:

dotnet ef migrations add InitialCreate -- FirstAppArg "This is all the second application argument" ThirdAppArg
@ajcvickers ajcvickers closed this Jul 31, 2020
@ajcvickers ajcvickers added this to the 5.0.0-preview7 milestone Jul 31, 2020
@ismailkocacan
Copy link

@ismailkocacan ismailkocacan commented Aug 2, 2020

@ismailkocacan From the comment above, use this syntax:

dotnet ef migrations add InitialCreate -- FirstAppArg "This is all the second application argument" ThirdAppArg

@ajcvickers Did you read my ask ? I don't want to migrations add with dotnet ef.

It works with Package Manager Console
Update-Database -Migration MigrationName -Context MasterDbContext -Args 'P1 P2'

How can I specifiy args parameters with dotnet ef when I update database ?

@ajcvickers
Copy link
Member

@ajcvickers ajcvickers commented Aug 2, 2020

@ismailkocacan It's the same syntax:

dotnet ef database update -- FirstAppArg "This is all the second application argument" ThirdAppArg
@macorx
Copy link

@macorx macorx commented Sep 9, 2020

@ismailkocacan It's the same syntax:

dotnet ef database update -- FirstAppArg "This is all the second application argument" ThirdAppArg

EF Core cli doesn't seem to be working on version 5.0.0-preview.8.20407.4.

public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
    public MyDbContext CreateDbContext(string[] args)
    {
        var connectionString = args[0];
        return new MyDbContext(new DbContextOptionsBuilder<MyDbContext>().UseSqlServer(connectionString).Options);
    }
}
C:\Temp\MyApp>dotnet ef database update -- FirstAppArg "This is all the second application argument" ThirdAppArg
Build started...
Build succeeded.
System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at MyApp.DesignTimeDbContextFactory.CreateDbContext(String[] args) in C:\Temp\MyApp\MyApp\DesignTimeDbContextFactory.cs:line 19
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContextFromFactory(Type factory)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.<>c__DisplayClass13_1.<FindContextTypes>b__9()
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func1 factory)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.UpdateDatabase(String targetMigration, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabaseImpl(String targetMigration, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabase.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
@ajcvickers
Copy link
Member

@ajcvickers ajcvickers commented Sep 9, 2020

@macorx What version of the ef tool are you using? (Found by running dotnet ef --info.)

@macorx
Copy link

@macorx macorx commented Sep 9, 2020

@ajcvickers

C:\repos\MyApp>dotnet ef --info

...

Entity Framework Core .NET Command-line Tools 5.0.0-preview.8.20407.4
@ajcvickers
Copy link
Member

@ajcvickers ajcvickers commented Sep 11, 2020

@macorx I am not able to reproduce this. Please open a new issue and attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

@macorx
Copy link

@macorx macorx commented Sep 14, 2020

@ajcvickers I found out what happened. I wrongly assumed I had to update only ef tool to version 5.0.0-preview.8.20407.4.
You must also update the project's package references to this version as well.

After I have done that, it has worked as expected.

@ajcvickers ajcvickers modified the milestones: 5.0.0-preview7, 5.0.0 Nov 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment