using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Dapper; #pragma warning disable 1573, 1591 // xml comments 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 /// 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 keys = KeyPropertiesCache(type); if (keys.Count() > 1) throw new DataException("Get only supports an entity with a single [Key] property"); if (!keys.Any()) throw new DataException("Get only supports en entity with a [Key] property"); var onlyKey = keys.First(); var name = GetTableName(type); // TODO: query information schema and only select fields that are both in information schema and underlying class / interface sql = "select * from " + name + " where " + onlyKey.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 /// Entity of T public static async 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)) { var keys = KeyPropertiesCache(type); if (keys.Count() > 1) throw new DataException("Get only supports an entity with a single [Key] property"); if (!keys.Any()) throw new DataException("Get only supports en entity with a [Key] property"); var name = GetTableName(type); // TODO: query information schema and only select fields that are both in information schema and underlying class / interface sql = "select * from " + name; GetQueries[cacheType.TypeHandle] = sql; } if (!type.IsInterface) { return await connection.QueryAsync(sql, null, transaction, commandTimeout: commandTimeout); } 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 /// Identity of inserted entity public static async Task InsertAsync(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null, ISqlAdapter sqlAdapter = null) where T : class { var isList = false; var type = typeof(T); if (type.IsArray || 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); sbColumnList.AppendFormat("[{0}]", 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 { if (sqlAdapter == null) sqlAdapter = GetFormatter(connection); return await sqlAdapter.InsertAsync(connection, transaction, commandTimeout, name, sbColumnList.ToString(), sbParameterList.ToString(), keyProperties, entityToInsert); } //insert list of entities var cmd = String.Format("insert into {0} ({1}) values ({2})", name, sbColumnList, sbParameterList); return await 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 /// 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.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(); for (var i = 0; i < nonIdProps.Count(); i++) { var property = nonIdProps.ElementAt(i); sb.AppendFormat("{0} = @{1}", property.Name, 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); sb.AppendFormat("{0} = @{1}", property.Name, 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 /// 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", "entityToDelete"); var type = typeof(T); if (type.IsArray || 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 /// 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 name = GetTableName(type); var statement = String.Format("delete from {0}", name); 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 = String.Format("insert into {0} ({1}) values ({2});select SCOPE_IDENTITY() id", tableName, columnList, parameterList); 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 = String.Format("insert into {0} ({1}) values ({2})", tableName, columnList, 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 : ISqlAdapter { public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = String.Format("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList); await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); var r = await connection.QueryAsync("select LAST_INSERT_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 id; var idp = pi.First(); idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return 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; 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 { public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) { var cmd = String.Format("insert into {0} ({1}) values ({2}); select last_insert_rowid() id", tableName, columnList, parameterList); 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; } }