Поделиться через


Руководство: Узнайте об использовании сложных сценариев в ASP.NET MVC с EF Core

В предыдущем руководстве вы реализовали наследование типа "таблица-на-иерархию". В этом руководстве представлено несколько разделов, которые полезно учитывать, когда вы выходите за рамки основных веб-приложений ASP.NET Core, использующих Entity Framework Core.

В этом руководстве вы:

  • Выполнение необработанных запросов SQL
  • Вызов запроса для возврата сущностей
  • Выполните запрос для получения других типов данных
  • Вызов запроса на обновление
  • Проверка запросов SQL
  • Создание слоя абстракции
  • Сведения об автоматическом обнаружении изменений
  • Узнайте о планах разработки и исходном коде EF Core
  • Узнайте, как использовать динамический LINQ для упрощения кода

Необходимые условия

Выполнение необработанных запросов SQL

Одним из преимуществ использования Entity Framework является то, что он избегает привязки кода слишком близко к конкретному методу хранения данных. Это делается путем создания запросов и команд SQL, которые также освобождают вас от необходимости писать их самостоятельно. Но существуют исключительные сценарии, когда необходимо выполнить определенные запросы SQL, созданные вручную. В этих сценариях API Entity Framework Code First включает методы, позволяющие передавать команды SQL непосредственно в базу данных. В EF Core 1.0 доступны следующие параметры:

  • Используйте метод DbSet.FromSql для запросов, возвращающих типы сущностей. Возвращаемые объекты должны иметь тип, ожидаемый объектом DbSet, и они автоматически отслеживаются контекстом базы данных, если только отключить отслеживание.

  • Используйте Database.ExecuteSqlCommand для команд, отличных от запросов.

Если вам нужно выполнить запрос, возвращающий типы, которые не являются сущностями, можно использовать ADO.NET с подключением к базе данных, предоставленному EF. Возвращаемые данные не отслеживаются контекстом базы данных, даже если этот метод используется для получения типов сущностей.

Как всегда верно при выполнении команд SQL в веб-приложении, необходимо принять меры предосторожности, чтобы защитить сайт от атак внедрения SQL. Один из способов сделать это — использовать параметризованные запросы, чтобы убедиться, что строки, отправленные веб-страницей, не могут быть интерпретированы как команды SQL. В этом руководстве вы будете использовать параметризованные запросы при интеграции ввода пользователей в запрос.

Вызов запроса для возврата сущностей

Класс DbSet<TEntity> предоставляет метод, который можно использовать для выполнения запроса, возвращающего сущность типа TEntity. Чтобы узнать, как это работает, вы измените код в методе Details контроллера отдела.

В методе Details в DepartmentsController.csзамените код, извлекающий раздел, вызовом метода FromSql, как показано в следующем выделенном коде:

public async Task<IActionResult> Details(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
    var department = await _context.Departments
        .FromSql(query, id)
        .Include(d => d.Administrator)
        .AsNoTracking()
        .FirstOrDefaultAsync();

    if (department == null)
    {
        return NotFound();
    }

    return View(department);
}

Чтобы убедиться, что новый код работает правильно, выберите вкладку Отделы, а затем Сведения для одного из отделов.

сведения о отделе

Вызов запроса для возврата других типов

Ранее вы создали сетку статистики учащихся на странице "Сведения", которая показала количество учащихся для каждой даты регистрации. Вы получили данные из набора сущностей "Учащиеся" (_context.Students) и использовали LINQ для проецирования результатов в список объектов модели представления EnrollmentDateGroup. Предположим, вы хотите написать сам SQL, а не использовать LINQ. Для этого необходимо выполнить SQL-запрос, который возвращает что-то другое, отличное от объектов сущностей. В EF Core 1.0 один из способов сделать это — записать код ADO.NET и получить подключение к базе данных из EF.

В HomeController.csзамените метод About следующим кодом:

public async Task<ActionResult> About()
{
    List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
    var conn = _context.Database.GetDbConnection();
    try
    {
        await conn.OpenAsync();
        using (var command = conn.CreateCommand())
        {
            string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
                + "FROM Person "
                + "WHERE Discriminator = 'Student' "
                + "GROUP BY EnrollmentDate";
            command.CommandText = query;
            DbDataReader reader = await command.ExecuteReaderAsync();

            if (reader.HasRows)
            {
                while (await reader.ReadAsync())
                {
                    var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
                    groups.Add(row);
                }
            }
            reader.Dispose();
        }
    }
    finally
    {
        conn.Close();
    }
    return View(groups);
}

