Asp.Net Core MVC CRUD с EF Core

  • Михаил
  • 12 мин. на прочтение
  • 163
  • 09 Dec 2022
  • 09 Dec 2022

Созданиеполучениеобновление и  удаление [CRUD] — основные операции в любом приложении. Приложения без какой-либо из этих операций были бы редки. В этой статье мы реализуем операции CRUD в приложении ASP.NET Core MVC с помощью Entity Framework Core — подход Code First. Чтобы продемонстрировать тему, мы создадим приложение для хранения и управления данными о сотрудниках.

Репозиторий GitHub для демонстрационного проекта:  https://goo.gl/E13H2R

Создать проект ASP.NET Core MVC

В Visual Studio выберите «Файл» > «Создать» > «Проект» (Ctrl + Shift + N). Затем выберите веб-приложение ASP.NET Core.

В мастере шаблонов выберите шаблон веб-приложения (MVC). Обязательно выберите последнюю версию ASP.NET Core в верхнем раскрывающемся списке. Снимите флажок HTTPS, который не требуется в среде разработки.

Настройка базы данных для EF Core

База данных для проекта будет создаваться и управляться с помощью EF Core — Code First Approach. Итак, прежде всего, мы должны установить соответствующие пакеты NuGet. Щелкните проект правой кнопкой мыши в обозревателе решений и выберите Управление пакетами NuGet. На вкладке обзора найдите и установите Microsoft.EntityFrameworkCoreи зависимые от него пакеты.

Теперь давайте определим класс модели для сущности сотрудника в Modelsпапке как Employee.

//File : /Models/Employee.cs
public class Employee
{
	[Key]
	public int EmployeeId { get; set; }

	[Column(TypeName ="nvarchar(250)")]
	[Required(ErrorMessage ="This field is required.")]
	[DisplayName("Full Name")]
	public string FullName { get; set; }

	[Column(TypeName = "varchar(10)")]
	[DisplayName("Emp. Code")]
	public string EmpCode { get; set; }

	[Column(TypeName = "varchar(100)")]
	public string Position { get; set; }

	[Column(TypeName = "varchar(100)")]
	[DisplayName("Office Location")]
	public string OfficeLocation { get; set; }
}

В Entity Framework фактические физические объекты в базе данных создаются и управляются из класса DBContext. В нашем приложении EmployeeContextвсю работу выполняет класс. Чтобы создать таблицу, соответствующую классу модели сотрудника, мы добавили Employeesсвойство типа DbSet.

//File : /Models/EmployeeContext.cs
public class EmployeeContext:DbContext
    {
        public EmployeeContext(DbContextOptions<EmployeeContext> options):base(options)
        { }

        public DbSet<Employee> Employees { get; set; }
    }

Внедрить класс DbContext с внедрением зависимостей.

Для взаимодействия с базой данных нам нужен экземпляр класса DbContext. Мы можем сделать это с помощью внедрения зависимостей. Для параметра конструктора класса optionsтребуется следующая информация.

  • Поставщик базы данных — будь то SQL Server, MySQL, PostgreSQL и т. д.
  • Строка подключения к БД

Прежде всего, мы сохраним строку подключения в appsettings.jsonфайле следующим образом.

{
	...,
	"ConnectionStrings": {
	  "DevConnection": "Server=(local)\\sqlexpress;Database=EmployeeDB;Trusted_Connection=True;MultipleActiveResultSets=True;"
	}
}

Теперь давайте создадим DbContextэкземпляр, передав строку подключения к БД и поставщика БД, используя внедрение зависимостей из Asp.Net Core. Для этого нам просто нужно обновить ConfigureServicesметод из Startupкласса. Вызовите AddDbConextметод из servicesколлекции следующим образом.

public void ConfigureServices(IServiceCollection services)
{
	...

	services.AddDbContext<EmployeeContext>(options =>
	options.UseSqlServer(Configuration.GetConnectionString("DevConnection")));
}

Как и когда работает эта инъекция зависимостей? мы обсудим это позже, как только мы добавим контроллер сотрудников в этот проект.

Инициировать миграцию БД

До сих пор мы моделировали нашу базу данных, давайте создадим фактическую базу данных. Прежде всего, откройте консоль диспетчера пакетов. Для этого вы можете щелкнуть правой кнопкой мыши проект в обозревателе решений, а затем перейти в «Инструменты» > «Диспетчер пакетов NuGet» > «Консоль диспетчера пакетов». Внутри консоли выполните следующие команды одну за другой.

Add-Migration "InitialCreate"
Update-Database

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

Создайте контроллер MVC для операций CRUD

