Основная проблема заключается в том, что имеющиеся материалы по EF Core в подавляющем большинстве не подразумевают использование Visual Studio (вместо этого подразумевается использование легковесного кроссплатформенного тулинга .NET Core) и уж точно не берут в расчёт, что кому-то захочется использовать EF Core в дэсктопном приложении на основе фрэймворка Windows Forms (вместо этого как правило подразумевается использование ASP.NET core). Но задачи и ситуации, в которых предпочтительно (или и вовсе необходимо) решение в виде дэсктопного приложения всё-ещё встречаются, и свои преимущества (как, разумеется, и недостатки) у Entity Framework Core по сравнению с Entity Framework 6 есть. Кроме того в доступных в Сети примерах как правило рассматриваются только наиболее элементарные действия типа добавления записи в БД, при этом даже вопрос последующего извлечения данных зачастую не рассматривается, а в нём уже есть не очевидные моменты. Также можно заметить, что EF Core достаточно активно развивается и многие инструкции теряют актуальность в т.ч. по этой причине.
В своих попытках решить такую элементарную на первый взгляд задачу, как разработка минимального демонстрационного приложения WinForms с использованием EF Core в VisualStudo 2015 я натолкнулся на целый проблем типа исключений, неожиданного поведения, непонимания как сделать нечто, что в туториалах подразумевается как самоочевидное и т.п. В результате некоторые шаги из тех, что я приведу ниже пришлось нащупывать практически в слепую, гугля, задавая вопросы и экспериментируя.
В качестве платформы я выбрал .NET Framework 4.6.1 (версия 4.6.2 почему-то не появляется в списке доступных у меня в Visual Studio, но и при выборе 4.6.1 всё работает, возможно будет работать и с более ранними версиями, но я не проверял), в качестве среды разработки — Visual Studio Community Edition 2015 Update 3, в качестве СУБД — SQLite, в качестве ОС — Windows 7 64-bit.
Ниже приведу список шагов, результатом которых стало исправно работающее демонстрационное приложение и в процессе выполнения которых не было получено ни одного сообщения об ошибке.
0. Проверяем, что установлены последние версии .NET Framework 4.6.2 (web installer, offline installer) и Visual Studio 2015 Update 3 (update web installer, update offline installer ISO, web installer полной версии Visual Studio 2015 Update Community Edition 3, offline installer ISO полной версии Visual Studio 2015 Update Community Edition 3 с интергрированным обновлением)
1. Устанавливаем Windows Management Framework 5.0 чтобы обновить PowerShell до версии 5.0. У меня (после установки Windows 7 и Visual Studio 2015 и всех обновлений к ним) в системе была версия 2.0 и далее в процессе я получил сообщение об ошибке с требованием более новой версии
2. Устанавливаем .NET Core 1.0.1 tools Preview 2 (web installer, может использоваться для создания offline-дистрибутива при помощи ключа /layout).
3. Обновляем расширение Visual Studio для работы с репозиториями NuGet. Для этого либо скачиваем актуальную на данный момент версию 3.5.0.1996 по прямой ссылке либо добавляем в настройки Visual Studio соответствующий репозиторий расширений для автоматического обновления.