Добавьте инструкцию using:

using System.Data.Common;

Запустите приложение и перейдите на страницу "Сведения". В нем отображаются те же данные, что и раньше.

О странице

Вызов запроса на обновление

Предположим, что администраторы Университета Contoso хотят выполнить глобальные изменения в базе данных, например изменение количества кредитов для каждого курса. Если университет предлагает много курсов, получать их все как сущности и изменять каждый по отдельности было бы неэффективно. В этом разделе вы реализуете веб-страницу, которая позволяет пользователю указать фактор, с помощью которого можно изменить количество кредитов для всех курсов, и вы будете вносить изменения, выполнив инструкцию SQL UPDATE. Веб-страница будет выглядеть следующим образом:

Страница обновления кредитов курса

В CoursesController.csдобавьте методы UpdateCourseCredits для HttpGet и HttpPost:

public IActionResult UpdateCourseCredits()
{
    return View();
}
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
    if (multiplier != null)
    {
        ViewData["RowsAffected"] = 
            await _context.Database.ExecuteSqlCommandAsync(
                "UPDATE Course SET Credits = Credits * {0}",
                parameters: multiplier);
    }
    return View();
}

Когда контроллер обрабатывает запрос HttpGet, ничего не возвращается в ViewData["RowsAffected"], а представление отображает пустое текстовое поле и кнопку отправки, как показано на предыдущем рисунке.

При нажатии кнопки Update вызывается метод HttpPost, а умножитель имеет значение, введенное в текстовом поле. Затем код выполняет SQL, который обновляет курсы и возвращает количество затронутых строк в представление ViewData. Когда представление получает значение RowsAffected, отображается количество обновленных строк.

В обозревателе решенийщелкните правой кнопкой мыши папку Views/Courses, а затем нажмите Добавить > Новый элемент.

В диалоговом окне "Добавить новый элемент", в левой области под "Установленные" щелкните ASP.NET Core, затем щелкните Razor Види назовите новое представление UpdateCourseCredits.cshtml.

В Views/Courses/UpdateCourseCredits.cshtmlзамените код шаблона следующим кодом:

@{
    ViewBag.Title = "UpdateCourseCredits";
}

<h2>Update Course Credits</h2>

