From 082ce740452273599642db6b8ee1ef843bf43cbc Mon Sep 17 00:00:00 2001 From: frankhommers Date: Thu, 1 Dec 2016 15:11:43 +0100 Subject: [PATCH 1/2] Implemented ColumnNameMapping --- Dapper.Contrib/SqlMapperExtensions.cs | 1472 +++++++++++++------------ 1 file changed, 749 insertions(+), 723 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 11c947b26..cb0c57cda 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -6,8 +6,8 @@ using System.Text; using System.Collections.Concurrent; using System.Reflection.Emit; - using Dapper; +using static Dapper.Contrib.Extensions.SqlMapperExtensions; #if COREFX using DataException = System.InvalidOperationException; @@ -17,850 +17,876 @@ namespace Dapper.Contrib.Extensions { - public static partial class SqlMapperExtensions + public static partial class SqlMapperExtensions + { + // ReSharper disable once MemberCanBePrivate.Global + public interface IProxy //must be kept public { - // ReSharper disable once MemberCanBePrivate.Global - public interface IProxy //must be kept public - { - bool IsDirty { get; set; } - } + bool IsDirty { get; set; } + } - public interface ITableNameMapper - { - string GetTableName(Type type); - } + public interface ITableNameMapper + { + string GetTableName(Type type); + } + + public delegate string GetDatabaseTypeDelegate(IDbConnection connection); + public delegate string TableNameMapperDelegate(Type type); + public delegate string ColumNameMapperDelegate(PropertyInfo propertyInfo); - public delegate string GetDatabaseTypeDelegate(IDbConnection connection); - public delegate string TableNameMapperDelegate(Type type); + private static readonly ConcurrentDictionary> KeyProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> ExplicitKeyProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary> KeyProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> ExplicitKeyProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary PropertyInfoToColumnName = new ConcurrentDictionary(); - private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter(); - private static readonly Dictionary AdapterDictionary - = new Dictionary - { + + private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter(); + private static readonly Dictionary AdapterDictionary + = new Dictionary + { {"sqlconnection", new SqlServerAdapter()}, {"sqlceconnection", new SqlCeServerAdapter()}, {"npgsqlconnection", new PostgresAdapter()}, {"sqliteconnection", new SQLiteAdapter()}, {"mysqlconnection", new MySqlAdapter()}, - }; + }; - private static List ComputedPropertiesCache(Type type) - { - IEnumerable pi; - if (ComputedProperties.TryGetValue(type.TypeHandle, out pi)) - { - return pi.ToList(); - } - var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList(); + public static ColumNameMapperDelegate ColumnNameMapper; + private static string PropertyInfoToColumnNameCache(PropertyInfo propertyInfo) + { + string name = null; + if (PropertyInfoToColumnName.TryGetValue(propertyInfo, out name)) + { + return name; + } + if (ColumnNameMapper == null) + { + name = propertyInfo.Name; + } + else + { + name = ColumnNameMapper(propertyInfo); + } + PropertyInfoToColumnName[propertyInfo] = name; + return name; + } - ComputedProperties[type.TypeHandle] = computedProperties; - return computedProperties; - } - private static List ExplicitKeyPropertiesCache(Type type) - { - IEnumerable pi; - if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out pi)) - { - return pi.ToList(); - } + private static List ComputedPropertiesCache(Type type) + { + IEnumerable pi; + if (ComputedProperties.TryGetValue(type.TypeHandle, out pi)) + { + return pi.ToList(); + } - var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList(); + var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList(); - ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties; - return explicitKeyProperties; - } + ComputedProperties[type.TypeHandle] = computedProperties; + return computedProperties; + } - private static List KeyPropertiesCache(Type type) - { + private static List ExplicitKeyPropertiesCache(Type type) + { + IEnumerable pi; + if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out pi)) + { + return pi.ToList(); + } - IEnumerable pi; - if (KeyProperties.TryGetValue(type.TypeHandle, out pi)) - { - return pi.ToList(); - } - - var allProperties = TypePropertiesCache(type); - var keyProperties = allProperties.Where(p => - { - return p.GetCustomAttributes(true).Any(a => a is KeyAttribute); - }).ToList(); - - if (keyProperties.Count == 0) - { - var idProp = allProperties.FirstOrDefault(p => p.Name.ToLower() == "id"); - if (idProp != null && !idProp.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) - { - keyProperties.Add(idProp); - } - } - - KeyProperties[type.TypeHandle] = keyProperties; - return keyProperties; - } + var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList(); + + ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties; + return explicitKeyProperties; + } + + private static List KeyPropertiesCache(Type type) + { - private static List TypePropertiesCache(Type type) + IEnumerable pi; + if (KeyProperties.TryGetValue(type.TypeHandle, out pi)) + { + return pi.ToList(); + } + + var allProperties = TypePropertiesCache(type); + var keyProperties = allProperties.Where(p => + { + return p.GetCustomAttributes(true).Any(a => a is KeyAttribute); + }).ToList(); + + if (keyProperties.Count == 0) + { + var idProp = allProperties.FirstOrDefault(p => p.Name.ToLower() == "id"); + if (idProp != null && !idProp.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) { - IEnumerable pis; - if (TypeProperties.TryGetValue(type.TypeHandle, out pis)) - { - return pis.ToList(); - } - - var properties = type.GetProperties().Where(IsWriteable).ToArray(); - TypeProperties[type.TypeHandle] = properties; - return properties.ToList(); + keyProperties.Add(idProp); } + } - private static bool IsWriteable(PropertyInfo pi) - { - var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList(); - if (attributes.Count != 1) return true; + KeyProperties[type.TypeHandle] = keyProperties; + return keyProperties; + } - var writeAttribute = (WriteAttribute)attributes[0]; - return writeAttribute.Write; - } + private static List TypePropertiesCache(Type type) + { + IEnumerable pis; + if (TypeProperties.TryGetValue(type.TypeHandle, out pis)) + { + return pis.ToList(); + } + + var properties = type.GetProperties().Where(IsWriteable).ToArray(); + TypeProperties[type.TypeHandle] = properties; + return properties.ToList(); + } - private static PropertyInfo GetSingleKey(string method) - { - var type = typeof (T); - var keys = KeyPropertiesCache(type); - var explicitKeys = ExplicitKeyPropertiesCache(type); - var keyCount = keys.Count + explicitKeys.Count; - if (keyCount > 1) - throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property"); - if (keyCount == 0) - throw new DataException($"{method} only supports an entity with a [Key] or an [ExplicitKey] property"); - - return keys.Any() ? keys.First() : explicitKeys.First(); - } + private static bool IsWriteable(PropertyInfo pi) + { + var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList(); + if (attributes.Count != 1) return true; - /// - /// Returns a single entity by a single id from table "Ts". - /// Id 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 - /// 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 T Get(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var type = typeof(T); + var writeAttribute = (WriteAttribute)attributes[0]; + return writeAttribute.Write; + } - string sql; - if (!GetQueries.TryGetValue(type.TypeHandle, out sql)) - { - var key = GetSingleKey(nameof(Get)); - var name = GetTableName(type); + private static PropertyInfo GetSingleKey(string method) + { + var type = typeof(T); + var keys = KeyPropertiesCache(type); + var explicitKeys = ExplicitKeyPropertiesCache(type); + var keyCount = keys.Count + explicitKeys.Count; + if (keyCount > 1) + throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property"); + if (keyCount == 0) + throw new DataException($"{method} only supports an entity with a [Key] or an [ExplicitKey] property"); + + return keys.Any() ? keys.First() : explicitKeys.First(); + } - sql = $"select * from {name} where {key.Name} = @id"; - GetQueries[type.TypeHandle] = sql; - } + /// + /// Returns a single entity by a single id from table "Ts". + /// Id 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 + /// 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 T Get(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class + { + var type = typeof(T); - var dynParms = new DynamicParameters(); - dynParms.Add("@id", id); + string sql; + if (!GetQueries.TryGetValue(type.TypeHandle, out sql)) + { + var key = GetSingleKey(nameof(Get)); + var name = GetTableName(type); - T obj; + sql = $"SELECT * FROM {name} WHERE {PropertyInfoToColumnNameCache(key)} = @id"; + GetQueries[type.TypeHandle] = sql; + } - if (type.IsInterface()) - { - var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary; + var dynParms = new DynamicParameters(); + dynParms.Add("@id", id); - if (res == null) - return null; + T obj; - obj = ProxyGenerator.GetInterfaceProxy(); + if (type.IsInterface()) + { + var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary; - foreach (var property in TypePropertiesCache(type)) - { - var val = res[property.Name]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); - } + if (res == null) + return null; - ((IProxy)obj).IsDirty = false; //reset change tracking and return - } - else - { - obj = connection.Query(sql, dynParms, transaction, commandTimeout: commandTimeout).FirstOrDefault(); - } - return obj; - } + obj = ProxyGenerator.GetInterfaceProxy(); - /// - /// 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 IEnumerable GetAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class + foreach (var property in TypePropertiesCache(type)) { - 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.Query(sql, null, transaction, commandTimeout: commandTimeout); - - var result = connection.Query(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; + var val = res[PropertyInfoToColumnNameCache(property)]; + property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); } - /// - /// Specify a custom table name mapper based on the POCO type name - /// - public static TableNameMapperDelegate TableNameMapper; + ((IProxy)obj).IsDirty = false; //reset change tracking and return + } + else + { + obj = connection.Query(sql, dynParms, transaction, commandTimeout: commandTimeout).FirstOrDefault(); + } + return obj; + } - private static string GetTableName(Type type) + /// + /// 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 IEnumerable GetAll(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.Query(sql, null, transaction, commandTimeout: commandTimeout); + + var result = connection.Query(sql); + var list = new List(); + foreach (IDictionary res in result) + { + var obj = ProxyGenerator.GetInterfaceProxy(); + foreach (var property in TypePropertiesCache(type)) { - string name; - if (TypeTableName.TryGetValue(type.TypeHandle, out name)) return name; - - if (TableNameMapper != null) - { - name = TableNameMapper(type); - } - else - { - //NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework - var tableAttr = type + var val = res[PropertyInfoToColumnNameCache(property)]; + property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + } + ((IProxy)obj).IsDirty = false; //reset change tracking and return + list.Add(obj); + } + return list; + } + + /// + /// Specify a custom table name mapper based on the POCO type name + /// + public static TableNameMapperDelegate TableNameMapper; + + private static string GetTableName(Type type) + { + string name; + if (TypeTableName.TryGetValue(type.TypeHandle, out name)) return name; + + if (TableNameMapper != null) + { + name = TableNameMapper(type); + } + else + { + //NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework + var tableAttr = type #if COREFX .GetTypeInfo() #endif .GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic; - if (tableAttr != null) - name = tableAttr.Name; - else - { - name = type.Name + "s"; - if (type.IsInterface() && name.StartsWith("I")) - name = name.Substring(1); - } - } - - TypeTableName[type.TypeHandle] = name; - return name; + if (tableAttr != null) + name = tableAttr.Name; + else + { + name = type.Name + "s"; + if (type.IsInterface() && name.StartsWith("I")) + name = name.Substring(1); } + } + TypeTableName[type.TypeHandle] = name; + return name; + } - /// - /// Inserts an entity into table "Ts" and returns identity id or number if inserted rows if inserting a list. - /// - /// Open SqlConnection - /// Entity to insert, can be list of entities - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// Identity of inserted entity, or number of inserted rows if inserting a list - public static long Insert(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var isList = false; - - var type = typeof(T); - - 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(); - - var adapter = GetFormatter(connection); - - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) - { - var property = allPropertiesExceptKeyAndComputed.ElementAt(i); - adapter.AppendColumnName(sbColumnList, property.Name); //fix for issue #336 - 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(", "); - } - - int returnVal; - var wasClosed = connection.State == ConnectionState.Closed; - if (wasClosed) connection.Open(); - - if (!isList) //single entity - { - returnVal = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(), - sbParameterList.ToString(), keyProperties, entityToInsert); - } - else - { - //insert list of entities - var cmd = $"insert into {name} ({sbColumnList}) values ({sbParameterList})"; - returnVal = connection.Execute(cmd, entityToInsert, transaction, commandTimeout); - } - if (wasClosed) connection.Close(); - return returnVal; - } - /// - /// Updates entity in table "Ts", 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 bool Update(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).ToList(); //added ToList() due to issue #418, must work on a list copy - 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); //fix for issue #336 - 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); //fix for issue #336 - if (i < keyProperties.Count - 1) - sb.AppendFormat(" and "); - } - var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction); - return updated > 0; - } + /// + /// Inserts an entity into table "Ts" and returns identity id or number if inserted rows if inserting a list. + /// + /// Open SqlConnection + /// Entity to insert, can be list of entities + /// The transaction to run under, null (the default) if none + /// Number of seconds before command execution timeout + /// Identity of inserted entity, or number of inserted rows if inserting a list + public static long Insert(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class + { + var isList = false; + + var type = typeof(T); + + 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(); + + var adapter = GetFormatter(connection); + + for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) + { + var property = allPropertiesExceptKeyAndComputed.ElementAt(i); + adapter.AppendColumnName(sbColumnList, PropertyInfoToColumnNameCache(property)); //fix for issue #336 + 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(", "); + } + + int returnVal; + var wasClosed = connection.State == ConnectionState.Closed; + if (wasClosed) connection.Open(); + + if (!isList) //single entity + { + returnVal = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(), + sbParameterList.ToString(), keyProperties, ColumnNameMapper, entityToInsert); + } + else + { + //insert list of entities + var cmd = $"INSERT INTO {name} ({sbColumnList}) VALUES ({sbParameterList})"; + returnVal = connection.Execute(cmd, entityToInsert, transaction, commandTimeout); + } + if (wasClosed) connection.Close(); + return returnVal; + } - /// - /// Delete entity in table "Ts". - /// - /// 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 bool Delete(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).ToList(); //added ToList() due to issue #418, must work on a list copy - 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); - - var adapter = GetFormatter(connection); - - for (var i = 0; i < keyProperties.Count; i++) - { - var property = keyProperties.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 - if (i < keyProperties.Count - 1) - sb.AppendFormat(" and "); - } - var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction, commandTimeout); - return deleted > 0; - } + /// + /// Updates entity in table "Ts", 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 bool Update(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).ToList(); //added ToList() due to issue #418, must work on a list copy + 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, PropertyInfoToColumnNameCache(property)); //fix for issue #336 + 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, PropertyInfoToColumnNameCache(property)); //fix for issue #336 + if (i < keyProperties.Count - 1) + sb.AppendFormat(" AND "); + } + var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction); + return updated > 0; + } - /// - /// Delete all entities in the table related to the type T. - /// - /// 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 bool DeleteAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var type = typeof(T); - var name = GetTableName(type); - var statement = $"delete from {name}"; - var deleted = connection.Execute(statement, null, transaction, commandTimeout); - return deleted > 0; - } + /// + /// Delete entity in table "Ts". + /// + /// 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 bool Delete(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).ToList(); //added ToList() due to issue #418, must work on a list copy + 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); + + var adapter = GetFormatter(connection); + + for (var i = 0; i < keyProperties.Count; i++) + { + var property = keyProperties.ElementAt(i); + adapter.AppendColumnNameEqualsValue(sb, PropertyInfoToColumnNameCache(property)); //fix for issue #336 + if (i < keyProperties.Count - 1) + sb.AppendFormat(" AND "); + } + var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction, commandTimeout); + return deleted > 0; + } - /// - /// Specifies a custom callback that detects the database type instead of relying on the default strategy (the name of the connection type object). - /// Please note that this callback is global and will be used by all the calls that require a database specific adapter. - /// - public static GetDatabaseTypeDelegate GetDatabaseType; - - private static ISqlAdapter GetFormatter(IDbConnection connection) - { - var name = GetDatabaseType?.Invoke(connection).ToLower() - ?? connection.GetType().Name.ToLower(); + /// + /// Delete all entities in the table related to the type T. + /// + /// 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 bool DeleteAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class + { + var type = typeof(T); + var name = GetTableName(type); + var statement = $"DELETE FROM {name}"; + var deleted = connection.Execute(statement, null, transaction, commandTimeout); + return deleted > 0; + } - return !AdapterDictionary.ContainsKey(name) - ? DefaultAdapter - : AdapterDictionary[name]; - } + /// + /// Specifies a custom callback that detects the database type instead of relying on the default strategy (the name of the connection type object). + /// Please note that this callback is global and will be used by all the calls that require a database specific adapter. + /// + public static GetDatabaseTypeDelegate GetDatabaseType; - static class ProxyGenerator - { - private static readonly Dictionary TypeCache = new Dictionary(); + private static ISqlAdapter GetFormatter(IDbConnection connection) + { + var name = GetDatabaseType?.Invoke(connection).ToLower() + ?? connection.GetType().Name.ToLower(); + + return !AdapterDictionary.ContainsKey(name) + ? DefaultAdapter + : AdapterDictionary[name]; + } - private static AssemblyBuilder GetAsmBuilder(string name) - { + static class ProxyGenerator + { + private static readonly Dictionary TypeCache = new Dictionary(); + + private static AssemblyBuilder GetAsmBuilder(string name) + { #if COREFX return AssemblyBuilder.DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); #else - return Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); + return Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); #endif - } + } - public static T GetInterfaceProxy() - { - Type typeOfT = typeof(T); + public static T GetInterfaceProxy() + { + Type typeOfT = typeof(T); - Type k; - if (TypeCache.TryGetValue(typeOfT, out k)) - { - return (T)Activator.CreateInstance(k); - } - var assemblyBuilder = GetAsmBuilder(typeOfT.Name); + Type k; + if (TypeCache.TryGetValue(typeOfT, out k)) + { + return (T)Activator.CreateInstance(k); + } + var assemblyBuilder = GetAsmBuilder(typeOfT.Name); - var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter + var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter - var interfaceType = typeof(IProxy); - var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(), - TypeAttributes.Public | TypeAttributes.Class); - typeBuilder.AddInterfaceImplementation(typeOfT); - typeBuilder.AddInterfaceImplementation(interfaceType); + var interfaceType = typeof(IProxy); + var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(), + TypeAttributes.Public | TypeAttributes.Class); + typeBuilder.AddInterfaceImplementation(typeOfT); + typeBuilder.AddInterfaceImplementation(interfaceType); - //create our _isDirty field, which implements IProxy - var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder); + //create our _isDirty field, which implements IProxy + var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder); - // Generate a field for each property, which implements the T - foreach (var property in typeof(T).GetProperties()) - { - var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute); - CreateProperty(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId); - } + // Generate a field for each property, which implements the T + foreach (var property in typeof(T).GetProperties()) + { + var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute); + CreateProperty(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId); + } #if COREFX var generatedType = typeBuilder.CreateTypeInfo().AsType(); #else - var generatedType = typeBuilder.CreateType(); + var generatedType = typeBuilder.CreateType(); #endif - TypeCache.Add(typeOfT, generatedType); - return (T)Activator.CreateInstance(generatedType); - } - - - private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder) - { - var propType = typeof(bool); - var field = typeBuilder.DefineField("_" + "IsDirty", propType, FieldAttributes.Private); - var property = typeBuilder.DefineProperty("IsDirty", - System.Reflection.PropertyAttributes.None, - propType, - new[] { propType }); - - const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName | - MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig; - - // Define the "get" and "set" accessor methods - var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + "IsDirty", - getSetAttr, - propType, - Type.EmptyTypes); - var currGetIl = currGetPropMthdBldr.GetILGenerator(); - currGetIl.Emit(OpCodes.Ldarg_0); - currGetIl.Emit(OpCodes.Ldfld, field); - currGetIl.Emit(OpCodes.Ret); - var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + "IsDirty", - getSetAttr, - null, - new[] { propType }); - var currSetIl = currSetPropMthdBldr.GetILGenerator(); - currSetIl.Emit(OpCodes.Ldarg_0); - currSetIl.Emit(OpCodes.Ldarg_1); - currSetIl.Emit(OpCodes.Stfld, field); - currSetIl.Emit(OpCodes.Ret); - - property.SetGetMethod(currGetPropMthdBldr); - property.SetSetMethod(currSetPropMthdBldr); - var getMethod = typeof(IProxy).GetMethod("get_" + "IsDirty"); - var setMethod = typeof(IProxy).GetMethod("set_" + "IsDirty"); - typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); - typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); - - return currSetPropMthdBldr; - } - - private static void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity) - { - //Define the field and the property - var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private); - var property = typeBuilder.DefineProperty(propertyName, - System.Reflection.PropertyAttributes.None, - propType, - new[] { propType }); - - const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.Virtual | - MethodAttributes.HideBySig; - - // Define the "get" and "set" accessor methods - var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, - getSetAttr, - propType, - Type.EmptyTypes); - - var currGetIl = currGetPropMthdBldr.GetILGenerator(); - currGetIl.Emit(OpCodes.Ldarg_0); - currGetIl.Emit(OpCodes.Ldfld, field); - currGetIl.Emit(OpCodes.Ret); - - var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, - getSetAttr, - null, - new[] { propType }); - - //store value in private field and set the isdirty flag - var currSetIl = currSetPropMthdBldr.GetILGenerator(); - currSetIl.Emit(OpCodes.Ldarg_0); - currSetIl.Emit(OpCodes.Ldarg_1); - currSetIl.Emit(OpCodes.Stfld, field); - currSetIl.Emit(OpCodes.Ldarg_0); - currSetIl.Emit(OpCodes.Ldc_I4_1); - currSetIl.Emit(OpCodes.Call, setIsDirtyMethod); - currSetIl.Emit(OpCodes.Ret); - - //TODO: Should copy all attributes defined by the interface? - if (isIdentity) - { - var keyAttribute = typeof(KeyAttribute); - var myConstructorInfo = keyAttribute.GetConstructor(new Type[] { }); - var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, new object[] { }); - property.SetCustomAttribute(attributeBuilder); - } - - property.SetGetMethod(currGetPropMthdBldr); - property.SetSetMethod(currSetPropMthdBldr); - var getMethod = typeof(T).GetMethod("get_" + propertyName); - var setMethod = typeof(T).GetMethod("set_" + propertyName); - typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); - typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); - } - } - } - - [AttributeUsage(AttributeTargets.Class)] - public class TableAttribute : Attribute - { - public TableAttribute(string tableName) + TypeCache.Add(typeOfT, generatedType); + return (T)Activator.CreateInstance(generatedType); + } + + + private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder) + { + var propType = typeof(bool); + var field = typeBuilder.DefineField("_" + "IsDirty", propType, FieldAttributes.Private); + var property = typeBuilder.DefineProperty("IsDirty", + System.Reflection.PropertyAttributes.None, + propType, + new[] { propType }); + + const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName | + MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig; + + // Define the "get" and "set" accessor methods + var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + "IsDirty", + getSetAttr, + propType, + Type.EmptyTypes); + var currGetIl = currGetPropMthdBldr.GetILGenerator(); + currGetIl.Emit(OpCodes.Ldarg_0); + currGetIl.Emit(OpCodes.Ldfld, field); + currGetIl.Emit(OpCodes.Ret); + var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + "IsDirty", + getSetAttr, + null, + new[] { propType }); + var currSetIl = currSetPropMthdBldr.GetILGenerator(); + currSetIl.Emit(OpCodes.Ldarg_0); + currSetIl.Emit(OpCodes.Ldarg_1); + currSetIl.Emit(OpCodes.Stfld, field); + currSetIl.Emit(OpCodes.Ret); + + property.SetGetMethod(currGetPropMthdBldr); + property.SetSetMethod(currSetPropMthdBldr); + var getMethod = typeof(IProxy).GetMethod("get_" + "IsDirty"); + var setMethod = typeof(IProxy).GetMethod("set_" + "IsDirty"); + typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); + typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); + + return currSetPropMthdBldr; + } + + private static void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity) + { + //Define the field and the property + var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private); + var property = typeBuilder.DefineProperty(propertyName, + System.Reflection.PropertyAttributes.None, + propType, + new[] { propType }); + + const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.Virtual | + MethodAttributes.HideBySig; + + // Define the "get" and "set" accessor methods + var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, + getSetAttr, + propType, + Type.EmptyTypes); + + var currGetIl = currGetPropMthdBldr.GetILGenerator(); + currGetIl.Emit(OpCodes.Ldarg_0); + currGetIl.Emit(OpCodes.Ldfld, field); + currGetIl.Emit(OpCodes.Ret); + + var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, + getSetAttr, + null, + new[] { propType }); + + //store value in private field and set the isdirty flag + var currSetIl = currSetPropMthdBldr.GetILGenerator(); + currSetIl.Emit(OpCodes.Ldarg_0); + currSetIl.Emit(OpCodes.Ldarg_1); + currSetIl.Emit(OpCodes.Stfld, field); + currSetIl.Emit(OpCodes.Ldarg_0); + currSetIl.Emit(OpCodes.Ldc_I4_1); + currSetIl.Emit(OpCodes.Call, setIsDirtyMethod); + currSetIl.Emit(OpCodes.Ret); + + //TODO: Should copy all attributes defined by the interface? + if (isIdentity) { - Name = tableName; + var keyAttribute = typeof(KeyAttribute); + var myConstructorInfo = keyAttribute.GetConstructor(new Type[] { }); + var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, new object[] { }); + property.SetCustomAttribute(attributeBuilder); } - // ReSharper disable once MemberCanBePrivate.Global - // ReSharper disable once UnusedAutoPropertyAccessor.Global - public string Name { get; set; } - } - - // do not want to depend on data annotations that is not in client profile - [AttributeUsage(AttributeTargets.Property)] - public class KeyAttribute : Attribute - { + property.SetGetMethod(currGetPropMthdBldr); + property.SetSetMethod(currSetPropMthdBldr); + var getMethod = typeof(T).GetMethod("get_" + propertyName); + var setMethod = typeof(T).GetMethod("set_" + propertyName); + typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); + typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); + } } + } - [AttributeUsage(AttributeTargets.Property)] - public class ExplicitKeyAttribute : Attribute + [AttributeUsage(AttributeTargets.Class)] + public class TableAttribute : Attribute + { + public TableAttribute(string tableName) { + Name = tableName; } - [AttributeUsage(AttributeTargets.Property)] - public class WriteAttribute : Attribute + // ReSharper disable once MemberCanBePrivate.Global + // ReSharper disable once UnusedAutoPropertyAccessor.Global + public string Name { get; set; } + } + + // do not want to depend on data annotations that is not in client profile + [AttributeUsage(AttributeTargets.Property)] + public class KeyAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public class ExplicitKeyAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public class WriteAttribute : Attribute + { + public WriteAttribute(bool write) { - public WriteAttribute(bool write) - { - Write = write; - } - public bool Write { get; } + Write = write; } + public bool Write { get; } + } - [AttributeUsage(AttributeTargets.Property)] - public class ComputedAttribute : Attribute - { - } + [AttributeUsage(AttributeTargets.Property)] + public class ComputedAttribute : Attribute + { + } } public partial interface ISqlAdapter { - int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert); - - //new methods for issue #336 - void AppendColumnName(StringBuilder sb, string columnName); - void AppendColumnNameEqualsValue(StringBuilder sb, string columnName); + int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert); + + //new methods for issue #336 + void AppendColumnName(StringBuilder sb, string columnName); + void AppendColumnNameEqualsValue(StringBuilder sb, string columnName); } public partial class SqlServerAdapter : ISqlAdapter { - public int Insert(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 = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) + { + var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList});SELECT SCOPE_IDENTITY() [id]"; + var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); - var first = multi.Read().FirstOrDefault(); - if (first == null || first.id == null) return 0; + var first = multi.Read().FirstOrDefault(); + if (first == null || first.id == null) return 0; - var id = (int)first.id; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) return id; + var id = (int)first.id; + var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); + if (!propertyInfos.Any()) return id; - var idProperty = propertyInfos.First(); - idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); + var idProperty = propertyInfos.First(); + idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); - return id; - } + return id; + } - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}]", columnName); - } + public void AppendColumnName(StringBuilder sb, string columnName) + { + sb.AppendFormat("[{0}]", columnName); + } - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}] = @{1}", columnName, columnName); - } + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + { + sb.AppendFormat("[{0}] = @{1}", columnName, columnName); + } } public partial class SqlCeServerAdapter : ISqlAdapter { - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; - connection.Execute(cmd, entityToInsert, transaction, commandTimeout); - var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ToList(); + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) + { + var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})"; + connection.Execute(cmd, entityToInsert, transaction, commandTimeout); + var r = connection.Query("SELECT @@IDENTITY [id]", transaction: transaction, commandTimeout: commandTimeout).ToList(); - if (r.First().id == null) return 0; - var id = (int) r.First().id; + if (r.First().id == null) return 0; + var id = (int)r.First().id; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) return id; + var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); + if (!propertyInfos.Any()) return id; - var idProperty = propertyInfos.First(); - idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); + var idProperty = propertyInfos.First(); + idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); - return id; - } + return id; + } - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}]", columnName); - } + public void AppendColumnName(StringBuilder sb, string columnName) + { + sb.AppendFormat("[{0}]", columnName); + } - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}] = @{1}", columnName, columnName); - } + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + { + sb.AppendFormat("[{0}] = @{1}", columnName, columnName); + } } public partial class MySqlAdapter : ISqlAdapter { - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; - connection.Execute(cmd, entityToInsert, transaction, commandTimeout); - var r = connection.Query("Select LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout); - - var id = r.First().id; - if (id == null) return 0; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) return Convert.ToInt32(id); - - var idp = propertyInfos.First(); - idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); - - return Convert.ToInt32(id); - } - - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("`{0}`", columnName); - } - - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("`{0}` = @{1}", columnName, columnName); - } + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) + { + var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; + connection.Execute(cmd, entityToInsert, transaction, commandTimeout); + var r = connection.Query("Select LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout); + + var id = r.First().id; + if (id == null) return 0; + var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); + if (!propertyInfos.Any()) return Convert.ToInt32(id); + + var idp = propertyInfos.First(); + idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); + + return Convert.ToInt32(id); + } + + public void AppendColumnName(StringBuilder sb, string columnName) + { + sb.AppendFormat("`{0}`", columnName); + } + + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + { + sb.AppendFormat("`{0}` = @{1}", columnName, columnName); + } } public partial class PostgresAdapter : ISqlAdapter { - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, 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 { - 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 "); - var first = true; - foreach (var property in propertyInfos) - { - if (!first) - sb.Append(", "); - first = false; - sb.Append(property.Name); - } - } - - var results = connection.Query(sb.ToString(), entityToInsert, transaction, commandTimeout: commandTimeout).ToList(); - - // 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; + sb.Append(" RETURNING "); + var first = true; + foreach (var property in propertyInfos) + { + if (!first) + sb.Append(", "); + first = false; + sb.AppendFormat("\"{0}\"", columnNameMapper(property)); + } } - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("\"{0}\"", columnName); - } + var results = connection.Query(sb.ToString(), entityToInsert, transaction, commandTimeout: commandTimeout).ToList(); - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + // 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) { - sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); + var value = ((IDictionary)results.First())[columnNameMapper(p)]; + p.SetValue(entityToInsert, value, null); + if (id == 0) + id = Convert.ToInt32(value); } + return id; + } + + public void AppendColumnName(StringBuilder sb, string columnName) + { + sb.AppendFormat("\"{0}\"", columnName); + } + + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + { + sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); + } } public partial class SQLiteAdapter : ISqlAdapter { - public int Insert(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 = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); - - var id = (int)multi.Read().First().id; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) return id; - - var idProperty = propertyInfos.First(); - idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); - - return id; - } - - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("\"{0}\"", columnName); - } - - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); - } + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) + { + var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id"; + var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); + + var id = (int)multi.Read().First().id; + var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); + if (!propertyInfos.Any()) return id; + + var idProperty = propertyInfos.First(); + idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); + + return id; + } + + public void AppendColumnName(StringBuilder sb, string columnName) + { + sb.AppendFormat("\"{0}\"", columnName); + } + + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + { + sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); + } } From 0498a88136a6b1c9d18e93bcba1bc05cde243bc6 Mon Sep 17 00:00:00 2001 From: frankhommers Date: Thu, 1 Dec 2016 15:34:34 +0100 Subject: [PATCH 2/2] Implemented ColumnNameMapping (fixed tabs) --- Dapper.Contrib/SqlMapperExtensions.cs | 1484 ++++++++++++------------- 1 file changed, 742 insertions(+), 742 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index cb0c57cda..828b484dc 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -17,876 +17,876 @@ namespace Dapper.Contrib.Extensions { - public static partial class SqlMapperExtensions - { - // ReSharper disable once MemberCanBePrivate.Global - public interface IProxy //must be kept public + public static partial class SqlMapperExtensions { - bool IsDirty { get; set; } - } + // ReSharper disable once MemberCanBePrivate.Global + public interface IProxy //must be kept public + { + bool IsDirty { get; set; } + } - public interface ITableNameMapper - { - string GetTableName(Type type); - } + public interface ITableNameMapper + { + string GetTableName(Type type); + } - public delegate string GetDatabaseTypeDelegate(IDbConnection connection); - public delegate string TableNameMapperDelegate(Type type); - public delegate string ColumNameMapperDelegate(PropertyInfo propertyInfo); + public delegate string GetDatabaseTypeDelegate(IDbConnection connection); + public delegate string TableNameMapperDelegate(Type type); + public delegate string ColumNameMapperDelegate(PropertyInfo propertyInfo); - private static readonly ConcurrentDictionary> KeyProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> ExplicitKeyProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary> KeyProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> ExplicitKeyProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary PropertyInfoToColumnName = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary PropertyInfoToColumnName = new ConcurrentDictionary(); - private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter(); - private static readonly Dictionary AdapterDictionary - = new Dictionary - { + private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter(); + private static readonly Dictionary AdapterDictionary + = new Dictionary + { {"sqlconnection", new SqlServerAdapter()}, {"sqlceconnection", new SqlCeServerAdapter()}, {"npgsqlconnection", new PostgresAdapter()}, {"sqliteconnection", new SQLiteAdapter()}, {"mysqlconnection", new MySqlAdapter()}, - }; + }; - public static ColumNameMapperDelegate ColumnNameMapper; - private static string PropertyInfoToColumnNameCache(PropertyInfo propertyInfo) - { - string name = null; - if (PropertyInfoToColumnName.TryGetValue(propertyInfo, out name)) - { - return name; - } - if (ColumnNameMapper == null) - { - name = propertyInfo.Name; - } - else - { - name = ColumnNameMapper(propertyInfo); - } - PropertyInfoToColumnName[propertyInfo] = name; - return name; - } - + public static ColumNameMapperDelegate ColumnNameMapper; + private static string PropertyInfoToColumnNameCache(PropertyInfo propertyInfo) + { + string name = null; + if (PropertyInfoToColumnName.TryGetValue(propertyInfo, out name)) + { + return name; + } + if (ColumnNameMapper == null) + { + name = propertyInfo.Name; + } + else + { + name = ColumnNameMapper(propertyInfo); + } + PropertyInfoToColumnName[propertyInfo] = name; + return name; + } - private static List ComputedPropertiesCache(Type type) - { - IEnumerable pi; - if (ComputedProperties.TryGetValue(type.TypeHandle, out pi)) - { - return pi.ToList(); - } - var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList(); + private static List ComputedPropertiesCache(Type type) + { + IEnumerable pi; + if (ComputedProperties.TryGetValue(type.TypeHandle, out pi)) + { + return pi.ToList(); + } - ComputedProperties[type.TypeHandle] = computedProperties; - return computedProperties; - } + var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList(); - private static List ExplicitKeyPropertiesCache(Type type) - { - IEnumerable pi; - if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out pi)) - { - return pi.ToList(); - } + ComputedProperties[type.TypeHandle] = computedProperties; + return computedProperties; + } - var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList(); + private static List ExplicitKeyPropertiesCache(Type type) + { + IEnumerable pi; + if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out pi)) + { + return pi.ToList(); + } - ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties; - return explicitKeyProperties; - } + var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList(); - private static List KeyPropertiesCache(Type type) - { + ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties; + return explicitKeyProperties; + } - IEnumerable pi; - if (KeyProperties.TryGetValue(type.TypeHandle, out pi)) - { - return pi.ToList(); - } - - var allProperties = TypePropertiesCache(type); - var keyProperties = allProperties.Where(p => - { - return p.GetCustomAttributes(true).Any(a => a is KeyAttribute); - }).ToList(); - - if (keyProperties.Count == 0) - { - var idProp = allProperties.FirstOrDefault(p => p.Name.ToLower() == "id"); - if (idProp != null && !idProp.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) + private static List KeyPropertiesCache(Type type) { - keyProperties.Add(idProp); + + IEnumerable pi; + if (KeyProperties.TryGetValue(type.TypeHandle, out pi)) + { + return pi.ToList(); + } + + var allProperties = TypePropertiesCache(type); + var keyProperties = allProperties.Where(p => + { + return p.GetCustomAttributes(true).Any(a => a is KeyAttribute); + }).ToList(); + + if (keyProperties.Count == 0) + { + var idProp = allProperties.FirstOrDefault(p => p.Name.ToLower() == "id"); + if (idProp != null && !idProp.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) + { + keyProperties.Add(idProp); + } + } + + KeyProperties[type.TypeHandle] = keyProperties; + return keyProperties; } - } - KeyProperties[type.TypeHandle] = keyProperties; - return keyProperties; - } + private static List TypePropertiesCache(Type type) + { + IEnumerable pis; + if (TypeProperties.TryGetValue(type.TypeHandle, out pis)) + { + return pis.ToList(); + } + + var properties = type.GetProperties().Where(IsWriteable).ToArray(); + TypeProperties[type.TypeHandle] = properties; + return properties.ToList(); + } - private static List TypePropertiesCache(Type type) - { - IEnumerable pis; - if (TypeProperties.TryGetValue(type.TypeHandle, out pis)) - { - return pis.ToList(); - } - - var properties = type.GetProperties().Where(IsWriteable).ToArray(); - TypeProperties[type.TypeHandle] = properties; - return properties.ToList(); - } + private static bool IsWriteable(PropertyInfo pi) + { + var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList(); + if (attributes.Count != 1) return true; - private static bool IsWriteable(PropertyInfo pi) - { - var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList(); - if (attributes.Count != 1) return true; + var writeAttribute = (WriteAttribute)attributes[0]; + return writeAttribute.Write; + } - var writeAttribute = (WriteAttribute)attributes[0]; - return writeAttribute.Write; - } + private static PropertyInfo GetSingleKey(string method) + { + var type = typeof(T); + var keys = KeyPropertiesCache(type); + var explicitKeys = ExplicitKeyPropertiesCache(type); + var keyCount = keys.Count + explicitKeys.Count; + if (keyCount > 1) + throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property"); + if (keyCount == 0) + throw new DataException($"{method} only supports an entity with a [Key] or an [ExplicitKey] property"); + + return keys.Any() ? keys.First() : explicitKeys.First(); + } - private static PropertyInfo GetSingleKey(string method) - { - var type = typeof(T); - var keys = KeyPropertiesCache(type); - var explicitKeys = ExplicitKeyPropertiesCache(type); - var keyCount = keys.Count + explicitKeys.Count; - if (keyCount > 1) - throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property"); - if (keyCount == 0) - throw new DataException($"{method} only supports an entity with a [Key] or an [ExplicitKey] property"); - - return keys.Any() ? keys.First() : explicitKeys.First(); - } + /// + /// Returns a single entity by a single id from table "Ts". + /// Id 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 + /// 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 T Get(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class + { + var type = typeof(T); - /// - /// Returns a single entity by a single id from table "Ts". - /// Id 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 - /// 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 T Get(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(Get)); + var name = GetTableName(type); - string sql; - if (!GetQueries.TryGetValue(type.TypeHandle, out sql)) - { - var key = GetSingleKey(nameof(Get)); - var name = GetTableName(type); + sql = $"SELECT * FROM {name} WHERE {PropertyInfoToColumnNameCache(key)} = @id"; + GetQueries[type.TypeHandle] = sql; + } - sql = $"SELECT * FROM {name} WHERE {PropertyInfoToColumnNameCache(key)} = @id"; - GetQueries[type.TypeHandle] = sql; - } + var dynParms = new DynamicParameters(); + dynParms.Add("@id", id); - var dynParms = new DynamicParameters(); - dynParms.Add("@id", id); + T obj; - T obj; + if (type.IsInterface()) + { + var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary; - if (type.IsInterface()) - { - var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary; + if (res == null) + return null; - if (res == null) - return null; + obj = ProxyGenerator.GetInterfaceProxy(); - obj = ProxyGenerator.GetInterfaceProxy(); + foreach (var property in TypePropertiesCache(type)) + { + var val = res[PropertyInfoToColumnNameCache(property)]; + property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + } - foreach (var property in TypePropertiesCache(type)) - { - var val = res[PropertyInfoToColumnNameCache(property)]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + ((IProxy)obj).IsDirty = false; //reset change tracking and return + } + else + { + obj = connection.Query(sql, dynParms, transaction, commandTimeout: commandTimeout).FirstOrDefault(); + } + return obj; } - ((IProxy)obj).IsDirty = false; //reset change tracking and return - } - else - { - obj = connection.Query(sql, dynParms, transaction, commandTimeout: commandTimeout).FirstOrDefault(); - } - 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 IEnumerable GetAll(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.Query(sql, null, transaction, commandTimeout: commandTimeout); - - var result = connection.Query(sql); - var list = new List(); - foreach (IDictionary res in result) - { - var obj = ProxyGenerator.GetInterfaceProxy(); - foreach (var property in TypePropertiesCache(type)) + /// + /// 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 IEnumerable GetAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class { - var val = res[PropertyInfoToColumnNameCache(property)]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + 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.Query(sql, null, transaction, commandTimeout: commandTimeout); + + var result = connection.Query(sql); + var list = new List(); + foreach (IDictionary res in result) + { + var obj = ProxyGenerator.GetInterfaceProxy(); + foreach (var property in TypePropertiesCache(type)) + { + var val = res[PropertyInfoToColumnNameCache(property)]; + property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + } + ((IProxy)obj).IsDirty = false; //reset change tracking and return + list.Add(obj); + } + return list; } - ((IProxy)obj).IsDirty = false; //reset change tracking and return - list.Add(obj); - } - return list; - } - /// - /// Specify a custom table name mapper based on the POCO type name - /// - public static TableNameMapperDelegate TableNameMapper; + /// + /// Specify a custom table name mapper based on the POCO type name + /// + public static TableNameMapperDelegate TableNameMapper; - private static string GetTableName(Type type) - { - string name; - if (TypeTableName.TryGetValue(type.TypeHandle, out name)) return name; - - if (TableNameMapper != null) - { - name = TableNameMapper(type); - } - else - { - //NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework - var tableAttr = type + private static string GetTableName(Type type) + { + string name; + if (TypeTableName.TryGetValue(type.TypeHandle, out name)) return name; + + if (TableNameMapper != null) + { + name = TableNameMapper(type); + } + else + { + //NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework + var tableAttr = type #if COREFX .GetTypeInfo() #endif .GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic; - if (tableAttr != null) - name = tableAttr.Name; - else - { - name = type.Name + "s"; - if (type.IsInterface() && name.StartsWith("I")) - name = name.Substring(1); + if (tableAttr != null) + name = tableAttr.Name; + else + { + name = type.Name + "s"; + if (type.IsInterface() && name.StartsWith("I")) + name = name.Substring(1); + } + } + + TypeTableName[type.TypeHandle] = name; + return name; } - } - - TypeTableName[type.TypeHandle] = name; - return name; - } - /// - /// Inserts an entity into table "Ts" and returns identity id or number if inserted rows if inserting a list. - /// - /// Open SqlConnection - /// Entity to insert, can be list of entities - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// Identity of inserted entity, or number of inserted rows if inserting a list - public static long Insert(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var isList = false; - - var type = typeof(T); - - 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(); - - var adapter = GetFormatter(connection); - - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) - { - var property = allPropertiesExceptKeyAndComputed.ElementAt(i); - adapter.AppendColumnName(sbColumnList, PropertyInfoToColumnNameCache(property)); //fix for issue #336 - 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(", "); - } - - int returnVal; - var wasClosed = connection.State == ConnectionState.Closed; - if (wasClosed) connection.Open(); - - if (!isList) //single entity - { - returnVal = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(), - sbParameterList.ToString(), keyProperties, ColumnNameMapper, entityToInsert); - } - else - { - //insert list of entities - var cmd = $"INSERT INTO {name} ({sbColumnList}) VALUES ({sbParameterList})"; - returnVal = connection.Execute(cmd, entityToInsert, transaction, commandTimeout); - } - if (wasClosed) connection.Close(); - return returnVal; - } + /// + /// Inserts an entity into table "Ts" and returns identity id or number if inserted rows if inserting a list. + /// + /// Open SqlConnection + /// Entity to insert, can be list of entities + /// The transaction to run under, null (the default) if none + /// Number of seconds before command execution timeout + /// Identity of inserted entity, or number of inserted rows if inserting a list + public static long Insert(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class + { + var isList = false; + + var type = typeof(T); + + 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(); + + var adapter = GetFormatter(connection); + + for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) + { + var property = allPropertiesExceptKeyAndComputed.ElementAt(i); + adapter.AppendColumnName(sbColumnList, PropertyInfoToColumnNameCache(property)); //fix for issue #336 + 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(", "); + } + + int returnVal; + var wasClosed = connection.State == ConnectionState.Closed; + if (wasClosed) connection.Open(); + + if (!isList) //single entity + { + returnVal = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(), + sbParameterList.ToString(), keyProperties, ColumnNameMapper, entityToInsert); + } + else + { + //insert list of entities + var cmd = $"INSERT INTO {name} ({sbColumnList}) VALUES ({sbParameterList})"; + returnVal = connection.Execute(cmd, entityToInsert, transaction, commandTimeout); + } + if (wasClosed) connection.Close(); + return returnVal; + } - /// - /// Updates entity in table "Ts", 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 bool Update(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).ToList(); //added ToList() due to issue #418, must work on a list copy - 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, PropertyInfoToColumnNameCache(property)); //fix for issue #336 - 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, PropertyInfoToColumnNameCache(property)); //fix for issue #336 - if (i < keyProperties.Count - 1) - sb.AppendFormat(" AND "); - } - var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction); - return updated > 0; - } + /// + /// Updates entity in table "Ts", 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 bool Update(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).ToList(); //added ToList() due to issue #418, must work on a list copy + 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, PropertyInfoToColumnNameCache(property)); //fix for issue #336 + 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, PropertyInfoToColumnNameCache(property)); //fix for issue #336 + if (i < keyProperties.Count - 1) + sb.AppendFormat(" AND "); + } + var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction); + return updated > 0; + } - /// - /// Delete entity in table "Ts". - /// - /// 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 bool Delete(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).ToList(); //added ToList() due to issue #418, must work on a list copy - 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); - - var adapter = GetFormatter(connection); - - for (var i = 0; i < keyProperties.Count; i++) - { - var property = keyProperties.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, PropertyInfoToColumnNameCache(property)); //fix for issue #336 - if (i < keyProperties.Count - 1) - sb.AppendFormat(" AND "); - } - var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction, commandTimeout); - return deleted > 0; - } + /// + /// Delete entity in table "Ts". + /// + /// 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 bool Delete(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).ToList(); //added ToList() due to issue #418, must work on a list copy + 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); + + var adapter = GetFormatter(connection); + + for (var i = 0; i < keyProperties.Count; i++) + { + var property = keyProperties.ElementAt(i); + adapter.AppendColumnNameEqualsValue(sb, PropertyInfoToColumnNameCache(property)); //fix for issue #336 + if (i < keyProperties.Count - 1) + sb.AppendFormat(" AND "); + } + var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction, commandTimeout); + return deleted > 0; + } - /// - /// Delete all entities in the table related to the type T. - /// - /// 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 bool DeleteAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var type = typeof(T); - var name = GetTableName(type); - var statement = $"DELETE FROM {name}"; - var deleted = connection.Execute(statement, null, transaction, commandTimeout); - return deleted > 0; - } + /// + /// Delete all entities in the table related to the type T. + /// + /// 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 bool DeleteAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class + { + var type = typeof(T); + var name = GetTableName(type); + var statement = $"DELETE FROM {name}"; + var deleted = connection.Execute(statement, null, transaction, commandTimeout); + return deleted > 0; + } - /// - /// Specifies a custom callback that detects the database type instead of relying on the default strategy (the name of the connection type object). - /// Please note that this callback is global and will be used by all the calls that require a database specific adapter. - /// - public static GetDatabaseTypeDelegate GetDatabaseType; + /// + /// Specifies a custom callback that detects the database type instead of relying on the default strategy (the name of the connection type object). + /// Please note that this callback is global and will be used by all the calls that require a database specific adapter. + /// + public static GetDatabaseTypeDelegate GetDatabaseType; - private static ISqlAdapter GetFormatter(IDbConnection connection) - { - var name = GetDatabaseType?.Invoke(connection).ToLower() - ?? connection.GetType().Name.ToLower(); + private static ISqlAdapter GetFormatter(IDbConnection connection) + { + var name = GetDatabaseType?.Invoke(connection).ToLower() + ?? connection.GetType().Name.ToLower(); - return !AdapterDictionary.ContainsKey(name) - ? DefaultAdapter - : AdapterDictionary[name]; - } + return !AdapterDictionary.ContainsKey(name) + ? DefaultAdapter + : AdapterDictionary[name]; + } - static class ProxyGenerator - { - private static readonly Dictionary TypeCache = new Dictionary(); + static class ProxyGenerator + { + private static readonly Dictionary TypeCache = new Dictionary(); - private static AssemblyBuilder GetAsmBuilder(string name) - { + private static AssemblyBuilder GetAsmBuilder(string name) + { #if COREFX return AssemblyBuilder.DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); #else - return Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); + return Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); #endif - } + } - public static T GetInterfaceProxy() - { - Type typeOfT = typeof(T); + public static T GetInterfaceProxy() + { + Type typeOfT = typeof(T); - Type k; - if (TypeCache.TryGetValue(typeOfT, out k)) - { - return (T)Activator.CreateInstance(k); - } - var assemblyBuilder = GetAsmBuilder(typeOfT.Name); + Type k; + if (TypeCache.TryGetValue(typeOfT, out k)) + { + return (T)Activator.CreateInstance(k); + } + var assemblyBuilder = GetAsmBuilder(typeOfT.Name); - var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter + var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter - var interfaceType = typeof(IProxy); - var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(), - TypeAttributes.Public | TypeAttributes.Class); - typeBuilder.AddInterfaceImplementation(typeOfT); - typeBuilder.AddInterfaceImplementation(interfaceType); + var interfaceType = typeof(IProxy); + var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(), + TypeAttributes.Public | TypeAttributes.Class); + typeBuilder.AddInterfaceImplementation(typeOfT); + typeBuilder.AddInterfaceImplementation(interfaceType); - //create our _isDirty field, which implements IProxy - var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder); + //create our _isDirty field, which implements IProxy + var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder); - // Generate a field for each property, which implements the T - foreach (var property in typeof(T).GetProperties()) - { - var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute); - CreateProperty(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId); - } + // Generate a field for each property, which implements the T + foreach (var property in typeof(T).GetProperties()) + { + var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute); + CreateProperty(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId); + } #if COREFX var generatedType = typeBuilder.CreateTypeInfo().AsType(); #else - var generatedType = typeBuilder.CreateType(); + var generatedType = typeBuilder.CreateType(); #endif - TypeCache.Add(typeOfT, generatedType); - return (T)Activator.CreateInstance(generatedType); - } - - - private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder) - { - var propType = typeof(bool); - var field = typeBuilder.DefineField("_" + "IsDirty", propType, FieldAttributes.Private); - var property = typeBuilder.DefineProperty("IsDirty", - System.Reflection.PropertyAttributes.None, - propType, - new[] { propType }); - - const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName | - MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig; - - // Define the "get" and "set" accessor methods - var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + "IsDirty", - getSetAttr, - propType, - Type.EmptyTypes); - var currGetIl = currGetPropMthdBldr.GetILGenerator(); - currGetIl.Emit(OpCodes.Ldarg_0); - currGetIl.Emit(OpCodes.Ldfld, field); - currGetIl.Emit(OpCodes.Ret); - var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + "IsDirty", - getSetAttr, - null, - new[] { propType }); - var currSetIl = currSetPropMthdBldr.GetILGenerator(); - currSetIl.Emit(OpCodes.Ldarg_0); - currSetIl.Emit(OpCodes.Ldarg_1); - currSetIl.Emit(OpCodes.Stfld, field); - currSetIl.Emit(OpCodes.Ret); - - property.SetGetMethod(currGetPropMthdBldr); - property.SetSetMethod(currSetPropMthdBldr); - var getMethod = typeof(IProxy).GetMethod("get_" + "IsDirty"); - var setMethod = typeof(IProxy).GetMethod("set_" + "IsDirty"); - typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); - typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); - - return currSetPropMthdBldr; - } - - private static void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity) - { - //Define the field and the property - var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private); - var property = typeBuilder.DefineProperty(propertyName, - System.Reflection.PropertyAttributes.None, - propType, - new[] { propType }); - - const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.Virtual | - MethodAttributes.HideBySig; - - // Define the "get" and "set" accessor methods - var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, - getSetAttr, - propType, - Type.EmptyTypes); - - var currGetIl = currGetPropMthdBldr.GetILGenerator(); - currGetIl.Emit(OpCodes.Ldarg_0); - currGetIl.Emit(OpCodes.Ldfld, field); - currGetIl.Emit(OpCodes.Ret); - - var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, - getSetAttr, - null, - new[] { propType }); - - //store value in private field and set the isdirty flag - var currSetIl = currSetPropMthdBldr.GetILGenerator(); - currSetIl.Emit(OpCodes.Ldarg_0); - currSetIl.Emit(OpCodes.Ldarg_1); - currSetIl.Emit(OpCodes.Stfld, field); - currSetIl.Emit(OpCodes.Ldarg_0); - currSetIl.Emit(OpCodes.Ldc_I4_1); - currSetIl.Emit(OpCodes.Call, setIsDirtyMethod); - currSetIl.Emit(OpCodes.Ret); - - //TODO: Should copy all attributes defined by the interface? - if (isIdentity) + TypeCache.Add(typeOfT, generatedType); + return (T)Activator.CreateInstance(generatedType); + } + + + private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder) + { + var propType = typeof(bool); + var field = typeBuilder.DefineField("_" + "IsDirty", propType, FieldAttributes.Private); + var property = typeBuilder.DefineProperty("IsDirty", + System.Reflection.PropertyAttributes.None, + propType, + new[] { propType }); + + const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName | + MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig; + + // Define the "get" and "set" accessor methods + var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + "IsDirty", + getSetAttr, + propType, + Type.EmptyTypes); + var currGetIl = currGetPropMthdBldr.GetILGenerator(); + currGetIl.Emit(OpCodes.Ldarg_0); + currGetIl.Emit(OpCodes.Ldfld, field); + currGetIl.Emit(OpCodes.Ret); + var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + "IsDirty", + getSetAttr, + null, + new[] { propType }); + var currSetIl = currSetPropMthdBldr.GetILGenerator(); + currSetIl.Emit(OpCodes.Ldarg_0); + currSetIl.Emit(OpCodes.Ldarg_1); + currSetIl.Emit(OpCodes.Stfld, field); + currSetIl.Emit(OpCodes.Ret); + + property.SetGetMethod(currGetPropMthdBldr); + property.SetSetMethod(currSetPropMthdBldr); + var getMethod = typeof(IProxy).GetMethod("get_" + "IsDirty"); + var setMethod = typeof(IProxy).GetMethod("set_" + "IsDirty"); + typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); + typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); + + return currSetPropMthdBldr; + } + + private static void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity) + { + //Define the field and the property + var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private); + var property = typeBuilder.DefineProperty(propertyName, + System.Reflection.PropertyAttributes.None, + propType, + new[] { propType }); + + const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.Virtual | + MethodAttributes.HideBySig; + + // Define the "get" and "set" accessor methods + var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, + getSetAttr, + propType, + Type.EmptyTypes); + + var currGetIl = currGetPropMthdBldr.GetILGenerator(); + currGetIl.Emit(OpCodes.Ldarg_0); + currGetIl.Emit(OpCodes.Ldfld, field); + currGetIl.Emit(OpCodes.Ret); + + var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, + getSetAttr, + null, + new[] { propType }); + + //store value in private field and set the isdirty flag + var currSetIl = currSetPropMthdBldr.GetILGenerator(); + currSetIl.Emit(OpCodes.Ldarg_0); + currSetIl.Emit(OpCodes.Ldarg_1); + currSetIl.Emit(OpCodes.Stfld, field); + currSetIl.Emit(OpCodes.Ldarg_0); + currSetIl.Emit(OpCodes.Ldc_I4_1); + currSetIl.Emit(OpCodes.Call, setIsDirtyMethod); + currSetIl.Emit(OpCodes.Ret); + + //TODO: Should copy all attributes defined by the interface? + if (isIdentity) + { + var keyAttribute = typeof(KeyAttribute); + var myConstructorInfo = keyAttribute.GetConstructor(new Type[] { }); + var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, new object[] { }); + property.SetCustomAttribute(attributeBuilder); + } + + property.SetGetMethod(currGetPropMthdBldr); + property.SetSetMethod(currSetPropMthdBldr); + var getMethod = typeof(T).GetMethod("get_" + propertyName); + var setMethod = typeof(T).GetMethod("set_" + propertyName); + typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); + typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); + } + } + } + + [AttributeUsage(AttributeTargets.Class)] + public class TableAttribute : Attribute + { + public TableAttribute(string tableName) { - var keyAttribute = typeof(KeyAttribute); - var myConstructorInfo = keyAttribute.GetConstructor(new Type[] { }); - var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, new object[] { }); - property.SetCustomAttribute(attributeBuilder); + Name = tableName; } - property.SetGetMethod(currGetPropMthdBldr); - property.SetSetMethod(currSetPropMthdBldr); - var getMethod = typeof(T).GetMethod("get_" + propertyName); - var setMethod = typeof(T).GetMethod("set_" + propertyName); - typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); - typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); - } + // ReSharper disable once MemberCanBePrivate.Global + // ReSharper disable once UnusedAutoPropertyAccessor.Global + public string Name { get; set; } } - } - [AttributeUsage(AttributeTargets.Class)] - public class TableAttribute : Attribute - { - public TableAttribute(string tableName) + // do not want to depend on data annotations that is not in client profile + [AttributeUsage(AttributeTargets.Property)] + public class KeyAttribute : Attribute { - Name = tableName; } - // ReSharper disable once MemberCanBePrivate.Global - // ReSharper disable once UnusedAutoPropertyAccessor.Global - public string Name { get; set; } - } - - // do not want to depend on data annotations that is not in client profile - [AttributeUsage(AttributeTargets.Property)] - public class KeyAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public class ExplicitKeyAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public class WriteAttribute : Attribute - { - public WriteAttribute(bool write) + [AttributeUsage(AttributeTargets.Property)] + public class ExplicitKeyAttribute : Attribute { - Write = write; } - public bool Write { get; } - } - [AttributeUsage(AttributeTargets.Property)] - public class ComputedAttribute : Attribute - { - } + [AttributeUsage(AttributeTargets.Property)] + public class WriteAttribute : Attribute + { + public WriteAttribute(bool write) + { + Write = write; + } + public bool Write { get; } + } + + [AttributeUsage(AttributeTargets.Property)] + public class ComputedAttribute : Attribute + { + } } public partial interface ISqlAdapter { - int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert); + int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert); - //new methods for issue #336 - void AppendColumnName(StringBuilder sb, string columnName); - void AppendColumnNameEqualsValue(StringBuilder sb, string columnName); + //new methods for issue #336 + void AppendColumnName(StringBuilder sb, string columnName); + void AppendColumnNameEqualsValue(StringBuilder sb, string columnName); } public partial class SqlServerAdapter : ISqlAdapter { - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) - { - var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList});SELECT SCOPE_IDENTITY() [id]"; - var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) + { + var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList});SELECT SCOPE_IDENTITY() [id]"; + var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); - var first = multi.Read().FirstOrDefault(); - if (first == null || first.id == null) return 0; + var first = multi.Read().FirstOrDefault(); + if (first == null || first.id == null) return 0; - var id = (int)first.id; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) return id; + var id = (int)first.id; + var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); + if (!propertyInfos.Any()) return id; - var idProperty = propertyInfos.First(); - idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); + var idProperty = propertyInfos.First(); + idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); - return id; - } + return id; + } - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}]", columnName); - } + public void AppendColumnName(StringBuilder sb, string columnName) + { + sb.AppendFormat("[{0}]", columnName); + } - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}] = @{1}", columnName, columnName); - } + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + { + sb.AppendFormat("[{0}] = @{1}", columnName, columnName); + } } public partial class SqlCeServerAdapter : ISqlAdapter { - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) - { - var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})"; - connection.Execute(cmd, entityToInsert, transaction, commandTimeout); - var r = connection.Query("SELECT @@IDENTITY [id]", transaction: transaction, commandTimeout: commandTimeout).ToList(); + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) + { + var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})"; + connection.Execute(cmd, entityToInsert, transaction, commandTimeout); + var r = connection.Query("SELECT @@IDENTITY [id]", transaction: transaction, commandTimeout: commandTimeout).ToList(); - if (r.First().id == null) return 0; - var id = (int)r.First().id; + if (r.First().id == null) return 0; + var id = (int)r.First().id; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) return id; + var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); + if (!propertyInfos.Any()) return id; - var idProperty = propertyInfos.First(); - idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); + var idProperty = propertyInfos.First(); + idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); - return id; - } + return id; + } - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}]", columnName); - } + public void AppendColumnName(StringBuilder sb, string columnName) + { + sb.AppendFormat("[{0}]", columnName); + } - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}] = @{1}", columnName, columnName); - } + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + { + sb.AppendFormat("[{0}] = @{1}", columnName, columnName); + } } public partial class MySqlAdapter : ISqlAdapter { - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) - { - var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; - connection.Execute(cmd, entityToInsert, transaction, commandTimeout); - var r = connection.Query("Select LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout); - - var id = r.First().id; - if (id == null) return 0; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) return Convert.ToInt32(id); - - var idp = propertyInfos.First(); - idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); - - return Convert.ToInt32(id); - } - - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("`{0}`", columnName); - } - - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("`{0}` = @{1}", columnName, columnName); - } + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) + { + var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; + connection.Execute(cmd, entityToInsert, transaction, commandTimeout); + var r = connection.Query("Select LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout); + + var id = r.First().id; + if (id == null) return 0; + var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); + if (!propertyInfos.Any()) return Convert.ToInt32(id); + + var idp = propertyInfos.First(); + idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); + + return Convert.ToInt32(id); + } + + public void AppendColumnName(StringBuilder sb, string columnName) + { + sb.AppendFormat("`{0}`", columnName); + } + + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + { + sb.AppendFormat("`{0}` = @{1}", columnName, columnName); + } } public partial class PostgresAdapter : ISqlAdapter { - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, 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 + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) { - sb.Append(" RETURNING "); - var first = true; - foreach (var property in propertyInfos) - { - if (!first) - sb.Append(", "); - first = false; - sb.AppendFormat("\"{0}\"", columnNameMapper(property)); - } + 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 "); + var first = true; + foreach (var property in propertyInfos) + { + if (!first) + sb.Append(", "); + first = false; + sb.AppendFormat("\"{0}\"", columnNameMapper(property)); + } + } + + var results = connection.Query(sb.ToString(), entityToInsert, transaction, commandTimeout: commandTimeout).ToList(); + + // 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())[columnNameMapper(p)]; + p.SetValue(entityToInsert, value, null); + if (id == 0) + id = Convert.ToInt32(value); + } + return id; } - var results = connection.Query(sb.ToString(), entityToInsert, transaction, commandTimeout: commandTimeout).ToList(); + public void AppendColumnName(StringBuilder sb, string columnName) + { + sb.AppendFormat("\"{0}\"", columnName); + } - // 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) + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) { - var value = ((IDictionary)results.First())[columnNameMapper(p)]; - p.SetValue(entityToInsert, value, null); - if (id == 0) - id = Convert.ToInt32(value); + sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); } - return id; - } - - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("\"{0}\"", columnName); - } - - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); - } } public partial class SQLiteAdapter : ISqlAdapter { - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) - { - var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id"; - var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); - - var id = (int)multi.Read().First().id; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) return id; - - var idProperty = propertyInfos.First(); - idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); - - return id; - } - - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("\"{0}\"", columnName); - } - - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); - } + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, ColumNameMapperDelegate columnNameMapper, object entityToInsert) + { + var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id"; + var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); + + var id = (int)multi.Read().First().id; + var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); + if (!propertyInfos.Any()) return id; + + var idProperty = propertyInfos.First(); + idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); + + return id; + } + + public void AppendColumnName(StringBuilder sb, string columnName) + { + sb.AppendFormat("\"{0}\"", columnName); + } + + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + { + sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); + } }