Система управления библиотекой книг на C#
Приветствую! Здесь вы наверняка найдете, что ищете. Примеры в лаборатории рассчитаны на то, что мы разбираем что-то конкретное.
Текущая статья посвящена учебной системе управления библиотекой на C# — сущности, CRUD и слои приложения.
Поэтому за теорией по текущей теме вам — в энциклопедию. Если ещё не погружались, то маршрут прост:
- Основы
- Система и сеть
- Данные и разметка
- Код и разработка
- Языки
- Искусственный интеллект
- Проект
- Инфраструктура и безопасность
- Спин-офф
Обязательно пройдитесь.
А теперь приступим к нашему предмету.
Система управления библиотекой книг на C#
Система управления библиотекой книг — это классический учебный проект, который демонстрирует применение ключевых концепций современной разработки на платформе .NET. В данном примере реализуется полноценное веб-приложение с использованием ASP.NET Core, Entity Framework Core и LINQ. Такая система позволяет управлять каталогом книг, отслеживать выдачу экземпляров читателям, контролировать сроки возврата и формировать отчёты.
Проект охватывает такие аспекты, как:
- проектирование доменной модели;
- работа с реляционной базой данных через ORM;
- реализация CRUD-операций (Create, Read, Update, Delete);
- применение шаблонов MVC и Repository;
- использование LINQ для фильтрации, сортировки и агрегации данных;
- построение RESTful API или серверного рендеринга представлений;
- обеспечение согласованности данных и обработка ошибок.
Доменная модель
Доменная модель — это набор классов, отражающих сущности предметной области и их взаимосвязи. В системе управления библиотекой можно выделить следующие основные сущности:
Book— книга в каталоге;Author— автор книги;Genre— жанр книги;Reader— читатель (пользователь библиотеки);Loan— запись о выдаче книги читателю.
Каждая сущность представляет собой отдельный класс с набором свойств и связей.
Класс Book
Класс Author
public class Author
{
public int Id { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public ICollection<Book> Books { get; set; } = new List<Book>();
}
Класс Genre
public class Genre
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public ICollection<Book> Books { get; set; } = new List<Book>();
}
Класс Reader
public class Reader
{
public int Id { get; set; }
public string FullName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public DateTime RegistrationDate { get; set; }
public ICollection<Loan> Loans { get; set; } = new List<Loan>();
}
Класс Loan
Эти классы образуют граф объектов, где связи между сущностями выражены через навигационные свойства (Author, Genre, Loans и т.д.). Такая структура позволяет легко переходить от одной сущности к связанной без написания дополнительных SQL-запросов.
Настройка Entity Framework Core
Entity Framework Core (EF Core) — это объектно-реляционный маппер (ORM), входящий в экосистему .NET. Он позволяет работать с базой данных через объекты C#, автоматически генерируя SQL-запросы.
Контекст базы данных
Контекст — это основной класс EF Core, представляющий сессию работы с базой данных. Он наследуется от DbContext и содержит DbSet<T> для каждой сущности.
Метод OnModelCreating используется для явного указания связей между таблицами. Хотя EF Core может вывести многие связи автоматически, явная конфигурация повышает надёжность и читаемость кода.
Реализация CRUD-операций
CRUD-операции — это базовые действия: создание, чтение, обновление и удаление записей. В ASP.NET Core они обычно реализуются в контроллерах.
Пример контроллера книг
Ключевые моменты:
- Использование
Include()для загрузки связанных данных (автора и жанра); - Асинхронные методы (
async/await) для эффективной работы с I/O; - Обработка ошибок:
NotFound(),BadRequest(), исключения параллелизма.
Работа с данными через LINQ
LINQ (Language Integrated Query) — это мощный механизм языка C# для запросов к коллекциям и базам данных. В контексте EF Core LINQ-выражения транслируются в SQL.
Примеры практических запросов
Поиск всех невозвращённых книг
var overdueLoans = await _context.Loans
.Where(l => !l.IsReturned)
.Include(l => l.Book)
.Include(l => l.Reader)
.ToListAsync();
Книги, выданные конкретному читателю
var loansByReader = await _context.Loans
.Where(l => l.ReaderId == readerId && !l.IsReturned)
.Select(l => l.Book)
.ToListAsync();
Подсчёт количества книг по жанрам
var genreCounts = await _context.Books
.GroupBy(b => b.Genre.Name)
.Select(g => new { Genre = g.Key, Count = g.Count() })
.ToListAsync();
Поиск книг по ключевому слову в названии
var books = await _context.Books
.Where(b => b.Title.Contains(searchTerm))
.Include(b => b.Author)
.ToListAsync();
Самые популярные книги (по количеству выдач)
var popularBooks = await _context.Books
.Select(b => new
{
Book = b,
LoanCount = b.Loans.Count
})
.OrderByDescending(x => x.LoanCount)
.Take(10)
.ToListAsync();
LINQ делает код декларативным: вместо описания как получить данные, разработчик описывает что нужно получить. Это повышает читаемость и снижает вероятность ошибок.
Архитектурные соображения
Хотя прямое использование DbContext в контроллере допустимо для простых приложений, в реальных проектах рекомендуется применять паттерны:
Паттерн Repository
Создаётся интерфейс IBookRepository и его реализация BookRepository, инкапсулирующая все операции с книгами. Это упрощает тестирование и замену реализации.
public interface IBookRepository
{
Task<IEnumerable<Book>> GetAllAsync();
Task<Book?> GetByIdAsync(int id);
Task AddAsync(Book book);
Task UpdateAsync(Book book);
Task DeleteAsync(int id);
}
Dependency Injection
ASP.NET Core поддерживает внедрение зависимостей "из коробки". Репозитории регистрируются в контейнере:
// Program.cs
builder.Services.AddDbContext<LibraryContext>(/* .. */);
builder.Services.AddScoped<IBookRepository, BookRepository>();
Контроллер получает репозиторий через конструктор:
public BooksController(IBookRepository repository)
{
_repository = repository;
}
Такой подход делает код более модульным и тестируемым.
Обработка бизнес-логики
Простое хранение данных — лишь часть системы. Библиотека требует реализации бизнес-правил:
- одна книга не может быть выдана двум читателям одновременно;
- читатель не может взять больше N книг;
- при возврате книги обновляется статус выдачи.
Эти правила лучше выносить в отдельные сервисы.
Пример сервиса выдачи
Здесь Result — пользовательский тип, инкапсулирующий успешный результат или ошибку (паттерн "Railway Oriented Programming").
Валидация и безопасность
Важно проверять входные данные:
- ISBN должен соответствовать формату;
- год издания не может быть в будущем;
- email читателя должен быть валидным.
Для этого используются атрибуты валидации:
public class Book
{
[Required, MaxLength(200)]
public string Title { get; set; } = string.Empty;
[Range(1000, 3000)]
public int Year { get; set; }
[RegularExpression(@"^\d{10}(\d{3})?$")]
public string ISBN { get; set; } = string.Empty;
}
В контроллере проверяется состояние модели:
if (!ModelState.IsValid)
return BadRequest(ModelState);
Также необходимо защищать API от SQL-инъекций, но EF Core автоматически параметризует все запросы, поэтому риск минимален при использовании LINQ.
Тестирование
Проект следует покрывать unit- и integration-тестами. Например, тест выдачи книги:
Использование in-memory базы данных (UseInMemoryDatabase) ускоряет выполнение тестов.
Расширение функциональности
Систему можно развивать в разных направлениях:
- добавление аутентификации и ролей (библиотекарь, читатель);
- экспорт отчётов в PDF или Excel;
- интеграция с внешними API (например, OpenLibrary для автозаполнения данных по ISBN);
- фоновые задачи для уведомления о просроченных книгах;
- кэширование часто запрашиваемых данных;
- локализация интерфейса.
Дополнительные возможности и расширения
Система управления библиотекой книг предоставляет широкие возможности для расширения функциональности. Ниже приведены направления, в которых можно развивать проект, сохраняя его архитектурную целостность.
Поддержка нескольких экземпляров одной книги
В текущей модели каждая запись Book представляет один экземпляр. Однако в реальной библиотеке может быть несколько копий одного издания. Для поддержки этого сценария вводится новая сущность — BookCopy.
public class BookCopy
{
public int Id { get; set; }
public int BookId { get; set; }
public Book Book { get; set; } = null!;
public string InventoryNumber { get; set; } = Guid.NewGuidToString();
public bool IsAvailable { get; set; } = true;
public ICollection<Loan> Loans { get; set; } = new List<Loan>();
}
Теперь выдача книги происходит не по BookId, а по BookCopyId. Это позволяет точно отслеживать физическое состояние каждого экземпляра (например, повреждён ли он), а также управлять доступностью отдельных копий.
Уведомления о просроченных книгах
Библиотека может автоматически отправлять напоминания читателям, не вернувшим книги в срок. Реализация требует:
- фоновой службы (hosted service);
- интерфейса уведомлений (
INotificationService); - логики проверки просрочки.
Регистрация фоновой службы в Program.cs:
builder.Services.AddHostedService<OverdueNotificationService>();
Такой подход демонстрирует применение шаблона Background Service, который является стандартным способом выполнения периодических задач в ASP.NET Core.
Поиск по ISBN и автору через API
Для удобства пользователей добавляются специализированные методы поиска:
Этот эндпоинт позволяет гибко комбинировать параметры поиска и возвращает только релевантные результаты.
Локализация и интернационализация
Если система предназначена для международного использования, следует реализовать поддержку нескольких языков. ASP.NET Core предоставляет встроенные механизмы локализации через:
- файлы ресурсов (
.resx); - middleware
RequestLocalization; - атрибуты
[Display(Name = "..")]с ключами.
Пример настройки:
// Program.cs
var supportedCultures = new[] { "ru", "en" };
var localizationOptions = new RequestLocalizationOptionsSetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
После этого все строки интерфейса (например, названия полей в формах) могут загружаться из соответствующих .resx-файлов.
Безопасность и производительность
Защита от overposting
При использовании автоматической привязки модели ([FromBody] Book book) существует риск overposting — когда клиент отправляет поля, которые не должны обновляться (например, Id или AuthorId). Решение — использовать DTO (Data Transfer Object).
public class CreateBookDto
{
public string Title { get; set; } = string.Empty;
public int Year { get; set; }
public string ISBN { get; set; } = string.Empty;
public int AuthorId { get; set; }
public int GenreId { get; set; }
}
Контроллер работает только с этим DTO, а маппинг в сущность Book выполняется явно:
var book = new Book
{
Title = dto.Title,
Year = dto.Year,
ISBN = dto.ISBN,
AuthorId = dto.AuthorId,
GenreId = dto.GenreId
};
Это исключает возможность несанкционированного изменения внутренних полей.
Кэширование часто запрашиваемых данных
Если каталог книг редко изменяется, но часто читается, имеет смысл кэшировать результаты. ASP.NET Core предлагает несколько уровней кэширования:
- In-memory cache — для одного сервера;
- Distributed cache (Redis, SQL Server) — для масштабируемых систем.
Пример использования in-memory кэша:
Регистрация кэша:
builder.Services.AddMemoryCache();
Кэширование значительно снижает нагрузку на базу данных при высокой частоте запросов.
Развертывание и мониторинг
Конфигурация через appsettings.json
Параметры подключения к базе данных, сроки выдачи книг, email-сервера — всё это должно храниться в конфигурационных файлах, а не быть зашито в код.
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=LibraryDb;Trusted_Connection=true;"
},
"LibrarySettings": {
"LoanPeriodDays": 14,
"MaxBooksPerReader": 5
}
}
Использование в коде:
var loanPeriod = configuration.GetValue<int>("LibrarySettings:LoanPeriodDays");
Это позволяет легко адаптировать приложение под разные окружения (разработка, тестирование, продакшн).
Логирование
ASP.NET Core интегрирован с системой логирования. В контроллерах можно внедрять ILogger<T>:
public BooksController(LibraryContext context, ILogger<BooksController> logger)
{
_context = context;
_logger = logger;
}
[HttpPost]
public async Task<ActionResult<Book>> CreateBook(Book book)
{
_logger.LogInformation("Creating a new book: {Title}", book.Title);
// ..
}
Логи могут отправляться в файлы, консоль, или внешние системы (например, Elasticsearch + Kibana).
Health Checks
Для мониторинга работоспособности приложения ASP.NET Core предоставляет механизм health checks:
// Program.cs
builder.Services.AddHealthChecksAddDbContextCheck<LibraryContext>();
app.MapHealthChecks("/health");
Запрос к /health вернёт Healthy, если база данных доступна, и Unhealthy в противном случае. Это особенно полезно при использовании оркестраторов вроде Kubernetes.