RawDb.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Data;
  6. using System.Linq;
  7. using System.Runtime.CompilerServices;
  8. using System.Threading.Tasks;
  9. using Microsoft.Extensions.Caching.Memory;
  10. using Npgsql;
  11. namespace Benchmarks.Data
  12. {
  13. public interface IRawDb
  14. {
  15. Task<World> LoadSingleQueryRow();
  16. Task<World[]> LoadMultipleQueriesRows(int count);
  17. Task<World[]> LoadMultipleUpdatesRows(int count);
  18. Task<World[]> LoadCachedQueries(int count);
  19. Task<List<Fortune>> LoadFortunesRows();
  20. }
  21. public class RawDb : IRawDb
  22. {
  23. private readonly string _connectionString;
  24. private readonly MemoryCache _cache;
  25. public RawDb(AppSettings appSettings)
  26. {
  27. _connectionString = appSettings.ConnectionString;
  28. _cache = new MemoryCache(
  29. new MemoryCacheOptions()
  30. {
  31. ExpirationScanFrequency = TimeSpan.FromMinutes(60)
  32. });
  33. }
  34. public async Task<World> LoadSingleQueryRow()
  35. {
  36. using (var db = new NpgsqlConnection(_connectionString))
  37. {
  38. await db.OpenAsync();
  39. var (cmd, _) = CreateReadCommand(db, new ConcurrentRandom());
  40. using (cmd)
  41. {
  42. return await ReadSingleRow(cmd);
  43. }
  44. }
  45. }
  46. public async Task<List<Fortune>> LoadFortunesRows()
  47. {
  48. var result = new List<Fortune>();
  49. using (var db = new NpgsqlConnection(_connectionString))
  50. using (var cmd = db.CreateCommand())
  51. {
  52. cmd.CommandText = "SELECT id, message FROM fortune";
  53. await db.OpenAsync();
  54. using (var rdr = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection))
  55. {
  56. while (await rdr.ReadAsync())
  57. {
  58. result.Add(new Fortune(rdr.GetInt32(0), rdr.GetString(1)));
  59. }
  60. }
  61. }
  62. result.Add(new Fortune (0, "Additional fortune added at request time." ));
  63. result.Sort();
  64. return result;
  65. }
  66. public async Task<World[]> LoadMultipleQueriesRows(int count)
  67. {
  68. var random = new ConcurrentRandom();
  69. var result = new World[count];
  70. using (var db = new NpgsqlConnection(_connectionString))
  71. {
  72. await db.OpenAsync();
  73. var (cmd, parameter) = CreateReadCommand(db, random);
  74. using (cmd)
  75. {
  76. for (int i = 0; i < count; i++)
  77. {
  78. result[i] = await ReadSingleRow(cmd);
  79. parameter.Value = random.Next(1, 10001);
  80. }
  81. }
  82. }
  83. return result;
  84. }
  85. public async Task<World[]> LoadMultipleUpdatesRows(int count)
  86. {
  87. var random = new ConcurrentRandom();
  88. var results = new World[count];
  89. using (var db = new NpgsqlConnection(_connectionString))
  90. {
  91. await db.OpenAsync();
  92. var (queryCmd, queryParameter) = CreateReadCommand(db, random);
  93. using (queryCmd)
  94. {
  95. for (int i = 0; i < results.Length; i++)
  96. {
  97. results[i] = await ReadSingleRow(queryCmd);
  98. queryParameter.TypedValue = random.Next(1, 10001);
  99. }
  100. }
  101. using (var updateCmd = new NpgsqlCommand(BatchUpdateString.Query(count), db))
  102. {
  103. var ids = BatchUpdateString.Ids;
  104. var randoms = BatchUpdateString.Randoms;
  105. for (int i = 0; i < results.Length; i++)
  106. {
  107. var randomNumber = random.Next(1, 10001);
  108. updateCmd.Parameters.Add(new NpgsqlParameter<int>(parameterName: ids[i], value: results[i].id));
  109. updateCmd.Parameters.Add(new NpgsqlParameter<int>(parameterName: randoms[i], value: randomNumber));
  110. results[i].randomNumber = randomNumber;
  111. }
  112. await updateCmd.ExecuteNonQueryAsync();
  113. }
  114. }
  115. return results;
  116. }
  117. public Task<World[]> LoadCachedQueries(int count)
  118. {
  119. var result = new World[count];
  120. var cacheKeys = _cacheKeys;
  121. var cache = _cache;
  122. var random = new ConcurrentRandom();
  123. for (var i = 0; i < result.Length; i++)
  124. {
  125. var id = random.Next(1, 10001);
  126. var key = cacheKeys[id];
  127. var data = cache.Get<CachedWorld>(key);
  128. if (data != null)
  129. {
  130. result[i] = data;
  131. }
  132. else
  133. {
  134. return LoadUncachedQueries(random, id, i, count, this, result);
  135. }
  136. }
  137. return Task.FromResult(result);
  138. static async Task<World[]> LoadUncachedQueries(ConcurrentRandom random, int id, int i, int count, RawDb rawdb, World[] result)
  139. {
  140. using (var db = new NpgsqlConnection(rawdb._connectionString))
  141. {
  142. await db.OpenAsync();
  143. var (cmd, idParameter) = rawdb.CreateReadCommand(db,random);
  144. using (cmd)
  145. {
  146. Func<ICacheEntry, Task<CachedWorld>> create = async (entry) =>
  147. {
  148. return await rawdb.ReadSingleRow(cmd);
  149. };
  150. var cacheKeys = _cacheKeys;
  151. var key = cacheKeys[id];
  152. idParameter.TypedValue = id;
  153. for (; i < result.Length; i++)
  154. {
  155. var data = await rawdb._cache.GetOrCreateAsync<CachedWorld>(key, create);
  156. result[i] = data;
  157. id = random.Next(1, 10001);
  158. idParameter.TypedValue = id;
  159. key = cacheKeys[id];
  160. }
  161. }
  162. }
  163. return result;
  164. }
  165. }
  166. private (NpgsqlCommand readCmd, NpgsqlParameter<int> idParameter) CreateReadCommand(NpgsqlConnection connection, ConcurrentRandom random)
  167. {
  168. var cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = @Id", connection);
  169. var parameter = new NpgsqlParameter<int>(parameterName: "@Id", value: random.Next(1, 10001));
  170. cmd.Parameters.Add(parameter);
  171. return (cmd, parameter);
  172. }
  173. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  174. private async Task<World> ReadSingleRow(NpgsqlCommand cmd)
  175. {
  176. using (var rdr = await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.SingleRow))
  177. {
  178. await rdr.ReadAsync();
  179. return new World
  180. {
  181. id = rdr.GetInt32(0),
  182. randomNumber = rdr.GetInt32(1)
  183. };
  184. }
  185. }
  186. private static readonly object[] _cacheKeys = Enumerable.Range(0, 10001).Select((i) => new CacheKey(i)).ToArray();
  187. public sealed class CacheKey : IEquatable<CacheKey>
  188. {
  189. private readonly int _value;
  190. public CacheKey(int value)
  191. => _value = value;
  192. public bool Equals(CacheKey key)
  193. => key._value == _value;
  194. public override bool Equals(object obj)
  195. => ReferenceEquals(obj, this);
  196. public override int GetHashCode()
  197. => _value;
  198. public override string ToString()
  199. => _value.ToString();
  200. }
  201. }
  202. }