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 diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index f4c320680..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,7 +33,26 @@ 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"; + var aliasedColumns = new StringBuilder(); + + if(GetPersistentColumns == DefaultGetPersistentColumns) + { + aliasedColumns.Append("*"); + } + else + { + var adapter = GetFormatter(connection); + var allCols = GetAllColumns(type); + for (var i=0; i 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 @@ -62,10 +81,10 @@ public static async Task 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 @@ -83,7 +102,18 @@ public static Task> 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> 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 +172,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.PropertyInfo.Name); + if (i < persistentProperties.Count - 1) sbParameterList.Append(", "); } @@ -204,34 +232,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, property.PropertyInfo.Name); + 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, property.PropertyInfo.Name); if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } @@ -265,12 +289,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 +300,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 +328,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 +342,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; - var idp = pi.First(); + if (!keyProperties.Any()) return id; + + var idp = keyProperties.First().PropertyInfo; idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); return id; @@ -332,7 +354,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); @@ -341,10 +363,9 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran if (r.First() == null || r.First().id == null) return 0; var id = (int)r.First().id; - var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (!pi.Any()) return id; + 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; @@ -354,7 +375,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 +383,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); @@ -373,26 +393,25 @@ 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 +420,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 +433,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..6e37f9a6c 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -32,11 +32,11 @@ 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> 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> PersistentProperties = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); @@ -51,75 +51,100 @@ private static readonly Dictionary AdapterDictionary {"mysqlconnection", new MySqlAdapter()}, }; - private static List ComputedPropertiesCache(Type type) + private static PropertyMap MapProperty(PropertyInfo propInfo) { - 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(); - - ComputedProperties[type.TypeHandle] = computedProperties; - return computedProperties; + return new PropertyMap(propInfo.Name, propInfo); } - private static List ExplicitKeyPropertiesCache(Type type) + public static ColumnNameMapperDelegate GetKeyColumns = DefaultGetKeyColumns; + private static List DefaultGetKeyColumns(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 => !keyProperties.Contains(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 = GetKeyColumns(type); KeyProperties[type.TypeHandle] = keyProperties; return keyProperties; } - private static List TypePropertiesCache(Type type) + public static ColumnNameMapperDelegate GetAllColumns = DefaultGetAllColumns; + private static List DefaultGetAllColumns(Type type) { - IEnumerable pis; + return type.GetProperties() + .Where(p => IsWriteable(p) && !IsComputed(p)) + .Select(MapProperty) + .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(); + 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 = GetPersistentColumns(type); + PersistentProperties[type.TypeHandle] = properties; + return properties.ToList(); + } + + 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)) + .Select(k => k.PropertyInfo); + + return TypePropertiesCache(type) + .Where(t => !keyProperties.Contains(t.PropertyInfo)) + .ToList(); + } + private static bool IsWriteable(PropertyInfo pi) { var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList(); @@ -129,25 +154,30 @@ private static bool IsWriteable(PropertyInfo pi) return writeAttribute.Write; } - private static PropertyInfo GetSingleKey(string method) + 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); 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 default, 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(); } /// - /// 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 @@ -165,7 +195,26 @@ 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"; + var aliasedColumns = new StringBuilder(); + + if(GetPersistentColumns == DefaultGetPersistentColumns) + { + aliasedColumns.Append("*"); + } + else + { + var adapter = GetFormatter(connection); + var allCols = GetAllColumns(type); + for (var i=0; i(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 @@ -199,10 +248,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 @@ -220,7 +269,19 @@ public static IEnumerable 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 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); @@ -258,7 +319,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() @@ -306,27 +367,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.PropertyInfo.Name); + if (i < persistentProperties.Count - 1) sbParameterList.Append(", "); } @@ -377,35 +436,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, property.PropertyInfo.Name); //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, property.PropertyInfo.Name); //fix for issue #336 if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } @@ -438,13 +494,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 +508,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, property.PropertyInfo.Name); //fix for issue #336 if (i < keyProperties.Count - 1) sb.AppendFormat(" and "); } @@ -484,7 +538,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() @@ -591,7 +645,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, @@ -688,16 +742,17 @@ 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); - 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 { - 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 +761,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; @@ -720,15 +774,20 @@ 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); } } 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 +796,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; @@ -751,15 +809,20 @@ 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); } } 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 +830,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); @@ -781,34 +843,38 @@ 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); } } 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 +882,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); } @@ -831,24 +897,28 @@ 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); } } 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; @@ -859,8 +929,24 @@ 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}\" as \"{1}\"", columnName, propertyName); + } +} + +public class PropertyMap +{ + public PropertyMap(string columnName, PropertyInfo propInfo) { - sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); + ColumnName = columnName; + PropertyInfo = propInfo; } + public string ColumnName { get; private set; } + public PropertyInfo PropertyInfo { get; private set; } } diff --git a/Dapper.Tests.Contrib/TestSuite.cs b/Dapper.Tests.Contrib/TestSuite.cs index 02e179182..919d80c3a 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.GetPersistentColumns; + SqlMapperExtensions.GetPersistentColumns = + t => t == typeof(StrangelyMappedThing) ? new List {idCol, nameCol} + : origGetPersistent(t); + + var origGetKey = SqlMapperExtensions.GetKeyColumns; + SqlMapperExtensions.GetKeyColumns = + 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..bf549d01f 100644 --- a/Dapper.Tests.Contrib/TestSuites.cs +++ b/Dapper.Tests.Contrib/TestSuites.cs @@ -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"); } diff --git a/Dapper.Tests.Contrib/project.json b/Dapper.Tests.Contrib/project.json index a4ec28544..1ee2a93e8 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": { @@ -112,4 +112,4 @@ } } } -} \ No newline at end of file +} diff --git a/Dapper.Tests/project.json b/Dapper.Tests/project.json index 64188982e..3d8b684e0 100644 --- a/Dapper.Tests/project.json +++ b/Dapper.Tests/project.json @@ -141,4 +141,4 @@ } } } -} \ No newline at end of file +}