Давайте создадим новый контроллер MVC EmployeeController. Перейдите в «Контроллер» > «Добавить» > «Контроллер», затем выберите «Контроллер MVC с представлениями, используя шаблон Entity Framework». Выберите Employeeи EmployeeContextв качестве класса Model и DbContext соответственно.

Созданный контроллер MVC будет иметь все методы действий для всех операций CRUD, хотя я хотел бы упростить его следующим образом. Каждый из методов действия будет рассмотрен далее в этой статье.

 

public class EmployeeController : Controller
{
    private readonly EmployeeContext _context;
    //constructor
    public EmployeeController(EmployeeContext context)
    {
        _context = context;
    }

    // GET: Employee
    public async Task<IActionResult> Index()
    { ... }

    // GET: Employee/AddOrEdit
    public IActionResult AddOrEdit(int id = 0)
    { ... }

    // POST: Employee/AddOrEdit
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> AddOrEdit([Bind("EmployeeId,FullName,EmpCode,Position,OfficeLocation")] Employee employee)
    { ... }

    // GET: Employee/Delete/5
    public async Task<IActionResult> Delete(int? id)
    { ... }
}

Теперь несколько слов о внедрении зависимостей ASP.NET Core: Как вы знаете, экземпляр контроллера MVC будет создан после того, как к нему будет сделан запрос. Но есть параметр конструктора contextтипа EmployeeContext. Как мы можем передать значение для параметра, когда экземпляр контроллера создается самой платформой ASP.NET Core? Здесь возникает важность внедрения зависимостей, которое мы настроили выше в Startupклассе, всякий раз, когда конструктору контроллера требуется экземпляр DbContext, внедрение зависимостей передает экземпляр. Таким образом, остальные методы действий внутри контроллера могут взаимодействовать с базой данных через внедренный DbContextэкземпляр.

Получить список записей в представлении MVC

Назначение каждого метода действия в EmployeeController.

  • Index: Получить список записей из таблицы сотрудников.
  • AddOrEdit: Обработка операций вставки и обновления.
  • Delete: удалить запись о сотруднике с заданным идентификатором сотрудника.

Все вышеперечисленные методы действия либо манипулируют данными, либо извлекают существующие данные из базы данных. Теперь нам нужно спроектировать пользовательский интерфейс с бритвенными представлениями для отображения полученных данных или формой/кнопкой для отправки данных в методы действия. Вы могли видеть такие бритвенные взгляды в /Views/Employeeпапке.

Теперь давайте получим список сотрудников, используя Indexметод действия.

// GET: Employees
public async Task<IActionResult> Index()
{
    return View(await _context.Employees.ToListAsync());
}

Список записей из таблицы сотрудников можно легко получить с помощью DbSetсвойства Employees, как показано выше. Коллекция записей возвращается функцией View. Таким образом, должен быть файл представления бритвы index.cshtml(то же имя, что и у метода действия индекса) для отображения коллекции записей. Мы сделали это с помощью таблицы HTML, используя foreachцикл.

@model IEnumerable<Asp.netCoreMVCCRUD.Models.Employee>

@{
    ViewData["Title"] = "Index";
}

<h4>Employee Register</h4>
<hr />
<table class="table table-hover">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.FullName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.EmpCode)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Position)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.OfficeLocation)
            </th>
            <th>
                <a asp-action="AddOrEdit" class="btn btn-outline-success"><i class="far fa-plus-square"></i> Employee</a>
            </th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.FullName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EmpCode)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Position)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.OfficeLocation)
            </td>
            <td>
                <a asp-action="AddOrEdit" asp-route-id="@item.EmployeeId"><i class="fa fa-marker fa-lg"></i></a>
                <a asp-action="Delete" asp-route-id="@item.EmployeeId" class="text-danger ml-1" onclick="return confirm('Are you sure to delete this record?')"><i class="fa fa-trash-alt fa-lg"></i></a>
            </td>
        </tr>
}
    </tbody>
</table>

Новый сотрудник может быть вставлен с помощью кнопки + из заголовка последнего столбца. Последний столбец содержит кнопки для редактирования/обновления и операции удаления. Теперь индексное представление должно выглядеть так.

Вы видели здесь навигатор? внутри нашего бритвенного представления мы только сказали показать HTML-таблицу, откуда же взялась эта навигационная панель? Это из нашего глобального представления макета Shared/_Layout.cshtml. Не только панель навигации, но и все HTML-объявление, а также окружающие его теги body и head определяются в этом представлении макета. Таким образом, по умолчанию в любом проекте MVC будет заключен весь файл представлений. К нему следует добавить ссылку на таблицы стилей для Bootstrap и Font Awesome. В большинстве случаев Bootstrap уже будет там, просто добавьте ссылку на таблицу стилей Font Awesome.

