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: pluralizer // 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); T obj; if (type.IsInterface) { var res = (await connection.QueryAsync(sql, dynParms).ConfigureAwait(false)).FirstOrDefault() as IDictionary; if (res == null) return null; obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; property.SetValue(obj, val, null); } ((IProxy)obj).IsDirty = false; //reset change tracking and return } else { obj = (await connection.QueryAsync(sql, dynParms, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault(); } return obj; } /// /// 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 Task InsertAsync(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { var type = typeof(T); 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(", "); } var adapter = GetFormatter(connection); return adapter.InsertAsync(connection, transaction, commandTimeout, name, sbColumnList.ToString(), sbParameterList.ToString(), keyProperties, entityToInsert); } /// /// 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); var keyProperties = KeyPropertiesCache(type); if (!keyProperties.Any()) throw new ArgumentException("Entity must have at least one [Key] property"); var name = GetTableName(type); var sb = new StringBuilder(); sb.AppendFormat("update {0} set ", name); var allProperties = TypePropertiesCache(type); 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); var keyProperties = KeyPropertiesCache(type); if (!keyProperties.Any()) throw new ArgumentException("Entity must have at least one [Key] property"); var name = GetTableName(type); 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})", tableName, columnList, parameterList); await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); //NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE var r = await connection.QueryAsync("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); var id = (int)r.First().id; var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (propertyInfos.Any()) propertyInfos.First().SetValue(entityToInsert, id, 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})", tableName, columnList, parameterList); await connection.ExecuteAsync(cmd, entityToInsert, transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); var r = await connection.QueryAsync("select last_insert_rowid() id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); var id = (int)r.First().id; var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (propertyInfos.Any()) propertyInfos.First().SetValue(entityToInsert, id, null); return id; } }