Данный материал актуален для версии .NET Core 2.2
Предположим, что имеется действующее ASP.NET Core MVC приложение, готовое к запуску. Это может быть или ваш собственный проект, или пустое приложение, созданное из шаблона. Также имеется готовый настроенный MS SQL Server. Перед нами стоит задача - настроить связь между приложением и сервером, то есть чтобы все данные приложения хранились в некоторой базе данных.
Во-первых, определим доменную модель, то есть что мы будем хранить в базе данных. Например, на нашем сайте пользователи смогут публиковать и редактировать статьи. Создадим подобный класс:
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 между базой данных и доменной моделью нашего приложения.
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(). В этом месте непосредственно указывается вся конфигурация для нашего приложения, например, переадресация, кэш, сессия, маршрутизация и т.д. Нас в данном примере интересует настройки для контекста базы данных.
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. В данном примере воспользуемся первым вариантом.
Добавим новую первую миграцию с помощью команды:
add-migration _initial
Далее применим созданную миграцию и обновим базу данных. В нашем примере база данных еще не существует, и она будет создана. Применяем команду в Package Manager Console:
update-database
На данном этапе связь между веб-приложением и сервером баз данных установлена. Чтобы было удобнее работать со статьями и совершать над ними стандартные CRUD-операции (create, read, update, delete), создадим класс-репозиторий.
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(), чтобы была возможность использовать его в других классах.
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddTransient<ArticlesRepository>();
//...
}
Далее создадим новый контроллер, который и будет служить для всех операций со статьями.
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-разметка.
@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>
@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>