/Dapper.Contrib/SqlMapperExtensions.cs

http://github.com/SamSaffron/dapper-dot-net · C# · 1151 lines · 699 code · 146 blank · 306 comment · 116 complexity · 4f83afcff4dae70aa79fecd30c84bfa5 MD5 · raw file

Large files are truncated click here to view the full file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Text;
  7. using System.Collections.Concurrent;
  8. using System.Reflection.Emit;
  9. using System.Threading;
  10. using Dapper;
  11. namespace Dapper.Contrib.Extensions
  12. {
  13. /// <summary>
  14. /// The Dapper.Contrib extensions for Dapper
  15. /// </summary>
  16. public static partial class SqlMapperExtensions
  17. {
  18. /// <summary>
  19. /// Defined a proxy object with a possibly dirty state.
  20. /// </summary>
  21. public interface IProxy //must be kept public
  22. {
  23. /// <summary>
  24. /// Whether the object has been changed.
  25. /// </summary>
  26. bool IsDirty { get; set; }
  27. }
  28. /// <summary>
  29. /// Defines a table name mapper for getting table names from types.
  30. /// </summary>
  31. public interface ITableNameMapper
  32. {
  33. /// <summary>
  34. /// Gets a table name from a given <see cref="Type"/>.
  35. /// </summary>
  36. /// <param name="type">The <see cref="Type"/> to get a name from.</param>
  37. /// <returns>The table name for the given <paramref name="type"/>.</returns>
  38. string GetTableName(Type type);
  39. }
  40. /// <summary>
  41. /// The function to get a database type from the given <see cref="IDbConnection"/>.
  42. /// </summary>
  43. /// <param name="connection">The connection to get a database type name from.</param>
  44. public delegate string GetDatabaseTypeDelegate(IDbConnection connection);
  45. /// <summary>
  46. /// The function to get a a table name from a given <see cref="Type"/>
  47. /// </summary>
  48. /// <param name="type">The <see cref="Type"/> to get a table name for.</param>
  49. public delegate string TableNameMapperDelegate(Type type);
  50. private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> KeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
  51. private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> ExplicitKeyProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
  52. private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> TypeProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
  53. private static readonly ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>> ComputedProperties = new ConcurrentDictionary<RuntimeTypeHandle, IEnumerable<PropertyInfo>>();
  54. private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> GetQueries = new ConcurrentDictionary<RuntimeTypeHandle, string>();
  55. private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> TypeTableName = new ConcurrentDictionary<RuntimeTypeHandle, string>();
  56. private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter();
  57. private static readonly Dictionary<string, ISqlAdapter> AdapterDictionary
  58. = new Dictionary<string, ISqlAdapter>(6)
  59. {
  60. ["sqlconnection"] = new SqlServerAdapter(),
  61. ["sqlceconnection"] = new SqlCeServerAdapter(),
  62. ["npgsqlconnection"] = new PostgresAdapter(),
  63. ["sqliteconnection"] = new SQLiteAdapter(),
  64. ["mysqlconnection"] = new MySqlAdapter(),
  65. ["fbconnection"] = new FbAdapter()
  66. };
  67. private static List<PropertyInfo> ComputedPropertiesCache(Type type)
  68. {
  69. if (ComputedProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> pi))
  70. {
  71. return pi.ToList();
  72. }
  73. var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList();
  74. ComputedProperties[type.TypeHandle] = computedProperties;
  75. return computedProperties;
  76. }
  77. private static List<PropertyInfo> ExplicitKeyPropertiesCache(Type type)
  78. {
  79. if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> pi))
  80. {
  81. return pi.ToList();
  82. }
  83. var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList();
  84. ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties;
  85. return explicitKeyProperties;
  86. }
  87. private static List<PropertyInfo> KeyPropertiesCache(Type type)
  88. {
  89. if (KeyProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> pi))
  90. {
  91. return pi.ToList();
  92. }
  93. var allProperties = TypePropertiesCache(type);
  94. var keyProperties = allProperties.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)).ToList();
  95. if (keyProperties.Count == 0)
  96. {
  97. var idProp = allProperties.Find(p => string.Equals(p.Name, "id", StringComparison.CurrentCultureIgnoreCase));
  98. if (idProp != null && !idProp.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute))
  99. {
  100. keyProperties.Add(idProp);
  101. }
  102. }
  103. KeyProperties[type.TypeHandle] = keyProperties;
  104. return keyProperties;
  105. }
  106. private static List<PropertyInfo> TypePropertiesCache(Type type)
  107. {
  108. if (TypeProperties.TryGetValue(type.TypeHandle, out IEnumerable<PropertyInfo> pis))
  109. {
  110. return pis.ToList();
  111. }
  112. var properties = type.GetProperties().Where(IsWriteable).ToArray();
  113. TypeProperties[type.TypeHandle] = properties;
  114. return properties.ToList();
  115. }
  116. private static bool IsWriteable(PropertyInfo pi)
  117. {
  118. var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList();
  119. if (attributes.Count != 1) return true;
  120. var writeAttribute = (WriteAttribute)attributes[0];
  121. return writeAttribute.Write;
  122. }
  123. private static PropertyInfo GetSingleKey<T>(string method)
  124. {
  125. var type = typeof(T);
  126. var keys = KeyPropertiesCache(type);
  127. var explicitKeys = ExplicitKeyPropertiesCache(type);
  128. var keyCount = keys.Count + explicitKeys.Count;
  129. if (keyCount > 1)
  130. throw new DataException($"{method}<T> only supports an entity with a single [Key] or [ExplicitKey] property. [Key] Count: {keys.Count}, [ExplicitKey] Count: {explicitKeys.Count}");
  131. if (keyCount == 0)
  132. throw new DataException($"{method}<T> only supports an entity with a [Key] or an [ExplicitKey] property");
  133. return keys.Count > 0 ? keys[0] : explicitKeys[0];
  134. }
  135. /// <summary>
  136. /// Returns a single entity by a single id from table "Ts".
  137. /// Id must be marked with [Key] attribute.
  138. /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension
  139. /// for optimal performance.
  140. /// </summary>
  141. /// <typeparam name="T">Interface or type to create and populate</typeparam>
  142. /// <param name="connection">Open SqlConnection</param>
  143. /// <param name="id">Id of the entity to get, must be marked with [Key] attribute</param>
  144. /// <param name="transaction">The transaction to run under, null (the default) if none</param>
  145. /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
  146. /// <returns>Entity of T</returns>
  147. public static T Get<T>(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
  148. {
  149. var type = typeof(T);
  150. if (!GetQueries.TryGetValue(type.TypeHandle, out string sql))
  151. {
  152. var key = GetSingleKey<T>(nameof(Get));
  153. var name = GetTableName(type);
  154. sql = $"select * from {name} where {key.Name} = @id";
  155. GetQueries[type.TypeHandle] = sql;
  156. }
  157. var dynParams = new DynamicParameters();
  158. dynParams.Add("@id", id);
  159. T obj;
  160. if (type.IsInterface)
  161. {
  162. var res = connection.Query(sql, dynParams).FirstOrDefault() as IDictionary<string, object>;
  163. if (res == null)
  164. return null;
  165. obj = ProxyGenerator.GetInterfaceProxy<T>();
  166. foreach (var property in TypePropertiesCache(type))
  167. {
  168. var val = res[property.Name];
  169. if (val == null) continue;
  170. if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
  171. {
  172. var genericType = Nullable.GetUnderlyingType(property.PropertyType);
  173. if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null);
  174. }
  175. else
  176. {
  177. property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null);
  178. }
  179. }
  180. ((IProxy)obj).IsDirty = false; //reset change tracking and return
  181. }
  182. else
  183. {
  184. obj = connection.Query<T>(sql, dynParams, transaction, commandTimeout: commandTimeout).FirstOrDefault();
  185. }
  186. return obj;
  187. }
  188. /// <summary>
  189. /// Returns a list of entities from table "Ts".
  190. /// Id of T must be marked with [Key] attribute.
  191. /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension
  192. /// for optimal performance.
  193. /// </summary>
  194. /// <typeparam name="T">Interface or type to create and populate</typeparam>
  195. /// <param name="connection">Open SqlConnection</param>
  196. /// <param name="transaction">The transaction to run under, null (the default) if none</param>
  197. /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
  198. /// <returns>Entity of T</returns>
  199. public static IEnumerable<T> GetAll<T>(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
  200. {
  201. var type = typeof(T);
  202. var cacheType = typeof(List<T>);
  203. if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql))
  204. {
  205. GetSingleKey<T>(nameof(GetAll));
  206. var name = GetTableName(type);
  207. sql = "select * from " + name;
  208. GetQueries[cacheType.TypeHandle] = sql;
  209. }
  210. if (!type.IsInterface) return connection.Query<T>(sql, null, transaction, commandTimeout: commandTimeout);
  211. var result = connection.Query(sql);
  212. var list = new List<T>();
  213. foreach (IDictionary<string, object> res in result)
  214. {
  215. var obj = ProxyGenerator.GetInterfaceProxy<T>();
  216. foreach (var property in TypePropertiesCache(type))
  217. {
  218. var val = res[property.Name];
  219. if (val == null) continue;
  220. if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
  221. {
  222. var genericType = Nullable.GetUnderlyingType(property.PropertyType);
  223. if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null);
  224. }
  225. else
  226. {
  227. property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null);
  228. }
  229. }
  230. ((IProxy)obj).IsDirty = false; //reset change tracking and return
  231. list.Add(obj);
  232. }
  233. return list;
  234. }
  235. /// <summary>
  236. /// Specify a custom table name mapper based on the POCO type name
  237. /// </summary>
  238. public static TableNameMapperDelegate TableNameMapper;
  239. private static string GetTableName(Type type)
  240. {
  241. if (TypeTableName.TryGetValue(type.TypeHandle, out string name)) return name;
  242. if (TableNameMapper != null)
  243. {
  244. name = TableNameMapper(type);
  245. }
  246. else
  247. {
  248. //NOTE: This as dynamic trick falls back to handle both our own Table-attribute as well as the one in EntityFramework
  249. var tableAttrName =
  250. type.GetCustomAttribute<TableAttribute>(false)?.Name
  251. ?? (type.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic)?.Name;
  252. if (tableAttrName != null)
  253. {
  254. name = tableAttrName;
  255. }
  256. else
  257. {
  258. name = type.Name + "s";
  259. if (type.IsInterface && name.StartsWith("I"))
  260. name = name.Substring(1);
  261. }
  262. }
  263. TypeTableName[type.TypeHandle] = name;
  264. return name;
  265. }
  266. /// <summary>
  267. /// Inserts an entity into table "Ts" and returns identity id or number of inserted rows if inserting a list.
  268. /// </summary>
  269. /// <typeparam name="T">The type to insert.</typeparam>
  270. /// <param name="connection">Open SqlConnection</param>
  271. /// <param name="entityToInsert">Entity to insert, can be list of entities</param>
  272. /// <param name="transaction">The transaction to run under, null (the default) if none</param>
  273. /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
  274. /// <returns>Identity of inserted entity, or number of inserted rows if inserting a list</returns>
  275. public static long Insert<T>(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
  276. {
  277. var isList = false;
  278. var type = typeof(T);
  279. if (type.IsArray)
  280. {
  281. isList = true;
  282. type = type.GetElementType();
  283. }
  284. else if (type.IsGenericType)
  285. {
  286. var typeInfo = type.GetTypeInfo();
  287. bool implementsGenericIEnumerableOrIsGenericIEnumerable =
  288. typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
  289. typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>);
  290. if (implementsGenericIEnumerableOrIsGenericIEnumerable)
  291. {
  292. isList = true;
  293. type = type.GetGenericArguments()[0];
  294. }
  295. }
  296. var name = GetTableName(type);
  297. var sbColumnList = new StringBuilder(null);
  298. var allProperties = TypePropertiesCache(type);
  299. var keyProperties = KeyPropertiesCache(type);
  300. var computedProperties = ComputedPropertiesCache(type);
  301. var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
  302. var adapter = GetFormatter(connection);
  303. for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
  304. {
  305. var property = allPropertiesExceptKeyAndComputed[i];
  306. adapter.AppendColumnName(sbColumnList, property.Name); //fix for issue #336
  307. if (i < allPropertiesExceptKeyAndComputed.Count - 1)
  308. sbColumnList.Append(", ");
  309. }
  310. var sbParameterList = new StringBuilder(null);
  311. for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++)
  312. {
  313. var property = allPropertiesExceptKeyAndComputed[i];
  314. sbParameterList.AppendFormat("@{0}", property.Name);
  315. if (i < allPropertiesExceptKeyAndComputed.Count - 1)
  316. sbParameterList.Append(", ");
  317. }
  318. int returnVal;
  319. var wasClosed = connection.State == ConnectionState.Closed;
  320. if (wasClosed) connection.Open();
  321. if (!isList) //single entity
  322. {
  323. returnVal = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(),
  324. sbParameterList.ToString(), keyProperties, entityToInsert);
  325. }
  326. else
  327. {
  328. //insert list of entities
  329. var cmd = $"insert into {name} ({sbColumnList}) values ({sbParameterList})";
  330. returnVal = connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
  331. }
  332. if (wasClosed) connection.Close();
  333. return returnVal;
  334. }
  335. /// <summary>
  336. /// Updates entity in table "Ts", checks if the entity is modified if the entity is tracked by the Get() extension.
  337. /// </summary>
  338. /// <typeparam name="T">Type to be updated</typeparam>
  339. /// <param name="connection">Open SqlConnection</param>
  340. /// <param name="entityToUpdate">Entity to be updated</param>
  341. /// <param name="transaction">The transaction to run under, null (the default) if none</param>
  342. /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
  343. /// <returns>true if updated, false if not found or not modified (tracked entities)</returns>
  344. public static bool Update<T>(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
  345. {
  346. if (entityToUpdate is IProxy proxy && !proxy.IsDirty)
  347. {
  348. return false;
  349. }
  350. var type = typeof(T);
  351. if (type.IsArray)
  352. {
  353. type = type.GetElementType();
  354. }
  355. else if (type.IsGenericType)
  356. {
  357. var typeInfo = type.GetTypeInfo();
  358. bool implementsGenericIEnumerableOrIsGenericIEnumerable =
  359. typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
  360. typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>);
  361. if (implementsGenericIEnumerableOrIsGenericIEnumerable)
  362. {
  363. type = type.GetGenericArguments()[0];
  364. }
  365. }
  366. var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy
  367. var explicitKeyProperties = ExplicitKeyPropertiesCache(type);
  368. if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0)
  369. throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property");
  370. var name = GetTableName(type);
  371. var sb = new StringBuilder();
  372. sb.AppendFormat("update {0} set ", name);
  373. var allProperties = TypePropertiesCache(type);
  374. keyProperties.AddRange(explicitKeyProperties);
  375. var computedProperties = ComputedPropertiesCache(type);
  376. var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList();
  377. var adapter = GetFormatter(connection);
  378. for (var i = 0; i < nonIdProps.Count; i++)
  379. {
  380. var property = nonIdProps[i];
  381. adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336
  382. if (i < nonIdProps.Count - 1)
  383. sb.Append(", ");
  384. }
  385. sb.Append(" where ");
  386. for (var i = 0; i < keyProperties.Count; i++)
  387. {
  388. var property = keyProperties[i];
  389. adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336
  390. if (i < keyProperties.Count - 1)
  391. sb.Append(" and ");
  392. }
  393. var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction);
  394. return updated > 0;
  395. }
  396. /// <summary>
  397. /// Delete entity in table "Ts".
  398. /// </summary>
  399. /// <typeparam name="T">Type of entity</typeparam>
  400. /// <param name="connection">Open SqlConnection</param>
  401. /// <param name="entityToDelete">Entity to delete</param>
  402. /// <param name="transaction">The transaction to run under, null (the default) if none</param>
  403. /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
  404. /// <returns>true if deleted, false if not found</returns>
  405. public static bool Delete<T>(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
  406. {
  407. if (entityToDelete == null)
  408. throw new ArgumentException("Cannot Delete null Object", nameof(entityToDelete));
  409. var type = typeof(T);
  410. if (type.IsArray)
  411. {
  412. type = type.GetElementType();
  413. }
  414. else if (type.IsGenericType)
  415. {
  416. var typeInfo = type.GetTypeInfo();
  417. bool implementsGenericIEnumerableOrIsGenericIEnumerable =
  418. typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
  419. typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>);
  420. if (implementsGenericIEnumerableOrIsGenericIEnumerable)
  421. {
  422. type = type.GetGenericArguments()[0];
  423. }
  424. }
  425. var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy
  426. var explicitKeyProperties = ExplicitKeyPropertiesCache(type);
  427. if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0)
  428. throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property");
  429. var name = GetTableName(type);
  430. keyProperties.AddRange(explicitKeyProperties);
  431. var sb = new StringBuilder();
  432. sb.AppendFormat("delete from {0} where ", name);
  433. var adapter = GetFormatter(connection);
  434. for (var i = 0; i < keyProperties.Count; i++)
  435. {
  436. var property = keyProperties[i];
  437. adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336
  438. if (i < keyProperties.Count - 1)
  439. sb.Append(" and ");
  440. }
  441. var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction, commandTimeout);
  442. return deleted > 0;
  443. }
  444. /// <summary>
  445. /// Delete all entities in the table related to the type T.
  446. /// </summary>
  447. /// <typeparam name="T">Type of entity</typeparam>
  448. /// <param name="connection">Open SqlConnection</param>
  449. /// <param name="transaction">The transaction to run under, null (the default) if none</param>
  450. /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
  451. /// <returns>true if deleted, false if none found</returns>
  452. public static bool DeleteAll<T>(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
  453. {
  454. var type = typeof(T);
  455. var name = GetTableName(type);
  456. var statement = $"delete from {name}";
  457. var deleted = connection.Execute(statement, null, transaction, commandTimeout);
  458. return deleted > 0;
  459. }
  460. /// <summary>
  461. /// Specifies a custom callback that detects the database type instead of relying on the default strategy (the name of the connection type object).
  462. /// Please note that this callback is global and will be used by all the calls that require a database specific adapter.
  463. /// </summary>
  464. public static GetDatabaseTypeDelegate GetDatabaseType;
  465. private static ISqlAdapter GetFormatter(IDbConnection connection)
  466. {
  467. var name = GetDatabaseType?.Invoke(connection).ToLower()
  468. ?? connection.GetType().Name.ToLower();
  469. return AdapterDictionary.TryGetValue(name, out var adapter)
  470. ? adapter
  471. : DefaultAdapter;
  472. }
  473. private static class ProxyGenerator
  474. {
  475. private static readonly Dictionary<Type, Type> TypeCache = new Dictionary<Type, Type>();
  476. private static AssemblyBuilder GetAsmBuilder(string name)
  477. {
  478. #if NETSTANDARD2_0
  479. return AssemblyBuilder.DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run);
  480. #else
  481. return Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run);
  482. #endif
  483. }
  484. public static T GetInterfaceProxy<T>()
  485. {
  486. Type typeOfT = typeof(T);
  487. if (TypeCache.TryGetValue(typeOfT, out Type k))
  488. {
  489. return (T)Activator.CreateInstance(k);
  490. }
  491. var assemblyBuilder = GetAsmBuilder(typeOfT.Name);
  492. var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter
  493. var interfaceType = typeof(IProxy);
  494. var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(),
  495. TypeAttributes.Public | TypeAttributes.Class);
  496. typeBuilder.AddInterfaceImplementation(typeOfT);
  497. typeBuilder.AddInterfaceImplementation(interfaceType);
  498. //create our _isDirty field, which implements IProxy
  499. var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder);
  500. // Generate a field for each property, which implements the T
  501. foreach (var property in typeof(T).GetProperties())
  502. {
  503. var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute);
  504. CreateProperty<T>(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId);
  505. }
  506. #if NETSTANDARD2_0
  507. var generatedType = typeBuilder.CreateTypeInfo().AsType();
  508. #else
  509. var generatedType = typeBuilder.CreateType();
  510. #endif
  511. TypeCache.Add(typeOfT, generatedType);
  512. return (T)Activator.CreateInstance(generatedType);
  513. }
  514. private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder)
  515. {
  516. var propType = typeof(bool);
  517. var field = typeBuilder.DefineField("_" + nameof(IProxy.IsDirty), propType, FieldAttributes.Private);
  518. var property = typeBuilder.DefineProperty(nameof(IProxy.IsDirty),
  519. System.Reflection.PropertyAttributes.None,
  520. propType,
  521. new[] { propType });
  522. const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName
  523. | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig;
  524. // Define the "get" and "set" accessor methods
  525. var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + nameof(IProxy.IsDirty),
  526. getSetAttr,
  527. propType,
  528. Type.EmptyTypes);
  529. var currGetIl = currGetPropMthdBldr.GetILGenerator();
  530. currGetIl.Emit(OpCodes.Ldarg_0);
  531. currGetIl.Emit(OpCodes.Ldfld, field);
  532. currGetIl.Emit(OpCodes.Ret);
  533. var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + nameof(IProxy.IsDirty),
  534. getSetAttr,
  535. null,
  536. new[] { propType });
  537. var currSetIl = currSetPropMthdBldr.GetILGenerator();
  538. currSetIl.Emit(OpCodes.Ldarg_0);
  539. currSetIl.Emit(OpCodes.Ldarg_1);
  540. currSetIl.Emit(OpCodes.Stfld, field);
  541. currSetIl.Emit(OpCodes.Ret);
  542. property.SetGetMethod(currGetPropMthdBldr);
  543. property.SetSetMethod(currSetPropMthdBldr);
  544. var getMethod = typeof(IProxy).GetMethod("get_" + nameof(IProxy.IsDirty));
  545. var setMethod = typeof(IProxy).GetMethod("set_" + nameof(IProxy.IsDirty));
  546. typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
  547. typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
  548. return currSetPropMthdBldr;
  549. }
  550. private static void CreateProperty<T>(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity)
  551. {
  552. //Define the field and the property
  553. var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private);
  554. var property = typeBuilder.DefineProperty(propertyName,
  555. System.Reflection.PropertyAttributes.None,
  556. propType,
  557. new[] { propType });
  558. const MethodAttributes getSetAttr = MethodAttributes.Public
  559. | MethodAttributes.Virtual
  560. | MethodAttributes.HideBySig;
  561. // Define the "get" and "set" accessor methods
  562. var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName,
  563. getSetAttr,
  564. propType,
  565. Type.EmptyTypes);
  566. var currGetIl = currGetPropMthdBldr.GetILGenerator();
  567. currGetIl.Emit(OpCodes.Ldarg_0);
  568. currGetIl.Emit(OpCodes.Ldfld, field);
  569. currGetIl.Emit(OpCodes.Ret);
  570. var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName,
  571. getSetAttr,
  572. null,
  573. new[] { propType });
  574. //store value in private field and set the isdirty flag
  575. var currSetIl = currSetPropMthdBldr.GetILGenerator();
  576. currSetIl.Emit(OpCodes.Ldarg_0);
  577. currSetIl.Emit(OpCodes.Ldarg_1);
  578. currSetIl.Emit(OpCodes.Stfld, field);
  579. currSetIl.Emit(OpCodes.Ldarg_0);
  580. currSetIl.Emit(OpCodes.Ldc_I4_1);
  581. currSetIl.Emit(OpCodes.Call, setIsDirtyMethod);
  582. currSetIl.Emit(OpCodes.Ret);
  583. //TODO: Should copy all attributes defined by the interface?
  584. if (isIdentity)
  585. {
  586. var keyAttribute = typeof(KeyAttribute);
  587. var myConstructorInfo = keyAttribute.GetConstructor(new Type[] { });
  588. var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, new object[] { });
  589. property.SetCustomAttribute(attributeBuilder);
  590. }
  591. property.SetGetMethod(currGetPropMthdBldr);
  592. property.SetSetMethod(currSetPropMthdBldr);
  593. var getMethod = typeof(T).GetMethod("get_" + propertyName);
  594. var setMethod = typeof(T).GetMethod("set_" + propertyName);
  595. typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod);
  596. typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod);
  597. }
  598. }
  599. }
  600. /// <summary>
  601. /// Defines the name of a table to use in Dapper.Contrib commands.
  602. /// </summary>
  603. [AttributeUsage(AttributeTargets.Class)]
  604. public class TableAttribute : Attribute
  605. {
  606. /// <summary>
  607. /// Creates a table mapping to a specific name for Dapper.Contrib commands
  608. /// </summary>
  609. /// <param name="tableName">The name of this table in the database.</param>
  610. public TableAttribute(string tableName)
  611. {
  612. Name = tableName;
  613. }
  614. /// <summary>
  615. /// The name of the table in the database
  616. /// </summary>
  617. public string Name { get; set; }
  618. }
  619. /// <summary>
  620. /// Specifies that this field is a primary key in the database
  621. /// </summary>
  622. [AttributeUsage(AttributeTargets.Property)]
  623. public class KeyAttribute : Attribute
  624. {
  625. }
  626. /// <summary>
  627. /// Specifies that this field is a explicitly set primary key in the database
  628. /// </summary>
  629. [AttributeUsage(AttributeTargets.Property)]
  630. public class ExplicitKeyAttribute : Attribute
  631. {
  632. }
  633. /// <summary>
  634. /// Specifies whether a field is writable in the database.
  635. /// </summary>
  636. [AttributeUsage(AttributeTargets.Property)]
  637. public class WriteAttribute : Attribute
  638. {
  639. /// <summary>
  640. /// Specifies whether a field is writable in the database.
  641. /// </summary>
  642. /// <param name="write">Whether a field is writable in the database.</param>
  643. public WriteAttribute(bool write)
  644. {
  645. Write = write;
  646. }
  647. /// <summary>
  648. /// Whether a field is writable in the database.
  649. /// </summary>
  650. public bool Write { get; }
  651. }
  652. /// <summary>
  653. /// Specifies that this is a computed column.
  654. /// </summary>
  655. [AttributeUsage(AttributeTargets.Property)]
  656. public class ComputedAttribute : Attribute
  657. {
  658. }
  659. }
  660. /// <summary>
  661. /// The interface for all Dapper.Contrib database operations
  662. /// Implementing this is each provider's model.
  663. /// </summary>
  664. public partial interface ISqlAdapter
  665. {
  666. /// <summary>
  667. /// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
  668. /// </summary>
  669. /// <param name="connection">The connection to use.</param>
  670. /// <param name="transaction">The transaction to use.</param>
  671. /// <param name="commandTimeout">The command timeout to use.</param>
  672. /// <param name="tableName">The table to insert into.</param>
  673. /// <param name="columnList">The columns to set with this insert.</param>
  674. /// <param name="parameterList">The parameters to set for this insert.</param>
  675. /// <param name="keyProperties">The key columns in this table.</param>
  676. /// <param name="entityToInsert">The entity to insert.</param>
  677. /// <returns>The Id of the row created.</returns>
  678. int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert);
  679. /// <summary>
  680. /// Adds the name of a column.
  681. /// </summary>
  682. /// <param name="sb">The string builder to append to.</param>
  683. /// <param name="columnName">The column name.</param>
  684. void AppendColumnName(StringBuilder sb, string columnName);
  685. /// <summary>
  686. /// Adds a column equality to a parameter.
  687. /// </summary>
  688. /// <param name="sb">The string builder to append to.</param>
  689. /// <param name="columnName">The column name.</param>
  690. void AppendColumnNameEqualsValue(StringBuilder sb, string columnName);
  691. }
  692. /// <summary>
  693. /// The SQL Server database adapter.
  694. /// </summary>
  695. public partial class SqlServerAdapter : ISqlAdapter
  696. {
  697. /// <summary>
  698. /// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
  699. /// </summary>
  700. /// <param name="connection">The connection to use.</param>
  701. /// <param name="transaction">The transaction to use.</param>
  702. /// <param name="commandTimeout">The command timeout to use.</param>
  703. /// <param name="tableName">The table to insert into.</param>
  704. /// <param name="columnList">The columns to set with this insert.</param>
  705. /// <param name="parameterList">The parameters to set for this insert.</param>
  706. /// <param name="keyProperties">The key columns in this table.</param>
  707. /// <param name="entityToInsert">The entity to insert.</param>
  708. /// <returns>The Id of the row created.</returns>
  709. public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
  710. {
  711. var cmd = $"insert into {tableName} ({columnList}) values ({parameterList});select SCOPE_IDENTITY() id";
  712. var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout);
  713. var first = multi.Read().FirstOrDefault();
  714. if (first == null || first.id == null) return 0;
  715. var id = (int)first.id;
  716. var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
  717. if (propertyInfos.Length == 0) return id;
  718. var idProperty = propertyInfos[0];
  719. idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null);
  720. return id;
  721. }
  722. /// <summary>
  723. /// Adds the name of a column.
  724. /// </summary>
  725. /// <param name="sb">The string builder to append to.</param>
  726. /// <param name="columnName">The column name.</param>
  727. public void AppendColumnName(StringBuilder sb, string columnName)
  728. {
  729. sb.AppendFormat("[{0}]", columnName);
  730. }
  731. /// <summary>
  732. /// Adds a column equality to a parameter.
  733. /// </summary>
  734. /// <param name="sb">The string builder to append to.</param>
  735. /// <param name="columnName">The column name.</param>
  736. public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
  737. {
  738. sb.AppendFormat("[{0}] = @{1}", columnName, columnName);
  739. }
  740. }
  741. /// <summary>
  742. /// The SQL Server Compact Edition database adapter.
  743. /// </summary>
  744. public partial class SqlCeServerAdapter : ISqlAdapter
  745. {
  746. /// <summary>
  747. /// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
  748. /// </summary>
  749. /// <param name="connection">The connection to use.</param>
  750. /// <param name="transaction">The transaction to use.</param>
  751. /// <param name="commandTimeout">The command timeout to use.</param>
  752. /// <param name="tableName">The table to insert into.</param>
  753. /// <param name="columnList">The columns to set with this insert.</param>
  754. /// <param name="parameterList">The parameters to set for this insert.</param>
  755. /// <param name="keyProperties">The key columns in this table.</param>
  756. /// <param name="entityToInsert">The entity to insert.</param>
  757. /// <returns>The Id of the row created.</returns>
  758. public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
  759. {
  760. var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})";
  761. connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
  762. var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ToList();
  763. if (r[0].id == null) return 0;
  764. var id = (int)r[0].id;
  765. var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
  766. if (propertyInfos.Length == 0) return id;
  767. var idProperty = propertyInfos[0];
  768. idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null);
  769. return id;
  770. }
  771. /// <summary>
  772. /// Adds the name of a column.
  773. /// </summary>
  774. /// <param name="sb">The string builder to append to.</param>
  775. /// <param name="columnName">The column name.</param>
  776. public void AppendColumnName(StringBuilder sb, string columnName)
  777. {
  778. sb.AppendFormat("[{0}]", columnName);
  779. }
  780. /// <summary>
  781. /// Adds a column equality to a parameter.
  782. /// </summary>
  783. /// <param name="sb">The string builder to append to.</param>
  784. /// <param name="columnName">The column name.</param>
  785. public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
  786. {
  787. sb.AppendFormat("[{0}] = @{1}", columnName, columnName);
  788. }
  789. }
  790. /// <summary>
  791. /// The MySQL database adapter.
  792. /// </summary>
  793. public partial class MySqlAdapter : ISqlAdapter
  794. {
  795. /// <summary>
  796. /// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
  797. /// </summary>
  798. /// <param name="connection">The connection to use.</param>
  799. /// <param name="transaction">The transaction to use.</param>
  800. /// <param name="commandTimeout">The command timeout to use.</param>
  801. /// <param name="tableName">The table to insert into.</param>
  802. /// <param name="columnList">The columns to set with this insert.</param>
  803. /// <param name="parameterList">The parameters to set for this insert.</param>
  804. /// <param name="keyProperties">The key columns in this table.</param>
  805. /// <param name="entityToInsert">The entity to insert.</param>
  806. /// <returns>The Id of the row created.</returns>
  807. public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
  808. {
  809. var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})";
  810. connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
  811. var r = connection.Query("Select LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout);
  812. var id = r.First().id;
  813. if (id == null) return 0;
  814. var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
  815. if (propertyInfos.Length == 0) return Convert.ToInt32(id);
  816. var idp = propertyInfos[0];
  817. idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null);
  818. return Convert.ToInt32(id);
  819. }
  820. /// <summary>
  821. /// Adds the name of a column.
  822. /// </summary>
  823. /// <param name="sb">The string builder to append to.</param>
  824. /// <param name="columnName">The column name.</param>
  825. public void AppendColumnName(StringBuilder sb, string columnName)
  826. {
  827. sb.AppendFormat("`{0}`", columnName);
  828. }
  829. /// <summary>
  830. /// Adds a column equality to a parameter.
  831. /// </summary>
  832. /// <param name="sb">The string builder to append to.</param>
  833. /// <param name="columnName">The column name.</param>
  834. public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
  835. {
  836. sb.AppendFormat("`{0}` = @{1}", columnName, columnName);
  837. }
  838. }
  839. /// <summary>
  840. /// The Postgres database adapter.
  841. /// </summary>
  842. public partial class PostgresAdapter : ISqlAdapter
  843. {
  844. /// <summary>
  845. /// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
  846. /// </summary>
  847. /// <param name="connection">The connection to use.</param>
  848. /// <param name="transaction">The transaction to use.</param>
  849. /// <param name="commandTimeout">The command timeout to use.</param>
  850. /// <param name="tableName">The table to insert into.</param>
  851. /// <param name="columnList">The columns to set with this insert.</param>
  852. /// <param name="parameterList">The parameters to set for this insert.</param>
  853. /// <param name="keyProperties">The key columns in this table.</param>
  854. /// <param name="entityToInsert">The entity to insert.</param>
  855. /// <returns>The Id of the row created.</returns>
  856. public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
  857. {
  858. var sb = new StringBuilder();
  859. sb.AppendFormat("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList);
  860. // If no primary key then safe to assume a join table with not too much data to return
  861. var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
  862. if (propertyInfos.Length == 0)
  863. {
  864. sb.Append(" RETURNING *");
  865. }
  866. else
  867. {
  868. sb.Append(" RETURNING ");
  869. var first = true;
  870. foreach (var property in propertyInfos)
  871. {
  872. if (!first)
  873. sb.Append(", ");
  874. first = false;
  875. sb.Append(property.Name);
  876. }
  877. }
  878. var results = connection.Query(sb.ToString(), entityToInsert, transaction, commandTimeout: commandTimeout).ToList();
  879. // Return the key by assigning the corresponding property in the object - by product is that it supports compound primary keys
  880. var id = 0;
  881. foreach (var p in propertyInfos)
  882. {
  883. var value = ((IDictionary<string, object>)results[0])[p.Name.ToLower()];
  884. p.SetValue(entityToInsert, value, null);
  885. if (id == 0)
  886. id = Convert.ToInt32(value);
  887. }
  888. return id;
  889. }
  890. /// <summary>
  891. /// Adds the name of a column.
  892. /// </summary>
  893. /// <param name="sb">The string builder to append to.</param>
  894. /// <param name="columnName">The column name.</param>
  895. public void AppendColumnName(StringBuilder sb, string columnName)
  896. {
  897. sb.AppendFormat("\"{0}\"", columnName);
  898. }
  899. /// <summary>
  900. /// Adds a column equality to a parameter.
  901. /// </summary>
  902. /// <param name="sb">The string builder to append to.</param>
  903. /// <param name="columnName">The column name.</param>
  904. public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName)
  905. {
  906. sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName);
  907. }
  908. }
  909. /// <summary>
  910. /// The SQLite database adapter.
  911. /// </summary>
  912. public partial class SQLiteAdapter : ISqlAdapter
  913. {
  914. /// <summary>
  915. /// Inserts <paramref name="entityToInsert"/> into the database, returning the Id of the row created.
  916. /// </summary>
  917. /// <param name="connection">The connection to use.</param>
  918. /// <param name="transaction">The transaction to use.</param>
  919. /// <param name="commandTimeout">The command timeout to use.</param>
  920. /// <param name="tableName">The table to insert into.</param>
  921. /// <param name="columnList">The columns to set with this insert.</param>
  922. /// <param name="parameterList">The parameters to set for this insert.</param>
  923. /// <param name="keyProperties">The key columns in this table.</param>
  924. /// <param name="entityToInsert">The entity to insert.</param>
  925. /// <returns>The Id of the row created.</returns>
  926. public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable<PropertyInfo> keyProperties, object entityToInsert)
  927. {
  928. var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id";
  929. var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout);
  930. var id = (int)multi.Read().First().id;
  931. var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray();
  932. if (propertyInfos.Length == 0) return id;
  933. var idProperty = propertyInfos[0];
  934. idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null);
  935. return id;
  936. }
  937. /// <summary>
  938. /// Adds the name of a column.
  939. /// </…