4. Создаём новый проект типа Windows Forms Application. Я назвал его «Vs2015WinFormsEfcSqliteCodeFirst20170304Example» (актуальный на 04.03.2017 пример WinForms-приложения с использованием Entity Framework Core, подхода «code-first» и СУБД SQLite в Visual Studio 2015). В качестве target framework выбираем .NET Framework 4.6.1.
5. Нажимаем правой кнопкой мыши по нашему проекту в панели Solution Explorer, выбираем Manage NuGet Packages..., переходим на вкладку Browse, устанавливаем Microsoft.EntityFrameworkCore.Sqlite.Design и Microsoft.EntityFrameworkCore.Tools. Для установки актуальных версий может потребоваться установить галочку «Include prerelease», в стабильных версиях может чего-то не хватать или наличествовать неисправленные баги. Я установил последние на данный момент Microsoft.EntityFrameworkCore.Sqlite.Design 1.1.0 и Microsoft.EntityFrameworkCore.Tools 1.1.0-preview4-final. Если возникнут проблемы с установкой Microsoft.EntityFrameworkCore.Tools можно попробовать сделать это через командную строку NuGet: в меню выбрать Tools — NuGet Package Manager — Package Manager Console, в появившейся консоли (которая, кстати, ещё понадобится нам далее) после приглашения «PM>» ввести «Install-Package Microsoft.EntityFrameworkCore.Tools -Pre»
PM> Install-Package Microsoft.EntityFrameworkCore.Tools -Pre
6. Создаём файлы исходного кода классов модели данных. Для порядка я поместил их в подпапку «Model» (некоторые называют её «Entities», некоторые кидают все классы в корень проекта, а некоторые и вовсе в один файл). Модель описывает учебный пример базы данных, хранящей список городов и людей в/из них. Каждый человек может быть связан только с одним городом, может быть неизвестно из какого он города вообще.
Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleContext.cs
using Microsoft.EntityFrameworkCore; namespace Vs2015WinFormsEfcSqliteCodeFirst20170304Example.Model { public class Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleContext : DbContext { public DbSet<City> Cities { get; set; } public DbSet<Person> People { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite("Filename=Vs2015WinFormsEfcSqliteCodeFirst20170304Example.sqlite"); } } }
City.cs
using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; namespace Vs2015WinFormsEfcSqliteCodeFirst20170304Example.Model { public class City { public int Id { get; set; } public string Name { get; set; } [InverseProperty("City")] public virtual ICollection<Person> People { get; set; } } }
Person.cs
using System.ComponentModel.DataAnnotations.Schema; namespace Vs2015WinFormsEfcSqliteCodeFirst20170304Example.Model { public class Person { public int Id { get; set; } public string Name { get; set; } public string Surname { get; set; } public int? CityId { get; set; } [InverseProperty("People")] public virtual City City { get; set; } } }
7. Разрешаем скрипты PowerShell, если этого не сделать может возникнуть ошибка "...\packages\Microsoft.EntityFrameworkCore.Tools.1.1.0-preview4-final\tools\init.ps1 cannot be loaded because running scripts is disabled on this system." Для этого переходим в командную строку NuGet (выбрать Tools — NuGet Package Manager — Package Manager Console в меню) и выполняем следующую команду
PM> Set-ExecutionPolicy RemoteSigned
8. Создаём «миграции». Для этого, сохранив и откомпилировав наш код (просто чтобы удостовериться в отсутствии явных опечаток) переходим в командную строку NuGet и выполняем следующую команду.
PM> Add-Migration -Name "Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleMigration" -Context "Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleContext"
в результате у нас в проекте должна появиться папка «Migrations» и два файла в ней: «Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleContextModelSnapshot.cs» и «20170304204355_Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleMigration.cs» (разумеется число в начале последнего у вас будет другое — это ни что иное, как дата и время в момент генерации в очевидном формате, я её потом вообще удалил оставив только «Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleMigration.cs»).
9. Редактируем сгенерированные файлы чтобы добавить условие уникальности (unique constraint, также известное как вторичный ключ) на имя города (в реальности, конечно, бывают города с одинаковыми именами, но для примера будет не лишним)
Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleMigration.cs
using Microsoft.EntityFrameworkCore.Migrations; namespace Vs2015WinFormsEfcSqliteCodeFirst20170304Example.Migrations { public partial class Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleMigration : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Cities", columns: table => new { Id = table.Column<int>(nullable: false) .Annotation("Sqlite:Autoincrement", true), Name = table.Column<string>(nullable: true) }, constraints: table => { table.PrimaryKey("PK_Cities", x => x.Id); // Эта строка добавлена вручную table.UniqueConstraint("UQ_Cities_Name", x => x.Name); }); migrationBuilder.CreateTable( name: "People", columns: table => new { Id = table.Column<int>(nullable: false) .Annotation("Sqlite:Autoincrement", true), CityId = table.Column<int>(nullable: true), Name = table.Column<string>(nullable: true), Surname = table.Column<string>(nullable: true) }, constraints: table => { table.PrimaryKey("PK_People", x => x.Id); table.ForeignKey( name: "FK_People_Cities_CityId", column: x => x.CityId, principalTable: "Cities", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateIndex( name: "IX_People_CityId", table: "People", column: "CityId"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "People"); migrationBuilder.DropTable( name: "Cities"); } } }
Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleContextModelSnapshot.cs
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Vs2015WinFormsEfcSqliteCodeFirst20170304Example.Model; namespace Vs2015WinFormsEfcSqliteCodeFirst20170304Example.Migrations { [DbContext(typeof(Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleContext))] partial class Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { modelBuilder .HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); modelBuilder.Entity("Vs2015WinFormsEfcSqliteCodeFirst20170304Example.Model.City", b => { b.Property<int>("Id") .ValueGeneratedOnAdd(); b.Property<string>("Name"); b.HasKey("Id"); // Эта строка добавлена вручную b.HasIndex("Name").IsUnique(); b.ToTable("Cities"); }); modelBuilder.Entity("Vs2015WinFormsEfcSqliteCodeFirst20170304Example.Model.Person", b => { b.Property<int>("Id") .ValueGeneratedOnAdd(); b.Property<int?>("CityId"); b.Property<string>("Name"); b.Property<string>("Surname"); b.HasKey("Id"); b.HasIndex("CityId"); b.ToTable("People"); }); modelBuilder.Entity("Vs2015WinFormsEfcSqliteCodeFirst20170304Example.Model.Person", b => { b.HasOne("Vs2015WinFormsEfcSqliteCodeFirst20170304Example.Model.City", "City") .WithMany("People") .HasForeignKey("CityId"); }); } } }
10. Генерируем файл БД. Для этого в командной строке NuGet выполняем следующую команду
PM> Update-Database -Context "Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleContext"
Файл БД создастся в той же директории, где находится скомпилированный exe-шник нашего приложения, у меня это "...\Vs2015WinFormsEfcSqliteCodeFirst20170304Example\bin\Debug\Vs2015WinFormsEfcSqliteCodeFirst20170304Example.sqlite".
На данном этапе мы уже можем заглянуть внутрь созданного файла, например с помощью официального консольного клиента sqlite3 или бесплатной кроссплатформенной GUI оболочки DB Browser for SQLite, и удостовериться, что таблицы создались корректно. Помимо наших таблиц «Cities» и «People» мы также найдём там таблицу «__EFMigrationsHistory» со служебной информацией EF Core и «sqlite_sequence» со служебной информацией SQLite.
11. Теперь перейдём к дизайнеру нашей формы, разместим на ней кнопочку, щёлкнем и на ней дважды чтобы создать обработчик события нажатия на кнопку и перейти к редактированию его кода. Ниже привожу свой код, демонстрирующий основные действия с записями БД. Я ещё имею привычку всегда переименовывать Form1 в MainForm и контролы аналогично по смыслу (в данном случае единственный контрол button1 в mainButton), но это дело вкуса и принятых в вашей команде стандартов именования.
MainForm.cs
private void mainButton_Click(object sender, EventArgs e) { // Удаляем все записи из обеих таблиц using (var context = new Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleContext()) { foreach (var person in context.People) context.Remove(person); foreach (var city in context.Cities) context.Remove(city); context.SaveChanges(); } // Добавляем новые записи в таблицу городв и затем // новые записи в таблицу людей, ссылаясь на добавленные города using (var context = new Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleContext()) { var praha = new City { Name = "Praha" }; var london = new City { Name = "London" }; var madrid = new City { Name = "Madrid" }; var jan = new Person { Name = "Jan", City = praha }; var john = new Person { Name = "John", City = london }; var juan = new Person { Name = "Juan", City = madrid }; context.Cities.AddRange(praha, london, madrid); context.People.AddRange(jan, john, juan); context.SaveChanges(); } // Загружаем запись о добавленном ранее городе из БД, // добавляем нового человека в этот город, // изменяем добавленную ранее запись о человеке (добавляем фамилию) using (var context = new Vs2015WinFormsEfcSqliteCodeFirst20170304ExampleContext()) { // Обратите внимание на конструкцию Include(city => city.People) // если написать просто context.Cities.Single(city => city.Name == "London"); // то город найдётся, но его список .People будет равен null. // В production коде при использовании .Single также необходимо добавить обработку случаев, // когда удовлетворяющих запросу записей нет или когда их болше одной var london = context.Cities.Include(city => city.People)(city => city.Name == "London"); var peter = new Person { Name = "Peter", City = london }; var john = london.People.Single(person => person.Name == "John"); john.Surname = "Smith"; context.Add(peter); context.Update(john); context.SaveChanges(); } }
Разумеется в реальной жизни вы реализуете в приложении более богатый интерфейс, более осмысленную логику, добавите обработку исключений и валидацию данных для их предотвращения, приведённого же примера достаточно для понимания того, как это сделать.
Скачать весь приведённый выше и сопутствующий код можно тут.