@if (ViewData["RowsAffected"] == null)
{
    <form asp-action="UpdateCourseCredits">
        <div class="form-actions no-color">
            <p>
                Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
            </p>
            <p>
                <input type="submit" value="Update" class="btn btn-default" />
            </p>
        </div>
    </form>
}
@if (ViewData["RowsAffected"] != null)
{
    <p>
        Number of rows updated: @ViewData["RowsAffected"]
    </p>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Запустите метод UpdateCourseCredits, выбрав вкладку "Курсы", а затем добавьте "/UpdateCourseCredits" в конец URL-адреса в адресной строке браузера (например, http://localhost:5813/Courses/UpdateCourseCredits). Введите число в текстовом поле:

Страница обновлений кредитов курса

Щелкните Обновить. Отображается количество затронутых строк:

строк страницы

Нажмите кнопку Вернуться к списку, чтобы просмотреть список курсов с измененным количеством кредитов.

Обратите внимание, что рабочий код гарантирует, что обновления всегда приводят к допустимым данным. Упрощенный код, показанный здесь, может умножить достаточное количество кредитов, чтобы привести к числу больше 5. (Свойство Credits имеет атрибут [Range(0, 5)].) Запрос обновления будет работать, но недопустимые данные могут вызвать непредвиденные результаты в других частях системы, предполагающих, что количество кредитов равно 5 или меньше.

Дополнительные сведения о сырых SQL-запросах см. в сырых SQL-запросов.

Проверка запросов SQL

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

Откройте StudentsController.cs и в методе Details задайте точку останова в инструкции if (student == null).

Запустите приложение в режиме отладки и перейдите на страницу сведений для учащегося.

Перейдите в окно выходных данных, в котором отображаются выходные данные отладки, и вы увидите запрос:

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
    SELECT TOP(1) [s0].[ID]
    FROM [Person] AS [s0]
    WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
    ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]

Вы заметите что-то здесь, что может удивить вас: SQL выбирает до 2 строк (TOP(2)) из таблицы Person. Метод SingleOrDefaultAsync не сводится к 1 строке на сервере. Вот почему:

  • Если запрос вернет несколько строк, метод возвращает значение NULL.
  • Чтобы определить, будет ли запрос возвращать несколько строк, EF должен проверить, возвращает ли он по крайней мере 2.

Обратите внимание, что вам не нужно использовать режим отладки и делать остановку в точке испытания, чтобы получить результаты журналирования в окне выходных данных. Это просто удобный способ остановить логирование в точке, чтобы просмотреть выходные данные. Если этого не сделать, журналирование продолжится, и вам придется прокручивать назад, чтобы найти интересующие вас части.

Создание слоя абстракции

Многие разработчики пишут код для реализации репозитория и единицы рабочих шаблонов в виде оболочки вокруг кода, который работает с Entity Framework. Эти шаблоны предназначены для создания уровня абстракции между уровнем доступа к данным и уровнем бизнес-логики приложения. Реализация этих шаблонов может помочь изолировать приложение от изменений в хранилище данных и упростить автоматизированное модульное тестирование или разработку на основе тестов (TDD). Однако написание дополнительного кода для реализации этих шаблонов не всегда является лучшим выбором для приложений, использующих EF, по нескольким причинам:

  • Класс контекста EF сам изолирует код от кода, зависяющего от хранилища данных.

  • Класс контекста EF может выступать в качестве класса единицы работы для обновлений базы данных, которые вы выполняете с использованием EF.

  • EF включает функции для реализации TDD без написания кода репозитория.

Сведения о том, как реализовать шаблоны репозитория и единицы работы, см. в версии Entity Framework 5 этой серии учебников.

Entity Framework Core реализует поставщик базы данных в памяти, который можно использовать для тестирования. Дополнительные сведения см. в разделе Test with InMemory.

Автоматическое обнаружение изменений

Entity Framework определяет, как сущность изменилась (и поэтому какие обновления необходимо отправить в базу данных), сравнивая текущие значения сущности с исходными значениями. Исходные значения хранятся при запросе или присоединении сущности. Ниже приведены некоторые методы, вызывающие автоматическое обнаружение изменений:

  • DbContext.SaveChanges

  • DbContext.Entry

  • ChangeTracker.Entries

Если вы отслеживаете большое количество сущностей и вызываете один из этих методов много раз в цикле, вы можете получить значительные улучшения производительности, временно отключив автоматическое обнаружение изменений с помощью свойства ChangeTracker.AutoDetectChangesEnabled. Например:

_context.ChangeTracker.AutoDetectChangesEnabled = false;

EF Core о планах разработки и исходном коде

Источник Entity Framework Core находится в https://github.com/dotnet/efcore. Репозиторий EF Core содержит ночные сборки, отслеживание проблем, спецификации функций, заметки о собрании и дорожную карту для будущейразработки. Вы можете отправить или найти ошибки и внести свой вклад.

Хотя исходный код открыт, Entity Framework Core полностью поддерживается как продукт Майкрософт. Команда Microsoft Entity Framework контролирует, какие вклады принимаются и проверяет все изменения кода, чтобы обеспечить качество каждого выпуска.

Обратный инженер из существующей базы данных

Чтобы выполнить обратную инженерию модели данных, включая классы сущностей из существующей базы данных, используйте команду scaffold-dbcontext. Посмотрите руководство по началу работы.

Упрощение кода с помощью динамического LINQ

В третьем руководстве этой серии показано, как писать код LINQ путем жесткого написания имен столбцов в инструкции switch. С двумя столбцами на выбор это работает хорошо, но если у вас много столбцов, код может стать многословным. Чтобы решить эту проблему, можно использовать метод EF.Property, чтобы указать имя свойства в виде строки. Чтобы попробовать этот подход, замените метод Index в StudentsController следующим кодом.

 public async Task<IActionResult> Index(
     string sortOrder,
     string currentFilter,
     string searchString,
     int? pageNumber)
 {
     ViewData["CurrentSort"] = sortOrder;
     ViewData["NameSortParm"] = 
         String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
     ViewData["DateSortParm"] = 
         sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";

     if (searchString != null)
     {
         pageNumber = 1;
     }
     else
     {
         searchString = currentFilter;
     }

     ViewData["CurrentFilter"] = searchString;

     var students = from s in _context.Students
                    select s;
     
     if (!String.IsNullOrEmpty(searchString))
     {
         students = students.Where(s => s.LastName.Contains(searchString)
                                || s.FirstMidName.Contains(searchString));
     }

     if (string.IsNullOrEmpty(sortOrder))
     {
         sortOrder = "LastName";
     }

     bool descending = false;
     if (sortOrder.EndsWith("_desc"))
     {
         sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
         descending = true;
     }

     if (descending)
     {
         students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
     }
     else
     {
         students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
     }

     int pageSize = 3;
     return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), 
         pageNumber ?? 1, pageSize));
 }

