Dreamine.Database.Core 1.0.1
Dreamine.Database.Core 데이터베이스 기능과 관련 API를 제공합니다.
로딩중...
검색중...
일치하는것 없음
DatabaseProviderBase.cs
이 파일의 문서화 페이지로 가기
1using System.Collections.Concurrent;
2using System.Data;
3using Dapper;
4using Dreamine.Database.Abstractions;
6
8
17public abstract class DatabaseProviderBase : IDatabaseProvider
18{
27 private enum SqlKind
28 {
56 }
57
58 // Keyed by (entity type, provider kind, sql kind) — avoids re-building identical SQL on every call.
67 private static readonly ConcurrentDictionary<(Type, DatabaseProviderKind, SqlKind), string> SqlCache = new();
68
101 protected DatabaseProviderBase(string connectionString)
102 {
103 ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
104 ConnectionString = connectionString;
105 }
106
115 public abstract DatabaseProviderKind Kind { get; }
116
125 public string ConnectionString { get; }
126
135 protected virtual string ParameterPrefix => "@";
136
145 public virtual void EnsureDatabaseExists()
146 {
147 using var connection = CreateConnection();
148 connection.Open();
149 }
150
175 public virtual async Task EnsureDatabaseExistsAsync(CancellationToken cancellationToken = default)
176 {
177 using var connection = CreateConnection();
178 await OpenAsync(connection, cancellationToken).ConfigureAwait(false);
179 }
180
205 public bool IsTableExists<T>()
206 {
208 }
209
242 public Task<bool> IsTableExistsAsync<T>(CancellationToken cancellationToken = default)
243 {
244 return IsTableExistsAsync(DatabaseEntityMap.Create<T>().TableName, cancellationToken);
245 }
246
271 public abstract bool IsTableExists(string tableName);
272
305 public abstract Task<bool> IsTableExistsAsync(
306 string tableName,
307 CancellationToken cancellationToken = default);
308
325 public void CreateTable<T>()
326 {
327 var map = DatabaseEntityMap.Create<T>();
328 if (IsTableExists(map.TableName))
329 {
330 return;
331 }
332
333 var sql = BuildCreateTableSql(map);
334 ExecuteNonQuery(sql);
335 }
336
369 public async Task CreateTableAsync<T>(CancellationToken cancellationToken = default)
370 {
371 var map = DatabaseEntityMap.Create<T>();
372 if (await IsTableExistsAsync(map.TableName, cancellationToken).ConfigureAwait(false))
373 {
374 return;
375 }
376
377 var sql = BuildCreateTableSql(map);
378 await ExecuteNonQueryAsync(sql, cancellationToken: cancellationToken).ConfigureAwait(false);
379 }
380
429 public int ExecuteNonQuery(string sql, object? parameters = null)
430 {
431 ArgumentException.ThrowIfNullOrWhiteSpace(sql);
432
433 using var connection = CreateOpenedConnection();
434 return connection.Execute(sql, parameters);
435 }
436
493 public async Task<int> ExecuteNonQueryAsync(
494 string sql,
495 object? parameters = null,
496 CancellationToken cancellationToken = default)
497 {
498 ArgumentException.ThrowIfNullOrWhiteSpace(sql);
499
500 using var connection = CreateConnection();
501 await OpenAsync(connection, cancellationToken).ConfigureAwait(false);
502 return await connection.ExecuteAsync(new CommandDefinition(sql, parameters, cancellationToken: cancellationToken))
503 .ConfigureAwait(false);
504 }
505
562 public T? ExecuteScalar<T>(string sql, object? parameters = null)
563 {
564 ArgumentException.ThrowIfNullOrWhiteSpace(sql);
565
566 using var connection = CreateOpenedConnection();
567 return connection.ExecuteScalar<T>(sql, parameters);
568 }
569
634 public async Task<T?> ExecuteScalarAsync<T>(
635 string sql,
636 object? parameters = null,
637 CancellationToken cancellationToken = default)
638 {
639 ArgumentException.ThrowIfNullOrWhiteSpace(sql);
640
641 using var connection = CreateConnection();
642 await OpenAsync(connection, cancellationToken).ConfigureAwait(false);
643 return await connection.ExecuteScalarAsync<T>(
644 new CommandDefinition(sql, parameters, cancellationToken: cancellationToken))
645 .ConfigureAwait(false);
646 }
647
704 public IEnumerable<T> Query<T>(string sql, object? parameters = null)
705 {
706 ArgumentException.ThrowIfNullOrWhiteSpace(sql);
707
708 using var connection = CreateOpenedConnection();
709 return connection.Query<T>(sql, parameters).ToArray();
710 }
711
776 public async Task<IReadOnlyList<T>> QueryAsync<T>(
777 string sql,
778 object? parameters = null,
779 CancellationToken cancellationToken = default)
780 {
781 ArgumentException.ThrowIfNullOrWhiteSpace(sql);
782
783 using var connection = CreateConnection();
784 await OpenAsync(connection, cancellationToken).ConfigureAwait(false);
785 var rows = await connection.QueryAsync<T>(
786 new CommandDefinition(sql, parameters, cancellationToken: cancellationToken))
787 .ConfigureAwait(false);
788 return rows.ToArray();
789 }
790
831 public bool Insert<T>(T entity)
832 {
833 ArgumentNullException.ThrowIfNull(entity);
834
835 using var connection = CreateOpenedConnection();
836 return connection.Execute(GetOrBuildSql<T>(SqlKind.Insert), entity) > 0;
837 }
838
887 public async Task<bool> InsertAsync<T>(T entity, CancellationToken cancellationToken = default)
888 {
889 ArgumentNullException.ThrowIfNull(entity);
890
891 using var connection = CreateConnection();
892 await OpenAsync(connection, cancellationToken).ConfigureAwait(false);
893 return await connection.ExecuteAsync(
894 new CommandDefinition(GetOrBuildSql<T>(SqlKind.Insert), entity, cancellationToken: cancellationToken))
895 .ConfigureAwait(false) > 0;
896 }
897
946 public bool Update<T>(T entity)
947 {
948 ArgumentNullException.ThrowIfNull(entity);
949
950 using var connection = CreateOpenedConnection();
951 return connection.Execute(GetOrBuildSql<T>(SqlKind.Update), entity) > 0;
952 }
953
1010 public async Task<bool> UpdateAsync<T>(T entity, CancellationToken cancellationToken = default)
1011 {
1012 ArgumentNullException.ThrowIfNull(entity);
1013
1014 using var connection = CreateConnection();
1015 await OpenAsync(connection, cancellationToken).ConfigureAwait(false);
1016 return await connection.ExecuteAsync(
1017 new CommandDefinition(GetOrBuildSql<T>(SqlKind.Update), entity, cancellationToken: cancellationToken))
1018 .ConfigureAwait(false) > 0;
1019 }
1020
1069 public bool Delete<T>(T entity)
1070 {
1071 ArgumentNullException.ThrowIfNull(entity);
1072
1073 using var connection = CreateOpenedConnection();
1074 return connection.Execute(GetOrBuildSql<T>(SqlKind.Delete), entity) > 0;
1075 }
1076
1133 public async Task<bool> DeleteAsync<T>(T entity, CancellationToken cancellationToken = default)
1134 {
1135 ArgumentNullException.ThrowIfNull(entity);
1136
1137 using var connection = CreateConnection();
1138 await OpenAsync(connection, cancellationToken).ConfigureAwait(false);
1139 return await connection.ExecuteAsync(
1140 new CommandDefinition(GetOrBuildSql<T>(SqlKind.Delete), entity, cancellationToken: cancellationToken))
1141 .ConfigureAwait(false) > 0;
1142 }
1143
1160 protected abstract IDbConnection CreateConnection();
1161
1186 protected abstract string QuoteIdentifier(string identifier);
1187
1212 protected abstract string GetSqlType(DatabasePropertyMap property);
1213
1238 protected virtual string BuildCreateTableSql(DatabaseEntityMap map)
1239 {
1240 var columns = map.Properties.Select(property =>
1241 {
1242 var sql = $"{QuoteIdentifier(property.ColumnName)} {GetSqlType(property)}";
1243 if (property.IsKey)
1244 {
1245 sql += BuildPrimaryKeySql(property);
1246 }
1247
1248 return sql;
1249 });
1250
1251 return $"CREATE TABLE IF NOT EXISTS {QuoteIdentifier(map.TableName)} ({string.Join(", ", columns)})";
1252 }
1253
1278 protected virtual string BuildInsertSql(DatabaseEntityMap map)
1279 {
1280 var properties = map.InsertableProperties;
1281 var columns = string.Join(", ", properties.Select(x => QuoteIdentifier(x.ColumnName)));
1282 var values = string.Join(", ", properties.Select(x => ParameterPrefix + x.Property.Name));
1283 return $"INSERT INTO {QuoteIdentifier(map.TableName)} ({columns}) VALUES ({values})";
1284 }
1285
1318 protected virtual string BuildUpdateSql(DatabaseEntityMap map)
1319 {
1320 var key = RequireKey(map);
1321 var assignments = string.Join(
1322 ", ",
1323 map.UpdatableProperties.Select(x => $"{QuoteIdentifier(x.ColumnName)} = {ParameterPrefix}{x.Property.Name}"));
1324 return $"UPDATE {QuoteIdentifier(map.TableName)} SET {assignments} WHERE {QuoteIdentifier(key.ColumnName)} = {ParameterPrefix}{key.Property.Name}";
1325 }
1326
1359 protected virtual string BuildDeleteSql(DatabaseEntityMap map)
1360 {
1361 var key = RequireKey(map);
1362 return $"DELETE FROM {QuoteIdentifier(map.TableName)} WHERE {QuoteIdentifier(key.ColumnName)} = {ParameterPrefix}{key.Property.Name}";
1363 }
1364
1389 protected virtual string BuildPrimaryKeySql(DatabasePropertyMap property)
1390 {
1391 return " PRIMARY KEY";
1392 }
1393
1434 private string GetOrBuildSql<T>(SqlKind kind)
1435 {
1436 var key = (typeof(T), Kind, kind);
1437 return SqlCache.GetOrAdd(key, _ =>
1438 {
1439 var map = DatabaseEntityMap.Create<T>();
1440 return kind switch
1441 {
1445 _ => throw new ArgumentOutOfRangeException(nameof(kind))
1446 };
1447 });
1448 }
1449
1466 private IDbConnection CreateOpenedConnection()
1467 {
1468 var connection = CreateConnection();
1469 connection.Open();
1470 return connection;
1471 }
1472
1505 private static async Task OpenAsync(IDbConnection connection, CancellationToken cancellationToken)
1506 {
1507 if (connection is System.Data.Common.DbConnection dbConnection)
1508 {
1509 await dbConnection.OpenAsync(cancellationToken).ConfigureAwait(false);
1510 return;
1511 }
1512
1513 cancellationToken.ThrowIfCancellationRequested();
1514 connection.Open();
1515 }
1516
1550 {
1551 return map.Key ?? throw new InvalidOperationException(
1552 $"Entity [{map.EntityType.FullName}] does not define a key. Add [DatabaseKey] or an Id property.");
1553 }
1554}
IReadOnlyList< DatabasePropertyMap > UpdatableProperties
static DatabaseEntityMap Create(Type entityType)
IReadOnlyList< DatabasePropertyMap > InsertableProperties
IReadOnlyList< DatabasePropertyMap > Properties
async Task CreateTableAsync< T >(CancellationToken cancellationToken=default)
virtual async Task EnsureDatabaseExistsAsync(CancellationToken cancellationToken=default)
async Task< IReadOnlyList< T > > QueryAsync< T >(string sql, object? parameters=null, CancellationToken cancellationToken=default)
IEnumerable< T > Query< T >(string sql, object? parameters=null)
async Task< int > ExecuteNonQueryAsync(string sql, object? parameters=null, CancellationToken cancellationToken=default)
async Task< bool > UpdateAsync< T >(T entity, CancellationToken cancellationToken=default)
Task< bool > IsTableExistsAsync< T >(CancellationToken cancellationToken=default)
virtual string BuildCreateTableSql(DatabaseEntityMap map)
static readonly ConcurrentDictionary<(Type, DatabaseProviderKind, SqlKind), string > SqlCache
async Task< T?> ExecuteScalarAsync< T >(string sql, object? parameters=null, CancellationToken cancellationToken=default)
virtual string BuildPrimaryKeySql(DatabasePropertyMap property)
T? ExecuteScalar< T >(string sql, object? parameters=null)
Task< bool > IsTableExistsAsync(string tableName, CancellationToken cancellationToken=default)
static async Task OpenAsync(IDbConnection connection, CancellationToken cancellationToken)
int ExecuteNonQuery(string sql, object? parameters=null)
static DatabasePropertyMap RequireKey(DatabaseEntityMap map)
async Task< bool > DeleteAsync< T >(T entity, CancellationToken cancellationToken=default)
string GetSqlType(DatabasePropertyMap property)
async Task< bool > InsertAsync< T >(T entity, CancellationToken cancellationToken=default)