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;
}
}