<!--  Add following stylesheets to <head> tag  -->
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css" data-rocket-async="style" as="style" onload="this.onload=null;this.rel='stylesheet'" onerror="this.removeAttribute('data-rocket-async')"  />

Добавить или изменить запись

Обычно по умолчанию будут отдельные методы для операций вставки и обновления. здесь, в этом проекте, мы объединили их в один. Прежде всего, давайте определим GETи POSTметоды для операций вставки и обновления с AddOrEditметодом действия.

// GET: Employee/AddOrEdit
public IActionResult AddOrEdit(int id = 0)
{
    if (id == 0)
        return View(new Employee());
    else
        return View(_context.Employees.Find(id));
}

// POST: Employee/AddOrEdit
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddOrEdit([Bind("EmployeeId,FullName,EmpCode,Position,OfficeLocation")] Employee employee)
{
    if (ModelState.IsValid)
    {
        if (employee.EmployeeId == 0)
            _context.Add(employee);
        else
            _context.Update(employee);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View(employee);
}

А вот наш AddOrEdit.cshtmlвзгляд на описанный выше GETметод действия.

@model Asp.netCoreMVCCRUD.Models.Employee

@{
    ViewData["Title"] = "Create";
}

<h4>Employee Form</h4>
<hr />
<div class="row">
    <div class="col-md-6">
        <form asp-action="AddOrEdit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="EmployeeId" />
            <div class="form-group">
                <label asp-for="FullName" class="control-label"></label>
                <input asp-for="FullName" class="form-control" />
                <span asp-validation-for="FullName" class="text-danger"></span>
            </div>
            <div class="form-row">
                <div class="form-group col-md-6">
                    <label asp-for="EmpCode" class="control-label"></label>
                    <input asp-for="EmpCode" class="form-control" />
                    <span asp-validation-for="EmpCode" class="text-danger"></span>
                </div>
                <div class="form-group col-md-6">
                    <label asp-for="Position" class="control-label"></label>
                    <input asp-for="Position" class="form-control" />
                    <span asp-validation-for="Position" class="text-danger"></span>
                </div>
            </div>
            <div class="form-group">
                <label asp-for="OfficeLocation" class="control-label"></label>
                <input asp-for="OfficeLocation" class="form-control" />
                <span asp-validation-for="OfficeLocation" class="text-danger"></span>
            </div>
            <div class="form-row">
                <div class="form-group col-md-6">
                    <input type="submit" value="Submit" class="btn btn-primary btn-block" />
                </div>
                <div  class="form-group col-md-6">
                    <a asp-action="Index"  class="btn btn-secondary btn-block"><i class="fa fa-table"></i> Back to List</a>
                </div>
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Это представление возвращается AddOrEditметодом действия типа GET. Метод возвращает свежий экземпляр модели сотрудника, если idпараметр равен 0. idВ противном случае возвращается соответствующая запись сотрудника с данным . Наконец, возвращенная модель заполняется внутри AdddOrEdit.cshtmlпредставления. Для операции вставки форма выглядит следующим образом.

Эта форма будет отправлена AddOrEdit​​методом действия Post. Мы обработали обе операции внутри блока if-else на основе значения idпараметра. Нам просто нужно вызвать Addили Updateметод из DbContextобъекта для операций вставки и обновления соответственно. В конце концов, вызовите метод Entity Framework SaveChangesAsync. Если операция прошла успешно, мы будем перенаправлены на Indexметод действия, чтобы вернуть список записей с последней модификацией.

Проверка формы

Внутри нашей Employeeмодели мы определили FullNameсвойство с Requiredатрибутом, что означает, что свойство должно содержать значение перед отправкой вышеуказанной формы. Entity Framework Core Validation поддерживает множество подобных атрибутов. Мы проверили статус проверки модели в GETметоде действия AddOrEditсо ModelState.IsValidсвойством.

Если есть какая-либо ошибка проверки, та же форма возвращается с той же моделью, обновленной с сообщением об ошибке проверки.

Удалить запись

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

// GET: Employee/Delete/5
public async Task<IActionResult> Delete(int? id)
{
    var employee =await _context.Employees.FindAsync(id);
    _context.Employees.Remove(employee);
    await _context.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
}

Внутри метода FindAsyncизвлекает соответствующую запись с заданной idпеременной employee, а затем вызывает Removeметод из Employeesсвойства DbSet. и не забудьте вызвать SaveChangesAsyncметод, который выполняет соответствующие команды SQL в серверной части SQL Server Engine.