From 64666d5cf5b75aefb7fe181e41dee521f01b2bb4 Mon Sep 17 00:00:00 2001 From: David Chell Date: Wed, 7 Sep 2011 09:43:22 +1200 Subject: [PATCH 1/6] Added postresql support for Insert to return primary key of inserted row. --- Dapper.Contrib/SqlMapperExtensions.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 81964db9..d8a3a57f 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -145,7 +145,7 @@ namespace Dapper.Contrib.Extensions /// Entity to insert /// Identity of inserted entity public static long Insert(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { + { var type = typeof(T); @@ -174,12 +174,17 @@ namespace Dapper.Contrib.Extensions if (i < allPropertiesExceptKey.Count() - 1) sb.Append(", "); } - sb.Append(") "); - connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); - //NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE - var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout); - return (int)r.First().id; - + sb.Append(") RETURNING * "); + var r = connection.Query(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); + long id = 0; + foreach (var p in keyProperties) + { + var value = ((IDictionary)r.First())[p.Name.ToLower()]; + p.SetValue(entityToInsert, value, null); + if (id == 0) + id = Convert.ToInt64(value); + } + return id; } /// From 5633d8795fe4f9c383c5bcccba35461ed96c4ea4 Mon Sep 17 00:00:00 2001 From: David Chell Date: Wed, 7 Sep 2011 10:11:36 +1200 Subject: [PATCH 2/6] Reinstated support for Sql server using #define. Signed-off-by: David Chell --- Dapper.Contrib/SqlMapperExtensions.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index d8a3a57f..af407289 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -1,4 +1,6 @@ -using System; +#define POSTGRESQL + +using System; using System.Collections.Generic; using System.Data; using System.Diagnostics; @@ -174,9 +176,11 @@ namespace Dapper.Contrib.Extensions if (i < allPropertiesExceptKey.Count() - 1) sb.Append(", "); } + long id = 0; +#if POSTGRESQL sb.Append(") RETURNING * "); var r = connection.Query(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); - long id = 0; + // Return the key py assinging the corresponding property in the object - by product is that it supports compound primary keys foreach (var p in keyProperties) { var value = ((IDictionary)r.First())[p.Name.ToLower()]; @@ -184,6 +188,13 @@ namespace Dapper.Contrib.Extensions if (id == 0) id = Convert.ToInt64(value); } +#else + sb.Append(") "); + connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); + //NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE + var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout); + id = (int)r.First().id; +#endif return id; } From 0d9705b4164e6ca8b74f72dba6b2732671522a0d Mon Sep 17 00:00:00 2001 From: David Chell Date: Wed, 7 Sep 2011 13:49:22 +1200 Subject: [PATCH 3/6] Postgres insert implementation now only retrieves keys instead of the entire row. Added [Write(true|false)] attributes to class properties, enables the presence of properties that don't get inserted/updated in the database. --- Dapper.Contrib/SqlMapperExtensions.cs | 53 +++++++++++++++++++++------ 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index af407289..1a639815 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -56,12 +56,23 @@ namespace Dapper.Contrib.Extensions return TypeProperties[type.TypeHandle]; } - var properties = type.GetProperties(); + var properties = type.GetProperties().Where(IsWriteable); TypeProperties[type.TypeHandle] = properties; return properties; } - /// + public static bool IsWriteable(PropertyInfo pi) + { + object[] attributes = pi.GetCustomAttributes(typeof (WriteAttribute), false); + if (attributes.Length == 1) + { + WriteAttribute write = (WriteAttribute) attributes[0]; + return write.Write; + } + return true; + } + + /// /// Returns a single entity by a single id from table "Ts". 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. @@ -176,20 +187,30 @@ namespace Dapper.Contrib.Extensions if (i < allPropertiesExceptKey.Count() - 1) sb.Append(", "); } + sb.Append(") "); long id = 0; #if POSTGRESQL - sb.Append(") RETURNING * "); - var r = connection.Query(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); - // Return the key py assinging the corresponding property in the object - by product is that it supports compound primary keys - foreach (var p in keyProperties) + if (keyProperties.Count() > 0) { - var value = ((IDictionary)r.First())[p.Name.ToLower()]; - p.SetValue(entityToInsert, value, null); - if (id == 0) - id = Convert.ToInt64(value); + sb.Append(" RETURNING "); + for (var i = 0; i < keyProperties.Count(); i++) + { + var property = keyProperties.ElementAt(i); + sb.Append(property.Name); + if (i < keyProperties.Count() - 1) + sb.Append(", "); + } + var r = connection.Query(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); + // Return the key py assinging the corresponding property in the object - by product is that it supports compound primary keys + foreach (var p in keyProperties) + { + var value = ((IDictionary) r.First())[p.Name.ToLower()]; + p.SetValue(entityToInsert, value, null); + if (id == 0) + id = Convert.ToInt64(value); + } } #else - sb.Append(") "); connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); //NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout); @@ -451,4 +472,14 @@ namespace Dapper.Contrib.Extensions public class KeyAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + public class WriteAttribute : Attribute + { + public WriteAttribute(bool write) + { + Write = write; + } + public bool Write { get; private set; } + } } From 1a506e8e8164ec0545e9c9a229240fab573c5355 Mon Sep 17 00:00:00 2001 From: David Chell Date: Fri, 9 Sep 2011 16:52:00 +1200 Subject: [PATCH 4/6] Fixed Insert not inserting when the object had no primary key. --- Dapper.Contrib/SqlMapperExtensions.cs | 33 ++++++++++++--------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 1a639815..01657550 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -190,25 +190,22 @@ namespace Dapper.Contrib.Extensions sb.Append(") "); long id = 0; #if POSTGRESQL - if (keyProperties.Count() > 0) + sb.Append(" RETURNING "); + for (var i = 0; i < keyProperties.Count(); i++) { - sb.Append(" RETURNING "); - for (var i = 0; i < keyProperties.Count(); i++) - { - var property = keyProperties.ElementAt(i); - sb.Append(property.Name); - if (i < keyProperties.Count() - 1) - sb.Append(", "); - } - var r = connection.Query(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); - // Return the key py assinging the corresponding property in the object - by product is that it supports compound primary keys - foreach (var p in keyProperties) - { - var value = ((IDictionary) r.First())[p.Name.ToLower()]; - p.SetValue(entityToInsert, value, null); - if (id == 0) - id = Convert.ToInt64(value); - } + var property = keyProperties.ElementAt(i); + sb.Append(property.Name); + if (i < keyProperties.Count() - 1) + sb.Append(", "); + } + var r = connection.Query(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); + // Return the key py assinging the corresponding property in the object - by product is that it supports compound primary keys + foreach (var p in keyProperties) + { + var value = ((IDictionary) r.First())[p.Name.ToLower()]; + p.SetValue(entityToInsert, value, null); + if (id == 0) + id = Convert.ToInt64(value); } #else connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); From 390ba5b45eb0ec8ea8cdbf86dbe68cf38c067037 Mon Sep 17 00:00:00 2001 From: David Chell Date: Mon, 12 Sep 2011 08:00:35 +1200 Subject: [PATCH 5/6] Made Postgresql specific insert handle entities with no primary keys. --- Dapper.Contrib/SqlMapperExtensions.cs | 35 +++++++++++++++------------ 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 01657550..ee473cf3 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -190,23 +190,28 @@ namespace Dapper.Contrib.Extensions sb.Append(") "); long id = 0; #if POSTGRESQL - sb.Append(" RETURNING "); - for (var i = 0; i < keyProperties.Count(); i++) + if (keyProperties.Count() > 0) { - var property = keyProperties.ElementAt(i); - sb.Append(property.Name); - if (i < keyProperties.Count() - 1) - sb.Append(", "); - } - var r = connection.Query(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); - // Return the key py assinging the corresponding property in the object - by product is that it supports compound primary keys - foreach (var p in keyProperties) - { - var value = ((IDictionary) r.First())[p.Name.ToLower()]; - p.SetValue(entityToInsert, value, null); - if (id == 0) - id = Convert.ToInt64(value); + sb.Append(" RETURNING "); + for (var i = 0; i < keyProperties.Count(); i++) + { + var property = keyProperties.ElementAt(i); + sb.Append(property.Name); + if (i < keyProperties.Count() - 1) + sb.Append(", "); + } + var r = connection.Query(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); + // Return the key py assinging the corresponding property in the object - by product is that it supports compound primary keys + foreach (var p in keyProperties) + { + var value = ((IDictionary) r.First())[p.Name.ToLower()]; + p.SetValue(entityToInsert, value, null); + if (id == 0) + id = Convert.ToInt64(value); + } } + else + connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); #else connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); //NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE From d57970fbc328945e290d605b56df463833360d53 Mon Sep 17 00:00:00 2001 From: David Chell Date: Thu, 19 Jan 2012 14:41:07 +1300 Subject: [PATCH 6/6] Removed POSTGRESQL define and added SqlAdapter dictionary to handle variances in DBMSs --- Dapper.Contrib/SqlMapperExtensions.cs | 121 ++++++++++++++++---------- 1 file changed, 76 insertions(+), 45 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 2fe5fdec..dc9a06cb 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -1,6 +1,4 @@ -#define POSTGRESQL - -using System; +using System; using System.Collections.Generic; using System.Data; using System.Diagnostics; @@ -11,6 +9,7 @@ using System.Collections.Concurrent; using System.Reflection.Emit; using System.Threading; using System.Runtime.CompilerServices; +using Dapper; namespace Dapper.Contrib.Extensions { @@ -27,6 +26,11 @@ namespace Dapper.Contrib.Extensions private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); + private static readonly Dictionary AdapterDictionary = new Dictionary() { + {"sqlconnection", new SqlServerAdapter()}, + {"npgsqlconnection", new PostgresAdapter()} + }; + private static IEnumerable KeyPropertiesCache(Type type) { @@ -167,60 +171,30 @@ namespace Dapper.Contrib.Extensions var name = GetTableName(type); - var sb = new StringBuilder(null); - sb.AppendFormat("insert into {0} (", name); + var sbColumnList = new StringBuilder(null); - var allProperties = TypePropertiesCache(type); + var allProperties = TypePropertiesCache(type); var keyProperties = KeyPropertiesCache(type); var allPropertiesExceptKey = allProperties.Except(keyProperties); for (var i = 0; i < allPropertiesExceptKey.Count(); i++) { var property = allPropertiesExceptKey.ElementAt(i); - sb.Append(property.Name); + sbColumnList.Append(property.Name); if (i < allPropertiesExceptKey.Count() - 1) - sb.Append(", "); + sbColumnList.Append(", "); } - sb.Append(") values ("); - for (var i = 0; i < allPropertiesExceptKey.Count(); i++) + var sbParameterList = new StringBuilder(null); + for (var i = 0; i < allPropertiesExceptKey.Count(); i++) { var property = allPropertiesExceptKey.ElementAt(i); - sb.AppendFormat("@{0}", property.Name); + sbParameterList.AppendFormat("@{0}", property.Name); if (i < allPropertiesExceptKey.Count() - 1) - sb.Append(", "); + sbParameterList.Append(", "); } - sb.Append(") "); - long id = 0; -#if POSTGRESQL - if (keyProperties.Count() > 0) - { - sb.Append(" RETURNING "); - for (var i = 0; i < keyProperties.Count(); i++) - { - var property = keyProperties.ElementAt(i); - sb.Append(property.Name); - if (i < keyProperties.Count() - 1) - sb.Append(", "); - } - var r = connection.Query(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); - // Return the key py assinging the corresponding property in the object - by product is that it supports compound primary keys - foreach (var p in keyProperties) - { - var value = ((IDictionary) r.First())[p.Name.ToLower()]; - p.SetValue(entityToInsert, value, null); - if (id == 0) - id = Convert.ToInt64(value); - } - } - else - connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); -#else - connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); - //NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE - var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout); - id = (int)r.First().id; -#endif + ISqlAdapter adapter = GetFormatter(connection); + int id = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(), sbParameterList.ToString(), keyProperties, entityToInsert); return id; } @@ -242,7 +216,7 @@ namespace Dapper.Contrib.Extensions var type = typeof(T); var keyProperties = KeyPropertiesCache(type); - if (keyProperties.Count() == 0) + if (!keyProperties.Any()) throw new ArgumentException("Entity must have at least one [Key] property"); var name = GetTableName(type); @@ -303,8 +277,15 @@ namespace Dapper.Contrib.Extensions return deleted > 0; } + public static ISqlAdapter GetFormatter(IDbConnection connection) + { + string name = connection.GetType().Name.ToLower(); + if (!AdapterDictionary.ContainsKey(name)) + return new SqlServerAdapter(); + return AdapterDictionary[name]; + } - class ProxyGenerator + class ProxyGenerator { private static readonly Dictionary TypeCache = new Dictionary(); @@ -489,3 +470,53 @@ namespace Dapper.Contrib.Extensions public bool Write { get; private set; } } } + +public interface ISqlAdapter +{ + int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert); +} + +public class SqlServerAdapter : ISqlAdapter +{ + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) + { + string cmd = String.Format("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList); + + connection.Execute(cmd, entityToInsert, transaction: transaction, commandTimeout: commandTimeout); + + //NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE + var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout); + return (int)r.First().id; + } +} + +public class PostgresAdapter : ISqlAdapter +{ + public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, String tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList); + + sb.Append(" RETURNING "); + bool first = true; + foreach(var property in keyProperties) + { + if (!first) + sb.Append(", "); + first = false; + sb.Append(property.Name); + } + var results = connection.Query(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout); + + // Return the key by assinging the corresponding property in the object - by product is that it supports compound primary keys + int id = 0; + foreach (var p in keyProperties) + { + var value = ((IDictionary)results.First())[p.Name.ToLower()]; + p.SetValue(entityToInsert, value, null); + if (id == 0) + id = Convert.ToInt32(value); + } + return id; + } +} \ No newline at end of file