2. База данных и модели¶
Цель¶
Создать модели Users, Roles, Events, подключить EF Core и выполнить первую миграцию.
Объяснение¶
Пользователь имеет одну роль. Событие связано с пользователем и описывает действие в системе.
На этом этапе вы создаёте основу данных. Если сравнить приложение с журналом наблюдений, то таблица ActivityEvents — это страницы журнала, Users — список тех, кто выполнял действия, а Roles — права этих людей.
Связи между таблицами нужны для ответов на вопросы аналитики. Например, недостаточно знать, что произошло событие Error. Важно понимать, у какого пользователя оно произошло и какую роль он выполнял.
SQLite выбран для учебного стенда потому, что не требует отдельной установки сервера БД. При этом код EF Core остаётся почти таким же, как для PostgreSQL или MS SQL.
Код¶
Начните с доменных сущностей. Они описывают не таблицы как технические объекты, а основные понятия системы: роль, пользователя и событие активности. EF Core затем построит по этим классам структуру базы данных.
ActivityMonitoring.Domain/Entities/Role.cs:
namespace ActivityMonitoring.Domain.Entities;
public class Role
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public List<AppUser> Users { get; set; } = new();
}
Пользователь хранит имя, признак активности и ссылку на роль. Навигационное свойство Events нужно, чтобы из пользователя можно было получить связанные события, а через RoleId EF Core создаст связь с таблицей ролей.
ActivityMonitoring.Domain/Entities/AppUser.cs:
namespace ActivityMonitoring.Domain.Entities;
public class AppUser
{
public int Id { get; set; }
public string UserName { get; set; } = string.Empty;
public string PasswordHash { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public int RoleId { get; set; }
public Role Role { get; set; } = null!;
public List<ActivityEvent> Events { get; set; } = new();
}
Событие активности — центральная запись журнала. В ней фиксируется, кто выполнил действие, откуда оно пришло, насколько оно важно и когда произошло. Эти поля дальше будут использоваться в фильтрах, аналитике и CSV-отчётах.
ActivityMonitoring.Domain/Entities/ActivityEvent.cs:
namespace ActivityMonitoring.Domain.Entities;
public class ActivityEvent
{
public int Id { get; set; }
public int UserId { get; set; }
public AppUser User { get; set; } = null!;
public string Type { get; set; } = string.Empty;
public string Source { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Severity { get; set; } = "Info";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
После описания сущностей нужен контекст EF Core. AppDbContext связывает классы с таблицами, задаёт ограничения и добавляет стартовые данные, чтобы приложение можно было проверить сразу после первой миграции.
ActivityMonitoring.Infrastructure/Persistence/AppDbContext.cs:
using ActivityMonitoring.Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace ActivityMonitoring.Infrastructure.Persistence;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Role> Roles => Set<Role>();
public DbSet<AppUser> Users => Set<AppUser>();
public DbSet<ActivityEvent> ActivityEvents => Set<ActivityEvent>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Role>().HasIndex(x => x.Name).IsUnique();
modelBuilder.Entity<AppUser>().HasIndex(x => x.UserName).IsUnique();
modelBuilder.Entity<ActivityEvent>()
.Property(x => x.Description)
.HasMaxLength(500);
modelBuilder.Entity<Role>().HasData(
new Role { Id = 1, Name = "Admin" },
new Role { Id = 2, Name = "Analyst" },
new Role { Id = 3, Name = "Operator" });
modelBuilder.Entity<AppUser>().HasData(
new AppUser { Id = 1, UserName = "admin", PasswordHash = "Admin123!", RoleId = 1 },
new AppUser { Id = 2, UserName = "analyst", PasswordHash = "Analyst123!", RoleId = 2 },
new AppUser { Id = 3, UserName = "operator", PasswordHash = "Operator123!", RoleId = 3 });
}
}
Для учебной практики пароль временно хранится открыто, чтобы не усложнять первый запуск. На этапе авторизации пароль будет заменён на хэш.
В рабочей системе так делать нельзя. Пароль должен храниться только в виде хэша. Здесь открытое значение используется как промежуточный учебный шаг, чтобы сначала проверить таблицы, миграции и входные данные.
Далее добавьте настройки приложения. Строка подключения указывает, где лежит SQLite-файл, а секция Jwt заранее готовит параметры, которые будут использованы на этапе авторизации.
ActivityMonitoring.Api/appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=activity-monitoring.db"
},
"Jwt": {
"Issuer": "ActivityMonitoring",
"Audience": "ActivityMonitoringClient",
"Key": "development-secret-key-change-it-please-123456"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Теперь подключите контекст к API. Именно startup-проект создаёт DI-контейнер и читает конфигурацию, поэтому регистрация AppDbContext выполняется в Program.cs проекта ActivityMonitoring.Api.
ActivityMonitoring.Api/Program.cs:
using ActivityMonitoring.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapControllers();
app.Run();
Когда модели и контекст готовы, создайте миграцию и примените её к базе. Первая команда фиксирует схему в коде миграции, вторая физически создаёт таблицы и стартовые записи в SQLite.
dotnet ef migrations add InitialCreate --project ActivityMonitoring.Infrastructure --startup-project ActivityMonitoring.Api
dotnet ef database update --project ActivityMonitoring.Infrastructure --startup-project ActivityMonitoring.Api
Результат¶
В папке API появляется файл activity-monitoring.db. База содержит таблицы Roles, Users, ActivityEvents.
Проверка этапа:
- Есть папка
Migrationsв проектеInfrastructure. - Команда
database updateзавершилась без ошибки. - Приложение запускается.
- В базе есть стартовые роли
Admin,Analyst,Operator.
Если появляется ошибка Unable to create a DbContext, проверьте, что строка подключения находится в appsettings.json проекта ActivityMonitoring.Api, потому что именно API является startup-проектом.