简体   繁体   中英

Code First Entity Framework with ASP.NET Core: Initial migration seems to expect tables already exist

I'm trying to create a Code First Entity Framework ASP.NET Core 2 project in Visual Studio Code. I've been following the Create a Web API with ASP.NET Core MVC and Visual Studio Code on Linux, macOS, and Windows tutorial, which uses an in-memory datastore as its DbContext . I'm trying to move this to LocalDB.

The tutorial Getting Started with EF Core on ASP.NET Core with a New database suggests I should be able to do this with a migration.

Once you have a model, you can use migrations to create a database.

Open the PMC:

Tools –> NuGet Package Manager –> Package Manager Console

Run Add-Migration InitialCreate to scaffold a migration to create the initial set of tables for your model. If you receive an error stating The term 'add-migration' is not recognized as the name of a cmdlet, close and reopen Visual Studio.

Run Update-Database to apply the new migration to the database. This command creates the database before applying migrations.

The VS Code equivalent of using the Package Manager Console seems to be :

dotnet ef migrations add InitialCreate

I've added EF's Design namespace with ...

dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet restore

And have the reference in my csproj:

<ItemGroup>
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.1" />
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>

But when I try that the dotnet ef migrations add command afterwards, it acts like the table for my TodoItems model needed to already exist in the database. It was my understanding that the migration would create the tables based on my models.

c:\Projects\TodoApi>dotnet ef migrations add InitialCreate -v
Using project 'c:\Projects\TodoApi\TodoApi.csproj'.
Using startup project 'c:\Projects\TodoApi\TodoApi.csproj'.
Writing 'c:\Projects\TodoApi\obj\TodoApi.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\UserName\AppData\Local\Temp\tmp945E.tmp /verbosity:quiet /nologo c:\Projects\TodoApi\TodoApi.csproj
Writing 'c:\Projects\TodoApi\obj\TodoApi.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\UserName\AppData\Local\Temp\tmp96FF.tmp /verbosity:quiet /nologo c:\Projects\TodoApi\TodoApi.csproj
dotnet build c:\Projects\TodoApi\TodoApi.csproj /verbosity:quiet /nologo

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:02.29
dotnet exec --depsfile c:\Projects\TodoApi\bin\Debug\netcoreapp2.0\TodoApi.deps.json --additionalprobingpath C:\Users\UserName\.nuget\packages --additionalprobingpath "C:\Program Files (x86)\Microsoft SDKs\NuGetPackagesFallback" --additionalprobingpath "C:\Program Files\dotnet\sdk\NuGetFallbackFolder" --runtimeconfig c:\Projects\TodoApi\bin\Debug\netcoreapp2.0\TodoApi.runtimeconfig.json C:\Users\UserName\.nuget\packages\microsoft.entityframeworkcore.tools.dotnet\2.0.0\tools\netcoreapp2.0\ef.dll migrations add InitialCreate --assembly c:\Projects\TodoApi\bin\Debug\netcoreapp2.0\TodoApi.dll --startup-assembly c:\Projects\TodoApi\bin\Debug\netcoreapp2.0\TodoApi.dll --project-dir c:\Projects\TodoApi\ --verbose --root-namespace TodoApi
Using assembly 'TodoApi'.
Using startup assembly 'TodoApi'.
Using application base 'c:\Projects\TodoApi\bin\Debug\netcoreapp2.0'.
Using working directory 'c:\Projects\TodoApi'.
Using root namespace 'TodoApi'.
Using project directory 'c:\Projects\TodoApi\'.
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider...
Finding BuildWebHost method...
Using environment 'Development'.
Using application service provider from BuildWebHost method on 'Program'.
Found DbContext 'DatabaseContext'.
Finding DbContext classes in the project...
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
      Failed executing DbCommand (7ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT CASE
          WHEN EXISTS (
              SELECT 1
              FROM [TodoItems] AS [t])
          THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
      END
System.Data.SqlClient.SqlException (0x80131904): Invalid object name 'TodoItems'.

What needs to be done to ensure tables are created based on my models?

The only gotcha I can think of offhand is that I have the connection string inline rather than in appsettings.json, but I'm not sure why that'd be a big deal, unless the migration is looking for a config by default.

public void ConfigureServices(IServiceCollection services)
{
    // In memory: services.AddDbContext<DatabaseContext>(opt => opt.UseInMemoryDatabase("TodoList")); // <<< REMOVED
    string strCxn = "Server=(localdb)\\mssqllocaldb;Database=Contacts;Trusted_Connection=True;MultipleActiveResultSets=true"; // <<< ADDED

    services.AddDbContext<DatabaseContext>(options => options.UseSqlServer(strCxn)); // <<< ADDED
    services.AddMvc();
}

Fwiw, those are the only changes I've made to the TodoApi tutorial's code to move from the in-memory db to LocalDB.


Update: Fwiw, I tried changing the database name in the connection string..

string strCxn = "Server=(localdb)\\mssqllocaldb;Database=Contacts2;Trusted_Connection=True;MultipleActiveResultSets=true";

(Changing Contacts to Contacts2 , just in case it thought that, since it found the Contacts db initially, a migration had already taken place...)

That didn't work either, though the error changed in a way that suggests the connection string is working and being read.

Cannot open database "Contacts2" requested by the login. The login failed.
Login failed for user 'COMPUTER-NAME\UserName'.

As part of the DbContext 's constructor, I was seeding data...

public class DatabaseContext : DbContext    {
    public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
    {
        this.MinimallySeedDatabase();
    }

    public void MinimallySeedDatabase()
    {
        State NY = this.States.Where(s => s.Abbr.Equals("NY")).FirstOrDefault();
        if (null == NY)
        {
            NY = new State {
                Name = "New York",
                Abbr = "NY"
            };
            this.States.Add(NY);
            this.SaveChanges();
        }

        // etc etc etc...
    }

    public DbSet<Contact> Contacts {get; set;}
    public DbSet<Address> Addresses {get; set;}
    public DbSet<AddyType> AddyTypes {get; set;}
    public DbSet<State> States {get; set;}
}

Part of the migration's execution involves instantiating the DbContext obviously before the tables for the DbSet s are migrated over. Since the constructor, in this example, requires accessing States, and because the migration hasn't gotten to the point it's created the States table to pull from yet, the migration blows up.

As Kirk mentions , if I take out the seeding, I'm fine. And if you want to seed, you need to move that somewhere other than the constructor, even though the seeding in the constructor works fine outside of a migration attempt/in normal Kestrel testing.

For me, I just commented out this.MinimallySeedDatabase(); when I ran the migration.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM