Связываем ASP.NET Core MVC с MS SQL Server (Entity Framework Core)

Дата публикации: 14.05.2019. Категория: ASP.NET Core MVC
Последнее обновление: 20.01.2020

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

Данный материал актуален для версии .NET Core 2.2

Предположим, что имеется действующее ASP.NET Core MVC приложение, готовое к запуску. Это может быть или ваш собственный проект, или пустое приложение, созданное из шаблона. Также имеется готовый настроенный MS SQL Server. Перед нами стоит задача - настроить связь между приложением и сервером, то есть чтобы все данные приложения хранились в некоторой базе данных.

Во-первых, определим доменную модель, то есть что мы будем хранить в базе данных. Например, на нашем сайте пользователи смогут публиковать и редактировать статьи. Создадим подобный класс:

Article.cs
public class Article
{
    //свойство Id будет служить первичным ключом в соответствующей таблице в БД
    public Guid Id { get; set; }

    [Required(ErrorMessage = "Заполните название")]
    [Display(Name = "Название")]
    public string Title { get; set; }

    [Display(Name = "Содержание")]
    public string Text { get; set; }
}

Во-вторых, нам понадобится так называемый контекст базы данных. Это специальный класс, который координирует работу Entity Framework между базой данных и доменной моделью нашего приложения.

AppDbContext.cs
public class AppDbContext : IdentityDbContext<IdentityUser>
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<Article> Articles { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Article>().HasData(new Article
        {
            Id = new Guid("716C2E99-6F6C-4472-81A5-43C56E11637C"),
            Title = "Новый спутник запущен на орбиту",
            Text = "text text"
        });
    }
}

В описанном выше классе важно следующее:

  • наш класс наследуется от класса IdentityDbContext , тем самым мы добавляем в наше приложение функциональность технологии ASP.NET Identity (система аутентификации и авторизации пользователей).
  • свойство типа DbSet<Article> позволяет соотнести объекты в приложении с соответствующими записями в таблице базы данных. Все LINQ-запросы к этому свойству будут транслироваться в SQL-запросы.
  • в переопределенном методе OnModelCreating() мы добавляем одну новую статью. Таким образом, при создании базы данных в таблицу Articles сразу будет добавлена первая запись. Данный подход иногда очень удобен, когда требуется заполнить базу данных тестовыми данными.

Далее переключаемся в класс Startup.cs, в метод ConfigureServices(). В этом месте непосредственно указывается вся конфигурация для нашего приложения, например, переадресация, кэш, сессия, маршрутизация и т.д. Нас в данном примере интересует настройки для контекста базы данных.

Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    //...
    services.AddDbContext<AppDbContext>(options => options.UseSqlServer(
        "Data Source=(local); Database=Articles; Persist Security Info=False; MultipleActiveResultSets=True; Trusted_Connection=True;"
        ));
    //...
}

В коде выше мы регистрируем наш контекст и через опции указываем, что он будет подключаться к базе данных на сервере MS SQL. Далее в опциях сервера мы определяем строку подключения к базе данных. В данном примере строка подключения означает следующее:

  • источник данных - локальный SQL-сервер.
  • название базы данных - Articles.
  • Persist Security Info=False - запрещаем получение важных данных из строки подключения после открытия соединения.
  • MultipleActiveResultSets=True - также разрешаем возможность выполнения нескольких пакетов по одному соединению (MARS).
  • Trusted_Connection=True - даем возможность при соединении использовать режим Windows-аутентификации.

* в вашем проекте строка подключения конечно же может отличаться.

К данному моменту мы определили доменную модель, создали пользовательский контекст базы данных, и также настроили его для работы. Все готово для создания первой миграции. Для работы с миграциями и вообще Entity Framework можно использовать либо командное окно в Visual Studio Package Manager Console, либо стандартный PowerShell. В данном примере воспользуемся первым вариантом.

Как найти окно Package Manager Console
Как найти окно Package Manager Console

Добавим новую первую миграцию с помощью команды:
add-migration _initial

После создания первой миграции в проекте появится папка Migrations
После создания первой миграции в проекте появится папка Migrations

Далее применим созданную миграцию и обновим базу данных. В нашем примере база данных еще не существует, и она будет создана. Применяем команду в Package Manager Console:
update-database

На данном этапе связь между веб-приложением и сервером баз данных установлена. Чтобы было удобнее работать со статьями и совершать над ними стандартные CRUD-операции (create, read, update, delete), создадим класс-репозиторий.

ArticlesRepository.cs
public class ArticlesRepository
{
    //класс-репозиторий напрямую обращается к контексту базы данных
    private readonly AppDbContext context;
    public ArticlesRepository(AppDbContext context)
    {
        this.context = context;
    }

    //выбрать все записи из таблицы Articles
    public IQueryable<Article> GetArticles()
    {
        return context.Articles.OrderBy(x => x.Title);
    }

    //найти определенную запись по id
    public Article GetArticleById(Guid id)
    {
        return context.Articles.Single(x => x.Id == id);
    }

    //сохранить новую либо обновить существующую запись в БД
    public Guid SaveArticle(Article entity)
    {
        if (entity.Id == default)
            context.Entry(entity).State = EntityState.Added;
        else
            context.Entry(entity).State = EntityState.Modified;
        context.SaveChanges();

        return entity.Id;
    }
    
    //удалить существующую запись
    public void DeleteArticle(Article entity)
    {
        context.Articles.Remove(entity);
        context.SaveChanges();
    }
}

Благодаря классу-репозиторию мы скрываем детали работы контекста базы данных. Теперь все манипуляции со статьями будут проходить только через репозиторий. Также зарегистрируем репозиторий как сервис в классе Startup.cs в методе ConfigureServices(), чтобы была возможность использовать его в других классах.

Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    //...
    services.AddTransient<ArticlesRepository>();
    //...
}

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

ArticlesController.cs
public class ArticlesController : Controller
{
    private readonly ArticlesRepository articlesRepository;
    public ArticlesController(ArticlesRepository articlesRepository)
    {
        this.articlesRepository = articlesRepository;
    }

    //выбираем все записи из БД и передаем их в представление
    public IActionResult Index()
    {
        var model = articlesRepository.GetArticles();
        return View(model);
    }

    public IActionResult ArticlesEdit(Guid id)
    {
        //либо создаем новую статью, либо выбираем существующую и передаем в качестве модели в представление
        Article model = id == default ? new Article() : articlesRepository.GetArticleById(id);
        return View(model);
    }
    [HttpPost] //в POST-версии метода сохраняем/обновляем запись в БД
    public IActionResult ArticlesEdit(Article model)
    {
        if (ModelState.IsValid)
        {
            articlesRepository.SaveArticle(model);
            return RedirectToAction("Index");
        }

        return View(model);
    }

    [HttpPost] //т.к. удаление статьи изменяет состояние приложения, нельзя использовать метод GET
    public IActionResult ArticlesDelete(Guid id)
    {
        articlesRepository.DeleteArticle(new Article() { Id = id });
        return RedirectToAction("Index");
    }
}

Последнее что остается - это создать соответствующие представления. Далее представлена простейшая HTML-разметка.

Index.cshtml
@model IQueryable<Article>

<div>
    <a asp-controller="Articles" asp-action="ArticlesEdit">Новая статья</a>
</div>
<div class="text-center">
    @foreach (Article entity in Model)
    {
        <div>
            @entity.Title
            |
            <a asp-controller="Articles" asp-action="ArticlesEdit" asp-route-id="@entity.Id">редактировать</a>
            |
            <form method="post" asp-controller="Articles" asp-action="ArticlesDelete">
                <input type="hidden" name="Id" value="@entity.Id"/>
                <input type="submit" value="удалить"/>
            </form>
        </div>
    }
</div>
ArticlesEdit.cshtml
@model Article

<div>
    <h1>Редактировать статью</h1>
    <div>
        <form asp-controller="Articles" asp-action="ArticlesEdit" method="post">
            <input type="hidden" asp-for="Id" />
            <div asp-validation-summary="All"></div>

            <div>
                <label asp-for="Title"></label>
                <input asp-for="Title" />
                <span asp-validation-for="Title"></span>
            </div>
            <div>
                <label asp-for="Text"></label>
                <textarea asp-for="Text"></textarea>
                <span asp-validation-for="Text"></span>
            </div>
            <input type="submit" value="Сохранить" />
        </form>
    </div>
</div>
Вернуться наверх
наверх