Загрузка данных
using Microsoft.Data.Sqlite;
using System.Data;
const string CS = "Data Source=bookstore.db";
// ============================================================
// 1. INIT DB (Topic: DbCommand, ExecuteNonQuery)
// ============================================================
using (var conn = new SqliteConnection(CS))
{
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandText = @"
CREATE TABLE IF NOT EXISTS Books (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Title TEXT, AuthorName TEXT, Genre TEXT,
PublicationYear INTEGER, CostPrice REAL, RetailPrice REAL,
IsSequel INTEGER, Stock INTEGER);
CREATE TABLE IF NOT EXISTS Sales (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
BookId INTEGER, Date TEXT, Quantity INTEGER, UnitPrice REAL);
CREATE TABLE IF NOT EXISTS Promotions (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT, DiscountPercent REAL, EndDate TEXT);
CREATE TABLE IF NOT EXISTS Reservations (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
BookId INTEGER, BuyerName TEXT, Quantity INTEGER);";
cmd.ExecuteNonQuery();
}
// ============================================================
// 2. AUTH (Topic: конфигурационные файлы — hard-coded here)
// ============================================================
Console.WriteLine("=== Bookstore Manager ===");
while (true)
{
Console.Write("Username: ");
var u = Console.ReadLine();
Console.Write("Password: ");
var p = ReadPassword();
if (u == "admin" && p == "admin123") break;
Console.WriteLine("Invalid. Try admin / admin123\n");
}
// ============================================================
// 3. MENU
// ============================================================
while (true)
{
Console.WriteLine(@"
=== MENU ===
1. List inventory
2. Add book
3. Delete book
4. Edit price
5. Sell book (transaction)
6. Write off book
7. Create promotion
8. Reserve for buyer
9. Disconnected demo (DataSet / DataAdapter)
10. Search
11. Analytics
0. Exit
");
Console.Write("Choice: ");
try
{
switch (Console.ReadLine())
{
case "1": List(); break;
case "2": Add(); break;
case "3": Delete(); break;
case "4": EditPrice(); break;
case "5": Sell(); break;
case "6": WriteOff(); break;
case "7": Promo(); break;
case "8": Reserve(); break;
case "9": DisconnectedDemo(); break;
case "10": Search(); break;
case "11": Analytics(); break;
case "0": return;
}
}
catch (Exception ex) { Console.WriteLine($"ERROR: {ex.Message}"); }
}
// ============================================================
// HELPERS
// ============================================================
static string ReadPassword()
{
var s = "";
ConsoleKeyInfo k;
while ((k = Console.ReadKey(true)).Key != ConsoleKey.Enter)
{
if (k.Key == ConsoleKey.Backspace && s.Length > 0) s = s[..^1];
else if (k.Key != ConsoleKey.Backspace) s += k.KeyChar;
}
Console.WriteLine();
return s;
}
static int ReadInt(string msg) { Console.Write(msg); return int.TryParse(Console.ReadLine(), out var n) ? n : 0; }
static decimal ReadDec(string msg) { Console.Write(msg); return decimal.TryParse(Console.ReadLine(), out var n) ? n : 0m; }
static SqliteConnection Open()
{
var c = new SqliteConnection(CS);
c.Open();
return c;
}
// ============================================================
// 1. LIST (Topic: DbDataReader)
// ============================================================
static void List()
{
using var conn = Open();
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT Id, Title, AuthorName, Genre, PublicationYear, Stock, RetailPrice FROM Books";
using var r = cmd.ExecuteReader();
Console.WriteLine($"\n{"ID",-4} {"Title",-30} {"Author",-18} {"Genre",-10} {"Year",-6} {"Stock",-6} {"Price",8}");
while (r.Read())
Console.WriteLine($"{r.GetInt32(0),-4} {r.GetString(1),-30} {r.GetString(2),-18} " +
$"{r.GetString(3),-10} {r.GetInt32(4),-6} {r.GetInt32(5),-6} ${r.GetDecimal(6),7:F2}");
}
// ============================================================
// 2. ADD (Topic: DbParameter)
// ============================================================
static void Add()
{
Console.Write("Title: "); var title = Console.ReadLine() ?? "";
Console.Write("Author: "); var author = Console.ReadLine() ?? "";
Console.Write("Genre: "); var genre = Console.ReadLine() ?? "";
var year = ReadInt("Year: ");
var cost = ReadDec("Cost price: ");
var retail = ReadDec("Retail price: ");
Console.Write("Sequel (y/N): "); var sequel = Console.ReadLine()?.ToLower() == "y" ? 1 : 0;
var stock = ReadInt("Stock: ");
using var conn = Open();
var cmd = conn.CreateCommand();
cmd.CommandText = @"INSERT INTO Books
(Title, AuthorName, Genre, PublicationYear, CostPrice, RetailPrice, IsSequel, Stock)
VALUES (@t, @a, @g, @y, @c, @r, @s, @st)";
cmd.Parameters.AddWithValue("@t", title);
cmd.Parameters.AddWithValue("@a", author);
cmd.Parameters.AddWithValue("@g", genre);
cmd.Parameters.AddWithValue("@y", year);
cmd.Parameters.AddWithValue("@c", cost);
cmd.Parameters.AddWithValue("@r", retail);
cmd.Parameters.AddWithValue("@s", sequel);
cmd.Parameters.AddWithValue("@st", stock);
Console.WriteLine($"Added. Rows affected: {cmd.ExecuteNonQuery()}");
}
// ============================================================
// 3. DELETE
// ============================================================
static void Delete()
{
var id = ReadInt("Book ID to delete: ");
using var conn = Open();
var cmd = conn.CreateCommand();
cmd.CommandText = "DELETE FROM Books WHERE Id = @id";
cmd.Parameters.AddWithValue("@id", id);
Console.WriteLine($"Deleted. Rows affected: {cmd.ExecuteNonQuery()}");
}
// ============================================================
// 4. EDIT PRICE
// ============================================================
static void EditPrice()
{
var id = ReadInt("Book ID: ");
var price = ReadDec("New retail price: ");
using var conn = Open();
var cmd = conn.CreateCommand();
cmd.CommandText = "UPDATE Books SET RetailPrice = @p WHERE Id = @id";
cmd.Parameters.AddWithValue("@p", price);
cmd.Parameters.AddWithValue("@id", id);
Console.WriteLine($"Updated. Rows affected: {cmd.ExecuteNonQuery()}");
}
// ============================================================
// 5. SELL (Topic: DbTransaction)
// ============================================================
static void Sell()
{
var id = ReadInt("Book ID: ");
var qty = ReadInt("Quantity: ");
using var conn = Open();
using var tx = conn.BeginTransaction();
try
{
// Check stock
var check = conn.CreateCommand();
check.Transaction = tx;
check.CommandText = "SELECT Stock, RetailPrice FROM Books WHERE Id = @id";
check.Parameters.AddWithValue("@id", id);
using (var r = check.ExecuteReader())
{
if (!r.Read()) { Console.WriteLine("Book not found."); return; }
var stock = r.GetInt32(0);
var price = r.GetDecimal(1);
if (stock < qty) { Console.WriteLine("Not enough stock."); return; }
r.Close();
// Decrement stock
var upd = conn.CreateCommand();
upd.Transaction = tx;
upd.CommandText = "UPDATE Books SET Stock = Stock - @q WHERE Id = @id";
upd.Parameters.AddWithValue("@q", qty);
upd.Parameters.AddWithValue("@id", id);
upd.ExecuteNonQuery();
// Insert sale
var ins = conn.CreateCommand();
ins.Transaction = tx;
ins.CommandText = @"INSERT INTO Sales (BookId, Date, Quantity, UnitPrice)
VALUES (@b, @d, @q, @p)";
ins.Parameters.AddWithValue("@b", id);
ins.Parameters.AddWithValue("@d", DateTime.UtcNow.ToString("o"));
ins.Parameters.AddWithValue("@q", qty);
ins.Parameters.AddWithValue("@p", price);
ins.ExecuteNonQuery();
tx.Commit();
Console.WriteLine($"Sold {qty} × '{id}' for ${price * qty:F2}");
}
}
catch { tx.Rollback(); throw; }
}
// ============================================================
// 6. WRITE OFF
// ============================================================
static void WriteOff()
{
var id = ReadInt("Book ID: ");
var qty = ReadInt("Quantity to write off: ");
using var conn = Open();
var cmd = conn.CreateCommand();
cmd.CommandText = "UPDATE Books SET Stock = Stock - @q WHERE Id = @id AND Stock >= @q";
cmd.Parameters.AddWithValue("@q", qty);
cmd.Parameters.AddWithValue("@id", id);
Console.WriteLine($"Written off. Rows affected: {cmd.ExecuteNonQuery()}");
}
// ============================================================
// 7. CREATE PROMOTION
// ============================================================
static void Promo()
{
Console.Write("Promotion name: "); var name = Console.ReadLine() ?? "Promo";
var disc = ReadDec("Discount %: ");
var days = ReadInt("Duration (days): ");
using var conn = Open();
var ins = conn.CreateCommand();
ins.CommandText = @"INSERT INTO Promotions (Name, DiscountPercent, EndDate)
VALUES (@n, @d, @e); SELECT last_insert_rowid();";
ins.Parameters.AddWithValue("@n", name);
ins.Parameters.AddWithValue("@d", disc);
ins.Parameters.AddWithValue("@e", DateTime.UtcNow.AddDays(days).ToString("o"));
var promoId = (long)ins.ExecuteScalar()!;
Console.WriteLine("Enter book IDs to include (empty line to finish):");
var ids = new List<int>();
while (true)
{
Console.Write("Book ID: ");
var line = Console.ReadLine();
if (string.IsNullOrWhiteSpace(line)) break;
if (int.TryParse(line, out var id)) ids.Add(id);
}
foreach (var id in ids)
{
var upd = conn.CreateCommand();
upd.CommandText = @"UPDATE Books
SET RetailPrice = RetailPrice * (1 - @d/100.0)
WHERE Id = @id";
upd.Parameters.AddWithValue("@d", disc);
upd.Parameters.AddWithValue("@id", id);
upd.ExecuteNonQuery();
}
Console.WriteLine($"Promotion #{promoId} applied to {ids.Count} book(s).");
}
// ============================================================
// 8. RESERVE
// ============================================================
static void Reserve()
{
var id = ReadInt("Book ID: ");
Console.Write("Buyer name: "); var buyer = Console.ReadLine() ?? "";
var qty = ReadInt("Quantity: ");
using var conn = Open();
using var tx = conn.BeginTransaction();
var upd = conn.CreateCommand();
upd.Transaction = tx;
upd.CommandText = "UPDATE Books SET Stock = Stock - @q WHERE Id = @id AND Stock >= @q";
upd.Parameters.AddWithValue("@q", qty);
upd.Parameters.AddWithValue("@id", id);
if (upd.ExecuteNonQuery() == 0) { Console.WriteLine("Not enough stock."); return; }
var ins = conn.CreateCommand();
ins.Transaction = tx;
ins.CommandText = "INSERT INTO Reservations (BookId, BuyerName, Quantity) VALUES (@b, @n, @q)";
ins.Parameters.AddWithValue("@b", id);
ins.Parameters.AddWithValue("@n", buyer);
ins.Parameters.AddWithValue("@q", qty);
ins.ExecuteNonQuery();
tx.Commit();
Console.WriteLine($"Reserved {qty} for {buyer}.");
}
// ============================================================
// 9. DISCONNECTED DEMO
// (Topic: DataSet, DataTable, DataRow, DataColumn,
// DbDataAdapter, SqlCommandBuilder)
// ============================================================
static void DisconnectedDemo()
{
using var conn = Open();
// DbDataAdapter → fills DataTable in memory (connection can close after)
var adapter = new SqliteDataAdapter("SELECT Id, Title, AuthorName, Genre, PublicationYear, Stock, RetailPrice FROM Books", conn);
// SqlCommandBuilder auto-generates INSERT/UPDATE/DELETE for adapter.Update()
var builder = new SqliteCommandBuilder(adapter);
var ds = new DataSet("Bookstore");
adapter.Fill(ds, "Books");
var table = ds.Tables["Books"]!;
Console.WriteLine($"\nLoaded {table.Rows.Count} rows (disconnected).");
// Inspect columns
Console.WriteLine("\nColumns:");
foreach (DataColumn c in table.Columns)
Console.WriteLine($" {c.ColumnName,-20} {c.DataType.Name}");
// Modify a row (in-memory)
if (table.Rows.Count > 0)
{
var row = table.Rows[0];
Console.WriteLine($"\nFirst row before: {row["Title"]} / stock {row["Stock"]}");
row["Stock"] = (int)row["Stock"]! + 1;
Console.WriteLine($"First row after: {row["Title"]} / stock {row["Stock"]}");
}
// Add a row (in-memory)
var newRow = table.NewRow();
newRow["Title"] = "Disconnected Demo Book";
newRow["AuthorName"] = "Nobody";
newRow["Genre"] = "Demo";
newRow["PublicationYear"] = DateTime.UtcNow.Year;
newRow["Stock"] = 1;
newRow["RetailPrice"] = 9.99m;
table.Rows.Add(newRow);
Console.WriteLine($"\nAdded a row in memory. Total rows: {table.Rows.Count}");
// Push changes back to the DB
int changed = adapter.Update(ds, "Books");
Console.WriteLine($"\nDataAdapter.Update() pushed {changed} change(s) back to DB.");
}
// ============================================================
// 10. SEARCH
// ============================================================
static void Search()
{
Console.Write("Search by (title/author/genre): ");
var field = Console.ReadLine()?.ToLower() ?? "title";
var col = field switch { "author" => "AuthorName", "genre" => "Genre", _ => "Title" };
Console.Write("Query: ");
var q = Console.ReadLine() ?? "";
using var conn = Open();
var cmd = conn.CreateCommand();
cmd.CommandText = $"SELECT Id, Title, AuthorName, Genre, RetailPrice FROM Books WHERE {col} LIKE @q";
cmd.Parameters.AddWithValue("@q", $"%{q}%");
using var r = cmd.ExecuteReader();
while (r.Read())
Console.WriteLine($"{r.GetInt32(0),-4} {r.GetString(1),-30} {r.GetString(2),-18} " +
$"{r.GetString(3),-10} ${r.GetDecimal(4),7:F2}");
}
// ============================================================
// 11. ANALYTICS (Topic: LINQ over DataSet, group by, join)
// ============================================================
static void Analytics()
{
Console.Write("Period in days (1/7/30/365): ");
var days = int.TryParse(Console.ReadLine(), out var d) ? d : 30;
var cutoff = DateTime.UtcNow.AddDays(-days).ToString("o");
using var conn = Open();
// Best sellers
Console.WriteLine($"\n=== Best sellers (last {days} days) ===");
var bs = conn.CreateCommand();
bs.CommandText = @"SELECT b.Title, SUM(s.Quantity) AS Sold
FROM Sales s JOIN Books b ON b.Id = s.BookId
WHERE s.Date >= @c
GROUP BY b.Id ORDER BY Sold DESC LIMIT 5";
bs.Parameters.AddWithValue("@c", cutoff);
using (var r = bs.ExecuteReader())
while (r.Read())
Console.WriteLine($" {r.GetString(0),-30} {r.GetInt32(1)} sold");
// Top authors
Console.WriteLine($"\n=== Top authors ===");
var ta = conn.CreateCommand();
ta.CommandText = @"SELECT b.AuthorName, SUM(s.Quantity) AS Sold
FROM Sales s JOIN Books b ON b.Id = s.BookId
WHERE s.Date >= @c
GROUP BY b.AuthorName ORDER BY Sold DESC LIMIT 5";
ta.Parameters.AddWithValue("@c", cutoff);
using (var r = ta.ExecuteReader())
while (r.Read())
Console.WriteLine($" {r.GetString(0),-30} {r.GetInt32(1)} sold");
// Top genres
Console.WriteLine($"\n=== Top genres ===");
var tg = conn.CreateCommand();
tg.CommandText = @"SELECT b.Genre, SUM(s.Quantity) AS Sold
FROM Sales s JOIN Books b ON b.Id = s.BookId
WHERE s.Date >= @c
GROUP BY b.Genre ORDER BY Sold DESC LIMIT 5";
tg.Parameters.AddWithValue("@c", cutoff);
using (var r = tg.ExecuteReader())
while (r.Read())
Console.WriteLine($" {r.GetString(0),-30} {r.GetInt32(1)} sold");
}