From 4e3714550b9b64a29329301429b908451b26ef92 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 24 Aug 2016 16:22:19 +0100 Subject: [PATCH 01/10] ignored files for JetBrains-Rider IDE --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8d001e7fe..238ed4abd 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,6 @@ NuGet.exe Test.DB.* TestResults/ Dapper.Tests/*.sdf -.dotnet/* \ No newline at end of file +.dotnet/* +.idea.*/ +Dapper.userprefs \ No newline at end of file From e610b40002a28069c2da5f4e2459f8284da5aff0 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 24 Aug 2016 20:39:02 +0100 Subject: [PATCH 02/10] pluggable column mapping --- Dapper.Contrib/SqlMapperExtensions.cs | 242 +++++++++++++++----------- 1 file changed, 141 insertions(+), 101 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 11c947b26..a8d541618 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -33,10 +33,10 @@ public interface ITableNameMapper 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> KeyProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> PersistentProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); @@ -51,75 +51,116 @@ private static readonly Dictionary AdapterDictionary {"mysqlconnection", new MySqlAdapter()}, }; - private static List ComputedPropertiesCache(Type type) + private static PropertyMap MapProperty(PropertyInfo propInfo) { - IEnumerable pi; + return new PropertyMap(propInfo.Name, propInfo); + } + + 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(); + var computedProperties = TypePropertiesCache(type).Where(p => p.PropertyInfo.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList(); ComputedProperties[type.TypeHandle] = computedProperties; return computedProperties; } - private static List ExplicitKeyPropertiesCache(Type type) + public static Func> GetKeyProperties; + private static List DefaultGetKeyProperties(Type type) { - IEnumerable pi; - if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out pi)) + var allProperties = TypePropertiesCache(type); + var keyProperties = allProperties.Where(p => p.PropertyInfo.GetCustomAttributes(true) + .Any(a => a is KeyAttribute)) + .ToList(); + + if (keyProperties.Count == 0) { - return pi.ToList(); + var idProp = allProperties.FirstOrDefault(p => p.PropertyInfo.Name.ToLower() == "id"); + if (idProp != null) + { + keyProperties.Add(idProp); + } } - var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList(); + var explicitKeyProperties = TypePropertiesCache(type) + .Where(p => p.PropertyInfo.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) + .ToList(); + - ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties; - return explicitKeyProperties; + keyProperties.AddRange(explicitKeyProperties); + + return keyProperties; } - private static List KeyPropertiesCache(Type type) + private static List KeyPropertiesCache(Type type) { - - IEnumerable pi; + 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); - } - } + var keyProperties = GetKeyProperties(type); KeyProperties[type.TypeHandle] = keyProperties; return keyProperties; } - private static List TypePropertiesCache(Type type) + public static Func> GetAllColumns = DefaultGetAllColumns; + private static List DefaultGetAllColumns(Type type) + { + return type.GetProperties() + .Where(IsWriteable) + .Select(MapProperty) + .ToList(); + } + + private static List TypePropertiesCache(Type type) { - IEnumerable pis; + IEnumerable pis; if (TypeProperties.TryGetValue(type.TypeHandle, out pis)) { return pis.ToList(); } - var properties = type.GetProperties().Where(IsWriteable).ToArray(); + var properties = GetAllColumns(type); TypeProperties[type.TypeHandle] = properties; return properties.ToList(); } + private static List PersistentPropertiesCache(Type type) + { + IEnumerable pis; + if (PersistentProperties.TryGetValue(type.TypeHandle, out pis)) + { + return pis.ToList(); + } + + var properties = GetPersistentProperties(type); + PersistentProperties[type.TypeHandle] = properties; + return properties.ToList(); + } + + public static Func> GetPersistentProperties; + private static List DefaultGetPersistentProperties(Type type) + { + var keyProperties = KeyPropertiesCache(type) + .Where(k => !k.PropertyInfo.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) + .Select(k => k.PropertyInfo); + + var computedProperties = ComputedPropertiesCache(type).Select(p => p.PropertyInfo); + var exclusions = keyProperties.Union(computedProperties); + + return TypePropertiesCache(type) + .Where(t => !exclusions.Contains(t.PropertyInfo)) + .ToList(); + } + private static bool IsWriteable(PropertyInfo pi) { var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList(); @@ -129,18 +170,17 @@ private static bool IsWriteable(PropertyInfo pi) return writeAttribute.Write; } - private static PropertyInfo GetSingleKey(string method) + private static PropertyMap GetSingleKey(string method) { var type = typeof (T); var keys = KeyPropertiesCache(type); - var explicitKeys = ExplicitKeyPropertiesCache(type); - var keyCount = keys.Count + explicitKeys.Count; + var keyCount = keys.Count; if (keyCount > 1) - throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property"); + throw new DataException($"{method} only supports an entity with a single key property, as defined by GetKeyProperties. By defualt, this is determined by [Key] or [ExplicitKey] attributes"); if (keyCount == 0) - throw new DataException($"{method} only supports an entity with a [Key] or an [ExplicitKey] property"); + throw new DataException($"{method} only supports an entity with a key property, as defined by GetKeyProperties. By defualt, this is determined by [Key] or [ExplicitKey] attributes"); - return keys.Any() ? keys.First() : explicitKeys.First(); + return keys.First(); } /// @@ -165,7 +205,7 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction var key = GetSingleKey(nameof(Get)); var name = GetTableName(type); - sql = $"select * from {name} where {key.Name} = @id"; + sql = $"select * from {name} where {key.ColumnName} = @id"; GetQueries[type.TypeHandle] = sql; } @@ -185,8 +225,8 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction foreach (var property in TypePropertiesCache(type)) { - var val = res[property.Name]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + var val = res[property.ColumnName]; + property.PropertyInfo.SetValue(obj, Convert.ChangeType(val, property.PropertyInfo.PropertyType), null); } ((IProxy)obj).IsDirty = false; //reset change tracking and return @@ -233,8 +273,8 @@ public static IEnumerable GetAll(this IDbConnection connection, IDbTransac var obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { - var val = res[property.Name]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + var val = res[property.ColumnName]; + property.PropertyInfo.SetValue(obj, Convert.ChangeType(val, property.PropertyInfo.PropertyType), null); } ((IProxy)obj).IsDirty = false; //reset change tracking and return list.Add(obj); @@ -306,27 +346,25 @@ public static long Insert(this IDbConnection connection, T entityToInsert, ID 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 persistentProperties = PersistentPropertiesCache(type); var adapter = GetFormatter(connection); - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) + for (var i = 0; i < persistentProperties.Count; i++) { - var property = allPropertiesExceptKeyAndComputed.ElementAt(i); - adapter.AppendColumnName(sbColumnList, property.Name); //fix for issue #336 - if (i < allPropertiesExceptKeyAndComputed.Count - 1) + var property = persistentProperties.ElementAt(i); + adapter.AppendColumnName(sbColumnList, property.ColumnName); //fix for issue #336 + if (i < persistentProperties.Count - 1) sbColumnList.Append(", "); } var sbParameterList = new StringBuilder(null); - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) + for (var i = 0; i < persistentProperties.Count; i++) { - var property = allPropertiesExceptKeyAndComputed.ElementAt(i); - sbParameterList.AppendFormat("@{0}", property.Name); - if (i < allPropertiesExceptKeyAndComputed.Count - 1) + var property = persistentProperties.ElementAt(i); + sbParameterList.AppendFormat("@{0}", property.ColumnName); + if (i < persistentProperties.Count - 1) sbParameterList.Append(", "); } @@ -377,35 +415,32 @@ public static bool Update(this IDbConnection connection, T entityToUpdate, ID 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 keyProperties = KeyPropertiesCache(type); //added ToList() due to issue #418, must work on a list copy + + if (!keyProperties.Any()) + throw new ArgumentException("Entity must have at least one key property, defined by GetKeyProperties. By default, use [Key] or [ExplicitKey] attributes"); 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 persistentProperties = PersistentPropertiesCache(type); - var adapter = GetFormatter(connection); + var adapter = GetFormatter(connection); - for (var i = 0; i < nonIdProps.Count; i++) + for (var i = 0; i < persistentProperties.Count; i++) { - var property = nonIdProps.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 - if (i < nonIdProps.Count - 1) + var property = persistentProperties.ElementAt(i); + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); //fix for issue #336 + if (i < persistentProperties.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 + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); //fix for issue #336 if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } @@ -438,13 +473,11 @@ public static bool Delete(this IDbConnection connection, T entityToDelete, ID 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 keyProperties = KeyPropertiesCache(type); + if (!keyProperties.Any()) + throw new ArgumentException("Entity must have at least one key property, defined by GetKeyProperties. By default, use [Key] or [ExplicitKey] attributes"); var name = GetTableName(type); - keyProperties.AddRange(explicitKeyProperties); var sb = new StringBuilder(); sb.AppendFormat("delete from {0} where ", name); @@ -454,7 +487,7 @@ public static bool Delete(this IDbConnection connection, T entityToDelete, ID for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); //fix for issue #336 if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } @@ -688,7 +721,7 @@ 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); + int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert); //new methods for issue #336 void AppendColumnName(StringBuilder sb, string columnName); @@ -697,7 +730,7 @@ public partial interface ISqlAdapter public partial class SqlServerAdapter : 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, IList keyProperties, object entityToInsert) { var cmd = $"insert into {tableName} ({columnList}) values ({parameterList});select SCOPE_IDENTITY() id"; var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); @@ -706,10 +739,9 @@ public int Insert(IDbConnection connection, IDbTransaction transaction, int? com 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; + if (!keyProperties.Any()) return id; - var idProperty = propertyInfos.First(); + var idProperty = keyProperties.First().PropertyInfo; idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); return id; @@ -728,7 +760,7 @@ public void AppendColumnNameEqualsValue(StringBuilder sb, string 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) + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert) { var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; connection.Execute(cmd, entityToInsert, transaction, commandTimeout); @@ -737,10 +769,9 @@ public int Insert(IDbConnection connection, IDbTransaction transaction, int? com 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; + if (!keyProperties.Any()) return id; - var idProperty = propertyInfos.First(); + var idProperty = keyProperties.First().PropertyInfo; idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); return id; @@ -759,7 +790,7 @@ public void AppendColumnNameEqualsValue(StringBuilder sb, string 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) + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert) { var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; connection.Execute(cmd, entityToInsert, transaction, commandTimeout); @@ -767,10 +798,9 @@ public int Insert(IDbConnection connection, IDbTransaction transaction, int? com var id = r.First().id; if (id == null) return 0; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) return Convert.ToInt32(id); + if (!keyProperties.Any()) return Convert.ToInt32(id); - var idp = propertyInfos.First(); + var idp = keyProperties.First().PropertyInfo; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return Convert.ToInt32(id); @@ -790,25 +820,24 @@ public void AppendColumnNameEqualsValue(StringBuilder sb, string 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, IList keyProperties, object entityToInsert) { var sb = new StringBuilder(); sb.AppendFormat("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList); // If no primary key then safe to assume a join table with not too much data to return - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) + if (!keyProperties.Any()) sb.Append(" RETURNING *"); else { sb.Append(" RETURNING "); var first = true; - foreach (var property in propertyInfos) + foreach (var property in keyProperties) { if (!first) sb.Append(", "); first = false; - sb.Append(property.Name); + sb.Append(property.ColumnName); } } @@ -816,10 +845,10 @@ public int Insert(IDbConnection connection, IDbTransaction transaction, int? com // 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) + foreach (var p in keyProperties) { - var value = ((IDictionary)results.First())[p.Name.ToLower()]; - p.SetValue(entityToInsert, value, null); + var value = ((IDictionary)results.First())[p.ColumnName.ToLower()]; + p.PropertyInfo.SetValue(entityToInsert, value, null); if (id == 0) id = Convert.ToInt32(value); } @@ -839,16 +868,15 @@ public void AppendColumnNameEqualsValue(StringBuilder sb, string 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) + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList 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; + if (!keyProperties.Any()) return id; - var idProperty = propertyInfos.First(); + var idProperty = keyProperties.First().PropertyInfo; idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); return id; @@ -864,3 +892,15 @@ public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); } } + +public class PropertyMap +{ + public PropertyMap(string columnName, PropertyInfo propInfo) + { + ColumnName = columnName; + PropertyInfo = propInfo; + } + public string ColumnName { get; private set; } + public PropertyInfo PropertyInfo { get; private set; } +} + From 9bdbf3315a2d9363db545ed0bab0d5000e84b9af Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 24 Aug 2016 21:18:00 +0100 Subject: [PATCH 03/10] pluggable column mapping --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 110 ++++----- Dapper.Contrib/SqlMapperExtensions.cs | 242 ++++++++++++-------- 2 files changed, 190 insertions(+), 162 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index f4c320680..b8835f5bd 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -33,7 +33,7 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i var key = GetSingleKey(nameof(GetAsync)); var name = GetTableName(type); - sql = $"SELECT * FROM {name} WHERE {key.Name} = @id"; + sql = $"SELECT * FROM {name} WHERE {key.ColumnName} = @id"; GetQueries[type.TypeHandle] = sql; } @@ -52,8 +52,8 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i foreach (var property in TypePropertiesCache(type)) { - var val = res[property.Name]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + var val = res[property.ColumnName]; + property.PropertyInfo.SetValue(obj, Convert.ChangeType(val, property.PropertyInfo.PropertyType), null); } ((IProxy)obj).IsDirty = false; //reset change tracking and return @@ -102,8 +102,8 @@ private static async Task> GetAllAsyncImpl(IDbConnection conne var obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { - var val = res[property.Name]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + var val = res[property.ColumnName]; + property.PropertyInfo.SetValue(obj, Convert.ChangeType(val, property.PropertyInfo.PropertyType), null); } ((IProxy)obj).IsDirty = false; //reset change tracking and return list.Add(obj); @@ -142,25 +142,23 @@ public static Task InsertAsync(this IDbConnection connection, T entityTo var name = GetTableName(type); var sbColumnList = new StringBuilder(null); - var allProperties = TypePropertiesCache(type); + var persistentProperties = PersistentPropertiesCache(type); var keyProperties = KeyPropertiesCache(type); - var computedProperties = ComputedPropertiesCache(type); - var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) + for (var i = 0; i < persistentProperties.Count; i++) { - var property = allPropertiesExceptKeyAndComputed.ElementAt(i); - sqlAdapter.AppendColumnName(sbColumnList, property.Name); - if (i < allPropertiesExceptKeyAndComputed.Count - 1) + var property = persistentProperties.ElementAt(i); + sqlAdapter.AppendColumnName(sbColumnList, property.ColumnName); + if (i < persistentProperties.Count - 1) sbColumnList.Append(", "); } var sbParameterList = new StringBuilder(null); - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) + for (var i = 0; i < persistentProperties.Count; i++) { - var property = allPropertiesExceptKeyAndComputed.ElementAt(i); - sbParameterList.AppendFormat("@{0}", property.Name); - if (i < allPropertiesExceptKeyAndComputed.Count - 1) + var property = persistentProperties.ElementAt(i); + sbParameterList.AppendFormat("@{0}", property.ColumnName); + if (i < persistentProperties.Count - 1) sbParameterList.Append(", "); } @@ -204,34 +202,30 @@ public static async Task UpdateAsync(this IDbConnection connection, T e } var keyProperties = KeyPropertiesCache(type); - var explicitKeyProperties = ExplicitKeyPropertiesCache(type); - if (!keyProperties.Any() && !explicitKeyProperties.Any()) - throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); + if (!keyProperties.Any()) + throw new ArgumentException("Entity must have at least one key property, defined by GetKeyProperties. By default, use [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 persistentProperties = PersistentPropertiesCache(type); var adapter = GetFormatter(connection); - for (var i = 0; i < nonIdProps.Count; i++) + for (var i = 0; i < persistentProperties.Count; i++) { - var property = nonIdProps.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, property.Name); - if (i < nonIdProps.Count - 1) + var property = persistentProperties.ElementAt(i); + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); + if (i < persistentProperties.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); + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } @@ -265,12 +259,10 @@ public static async Task DeleteAsync(this IDbConnection connection, T e } var keyProperties = KeyPropertiesCache(type); - var explicitKeyProperties = ExplicitKeyPropertiesCache(type); - if (!keyProperties.Any() && !explicitKeyProperties.Any()) - throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); + if (!keyProperties.Any()) + throw new ArgumentException("Entity must have at least one key property, defined by GetKeyProperties. By default, use [Key] or [ExplicitKey] property"); var name = GetTableName(type); - keyProperties.AddRange(explicitKeyProperties); var sb = new StringBuilder(); sb.AppendFormat("DELETE FROM {0} WHERE ", name); @@ -278,7 +270,7 @@ public static async Task DeleteAsync(this IDbConnection connection, T e for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties.ElementAt(i); - sb.AppendFormat("{0} = @{1}", property.Name, property.Name); + sb.AppendFormat("{0} = @{1}", property.ColumnName, property.ColumnName); if (i < keyProperties.Count - 1) sb.AppendFormat(" AND "); } @@ -306,12 +298,12 @@ public static async Task DeleteAllAsync(this IDbConnection connection, public partial interface ISqlAdapter { - Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert); + Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert); } public partial class SqlServerAdapter { - public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) + public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) values ({parameterList}); SELECT SCOPE_IDENTITY() id"; var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout); @@ -320,10 +312,10 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran if (first == null || first.id == null) return 0; var id = (int)first.id; - var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!pi.Any()) return id; + + if (!keyProperties.Any()) return id; - var idp = pi.First(); + var idp = keyProperties.First().PropertyInfo; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; @@ -332,7 +324,7 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran public partial class SqlCeServerAdapter { - public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) + public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})"; await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); @@ -340,11 +332,10 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran if (r.First() == null || r.First().id == null) return 0; var id = (int)r.First().id; + + if (!keyProperties.Any()) return id; - var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!pi.Any()) return id; - - var idp = pi.First(); + var idp = keyProperties.First().PropertyInfo; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; @@ -354,7 +345,7 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran public partial class MySqlAdapter { public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, - string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) + string columnList, string parameterList, IList keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})"; await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); @@ -362,10 +353,9 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran var id = r.First().id; if (id == null) return 0; - var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!pi.Any()) return Convert.ToInt32(id); + if (!keyProperties.Any()) return Convert.ToInt32(id); - var idp = pi.First(); + var idp = keyProperties.First().PropertyInfo; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return Convert.ToInt32(id); @@ -374,25 +364,24 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran public partial class PostgresAdapter { - public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) + public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert) { var sb = new StringBuilder(); sb.AppendFormat("INSERT INTO {0} ({1}) VALUES ({2})", tableName, columnList, parameterList); // If no primary key then safe to assume a join table with not too much data to return - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) + if (!keyProperties.Any()) sb.Append(" RETURNING *"); else { sb.Append(" RETURNING "); bool first = true; - foreach (var property in propertyInfos) + foreach (var property in keyProperties) { if (!first) sb.Append(", "); first = false; - sb.Append(property.Name); + sb.Append(property.ColumnName); } } @@ -401,10 +390,10 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran // Return the key by assinging the corresponding property in the object - by product is that it supports compound primary keys var id = 0; var values = results.First(); - foreach (var p in propertyInfos) + foreach (var p in keyProperties) { - var value = values[p.Name.ToLower()]; - p.SetValue(entityToInsert, value, null); + var value = values[p.ColumnName.ToLower()]; + p.PropertyInfo.SetValue(entityToInsert, value, null); if (id == 0) id = Convert.ToInt32(value); } @@ -414,19 +403,18 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran public partial class SQLiteAdapter { - public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) + public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert) { var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id"; var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout); var id = (int)multi.Read().First().id; - var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!pi.Any()) return id; + if (!keyProperties.Any()) return id; - var idp = pi.First(); + var idp = keyProperties.First().PropertyInfo; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; } } -#endif +#endif diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 11c947b26..a8d541618 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -33,10 +33,10 @@ public interface ITableNameMapper 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> KeyProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> PersistentProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); @@ -51,75 +51,116 @@ private static readonly Dictionary AdapterDictionary {"mysqlconnection", new MySqlAdapter()}, }; - private static List ComputedPropertiesCache(Type type) + private static PropertyMap MapProperty(PropertyInfo propInfo) { - IEnumerable pi; + return new PropertyMap(propInfo.Name, propInfo); + } + + 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(); + var computedProperties = TypePropertiesCache(type).Where(p => p.PropertyInfo.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList(); ComputedProperties[type.TypeHandle] = computedProperties; return computedProperties; } - private static List ExplicitKeyPropertiesCache(Type type) + public static Func> GetKeyProperties; + private static List DefaultGetKeyProperties(Type type) { - IEnumerable pi; - if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out pi)) + var allProperties = TypePropertiesCache(type); + var keyProperties = allProperties.Where(p => p.PropertyInfo.GetCustomAttributes(true) + .Any(a => a is KeyAttribute)) + .ToList(); + + if (keyProperties.Count == 0) { - return pi.ToList(); + var idProp = allProperties.FirstOrDefault(p => p.PropertyInfo.Name.ToLower() == "id"); + if (idProp != null) + { + keyProperties.Add(idProp); + } } - var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList(); + var explicitKeyProperties = TypePropertiesCache(type) + .Where(p => p.PropertyInfo.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) + .ToList(); + - ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties; - return explicitKeyProperties; + keyProperties.AddRange(explicitKeyProperties); + + return keyProperties; } - private static List KeyPropertiesCache(Type type) + private static List KeyPropertiesCache(Type type) { - - IEnumerable pi; + 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); - } - } + var keyProperties = GetKeyProperties(type); KeyProperties[type.TypeHandle] = keyProperties; return keyProperties; } - private static List TypePropertiesCache(Type type) + public static Func> GetAllColumns = DefaultGetAllColumns; + private static List DefaultGetAllColumns(Type type) + { + return type.GetProperties() + .Where(IsWriteable) + .Select(MapProperty) + .ToList(); + } + + private static List TypePropertiesCache(Type type) { - IEnumerable pis; + IEnumerable pis; if (TypeProperties.TryGetValue(type.TypeHandle, out pis)) { return pis.ToList(); } - var properties = type.GetProperties().Where(IsWriteable).ToArray(); + var properties = GetAllColumns(type); TypeProperties[type.TypeHandle] = properties; return properties.ToList(); } + private static List PersistentPropertiesCache(Type type) + { + IEnumerable pis; + if (PersistentProperties.TryGetValue(type.TypeHandle, out pis)) + { + return pis.ToList(); + } + + var properties = GetPersistentProperties(type); + PersistentProperties[type.TypeHandle] = properties; + return properties.ToList(); + } + + public static Func> GetPersistentProperties; + private static List DefaultGetPersistentProperties(Type type) + { + var keyProperties = KeyPropertiesCache(type) + .Where(k => !k.PropertyInfo.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) + .Select(k => k.PropertyInfo); + + var computedProperties = ComputedPropertiesCache(type).Select(p => p.PropertyInfo); + var exclusions = keyProperties.Union(computedProperties); + + return TypePropertiesCache(type) + .Where(t => !exclusions.Contains(t.PropertyInfo)) + .ToList(); + } + private static bool IsWriteable(PropertyInfo pi) { var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList(); @@ -129,18 +170,17 @@ private static bool IsWriteable(PropertyInfo pi) return writeAttribute.Write; } - private static PropertyInfo GetSingleKey(string method) + private static PropertyMap GetSingleKey(string method) { var type = typeof (T); var keys = KeyPropertiesCache(type); - var explicitKeys = ExplicitKeyPropertiesCache(type); - var keyCount = keys.Count + explicitKeys.Count; + var keyCount = keys.Count; if (keyCount > 1) - throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property"); + throw new DataException($"{method} only supports an entity with a single key property, as defined by GetKeyProperties. By defualt, this is determined by [Key] or [ExplicitKey] attributes"); if (keyCount == 0) - throw new DataException($"{method} only supports an entity with a [Key] or an [ExplicitKey] property"); + throw new DataException($"{method} only supports an entity with a key property, as defined by GetKeyProperties. By defualt, this is determined by [Key] or [ExplicitKey] attributes"); - return keys.Any() ? keys.First() : explicitKeys.First(); + return keys.First(); } /// @@ -165,7 +205,7 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction var key = GetSingleKey(nameof(Get)); var name = GetTableName(type); - sql = $"select * from {name} where {key.Name} = @id"; + sql = $"select * from {name} where {key.ColumnName} = @id"; GetQueries[type.TypeHandle] = sql; } @@ -185,8 +225,8 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction foreach (var property in TypePropertiesCache(type)) { - var val = res[property.Name]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + var val = res[property.ColumnName]; + property.PropertyInfo.SetValue(obj, Convert.ChangeType(val, property.PropertyInfo.PropertyType), null); } ((IProxy)obj).IsDirty = false; //reset change tracking and return @@ -233,8 +273,8 @@ public static IEnumerable GetAll(this IDbConnection connection, IDbTransac var obj = ProxyGenerator.GetInterfaceProxy(); foreach (var property in TypePropertiesCache(type)) { - var val = res[property.Name]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + var val = res[property.ColumnName]; + property.PropertyInfo.SetValue(obj, Convert.ChangeType(val, property.PropertyInfo.PropertyType), null); } ((IProxy)obj).IsDirty = false; //reset change tracking and return list.Add(obj); @@ -306,27 +346,25 @@ public static long Insert(this IDbConnection connection, T entityToInsert, ID 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 persistentProperties = PersistentPropertiesCache(type); var adapter = GetFormatter(connection); - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) + for (var i = 0; i < persistentProperties.Count; i++) { - var property = allPropertiesExceptKeyAndComputed.ElementAt(i); - adapter.AppendColumnName(sbColumnList, property.Name); //fix for issue #336 - if (i < allPropertiesExceptKeyAndComputed.Count - 1) + var property = persistentProperties.ElementAt(i); + adapter.AppendColumnName(sbColumnList, property.ColumnName); //fix for issue #336 + if (i < persistentProperties.Count - 1) sbColumnList.Append(", "); } var sbParameterList = new StringBuilder(null); - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) + for (var i = 0; i < persistentProperties.Count; i++) { - var property = allPropertiesExceptKeyAndComputed.ElementAt(i); - sbParameterList.AppendFormat("@{0}", property.Name); - if (i < allPropertiesExceptKeyAndComputed.Count - 1) + var property = persistentProperties.ElementAt(i); + sbParameterList.AppendFormat("@{0}", property.ColumnName); + if (i < persistentProperties.Count - 1) sbParameterList.Append(", "); } @@ -377,35 +415,32 @@ public static bool Update(this IDbConnection connection, T entityToUpdate, ID 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 keyProperties = KeyPropertiesCache(type); //added ToList() due to issue #418, must work on a list copy + + if (!keyProperties.Any()) + throw new ArgumentException("Entity must have at least one key property, defined by GetKeyProperties. By default, use [Key] or [ExplicitKey] attributes"); 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 persistentProperties = PersistentPropertiesCache(type); - var adapter = GetFormatter(connection); + var adapter = GetFormatter(connection); - for (var i = 0; i < nonIdProps.Count; i++) + for (var i = 0; i < persistentProperties.Count; i++) { - var property = nonIdProps.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 - if (i < nonIdProps.Count - 1) + var property = persistentProperties.ElementAt(i); + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); //fix for issue #336 + if (i < persistentProperties.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 + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); //fix for issue #336 if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } @@ -438,13 +473,11 @@ public static bool Delete(this IDbConnection connection, T entityToDelete, ID 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 keyProperties = KeyPropertiesCache(type); + if (!keyProperties.Any()) + throw new ArgumentException("Entity must have at least one key property, defined by GetKeyProperties. By default, use [Key] or [ExplicitKey] attributes"); var name = GetTableName(type); - keyProperties.AddRange(explicitKeyProperties); var sb = new StringBuilder(); sb.AppendFormat("delete from {0} where ", name); @@ -454,7 +487,7 @@ public static bool Delete(this IDbConnection connection, T entityToDelete, ID for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); //fix for issue #336 if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } @@ -688,7 +721,7 @@ 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); + int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert); //new methods for issue #336 void AppendColumnName(StringBuilder sb, string columnName); @@ -697,7 +730,7 @@ public partial interface ISqlAdapter public partial class SqlServerAdapter : 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, IList keyProperties, object entityToInsert) { var cmd = $"insert into {tableName} ({columnList}) values ({parameterList});select SCOPE_IDENTITY() id"; var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); @@ -706,10 +739,9 @@ public int Insert(IDbConnection connection, IDbTransaction transaction, int? com 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; + if (!keyProperties.Any()) return id; - var idProperty = propertyInfos.First(); + var idProperty = keyProperties.First().PropertyInfo; idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); return id; @@ -728,7 +760,7 @@ public void AppendColumnNameEqualsValue(StringBuilder sb, string 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) + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert) { var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; connection.Execute(cmd, entityToInsert, transaction, commandTimeout); @@ -737,10 +769,9 @@ public int Insert(IDbConnection connection, IDbTransaction transaction, int? com 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; + if (!keyProperties.Any()) return id; - var idProperty = propertyInfos.First(); + var idProperty = keyProperties.First().PropertyInfo; idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); return id; @@ -759,7 +790,7 @@ public void AppendColumnNameEqualsValue(StringBuilder sb, string 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) + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert) { var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; connection.Execute(cmd, entityToInsert, transaction, commandTimeout); @@ -767,10 +798,9 @@ public int Insert(IDbConnection connection, IDbTransaction transaction, int? com var id = r.First().id; if (id == null) return 0; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) return Convert.ToInt32(id); + if (!keyProperties.Any()) return Convert.ToInt32(id); - var idp = propertyInfos.First(); + var idp = keyProperties.First().PropertyInfo; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return Convert.ToInt32(id); @@ -790,25 +820,24 @@ public void AppendColumnNameEqualsValue(StringBuilder sb, string 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, IList keyProperties, object entityToInsert) { var sb = new StringBuilder(); sb.AppendFormat("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList); // If no primary key then safe to assume a join table with not too much data to return - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!propertyInfos.Any()) + if (!keyProperties.Any()) sb.Append(" RETURNING *"); else { sb.Append(" RETURNING "); var first = true; - foreach (var property in propertyInfos) + foreach (var property in keyProperties) { if (!first) sb.Append(", "); first = false; - sb.Append(property.Name); + sb.Append(property.ColumnName); } } @@ -816,10 +845,10 @@ public int Insert(IDbConnection connection, IDbTransaction transaction, int? com // 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) + foreach (var p in keyProperties) { - var value = ((IDictionary)results.First())[p.Name.ToLower()]; - p.SetValue(entityToInsert, value, null); + var value = ((IDictionary)results.First())[p.ColumnName.ToLower()]; + p.PropertyInfo.SetValue(entityToInsert, value, null); if (id == 0) id = Convert.ToInt32(value); } @@ -839,16 +868,15 @@ public void AppendColumnNameEqualsValue(StringBuilder sb, string 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) + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList 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; + if (!keyProperties.Any()) return id; - var idProperty = propertyInfos.First(); + var idProperty = keyProperties.First().PropertyInfo; idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); return id; @@ -864,3 +892,15 @@ public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); } } + +public class PropertyMap +{ + public PropertyMap(string columnName, PropertyInfo propInfo) + { + ColumnName = columnName; + PropertyInfo = propInfo; + } + public string ColumnName { get; private set; } + public PropertyInfo PropertyInfo { get; private set; } +} + From 4065d9e99293113716be9bbf785770dfd3908c78 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 29 Aug 2016 22:11:03 +0100 Subject: [PATCH 04/10] missing xunit dependencies in project.json --- Dapper.Tests.Contrib/project.json | 8 +++++--- Dapper.Tests/project.json | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Dapper.Tests.Contrib/project.json b/Dapper.Tests.Contrib/project.json index a4ec28544..328d9e276 100644 --- a/Dapper.Tests.Contrib/project.json +++ b/Dapper.Tests.Contrib/project.json @@ -40,7 +40,7 @@ "../Dapper.Tests/XunitSkippable.cs", "../Dapper/TypeExtensions.cs" ] - } + } }, "testRunner": "xunit", "frameworks": { @@ -108,8 +108,10 @@ "version": "1.0.0", "type": "platform" }, - "Microsoft.Data.Sqlite": "1.0.0" + "Microsoft.Data.Sqlite": "1.0.0", + "xunit": "2.1.0", + "dotnet-test-xunit": "1.0.0-rc3-*" } } } -} \ No newline at end of file +} diff --git a/Dapper.Tests/project.json b/Dapper.Tests/project.json index 64188982e..406df3970 100644 --- a/Dapper.Tests/project.json +++ b/Dapper.Tests/project.json @@ -137,8 +137,10 @@ "version": "1.0.0", "type": "platform" }, - "Microsoft.Data.Sqlite": "1.0.0" + "Microsoft.Data.Sqlite": "1.0.0", + "xunit": "2.1.0", + "dotnet-test-xunit": "1.0.0-rc3-*" } } } -} \ No newline at end of file +} From 54e0ffd998beaf8892c1b8bab9babf4a892c8260 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 25 Sep 2016 22:26:02 +0100 Subject: [PATCH 05/10] dotnet test project.json already fixed by StackEx team --- Dapper.Tests.Contrib/project.json | 4 +--- Dapper.Tests/project.json | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Dapper.Tests.Contrib/project.json b/Dapper.Tests.Contrib/project.json index 328d9e276..1ee2a93e8 100644 --- a/Dapper.Tests.Contrib/project.json +++ b/Dapper.Tests.Contrib/project.json @@ -108,9 +108,7 @@ "version": "1.0.0", "type": "platform" }, - "Microsoft.Data.Sqlite": "1.0.0", - "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-rc3-*" + "Microsoft.Data.Sqlite": "1.0.0" } } } diff --git a/Dapper.Tests/project.json b/Dapper.Tests/project.json index 406df3970..3d8b684e0 100644 --- a/Dapper.Tests/project.json +++ b/Dapper.Tests/project.json @@ -137,9 +137,7 @@ "version": "1.0.0", "type": "platform" }, - "Microsoft.Data.Sqlite": "1.0.0", - "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-rc3-*" + "Microsoft.Data.Sqlite": "1.0.0" } } } From 8807f74b9ce83e9499e610b0a2dbeed1ff7cf410 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 25 Sep 2016 22:38:15 +0100 Subject: [PATCH 06/10] fixes for failing tests --- Dapper.Contrib/SqlMapperExtensions.cs | 29 ++++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index a8d541618..8b028d0f9 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -32,6 +32,7 @@ public interface ITableNameMapper public delegate string GetDatabaseTypeDelegate(IDbConnection connection); public delegate string TableNameMapperDelegate(Type type); + public delegate List ColumnNameMapperDelegate(Type type); private static readonly ConcurrentDictionary> KeyProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); @@ -70,7 +71,7 @@ private static List ComputedPropertiesCache(Type type) return computedProperties; } - public static Func> GetKeyProperties; + public static ColumnNameMapperDelegate GetKeyProperties = DefaultGetKeyProperties; private static List DefaultGetKeyProperties(Type type) { var allProperties = TypePropertiesCache(type); @@ -88,7 +89,8 @@ private static List DefaultGetKeyProperties(Type type) } var explicitKeyProperties = TypePropertiesCache(type) - .Where(p => p.PropertyInfo.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) + .Where(p => !keyProperties.Contains(p) + && p.PropertyInfo.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) .ToList(); @@ -111,7 +113,7 @@ private static List KeyPropertiesCache(Type type) return keyProperties; } - public static Func> GetAllColumns = DefaultGetAllColumns; + public static ColumnNameMapperDelegate GetAllColumns = DefaultGetAllColumns; private static List DefaultGetAllColumns(Type type) { return type.GetProperties() @@ -146,7 +148,7 @@ private static List PersistentPropertiesCache(Type type) return properties.ToList(); } - public static Func> GetPersistentProperties; + public static ColumnNameMapperDelegate GetPersistentProperties = DefaultGetPersistentProperties; private static List DefaultGetPersistentProperties(Type type) { var keyProperties = KeyPropertiesCache(type) @@ -176,7 +178,7 @@ private static PropertyMap GetSingleKey(string method) var keys = KeyPropertiesCache(type); var keyCount = keys.Count; if (keyCount > 1) - throw new DataException($"{method} only supports an entity with a single key property, as defined by GetKeyProperties. By defualt, this is determined by [Key] or [ExplicitKey] attributes"); + throw new DataException($"{method} only supports an entity with a single key property, as defined by GetKeyProperties. By default, this is determined by [Key] or [ExplicitKey] attributes"); if (keyCount == 0) throw new DataException($"{method} only supports an entity with a key property, as defined by GetKeyProperties. By defualt, this is determined by [Key] or [ExplicitKey] attributes"); @@ -184,10 +186,10 @@ private static PropertyMap GetSingleKey(string method) } /// - /// Returns a single entity by a single id from table "Ts". + /// 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. + /// for optimal performance. /// /// Interface or type to create and populate /// Open SqlConnection @@ -239,10 +241,10 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction } /// - /// Returns a list of entites from table "Ts". + /// 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. + /// for optimal performance. /// /// Interface or type to create and populate /// Open SqlConnection @@ -298,7 +300,7 @@ private static string GetTableName(Type type) } else { - //NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework + //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() @@ -517,7 +519,7 @@ public static bool DeleteAll(this IDbConnection connection, IDbTransaction tr /// 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() @@ -624,7 +626,7 @@ private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder) private static void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity) { - //Define the field and the property + //Define the field and the property var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private); var property = typeBuilder.DefineProperty(propertyName, System.Reflection.PropertyAttributes.None, @@ -722,7 +724,7 @@ public class ComputedAttribute : Attribute public partial interface ISqlAdapter { int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert); - + //new methods for issue #336 void AppendColumnName(StringBuilder sb, string columnName); void AppendColumnNameEqualsValue(StringBuilder sb, string columnName); @@ -903,4 +905,3 @@ public PropertyMap(string columnName, PropertyInfo propInfo) public string ColumnName { get; private set; } public PropertyInfo PropertyInfo { get; private set; } } - From 2199f5bcbf08c0716bc9e48a5b29162bb2e11eda Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 28 Sep 2016 23:08:44 +0100 Subject: [PATCH 07/10] column map override - make tests green --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 32 +++++- Dapper.Contrib/SqlMapperExtensions.cs | 111 +++++++++++++------- Dapper.Tests.Contrib/TestSuite.cs | 59 ++++++++++- Dapper.Tests.Contrib/TestSuites.cs | 10 +- 4 files changed, 163 insertions(+), 49 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index b8835f5bd..c1bb645fd 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -33,7 +33,18 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i var key = GetSingleKey(nameof(GetAsync)); var name = GetTableName(type); - sql = $"SELECT * FROM {name} WHERE {key.ColumnName} = @id"; + var adapter = GetFormatter(connection); + var aliasedColumns = new StringBuilder(); + var allCols = GetAllColumns(type); + for (var i=0; i> GetAllAsync(this IDbConnection connection, GetSingleKey(nameof(GetAll)); var name = GetTableName(type); - sql = "SELECT * FROM " + name; + var adapter = GetFormatter(connection); + var aliasedColumns = new StringBuilder(); + var allCols = GetAllColumns(type); + for (var i=0; i InsertAsync(this IDbConnection connection, T entityTo for (var i = 0; i < persistentProperties.Count; i++) { var property = persistentProperties.ElementAt(i); - sbParameterList.AppendFormat("@{0}", property.ColumnName); + sbParameterList.AppendFormat("@{0}", property.PropertyInfo.Name); if (i < persistentProperties.Count - 1) sbParameterList.Append(", "); } @@ -217,7 +239,7 @@ public static async Task UpdateAsync(this IDbConnection connection, T e for (var i = 0; i < persistentProperties.Count; i++) { var property = persistentProperties.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName, property.PropertyInfo.Name); if (i < persistentProperties.Count - 1) sb.AppendFormat(", "); } @@ -225,7 +247,7 @@ public static async Task UpdateAsync(this IDbConnection connection, T e for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName, property.PropertyInfo.Name); if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 8b028d0f9..e3283dc2e 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -36,7 +36,6 @@ public interface ITableNameMapper private static readonly ConcurrentDictionary> KeyProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> PersistentProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); @@ -57,20 +56,6 @@ private static PropertyMap MapProperty(PropertyInfo propInfo) return new PropertyMap(propInfo.Name, propInfo); } - 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.PropertyInfo.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList(); - - ComputedProperties[type.TypeHandle] = computedProperties; - return computedProperties; - } - public static ColumnNameMapperDelegate GetKeyProperties = DefaultGetKeyProperties; private static List DefaultGetKeyProperties(Type type) { @@ -117,7 +102,7 @@ private static List KeyPropertiesCache(Type type) private static List DefaultGetAllColumns(Type type) { return type.GetProperties() - .Where(IsWriteable) + .Where(p => IsWriteable(p) && !IsComputed(p)) .Select(MapProperty) .ToList(); } @@ -155,11 +140,8 @@ private static List DefaultGetPersistentProperties(Type type) .Where(k => !k.PropertyInfo.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) .Select(k => k.PropertyInfo); - var computedProperties = ComputedPropertiesCache(type).Select(p => p.PropertyInfo); - var exclusions = keyProperties.Union(computedProperties); - return TypePropertiesCache(type) - .Where(t => !exclusions.Contains(t.PropertyInfo)) + .Where(t => !keyProperties.Contains(t.PropertyInfo)) .ToList(); } @@ -172,6 +154,12 @@ private static bool IsWriteable(PropertyInfo pi) return writeAttribute.Write; } + private static bool IsComputed(PropertyInfo pi) + { + var attributes = pi.GetCustomAttributes(typeof(ComputedAttribute), false).AsList(); + return attributes.Any(); + } + private static PropertyMap GetSingleKey(string method) { var type = typeof (T); @@ -207,7 +195,18 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction var key = GetSingleKey(nameof(Get)); var name = GetTableName(type); - sql = $"select * from {name} where {key.ColumnName} = @id"; + var adapter = GetFormatter(connection); + var aliasedColumns = new StringBuilder(); + var allCols = GetAllColumns(type); + for (var i=0; i GetAll(this IDbConnection connection, IDbTransac GetSingleKey(nameof(GetAll)); var name = GetTableName(type); - sql = "select * from " + name; + var adapter = GetFormatter(connection); + var aliasedColumns = new StringBuilder(); + var allCols = GetAllColumns(type); + for (var i=0; i(this IDbConnection connection, T entityToInsert, ID for (var i = 0; i < persistentProperties.Count; i++) { var property = persistentProperties.ElementAt(i); - sbParameterList.AppendFormat("@{0}", property.ColumnName); + sbParameterList.AppendFormat("@{0}", property.PropertyInfo.Name); if (i < persistentProperties.Count - 1) sbParameterList.Append(", "); } @@ -434,7 +445,7 @@ public static bool Update(this IDbConnection connection, T entityToUpdate, ID for (var i = 0; i < persistentProperties.Count; i++) { var property = persistentProperties.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); //fix for issue #336 + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName, property.PropertyInfo.Name); //fix for issue #336 if (i < persistentProperties.Count - 1) sb.AppendFormat(", "); } @@ -442,7 +453,7 @@ public static bool Update(this IDbConnection connection, T entityToUpdate, ID for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); //fix for issue #336 + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName, property.PropertyInfo.Name); //fix for issue #336 if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } @@ -489,7 +500,7 @@ public static bool Delete(this IDbConnection connection, T entityToDelete, ID for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties.ElementAt(i); - adapter.AppendColumnNameEqualsValue(sb, property.ColumnName); //fix for issue #336 + adapter.AppendColumnNameEqualsValue(sb, property.ColumnName, property.PropertyInfo.Name); //fix for issue #336 if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } @@ -727,7 +738,8 @@ public partial interface ISqlAdapter //new methods for issue #336 void AppendColumnName(StringBuilder sb, string columnName); - void AppendColumnNameEqualsValue(StringBuilder sb, string columnName); + void AppendColumnNameEqualsValue(StringBuilder sb, string columnName, string propertyName); + void AppendAliasedColumn(StringBuilder sb, string columnName, string propertyName); } public partial class SqlServerAdapter : ISqlAdapter @@ -754,9 +766,14 @@ public void AppendColumnName(StringBuilder sb, string columnName) sb.AppendFormat("[{0}]", columnName); } - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName, string propertyName) + { + sb.AppendFormat("[{0}] = @{1}", columnName, propertyName); + } + + public void AppendAliasedColumn(StringBuilder sb, string columnName, string propertyName) { - sb.AppendFormat("[{0}] = @{1}", columnName, columnName); + sb.AppendFormat("[{0}] as [{1}]", columnName, propertyName); } } @@ -784,9 +801,14 @@ public void AppendColumnName(StringBuilder sb, string columnName) sb.AppendFormat("[{0}]", columnName); } - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName, string propertyName) { - sb.AppendFormat("[{0}] = @{1}", columnName, columnName); + sb.AppendFormat("[{0}] = @{1}", columnName, propertyName); + } + + public void AppendAliasedColumn(StringBuilder sb, string columnName, string propertyName) + { + sb.AppendFormat("[{0}] as [{1}]", columnName, propertyName); } } @@ -813,9 +835,14 @@ public void AppendColumnName(StringBuilder sb, string columnName) sb.AppendFormat("`{0}`", columnName); } - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName, string propertyName) { - sb.AppendFormat("`{0}` = @{1}", columnName, columnName); + sb.AppendFormat("`{0}` = @{1}", columnName, propertyName); + } + + public void AppendAliasedColumn(StringBuilder sb, string columnName, string propertyName) + { + sb.AppendFormat("`{0}` as `{1}`", columnName, propertyName); } } @@ -862,9 +889,14 @@ public void AppendColumnName(StringBuilder sb, string columnName) sb.AppendFormat("\"{0}\"", columnName); } - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName, string propertyName) + { + sb.AppendFormat("\"{0}\" = @{1}", columnName, propertyName); + } + + public void AppendAliasedColumn(StringBuilder sb, string columnName, string propertyName) { - sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); + sb.AppendFormat("\"{0}\" as \"{1}\"", columnName, propertyName); } } @@ -889,9 +921,14 @@ public void AppendColumnName(StringBuilder sb, string columnName) sb.AppendFormat("\"{0}\"", columnName); } - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) + public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName, string propertyName) + { + sb.AppendFormat("\"{0}\" = @{1}", columnName, propertyName); + } + + public void AppendAliasedColumn(StringBuilder sb, string columnName, string propertyName) { - sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); + sb.AppendFormat("\"{0}\" as \"{1}\"", columnName, propertyName); } } diff --git a/Dapper.Tests.Contrib/TestSuite.cs b/Dapper.Tests.Contrib/TestSuite.cs index 02e179182..4fe979f3f 100644 --- a/Dapper.Tests.Contrib/TestSuite.cs +++ b/Dapper.Tests.Contrib/TestSuite.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Data; using System.Linq; - +using System.Reflection; using Dapper.Contrib.Extensions; #if !COREFX @@ -88,6 +88,13 @@ public class Result public int Order { get; set; } } + [Table("StrangelyMappedThing")] + public class StrangelyMappedThing + { + public int WeirdId { get; set; } + public string WonderfullName { get; set; } + } + public abstract partial class TestSuite { protected static readonly bool IsAppVeyor = Environment.GetEnvironmentVariable("Appveyor")?.ToUpperInvariant() == "TRUE"; @@ -171,7 +178,49 @@ public void InsertGetUpdateDeleteWithExplicitKey() o2.IsNull(); } } - + + [Fact] + public void InsertGetUpdateDeleteWithOverriddenColumnMaps() + { + using (var connection = GetOpenConnection()) + { + var idCol = new PropertyMap("strangeId", typeof(StrangelyMappedThing).GetProperty("WeirdId")); + var nameCol = new PropertyMap("strangeName", typeof(StrangelyMappedThing).GetProperty("WonderfullName")); + + var origGetAll = SqlMapperExtensions.GetAllColumns; + SqlMapperExtensions.GetAllColumns = + t => t == typeof(StrangelyMappedThing) ? new List {idCol, nameCol} + : origGetAll(t); + + var origGetPersistent = SqlMapperExtensions.GetPersistentProperties; + SqlMapperExtensions.GetPersistentProperties = + t => t == typeof(StrangelyMappedThing) ? new List {idCol, nameCol} + : origGetPersistent(t); + + var origGetKey = SqlMapperExtensions.GetKeyProperties; + SqlMapperExtensions.GetKeyProperties = + t => t == typeof(StrangelyMappedThing) ? new List {idCol} + : origGetKey(t); + + const int id = 1; + const string wonderfulName = "foo"; + var o1 = new StrangelyMappedThing {WeirdId = id, WonderfullName = wonderfulName}; + var originalxCount = connection.Query("Select Count(*) From StrangelyMappedThing").First(); + connection.Insert(o1); + var list1 = connection.Query("select * from StrangelyMappedThing").ToList(); + list1.Count.IsEqualTo(originalxCount + 1); + o1 = connection.Get(id); + o1.WeirdId.IsEqualTo(id); + o1.WonderfullName = "bar"; + connection.Update(o1); + o1 = connection.Get(id); + o1.WonderfullName.IsEqualTo("bar"); + connection.Delete(o1); + o1 = connection.Get(id); + o1.IsNull(); + } + } + [Fact] public void GetAllWithExplicitKey() { @@ -187,7 +236,7 @@ public void GetAllWithExplicitKey() } } - [Fact] + [Fact] public void InsertGetUpdateDeleteWithExplicitKeyNamedId() { using (var connection = GetOpenConnection()) @@ -208,8 +257,8 @@ public void InsertGetUpdateDeleteWithExplicitKeyNamedId() //o2.IsNull(); } } - - [Fact] + + [Fact] public void ShortIdentity() { using (var connection = GetOpenConnection()) diff --git a/Dapper.Tests.Contrib/TestSuites.cs b/Dapper.Tests.Contrib/TestSuites.cs index 320d20eb0..f90f18c56 100644 --- a/Dapper.Tests.Contrib/TestSuites.cs +++ b/Dapper.Tests.Contrib/TestSuites.cs @@ -30,7 +30,7 @@ public class SqlServerTestSuite : TestSuite public static string ConnectionString => IsAppVeyor ? @"Server=(local)\SQL2014;Database=tempdb;User ID=sa;Password=Password12!" - : $"Data Source=.;Initial Catalog={DbName};Integrated Security=True"; + : $"Data Source=localhost\\bluezinc;Initial Catalog={DbName};Integrated Security=True"; public override IDbConnection GetConnection() => new SqlConnection(ConnectionString); static SqlServerTestSuite() @@ -56,6 +56,8 @@ static SqlServerTestSuite() connection.Execute(@"CREATE TABLE ObjectY (ObjectYId int not null, Name nvarchar(100) not null);"); dropTable("ObjectZ"); connection.Execute(@"CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null);"); + dropTable("StrangelyMappedThing"); + connection.Execute(@"CREATE TABLE StrangelyMappedThing (strangeId int not null, strangeName nvarchar(100) not null);"); } } } @@ -104,6 +106,8 @@ static MySqlServerTestSuite() connection.Execute(@"CREATE TABLE ObjectY (ObjectYId int not null, Name nvarchar(100) not null);"); dropTable("ObjectZ"); connection.Execute(@"CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null);"); + dropTable("StrangelyMappedThing"); + connection.Execute(@"CREATE TABLE StrangelyMappedThing (strangeId int not null, strangeName nvarchar(100) not null);"); } } catch (MySqlException e) @@ -145,6 +149,7 @@ static SQLiteTestSuite() connection.Execute(@"CREATE TABLE ObjectX (ObjectXId nvarchar(100) not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE ObjectY (ObjectYId integer not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE ObjectZ (Id integer not null, Name nvarchar(100) not null) "); + connection.Execute(@"CREATE TABLE StrangelyMappedThing (strangeId int not null, strangeName nvarchar(100) not null);"); } } } @@ -156,7 +161,7 @@ public class SqlCETestSuite : TestSuite const string FileName = "Test.DB.sdf"; public static string ConnectionString => $"Data Source={FileName};"; public override IDbConnection GetConnection() => new SqlCeConnection(ConnectionString); - + static SqlCETestSuite() { if (File.Exists(FileName)) @@ -176,6 +181,7 @@ static SqlCETestSuite() connection.Execute(@"CREATE TABLE ObjectX (ObjectXId nvarchar(100) not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE ObjectY (ObjectYId int not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null) "); + connection.Execute(@"CREATE TABLE StrangelyMappedThing (strangeId int not null, strangeName nvarchar(100) not null);"); } Console.WriteLine("Created database"); } From daf531c229f58356e7f525915b8fe35c30901567 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 28 Sep 2016 23:30:01 +0100 Subject: [PATCH 08/10] more consistent naming for column mapping --- Dapper.Contrib/SqlMapperExtensions.cs | 12 ++++++------ Dapper.Tests.Contrib/TestSuite.cs | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index e3283dc2e..23ce6e75a 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -56,8 +56,8 @@ private static PropertyMap MapProperty(PropertyInfo propInfo) return new PropertyMap(propInfo.Name, propInfo); } - public static ColumnNameMapperDelegate GetKeyProperties = DefaultGetKeyProperties; - private static List DefaultGetKeyProperties(Type type) + public static ColumnNameMapperDelegate GetKeyColumns = DefaultGetKeyColumns; + private static List DefaultGetKeyColumns(Type type) { var allProperties = TypePropertiesCache(type); var keyProperties = allProperties.Where(p => p.PropertyInfo.GetCustomAttributes(true) @@ -92,7 +92,7 @@ private static List KeyPropertiesCache(Type type) return pi.ToList(); } - var keyProperties = GetKeyProperties(type); + var keyProperties = GetKeyColumns(type); KeyProperties[type.TypeHandle] = keyProperties; return keyProperties; @@ -128,13 +128,13 @@ private static List PersistentPropertiesCache(Type type) return pis.ToList(); } - var properties = GetPersistentProperties(type); + var properties = GetPersistentColumns(type); PersistentProperties[type.TypeHandle] = properties; return properties.ToList(); } - public static ColumnNameMapperDelegate GetPersistentProperties = DefaultGetPersistentProperties; - private static List DefaultGetPersistentProperties(Type type) + public static ColumnNameMapperDelegate GetPersistentColumns = DefaultGetPersistentColumns; + private static List DefaultGetPersistentColumns(Type type) { var keyProperties = KeyPropertiesCache(type) .Where(k => !k.PropertyInfo.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) diff --git a/Dapper.Tests.Contrib/TestSuite.cs b/Dapper.Tests.Contrib/TestSuite.cs index 4fe979f3f..919d80c3a 100644 --- a/Dapper.Tests.Contrib/TestSuite.cs +++ b/Dapper.Tests.Contrib/TestSuite.cs @@ -192,13 +192,13 @@ public void InsertGetUpdateDeleteWithOverriddenColumnMaps() t => t == typeof(StrangelyMappedThing) ? new List {idCol, nameCol} : origGetAll(t); - var origGetPersistent = SqlMapperExtensions.GetPersistentProperties; - SqlMapperExtensions.GetPersistentProperties = + var origGetPersistent = SqlMapperExtensions.GetPersistentColumns; + SqlMapperExtensions.GetPersistentColumns = t => t == typeof(StrangelyMappedThing) ? new List {idCol, nameCol} : origGetPersistent(t); - var origGetKey = SqlMapperExtensions.GetKeyProperties; - SqlMapperExtensions.GetKeyProperties = + var origGetKey = SqlMapperExtensions.GetKeyColumns; + SqlMapperExtensions.GetKeyColumns = t => t == typeof(StrangelyMappedThing) ? new List {idCol} : origGetKey(t); From c2c77dda02dc418b754e535f82c000c0b6f5f33a Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 28 Sep 2016 23:52:09 +0100 Subject: [PATCH 09/10] removing my sql instance name --- Dapper.Tests.Contrib/TestSuites.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.Tests.Contrib/TestSuites.cs b/Dapper.Tests.Contrib/TestSuites.cs index f90f18c56..bf549d01f 100644 --- a/Dapper.Tests.Contrib/TestSuites.cs +++ b/Dapper.Tests.Contrib/TestSuites.cs @@ -30,7 +30,7 @@ public class SqlServerTestSuite : TestSuite public static string ConnectionString => IsAppVeyor ? @"Server=(local)\SQL2014;Database=tempdb;User ID=sa;Password=Password12!" - : $"Data Source=localhost\\bluezinc;Initial Catalog={DbName};Integrated Security=True"; + : $"Data Source=.;Initial Catalog={DbName};Integrated Security=True"; public override IDbConnection GetConnection() => new SqlConnection(ConnectionString); static SqlServerTestSuite() From 1827f9b4b6dff9ebf5de524c51a8e81fe8cc4597 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 10 Nov 2016 20:40:20 +0000 Subject: [PATCH 10/10] Get does Select * by default --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 38 +++++++++++++-------- Dapper.Contrib/SqlMapperExtensions.cs | 22 ++++++++---- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index c1bb645fd..5b3f59851 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -14,9 +14,9 @@ namespace Dapper.Contrib.Extensions public static partial class SqlMapperExtensions { /// - /// Returns a single entity by a single id from table "Ts" asynchronously using .NET 4.5 Task. T must be of interface type. + /// Returns a single entity by a single id from table "Ts" asynchronously using .NET 4.5 Task. T must be of interface type. /// Id must be marked with [Key] attribute. - /// Created entity is tracked/intercepted for changes and used by the Update() extension. + /// Created entity is tracked/intercepted for changes and used by the Update() extension. /// /// Interface type to create and populate /// Open SqlConnection @@ -33,15 +33,23 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i var key = GetSingleKey(nameof(GetAsync)); var name = GetTableName(type); - var adapter = GetFormatter(connection); var aliasedColumns = new StringBuilder(); - var allCols = GetAllColumns(type); - for (var i=0; i GetAsync(this IDbConnection connection, dynamic i } /// - /// Returns a list of entites from table "Ts". + /// 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. + /// for optimal performance. /// /// Interface or type to create and populate /// Open SqlConnection @@ -231,7 +239,7 @@ public static async Task UpdateAsync(this IDbConnection connection, T e var sb = new StringBuilder(); sb.AppendFormat("update {0} set ", name); - + var persistentProperties = PersistentPropertiesCache(type); var adapter = GetFormatter(connection); @@ -334,7 +342,7 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran if (first == null || first.id == null) return 0; var id = (int)first.id; - + if (!keyProperties.Any()) return id; var idp = keyProperties.First().PropertyInfo; @@ -354,7 +362,7 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran if (r.First() == null || r.First().id == null) return 0; var id = (int)r.First().id; - + if (!keyProperties.Any()) return id; var idp = keyProperties.First().PropertyInfo; @@ -385,7 +393,7 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran } public partial class PostgresAdapter -{ +{ public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IList keyProperties, object entityToInsert) { var sb = new StringBuilder(); diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 23ce6e75a..6e37f9a6c 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -195,15 +195,23 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction var key = GetSingleKey(nameof(Get)); var name = GetTableName(type); - var adapter = GetFormatter(connection); var aliasedColumns = new StringBuilder(); - var allCols = GetAllColumns(type); - for (var i=0; i