DatabaseClient.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. using Microsoft.Data.SqlClient;
  2. using System.Data;
  3. namespace ToyStoreApp;
  4. public sealed class DatabaseClient : IDisposable
  5. {
  6. private readonly string _connectionString;
  7. public DatabaseClient(string connectionString)
  8. {
  9. _connectionString = connectionString;
  10. }
  11. public void Dispose()
  12. {
  13. }
  14. public AppUser? Authenticate(string login, string password)
  15. {
  16. using var connection = Open();
  17. using var command = new SqlCommand(@"
  18. SELECT TOP (1) u.UserId, u.FullName, r.Name
  19. FROM dbo.Users u
  20. JOIN dbo.Roles r ON r.RoleId = u.RoleId
  21. WHERE u.Login = @login
  22. AND u.PasswordHash = HASHBYTES('SHA2_256', CONVERT(NVARCHAR(4000), @password));", connection);
  23. command.Parameters.Add("@login", SqlDbType.NVarChar, 255).Value = login;
  24. command.Parameters.Add("@password", SqlDbType.NVarChar, 4000).Value = password;
  25. using var reader = command.ExecuteReader();
  26. return reader.Read()
  27. ? new AppUser(reader.GetInt32(0), reader.GetString(1), reader.GetString(2))
  28. : null;
  29. }
  30. public List<ProductView> LoadProducts(string search, string searchField, string supplier, string sortMode)
  31. {
  32. var searchExpression = searchField switch
  33. {
  34. "Артикул" => "p.Article",
  35. "Наименование" => "p.Name",
  36. "Категория" => "c.Name",
  37. "Описание" => "p.Description",
  38. "Производитель" => "m.Name",
  39. "Поставщик" => "s.Name",
  40. "Единица измерения" => "u.Name",
  41. "Цена" => "CONVERT(NVARCHAR(50), p.Price)",
  42. "Скидка" => "CONVERT(NVARCHAR(50), p.DiscountPercent)",
  43. "Цена со скидкой" => "CONVERT(NVARCHAR(50), ROUND(p.Price * (100 - p.DiscountPercent) / 100, 2))",
  44. "Количество на складе" => "CONVERT(NVARCHAR(50), p.StockQuantity)",
  45. _ => "CONCAT(p.Article, N' ', p.Name, N' ', c.Name, N' ', p.Description, N' ', m.Name, N' ', s.Name, N' ', u.Name, N' ', p.Price, N' ', p.DiscountPercent, N' ', p.StockQuantity)"
  46. };
  47. var orderBy = sortMode switch
  48. {
  49. "Цена по убыванию" => "p.Price DESC",
  50. "Цена по возрастанию" => "p.Price ASC",
  51. "Остаток по убыванию" => "p.StockQuantity DESC",
  52. "Остаток по возрастанию" => "p.StockQuantity ASC",
  53. _ => "p.Name ASC"
  54. };
  55. using var connection = Open();
  56. using var command = new SqlCommand($@"
  57. SELECT p.ProductId, p.Article, p.Name, c.Name, ISNULL(p.Description, N''),
  58. m.Name, s.Name, p.Price, p.DiscountPercent, u.Name, p.StockQuantity, p.ImagePath
  59. FROM dbo.Products p
  60. JOIN dbo.Categories c ON c.CategoryId = p.CategoryId
  61. JOIN dbo.Manufacturers m ON m.ManufacturerId = p.ManufacturerId
  62. JOIN dbo.Suppliers s ON s.SupplierId = p.SupplierId
  63. JOIN dbo.Units u ON u.UnitId = p.UnitId
  64. WHERE (@supplier = N'' OR s.Name = @supplier)
  65. AND (@search = N'' OR {searchExpression} LIKE N'%' + @search + N'%')
  66. ORDER BY {orderBy};", connection);
  67. command.Parameters.Add("@search", SqlDbType.NVarChar, 1000).Value = search;
  68. command.Parameters.Add("@supplier", SqlDbType.NVarChar, 200).Value = supplier == "Все поставщики" ? "" : supplier;
  69. var result = new List<ProductView>();
  70. using var reader = command.ExecuteReader();
  71. while (reader.Read())
  72. {
  73. result.Add(new ProductView
  74. {
  75. ProductId = reader.GetInt32(0),
  76. Article = reader.GetString(1),
  77. Name = reader.GetString(2),
  78. Category = reader.GetString(3),
  79. Description = reader.GetString(4),
  80. Manufacturer = reader.GetString(5),
  81. Supplier = reader.GetString(6),
  82. Price = reader.GetDecimal(7),
  83. DiscountPercent = reader.GetDecimal(8),
  84. Unit = reader.GetString(9),
  85. StockQuantity = reader.GetInt32(10),
  86. ImagePath = reader.GetString(11)
  87. });
  88. }
  89. return result;
  90. }
  91. public List<string> LoadSuppliers() => LoadDictionary("dbo.Suppliers");
  92. public List<string> LoadCategories() => LoadDictionary("dbo.Categories");
  93. public List<string> LoadManufacturers() => LoadDictionary("dbo.Manufacturers");
  94. public List<string> LoadUnits() => LoadDictionary("dbo.Units");
  95. public List<string> LoadStatuses() => LoadDictionary("dbo.OrderStatuses");
  96. public List<string> LoadProductArticles()
  97. {
  98. using var connection = Open();
  99. using var command = new SqlCommand("SELECT Article FROM dbo.Products ORDER BY Article;", connection);
  100. using var reader = command.ExecuteReader();
  101. var result = new List<string>();
  102. while (reader.Read())
  103. {
  104. result.Add(reader.GetString(0));
  105. }
  106. return result;
  107. }
  108. public Dictionary<int, string> LoadPickupPoints()
  109. {
  110. using var connection = Open();
  111. using var command = new SqlCommand("SELECT PickupPointId, Address FROM dbo.PickupPoints ORDER BY PickupPointId;", connection);
  112. using var reader = command.ExecuteReader();
  113. var result = new Dictionary<int, string>();
  114. while (reader.Read())
  115. {
  116. result[reader.GetInt32(0)] = reader.GetString(1);
  117. }
  118. return result;
  119. }
  120. public void SaveProduct(ProductView product)
  121. {
  122. using var connection = Open();
  123. using var transaction = connection.BeginTransaction();
  124. var unitId = EnsureDictionaryValue(connection, transaction, "dbo.Units", "UnitId", product.Unit);
  125. var supplierId = EnsureDictionaryValue(connection, transaction, "dbo.Suppliers", "SupplierId", product.Supplier);
  126. var manufacturerId = EnsureDictionaryValue(connection, transaction, "dbo.Manufacturers", "ManufacturerId", product.Manufacturer);
  127. var categoryId = EnsureDictionaryValue(connection, transaction, "dbo.Categories", "CategoryId", product.Category);
  128. using var command = new SqlCommand(product.ProductId == 0 ? @"
  129. INSERT INTO dbo.Products (Article, Name, UnitId, Price, SupplierId, ManufacturerId, CategoryId, DiscountPercent, StockQuantity, Description, ImagePath)
  130. VALUES (@article, @name, @unitId, @price, @supplierId, @manufacturerId, @categoryId, @discount, @stock, @description, @image);" : @"
  131. UPDATE dbo.Products
  132. SET Article=@article, Name=@name, UnitId=@unitId, Price=@price, SupplierId=@supplierId,
  133. ManufacturerId=@manufacturerId, CategoryId=@categoryId, DiscountPercent=@discount,
  134. StockQuantity=@stock, Description=@description, ImagePath=@image
  135. WHERE ProductId=@productId;", connection, transaction);
  136. command.Parameters.Add("@productId", SqlDbType.Int).Value = product.ProductId;
  137. command.Parameters.Add("@article", SqlDbType.NVarChar, 50).Value = product.Article;
  138. command.Parameters.Add("@name", SqlDbType.NVarChar, 1000).Value = product.Name;
  139. command.Parameters.Add("@unitId", SqlDbType.Int).Value = unitId;
  140. AddDecimal(command, "@price", product.Price);
  141. command.Parameters.Add("@supplierId", SqlDbType.Int).Value = supplierId;
  142. command.Parameters.Add("@manufacturerId", SqlDbType.Int).Value = manufacturerId;
  143. command.Parameters.Add("@categoryId", SqlDbType.Int).Value = categoryId;
  144. AddDecimal(command, "@discount", product.DiscountPercent);
  145. command.Parameters.Add("@stock", SqlDbType.Int).Value = product.StockQuantity;
  146. command.Parameters.Add("@description", SqlDbType.NVarChar).Value = string.IsNullOrWhiteSpace(product.Description) ? DBNull.Value : product.Description;
  147. command.Parameters.Add("@image", SqlDbType.NVarChar, 260).Value = string.IsNullOrWhiteSpace(product.ImagePath) ? "picture.png" : product.ImagePath;
  148. command.ExecuteNonQuery();
  149. transaction.Commit();
  150. }
  151. public bool ProductIsUsedInOrders(int productId)
  152. {
  153. using var connection = Open();
  154. using var command = new SqlCommand("SELECT COUNT(*) FROM dbo.OrderItems WHERE ProductId=@id;", connection);
  155. command.Parameters.Add("@id", SqlDbType.Int).Value = productId;
  156. return Convert.ToInt32(command.ExecuteScalar()) > 0;
  157. }
  158. public bool ProductImageIsUsed(string imagePath, int exceptProductId)
  159. {
  160. if (string.IsNullOrWhiteSpace(imagePath) ||
  161. imagePath.Equals("picture.png", StringComparison.OrdinalIgnoreCase))
  162. {
  163. return true;
  164. }
  165. using var connection = Open();
  166. using var command = new SqlCommand(@"
  167. SELECT COUNT(*)
  168. FROM dbo.Products
  169. WHERE ImagePath=@imagePath AND ProductId<>@productId;", connection);
  170. command.Parameters.Add("@imagePath", SqlDbType.NVarChar, 260).Value = imagePath;
  171. command.Parameters.Add("@productId", SqlDbType.Int).Value = exceptProductId;
  172. return Convert.ToInt32(command.ExecuteScalar()) > 0;
  173. }
  174. public void DeleteProduct(int productId)
  175. {
  176. using var connection = Open();
  177. using var command = new SqlCommand("DELETE FROM dbo.Products WHERE ProductId=@id;", connection);
  178. command.Parameters.Add("@id", SqlDbType.Int).Value = productId;
  179. command.ExecuteNonQuery();
  180. }
  181. public List<OrderView> LoadOrders()
  182. {
  183. using var connection = Open();
  184. using var command = new SqlCommand(@"
  185. SELECT o.OrderId, ISNULL(u.FullName, N''), pp.Address, os.Name, o.OrderDate, o.DeliveryDate, o.ReceiveCode,
  186. STRING_AGG(CONCAT(p.Article, N' x', oi.Quantity), N', ')
  187. FROM dbo.Orders o
  188. LEFT JOIN dbo.Users u ON u.UserId = o.UserId
  189. JOIN dbo.PickupPoints pp ON pp.PickupPointId = o.PickupPointId
  190. JOIN dbo.OrderStatuses os ON os.OrderStatusId = o.OrderStatusId
  191. LEFT JOIN dbo.OrderItems oi ON oi.OrderId = o.OrderId
  192. LEFT JOIN dbo.Products p ON p.ProductId = oi.ProductId
  193. GROUP BY o.OrderId, u.FullName, pp.Address, os.Name, o.OrderDate, o.DeliveryDate, o.ReceiveCode
  194. ORDER BY o.OrderId DESC;", connection);
  195. var result = new List<OrderView>();
  196. using var reader = command.ExecuteReader();
  197. while (reader.Read())
  198. {
  199. result.Add(new OrderView
  200. {
  201. OrderId = reader.GetInt32(0),
  202. Customer = reader.GetString(1),
  203. PickupPoint = reader.GetString(2),
  204. Status = reader.GetString(3),
  205. OrderDate = reader.IsDBNull(4) ? null : reader.GetDateTime(4),
  206. DeliveryDate = reader.IsDBNull(5) ? null : reader.GetDateTime(5),
  207. ReceiveCode = reader.GetString(6),
  208. Articles = reader.IsDBNull(7) ? "" : reader.GetString(7)
  209. });
  210. }
  211. return result;
  212. }
  213. public void DeleteOrder(int orderId)
  214. {
  215. using var connection = Open();
  216. using var command = new SqlCommand("DELETE FROM dbo.Orders WHERE OrderId=@id;", connection);
  217. command.Parameters.Add("@id", SqlDbType.Int).Value = orderId;
  218. command.ExecuteNonQuery();
  219. }
  220. public (string Article, int Quantity)? LoadFirstOrderItem(int orderId)
  221. {
  222. using var connection = Open();
  223. using var command = new SqlCommand(@"
  224. SELECT TOP (1) p.Article, oi.Quantity
  225. FROM dbo.OrderItems oi
  226. JOIN dbo.Products p ON p.ProductId = oi.ProductId
  227. WHERE oi.OrderId=@orderId
  228. ORDER BY oi.OrderItemId;", connection);
  229. command.Parameters.Add("@orderId", SqlDbType.Int).Value = orderId;
  230. using var reader = command.ExecuteReader();
  231. return reader.Read() ? (reader.GetString(0), reader.GetInt32(1)) : null;
  232. }
  233. public int SaveOrder(OrderView order, int pickupPointId, string article, int quantity, string? customerFullName)
  234. {
  235. using var connection = Open();
  236. using var transaction = connection.BeginTransaction();
  237. var orderId = order.OrderId == 0 ? NextOrderId(connection, transaction) : order.OrderId;
  238. int? userId = null;
  239. if (!string.IsNullOrWhiteSpace(customerFullName))
  240. {
  241. using var userCmd = new SqlCommand("SELECT TOP (1) UserId FROM dbo.Users WHERE FullName = @name;", connection, transaction);
  242. userCmd.Parameters.Add("@name", SqlDbType.NVarChar, 250).Value = customerFullName.Trim();
  243. var existing = userCmd.ExecuteScalar();
  244. if (existing != null && existing != DBNull.Value)
  245. {
  246. userId = Convert.ToInt32(existing);
  247. }
  248. }
  249. var statusId = EnsureDictionaryValue(connection, transaction, "dbo.OrderStatuses", "OrderStatusId", order.Status);
  250. using (var command = new SqlCommand(order.OrderId == 0 ? @"
  251. INSERT INTO dbo.Orders (OrderId, UserId, PickupPointId, OrderStatusId, OrderDate, DeliveryDate, ReceiveCode)
  252. VALUES (@orderId, @userId, @pickupPointId, @statusId, @orderDate, @deliveryDate, @receiveCode);" : @"
  253. UPDATE dbo.Orders
  254. SET UserId=@userId, PickupPointId=@pickupPointId, OrderStatusId=@statusId, OrderDate=@orderDate,
  255. DeliveryDate=@deliveryDate, ReceiveCode=@receiveCode
  256. WHERE OrderId=@orderId;", connection, transaction))
  257. {
  258. command.Parameters.Add("@orderId", SqlDbType.Int).Value = orderId;
  259. command.Parameters.Add("@pickupPointId", SqlDbType.Int).Value = pickupPointId;
  260. command.Parameters.Add("@userId", SqlDbType.Int).Value = userId.HasValue ? (object)userId.Value : DBNull.Value;
  261. command.Parameters.Add("@statusId", SqlDbType.Int).Value = statusId;
  262. command.Parameters.Add("@orderDate", SqlDbType.Date).Value = order.OrderDate.HasValue ? order.OrderDate.Value : DBNull.Value;
  263. command.Parameters.Add("@deliveryDate", SqlDbType.Date).Value = order.DeliveryDate.HasValue ? order.DeliveryDate.Value : DBNull.Value;
  264. command.Parameters.Add("@receiveCode", SqlDbType.NVarChar, 20).Value = string.IsNullOrWhiteSpace(order.ReceiveCode) ? "000" : order.ReceiveCode;
  265. command.ExecuteNonQuery();
  266. }
  267. using (var deleteItems = new SqlCommand("DELETE FROM dbo.OrderItems WHERE OrderId=@orderId;", connection, transaction))
  268. {
  269. deleteItems.Parameters.Add("@orderId", SqlDbType.Int).Value = orderId;
  270. deleteItems.ExecuteNonQuery();
  271. }
  272. using (var insertItem = new SqlCommand(@"
  273. INSERT INTO dbo.OrderItems (OrderId, ProductId, Quantity, PriceAtOrder)
  274. SELECT @orderId, ProductId, @quantity, Price
  275. FROM dbo.Products
  276. WHERE Article=@article;", connection, transaction))
  277. {
  278. insertItem.Parameters.Add("@orderId", SqlDbType.Int).Value = orderId;
  279. insertItem.Parameters.Add("@quantity", SqlDbType.Int).Value = quantity;
  280. insertItem.Parameters.Add("@article", SqlDbType.NVarChar, 50).Value = article;
  281. if (insertItem.ExecuteNonQuery() == 0)
  282. {
  283. throw new InvalidOperationException("Товар с указанным артикулом не найден.");
  284. }
  285. }
  286. transaction.Commit();
  287. return orderId;
  288. }
  289. private SqlConnection Open()
  290. {
  291. var connection = new SqlConnection(_connectionString);
  292. connection.Open();
  293. return connection;
  294. }
  295. private List<string> LoadDictionary(string tableName)
  296. {
  297. using var connection = Open();
  298. using var command = new SqlCommand($"SELECT Name FROM {tableName} ORDER BY Name;", connection);
  299. using var reader = command.ExecuteReader();
  300. var result = new List<string>();
  301. while (reader.Read())
  302. {
  303. result.Add(reader.GetString(0));
  304. }
  305. return result;
  306. }
  307. public List<string> LoadUsers()
  308. {
  309. using var connection = Open();
  310. using var command = new SqlCommand("SELECT FullName FROM dbo.Users ORDER BY FullName;", connection);
  311. using var reader = command.ExecuteReader();
  312. var result = new List<string>();
  313. while (reader.Read())
  314. {
  315. result.Add(reader.GetString(0));
  316. }
  317. return result;
  318. }
  319. private static int EnsureDictionaryValue(SqlConnection connection, SqlTransaction transaction, string tableName, string idColumn, string name)
  320. {
  321. if (string.IsNullOrWhiteSpace(name))
  322. {
  323. throw new InvalidOperationException("Не заполнено обязательное справочное значение.");
  324. }
  325. using (var select = new SqlCommand($"SELECT {idColumn} FROM {tableName} WHERE Name=@name;", connection, transaction))
  326. {
  327. select.Parameters.Add("@name", SqlDbType.NVarChar, 200).Value = name.Trim();
  328. var existing = select.ExecuteScalar();
  329. if (existing is not null && existing != DBNull.Value)
  330. {
  331. return Convert.ToInt32(existing);
  332. }
  333. }
  334. using var insert = new SqlCommand($"INSERT INTO {tableName} (Name) OUTPUT INSERTED.{idColumn} VALUES (@name);", connection, transaction);
  335. insert.Parameters.Add("@name", SqlDbType.NVarChar, 200).Value = name.Trim();
  336. return Convert.ToInt32(insert.ExecuteScalar());
  337. }
  338. private static int NextOrderId(SqlConnection connection, SqlTransaction transaction)
  339. {
  340. using var command = new SqlCommand("SELECT ISNULL(MAX(OrderId), 0) + 1 FROM dbo.Orders;", connection, transaction);
  341. return Convert.ToInt32(command.ExecuteScalar());
  342. }
  343. private static void AddDecimal(SqlCommand command, string name, decimal value)
  344. {
  345. var parameter = command.Parameters.Add(name, SqlDbType.Decimal);
  346. parameter.Precision = 18;
  347. parameter.Scale = 2;
  348. parameter.Value = value;
  349. }
  350. }