Code First Entity Framework с ASP.NET Core: первоначальная миграция предполагает, что таблицы уже существуют

Я пытаюсь создать проект Code First Entity Framework ASP.NET Core 2 в коде Visual Studio. Я следовал учебному пособию " Создание веб-API с ASP.NET Core MVC и кодом Visual Studio для Linux, macOS и Windows", в котором хранилище данных в памяти используется в качестве DbContext, Я пытаюсь переместить это в LocalDB.

Учебник Начало работы с EF Core на ASP.NET Core с новой базой данных предполагает, что я должен быть в состоянии сделать это с помощью миграции.

Если у вас есть модель, вы можете использовать миграции для создания базы данных.

Откройте PMC:

Сервис -> Диспетчер пакетов NuGet -> Консоль диспетчера пакетов

Запустите Add-Migration InitialCreate, чтобы создать миграцию, чтобы создать начальный набор таблиц для вашей модели. Если вы получили сообщение об ошибке, указывающее, что термин "миграция надстройки" не распознается как имя командлета, закройте и снова откройте Visual Studio.

Запустите Update-Database, чтобы применить новую миграцию к базе данных. Эта команда создает базу данных перед применением миграций.

Код VS, эквивалентный использованию консоли диспетчера пакетов, выглядит следующим образом:

dotnet ef migrations add InitialCreate

Я добавил пространство имен EF Design с помощью...

dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet restore

И есть ссылка в моем csproj:

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

Но когда я пытаюсь это dotnet ef migrations add После этого команда действует как таблица для моей модели TodoItems, которая должна существовать в базе данных. Насколько я понимаю, миграция создаст таблицы на основе моих моделей.

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'.

Что нужно сделать, чтобы таблицы создавались на основе моих моделей?

Единственный недостаток, который я могу придумать, заключается в том, что строка подключения встроена, а не в appsettings.json, но я не уверен, почему это будет иметь большое значение, если миграция не ищет конфигурацию по умолчанию.

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();
}

Между прочим, это единственные изменения, которые я внес в код учебника TodoApi, чтобы перейти от базы данных в памяти к LocalDB.


Обновление: Fwiw, я попытался изменить имя базы данных в строке подключения..

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

(Изменение Contacts в Contacts2просто на тот случай, если он подумал, что, поскольку изначально он нашел контактную базу данных, миграция уже произошла...)

Это тоже не сработало, хотя ошибка изменилась таким образом, что это говорит о том, что строка подключения работает и читается.

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

1 ответ

Решение

Как часть DbContextконструктор, я посеял данные...

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;}
}

Часть выполнения миграции включает в себя создание DbContext очевидно, перед таблицами для DbSetс перенесены. Поскольку в этом примере конструктору требуется доступ к состояниям, и поскольку миграция еще не достигла той точки, в которой он создал таблицу States, из которой следует извлечь данные, миграция разрушается.

Как упоминает Кирк, если я сниму посев, у меня все хорошо. И если вы хотите заполнить, вам нужно переместить его куда-нибудь, кроме конструктора, даже несмотря на то, что заполнение в конструкторе работает нормально вне попытки миграции / при обычном тестировании Kestrel.

Для меня я просто прокомментировал this.MinimallySeedDatabase(); когда я запустил миграцию.