/
ilyakos
/
ChatWithAI
Обзор
Документация
Войти
/
ilyakos
/
ChatWithAI
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
ChatWithAI.Database/AppDbContext.cs
102 строки
5 KB
ilyakos
feat: Добавляет сущности Prompt, Chat, Message с репозиториями
23 май 2026, 16:07
23 май 2026, 16:07
9fd07e3
Код
Авторство
О чём код?
using System.Linq.Expressions; using ChatWithAI.Database.Models; using Microsoft.EntityFrameworkCore; namespace ChatWithAI.Database; /// <summary> /// Контекст базы данных приложения. /// </summary> /// <remarks> /// <para> /// <b>Мягкое удаление (soft delete):</b> для всех сущностей, наследующих <see cref="BaseEntity"/>, /// автоматически применяется глобальный фильтр <c>WHERE "IsDeleted" = false</c>. /// Это означает, что удалённые записи невидимы для любых стандартных запросов EF Core — /// <c>Find</c>, <c>FirstOrDefault</c>, <c>Where</c>, <c>ToList</c> и т.д. /// </para> /// <para> /// <b>Партиальные индексы PostgreSQL:</b> уникальные ограничения на Email и Username у <see cref="User"/> /// реализованы как partial unique index с условием <c>WHERE "IsDeleted" = false</c>. /// Это позволяет повторно использовать Email/Username после мягкого удаления аккаунта /// и значительно уменьшает размер индекса, исключая из него удалённые строки. /// </para> /// <para> /// <b>Доступ к удалённым записям</b> (при необходимости аудита): /// <code> /// db.Users.IgnoreQueryFilters().Where(u => u.IsDeleted).ToList() /// </code> /// </para> /// </remarks> public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options) { /// <summary>Таблица пользователей.</summary> public DbSet<User> Users => Set<User>(); /// <summary>Таблица промтов.</summary> public DbSet<Prompt> Prompts => Set<Prompt>(); /// <summary>Таблица чатов.</summary> public DbSet<Chat> Chats => Set<Chat>(); /// <summary>Таблица сообщений.</summary> public DbSet<Message> Messages => Set<Message>(); /// <summary> /// Перехватывает физическое удаление сущностей <see cref="BaseEntity"/> и заменяет его мягким: /// вместо <c>DELETE</c> выставляет <c>IsDeleted = true</c> и <c>DeletedAt = UtcNow</c>. /// Защищает от случайного физического удаления при вызове <c>db.Remove()</c> напрямую. /// </summary> public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { foreach (var entry in ChangeTracker.Entries<BaseEntity>() .Where(e => e.State == EntityState.Deleted)) { entry.State = EntityState.Modified; entry.Entity.IsDeleted = true; entry.Entity.DeletedAt = DateTime.UtcNow; } return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } /// <inheritdoc/> protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); ApplySoftDeleteFilters(modelBuilder); modelBuilder.Entity<User>(entity => { // Партиальный уникальный индекс: Email уникален только среди активных пользователей. // Удалённые аккаунты не попадают в индекс — Email можно зарегистрировать повторно. entity.HasIndex(u => u.Email) .IsUnique() .HasFilter($"\"{nameof(BaseEntity.IsDeleted)}\" = false"); // Аналогично для Username. entity.HasIndex(u => u.Username) .IsUnique() .HasFilter($"\"{nameof(BaseEntity.IsDeleted)}\" = false"); }); } /// <summary> /// Регистрирует глобальный фильтр <c>IsDeleted = false</c> для каждой сущности, /// унаследованной от <see cref="BaseEntity"/>. /// Вызывается один раз при построении модели. /// </summary> private static void ApplySoftDeleteFilters(ModelBuilder modelBuilder) { foreach (var entityType in modelBuilder.Model.GetEntityTypes() .Where(t => typeof(BaseEntity).IsAssignableFrom(t.ClrType))) { var param = Expression.Parameter(entityType.ClrType, "e"); var isDeletedFalse = Expression.Lambda( Expression.Not(Expression.Property(param, nameof(BaseEntity.IsDeleted))), param); modelBuilder.Entity(entityType.ClrType).HasQueryFilter(isDeletedFalse); } } }