#if ASYNC using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Dapper; namespace Dapper.Contrib.Extensions { public static partial class SqlMapperExtensions { /// /// Returns a single entity by a single id from table "Ts" asynchronously using .NET 4.5 Task. T must be of interface type. /// Id must be marked with [Key] attribute. /// Created entity is tracked/intercepted for changes and used by the Update() extension. /// /// Interface type to create and populate /// Open SqlConnection /// Id of the entity to get, must be marked with [Key] attribute /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// Entity of T public static async Task GetAsync(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var type = typeof(T); string sql; if (!GetQueries.TryGetValue(type.TypeHandle, out sql)) { var key = GetSingleKey(nameof(GetAsync)); var name = GetTableName(type); sql = $"SELECT * FROM {name} WHERE {key.Name} = @id"; GetQueries[type.TypeHandle] = sql; } var dynParms = new DynamicParameters(); dynParms.Add("@id", id); if (!type.IsInterface()) return (await connection.QueryAsync(sql, dynParms, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault(); var res = (await connection.QueryAsync(sql, dynParms).ConfigureAwait(false)).FirstOrDefault() as IDictionary; if (res == null) return null; var obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); } ((IProxy)obj).IsDirty = false; //reset change tracking and return return obj; } /// /// Returns a list of entites from table "Ts". /// Id of T must be marked with [Key] attribute. /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension /// for optimal performance. /// /// Interface or type to create and populate /// Open SqlConnection /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// Entity of T public static Task> GetAllAsync(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var type = typeof(T); var cacheType = typeof(List); string sql; if (!GetQueries.TryGetValue(cacheType.TypeHandle, out sql)) { GetSingleKey(nameof(GetAll)); var name = GetTableName(type); sql = "SELECT * FROM " + name; GetQueries[cacheType.TypeHandle] = sql; } if (!type.IsInterface()) { return connection.QueryAsync(sql, null, transaction, commandTimeout); } return GetAllAsyncImpl(connection, transaction, commandTimeout, sql, type); } private static async Task> GetAllAsyncImpl(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string sql, Type type) where T : class { var result = await connection.QueryAsync(sql); var list = new List(); foreach (IDictionary res in result) { var obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); } ((IProxy)obj).IsDirty = false; //reset change tracking and return list.Add(obj); } return list; } /// /// Inserts an entity into table "Ts" asynchronously using .NET 4.5 Task and returns identity id. /// /// Open SqlConnection /// Entity to insert /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// The specific ISqlAdapter to use, auto-detected based on connection if null /// Identity of inserted entity public static Task InsertAsync(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null, ISqlAdapter sqlAdapter = null) where T : class { var type = typeof(T); if (sqlAdapter == null) sqlAdapter = GetFormatter(connection); var isList = false; if (type.IsArray) { isList = true; type = type.GetElementType(); } else if (type.IsGenericType()) { isList = true; type = type.GetGenericArguments()[0]; } var name = GetTableName(type); var sbColumnList = new StringBuilder(null); var allProperties = TypePropertiesCache(type); var keyProperties = KeyPropertiesCache(type); var computedProperties = ComputedPropertiesCache(type); var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) { var property = allPropertiesExceptKeyAndComputed.ElementAt(i); sqlAdapter.AppendColumnName(sbColumnList, property.Name); if (i < allPropertiesExceptKeyAndComputed.Count - 1) sbColumnList.Append(", "); } var sbParameterList = new StringBuilder(null); for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) { var property = allPropertiesExceptKeyAndComputed.ElementAt(i); sbParameterList.AppendFormat("@{0}", property.Name); if (i < allPropertiesExceptKeyAndComputed.Count - 1) sbParameterList.Append(", "); } if (!isList) //single entity { return sqlAdapter.InsertAsync(connection, transaction, commandTimeout, name, sbColumnList.ToString(), sbParameterList.ToString(), keyProperties, entityToInsert); } //insert list of entities var cmd = $"INSERT INTO {name} ({sbColumnList}) values ({sbParameterList})"; return connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout); } /// /// Updates entity in table "Ts" asynchronously using .NET 4.5 Task, checks if the entity is modified if the entity is tracked by the Get() extension. /// /// Type to be updated /// Open SqlConnection /// Entity to be updated /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// true if updated, false if not found or not modified (tracked entities) public static async Task UpdateAsync(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var proxy = entityToUpdate as IProxy; if (proxy != null) { if (!proxy.IsDirty) return false; } var type = typeof(T); if (type.IsArray) { type = type.GetElementType(); } else if (type.IsGenericType()) { type = type.GetGenericArguments()[0]; } var keyProperties = KeyPropertiesCache(type); var explicitKeyProperties = ExplicitKeyPropertiesCache(type); if (!keyProperties.Any() && !explicitKeyProperties.Any()) throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); var name = GetTableName(type); var sb = new StringBuilder(); sb.AppendFormat("update {0} set ", name); var allProperties = TypePropertiesCache(type); keyProperties.AddRange(explicitKeyProperties); var computedProperties = ComputedPropertiesCache(type); var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); var adapter = GetFormatter(connection); for (var i = 0; i < nonIdProps.Count; i++) { var property = nonIdProps.ElementAt(i); adapter.AppendColumnNameEqualsValue(sb, property.Name); if (i < nonIdProps.Count - 1) sb.AppendFormat(", "); } sb.Append(" where "); for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties.ElementAt(i); adapter.AppendColumnNameEqualsValue(sb, property.Name); if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } var updated = await connection.ExecuteAsync(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction).ConfigureAwait(false); return updated > 0; } /// /// Delete entity in table "Ts" asynchronously using .NET 4.5 Task. /// /// Type of entity /// Open SqlConnection /// Entity to delete /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// true if deleted, false if not found public static async Task DeleteAsync(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { if (entityToDelete == null) throw new ArgumentException("Cannot Delete null Object", nameof(entityToDelete)); var type = typeof(T); if (type.IsArray) { type = type.GetElementType(); } else if (type.IsGenericType()) { type = type.GetGenericArguments()[0]; } var keyProperties = KeyPropertiesCache(type); var explicitKeyProperties = ExplicitKeyPropertiesCache(type); if (!keyProperties.Any() && !explicitKeyProperties.Any()) throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); var name = GetTableName(type); keyProperties.AddRange(explicitKeyProperties); var sb = new StringBuilder(); sb.AppendFormat("DELETE FROM {0} WHERE ", name); for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties.ElementAt(i); sb.AppendFormat("{0} = @{1}", property.Name, property.Name); if (i < keyProperties.Count - 1) sb.AppendFormat(" AND "); } var deleted = await connection.ExecuteAsync(sb.ToString(), entityToDelete, transaction, commandTimeout).ConfigureAwait(false); return deleted > 0; } /// /// Delete all entities in the table related to the type T asynchronously using .NET 4.5 Task. /// /// Type of entity /// Open SqlConnection /// The transaction to run under, null (the default) if none /// Number of seconds before command execution timeout /// true if deleted, false if none found public static async Task DeleteAllAsync(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var type = typeof(T); var statement = "DELETE FROM " + GetTableName(type); var deleted = await connection.ExecuteAsync(statement, null, transaction, commandTimeout).ConfigureAwait(false); return deleted > 0; } } } public partial interface ISqlAdapter { Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert); } public partial class SqlServerAdapter { public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) values ({parameterList}); SELECT SCOPE_IDENTITY() id"; var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout); var first = multi.Read().FirstOrDefault(); if (first == null || first.id == null) return 0; var id = (int)first.id; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (!pi.Any()) return id; var idp = pi.First(); idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; } } public partial class SqlCeServerAdapter { public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})"; await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); var r = (await connection.QueryAsync("SELECT @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false)).ToList(); if (r.First() == null || r.First().id == null) return 0; var id = (int)r.First().id; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (!pi.Any()) return id; var idp = pi.First(); idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; } } public partial class MySqlAdapter { public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})"; await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); var r = await connection.QueryAsync("SELECT LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); var id = r.First().id; if (id == null) return 0; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (!pi.Any()) return Convert.ToInt32(id); var idp = pi.First(); idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return Convert.ToInt32(id); } } public partial class PostgresAdapter { public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var sb = new StringBuilder(); sb.AppendFormat("INSERT INTO {0} ({1}) VALUES ({2})", tableName, columnList, parameterList); // If no primary key then safe to assume a join table with not too much data to return var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (!propertyInfos.Any()) sb.Append(" RETURNING *"); else { sb.Append(" RETURNING "); bool first = true; foreach (var property in propertyInfos) { if (!first) sb.Append(", "); first = false; sb.Append(property.Name); } } var results = await connection.QueryAsync(sb.ToString(), entityToInsert, transaction, commandTimeout).ConfigureAwait(false); // Return the key by assinging the corresponding property in the object - by product is that it supports compound primary keys var id = 0; var values = results.First(); foreach (var p in propertyInfos) { var value = values[p.Name.ToLower()]; p.SetValue(entityToInsert, value, null); if (id == 0) id = Convert.ToInt32(value); } return id; } } public partial class SQLiteAdapter { public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id"; var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout); var id = (int)multi.Read().First().id; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (!pi.Any()) return id; var idp = pi.First(); idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; } } #endif