Подтверждения

Том Дайкстра и Рик Андерсон (twitter @RickAndMSFT) написали это руководство. Роуэн Миллер, Диего Вега и другие члены команды Entity Framework помогли с проверками кода и помогли отладить проблемы, возникающие при написании кода для учебников. Джон Паренте и Пол Голдман работали над обновлением руководства по ASP.NET Core 2.2.

Устранение распространенных ошибок

ContosoUniversity.dll используется в другом процессе

Сообщение об ошибке:

Не удается открыть "... bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll' потому что он используется другим процессом.

Решение:

Остановите сайт в IIS Express. Перейдите в системное трее Windows, найдите IIS Express и кликните правой кнопкой его значок, выберите сайт Университета Contoso, а затем щелкните Остановить сайт.

Миграция структурирована без кода в методах Up и Down

Возможные причины:

Команды EF CLI не закрывают и не сохраняют файлы кода автоматически. Если при выполнении команды migrations add есть несохраненные изменения, EF не будет находить изменения.

Решение:

Выполните команду migrations remove, сохраните изменения кода и повторно запустите команду migrations add.

Ошибки при выполнении обновления базы данных

При внесении изменений схемы в базу данных с существующими данными можно получить другие ошибки. Если вы получаете ошибки миграции, которые не можете разрешить, вы можете либо изменить имя базы данных в строке подключения, либо удалить базу данных. При использовании новой базы данных нет данных для миграции, и команда update-database гораздо чаще завершается без ошибок.

Самый простой подход — переименовать базу данных в appsettings.json. При следующем запуске database updateбудет создана новая база данных.

Чтобы удалить базу данных в SSOX, щелкните правой кнопкой мыши базу данных, щелкните Удалить, а затем в диалоговом окне Удалить базу данных выберите Закрыть существующие подключения и нажмите кнопку ОК.

Чтобы удалить базу данных с помощью интерфейса командной строки, выполните команду database drop CLI:

dotnet ef database drop

Ошибка при поиске экземпляра SQL Server

Сообщение об ошибке:

При установлении подключения к SQL Server произошла ошибка, связанная с сетью или экземпляром. Сервер не найден или недоступен. Убедитесь, что имя экземпляра правильно и что SQL Server настроен для разрешения удаленных подключений. (поставщик: сетевые интерфейсы SQL, ошибка: 26 — ошибка поиска сервера или экземпляра)

Решение:

Проверьте строку подключения. Если вы вручную удалили файл базы данных, измените имя базы данных в строке построения, чтобы начать работу с новой базой данных.

Получение кода

Скачать или просмотреть готовое приложение.

Дополнительные ресурсы

Для получения дополнительной информации о EF Coreсм. документацию по Entity Framework Core . Книга также доступна: *Entity Framework Core в действии*.

Сведения о развертывании веб-приложения см. в разделе Host и deploy ASP.NET Core.

Дополнительные сведения о других разделах, связанных с ASP.NET Core MVC, такими как проверка подлинности и авторизация, см. в обзоре ASP.NET Core.

Рекомендации по созданию надежного, безопасного, производительного, тестового и масштабируемого приложения ASP.NET Core см. в шаблонах веб-приложений Enterprise. Полный пример веб-приложения с высоким качеством рабочей среды, реализующий шаблоны, доступен.

Дальнейшие действия

В этом руководстве вы:

  • Выполненные необработанные запросы SQL
  • Вызывает запрос для возврата сущностей
  • Вызывается запрос для возврата других типов
  • Вызывается запрос на обновление
  • Просмотр запросов SQL
  • Создание слоя абстракции
  • Узнал об автоматическом обнаружении изменений
  • Узнано о исходном коде EF Core и планах разработки.
  • Узнайте, как использовать динамический LINQ для упрощения кода

В этом руководстве описано, как использовать Entity Framework Core в приложении ASP.NET Core MVC. Эта серия работала с новой базой данных; альтернативой является реверсивного инженера модели из существующей базы данных.