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 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); if (!GetQueries.TryGetValue(type.TypeHandle, out string sql)) { var key = GetSingleKey(nameof(GetAsync)); var name = GetTableName(type); sql = $"SELECT * FROM {name} WHERE {key.Name} = @id"; GetQueries[type.TypeHandle] = sql; } var dynParams = new DynamicParameters(); dynParams.Add("@id", id); if (!type.IsInterface) return (await connection.QueryAsync(sql, dynParams, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault(); if (!((await connection.QueryAsync(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() is IDictionary res)) { return null; } var obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; if (val == null) continue; if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var genericType = Nullable.GetUnderlyingType(property.PropertyType); if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); } else { property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); } } ((IProxy)obj).IsDirty = false; //reset change tracking and return return obj; } /// /// Returns a list of entities 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); if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string 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, transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); var list = new List(); foreach (IDictionary res in result) { var obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; if (val == null) continue; if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var genericType = Nullable.GetUnderlyingType(property.PropertyType); if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); } else { 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 Task and returns identity id. /// /// The type being inserted. /// 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); sqlAdapter ??= GetFormatter(connection); var isList = false; if (type.IsArray) { isList = true; type = type.GetElementType(); } else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) { isList = true; type = type.GetGenericArguments()[0]; } } var name = GetTableName(type); var sbColumnList = new StringBuilder(null); var allProperties = TypePropertiesCache(type); var keyProperties = KeyPropertiesCache(type).ToList(); var computedProperties = ComputedPropertiesCache(type); var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) { var property = allPropertiesExceptKeyAndComputed[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[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 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 { if ((entityToUpdate is IProxy proxy) && !proxy.IsDirty) { return false; } var type = typeof(T); if (type.IsArray) { type = type.GetElementType(); } else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) { type = type.GetGenericArguments()[0]; } } var keyProperties = KeyPropertiesCache(type).ToList(); var explicitKeyProperties = ExplicitKeyPropertiesCache(type); if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0) 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[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); if (i < nonIdProps.Count - 1) sb.Append(", "); } sb.Append(" where "); for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); if (i < keyProperties.Count - 1) sb.Append(" 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 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) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) { type = type.GetGenericArguments()[0]; } } var keyProperties = KeyPropertiesCache(type); var explicitKeyProperties = ExplicitKeyPropertiesCache(type); if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0) throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); var name = GetTableName(type); var allKeyProperties = keyProperties.Concat(explicitKeyProperties).ToList(); var sb = new StringBuilder(); sb.AppendFormat("DELETE FROM {0} WHERE ", name); var adapter = GetFormatter(connection); for (var i = 0; i < allKeyProperties.Count; i++) { var property = allKeyProperties[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); if (i < allKeyProperties.Count - 1) sb.Append(" 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 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 { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert); } public partial class SqlServerAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. 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).ConfigureAwait(false); var first = await multi.ReadFirstOrDefaultAsync().ConfigureAwait(false); if (first == null || first.id == null) return 0; var id = (int)first.id; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (pi.Length == 0) return id; var idp = pi[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; } } public partial class SqlCeServerAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. 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[0] == null || r[0].id == null) return 0; var id = (int)r[0].id; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (pi.Length == 0) return id; var idp = pi[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; } } public partial class MySqlAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. 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.Length == 0) return Convert.ToInt32(id); var idp = pi[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return Convert.ToInt32(id); } } public partial class PostgresAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. 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.Length == 0) { 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 assigning the corresponding property in the object - by product is that it supports compound primary keys var id = 0; foreach (var p in propertyInfos) { var value = ((IDictionary)results.First())[p.Name.ToLower()]; p.SetValue(entityToInsert, value, null); if (id == 0) id = Convert.ToInt32(value); } return id; } } public partial class SQLiteAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. 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).ConfigureAwait(false); var id = (int)(await multi.ReadFirstAsync().ConfigureAwait(false)).id; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (pi.Length == 0) return id; var idp = pi[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; } } public partial class FbAdapter { /// /// Inserts into the database, returning the Id of the row created. /// /// The connection to use. /// The transaction to use. /// The command timeout to use. /// The table to insert into. /// The columns to set with this insert. /// The parameters to set for this insert. /// The key columns in this table. /// The entity to insert. /// The Id of the row created. 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 propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); var keyName = propertyInfos[0].Name; var r = await connection.QueryAsync($"SELECT FIRST 1 {keyName} ID FROM {tableName} ORDER BY {keyName} DESC", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); var id = r.First().ID; if (id == null) return 0; if (propertyInfos.Length == 0) return Convert.ToInt32(id); var idp = propertyInfos[0]; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return Convert.ToInt32(id); } }