PageRenderTime 25ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 1ms

/PetaPoco/PetaPoco.cs

https://github.com/stangelandcl/PetaPoco
C# | 3170 lines | 2583 code | 399 blank | 188 comment | 417 complexity | 696b61070b94f81604e01f77bf97bc69 MD5 | raw file
Possible License(s): Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. /* PetaPoco v4.0.3.12 - A Tiny ORMish thing for your POCO's.
  2. * Copyright 2011-2012 Topten Software. All Rights Reserved.
  3. *
  4. * Apache License 2.0 - http://www.toptensoftware.com/petapoco/license
  5. *
  6. * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for
  7. * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice
  8. * and Adam Schroder (@schotime) for lots of suggestions, improvements and Oracle support
  9. */
  10. //#define PETAPOCO_NO_DYNAMIC //in your project settings on .NET 3.5
  11. using System;
  12. using System.Collections;
  13. using System.Collections.Generic;
  14. using System.Linq;
  15. using System.Text;
  16. using System.Configuration;
  17. using System.Data.Common;
  18. using System.Data;
  19. using System.Text.RegularExpressions;
  20. using System.Reflection;
  21. using System.Reflection.Emit;
  22. using System.Linq.Expressions;
  23. using System.Threading;
  24. namespace PetaPoco
  25. {
  26. // Poco's marked [Explicit] require all column properties to be marked
  27. [AttributeUsage(AttributeTargets.Class)]
  28. public class ExplicitColumnsAttribute : Attribute
  29. {
  30. }
  31. // For non-explicit pocos, causes a property to be ignored
  32. [AttributeUsage(AttributeTargets.Property)]
  33. public class IgnoreAttribute : Attribute
  34. {
  35. }
  36. // For explicit pocos, marks property as a column
  37. [AttributeUsage(AttributeTargets.Property)]
  38. public class ColumnAttribute : Attribute
  39. {
  40. public ColumnAttribute() { }
  41. public ColumnAttribute(string name) { Name = name; }
  42. public string Name { get; set; }
  43. }
  44. // For explicit pocos, marks property as a column
  45. [AttributeUsage(AttributeTargets.Property)]
  46. public class ResultColumnAttribute : ColumnAttribute
  47. {
  48. public ResultColumnAttribute() { }
  49. public ResultColumnAttribute(string name) : base(name) { }
  50. }
  51. // Specify the table name of a poco
  52. [AttributeUsage(AttributeTargets.Class)]
  53. public class TableNameAttribute : Attribute
  54. {
  55. public TableNameAttribute(string tableName)
  56. {
  57. Value = tableName;
  58. }
  59. public string Value { get; private set; }
  60. }
  61. // Specific the primary key of a poco class (and optional sequence name for Oracle)
  62. [AttributeUsage(AttributeTargets.Class)]
  63. public class PrimaryKeyAttribute : Attribute
  64. {
  65. public PrimaryKeyAttribute(string primaryKey)
  66. {
  67. Value = primaryKey;
  68. autoIncrement = true;
  69. }
  70. public string Value { get; private set; }
  71. public string sequenceName { get; set; }
  72. public bool autoIncrement { get; set; }
  73. }
  74. [AttributeUsage(AttributeTargets.Property)]
  75. public class AutoJoinAttribute : Attribute
  76. {
  77. public AutoJoinAttribute() { }
  78. }
  79. [AttributeUsage(AttributeTargets.Property)]
  80. public class VersionColumnAttribute : ColumnAttribute
  81. {
  82. public VersionColumnAttribute() {}
  83. public VersionColumnAttribute(string name) : base(name) { }
  84. }
  85. // Results from paged request
  86. public class Page<T>
  87. {
  88. public long CurrentPage { get; set; }
  89. public long TotalPages { get; set; }
  90. public long TotalItems { get; set; }
  91. public long ItemsPerPage { get; set; }
  92. public List<T> Items { get; set; }
  93. public object Context { get; set; }
  94. }
  95. // Pass as parameter value to force to DBType.AnsiString
  96. public class AnsiString
  97. {
  98. public AnsiString(string str)
  99. {
  100. Value = str;
  101. }
  102. public string Value { get; private set; }
  103. }
  104. // Used by IMapper to override table bindings for an object
  105. public class TableInfo
  106. {
  107. public string TableName { get; set; }
  108. public string PrimaryKey { get; set; }
  109. public bool AutoIncrement { get; set; }
  110. public string SequenceName { get; set; }
  111. }
  112. // Optionally provide an implementation of this to Database.Mapper
  113. public interface IMapper
  114. {
  115. void GetTableInfo(Type t, TableInfo ti);
  116. bool MapPropertyToColumn(PropertyInfo pi, ref string columnName, ref bool resultColumn);
  117. Func<object, object> GetFromDbConverter(PropertyInfo pi, Type SourceType);
  118. Func<object, object> GetToDbConverter(Type SourceType);
  119. }
  120. // This will be merged with IMapper in the next major version
  121. public interface IMapper2 : IMapper
  122. {
  123. Func<object, object> GetFromDbConverter(Type DestType, Type SourceType);
  124. }
  125. public abstract class DefaultMapper : IMapper2
  126. {
  127. public virtual void GetTableInfo(Type t, TableInfo ti) { }
  128. public virtual bool MapPropertyToColumn(PropertyInfo pi, ref string columnName, ref bool resultColumn)
  129. {
  130. return true;
  131. }
  132. public virtual Func<object, object> GetFromDbConverter(PropertyInfo pi, Type SourceType)
  133. {
  134. return GetFromDbConverter(pi.PropertyType, SourceType);
  135. }
  136. public virtual Func<object, object> GetToDbConverter(Type SourceType)
  137. {
  138. return null;
  139. }
  140. public virtual Func<object, object> GetFromDbConverter(Type DestType, Type SourceType)
  141. {
  142. return null;
  143. }
  144. }
  145. public interface IDatabaseQuery
  146. {
  147. void OpenSharedConnection();
  148. void CloseSharedConnection();
  149. int Execute(string sql, params object[] args);
  150. int Execute(Sql sql);
  151. T ExecuteScalar<T>(string sql, params object[] args);
  152. T ExecuteScalar<T>(Sql sql);
  153. List<T> Fetch<T>();
  154. List<T> Fetch<T>(string sql, params object[] args);
  155. List<T> Fetch<T>(Sql sql);
  156. List<T> Fetch<T>(long page, long itemsPerPage, string sql, params object[] args);
  157. List<T> Fetch<T>(long page, long itemsPerPage, Sql sql);
  158. Page<T> Page<T>(long page, long itemsPerPage, string sql, params object[] args);
  159. Page<T> Page<T>(long page, long itemsPerPage, Sql sql);
  160. List<T> SkipTake<T>(long skip, long take, string sql, params object[] args);
  161. List<T> SkipTake<T>(long skip, long take, Sql sql);
  162. List<TRet> Fetch<T1, T2, TRet>(Func<T1, T2, TRet> cb, string sql, params object[] args);
  163. List<TRet> Fetch<T1, T2, T3, TRet>(Func<T1, T2, T3, TRet> cb, string sql, params object[] args);
  164. List<TRet> Fetch<T1, T2, T3, T4, TRet>(Func<T1, T2, T3, T4, TRet> cb, string sql, params object[] args);
  165. IEnumerable<TRet> Query<T1, T2, TRet>(Func<T1, T2, TRet> cb, string sql, params object[] args);
  166. IEnumerable<TRet> Query<T1, T2, T3, TRet>(Func<T1, T2, T3, TRet> cb, string sql, params object[] args);
  167. IEnumerable<TRet> Query<T1, T2, T3, T4, TRet>(Func<T1, T2, T3, T4, TRet> cb, string sql, params object[] args);
  168. List<TRet> Fetch<T1, T2, TRet>(Func<T1, T2, TRet> cb, Sql sql);
  169. List<TRet> Fetch<T1, T2, T3, TRet>(Func<T1, T2, T3, TRet> cb, Sql sql);
  170. List<TRet> Fetch<T1, T2, T3, T4, TRet>(Func<T1, T2, T3, T4, TRet> cb, Sql sql);
  171. IEnumerable<TRet> Query<T1, T2, TRet>(Func<T1, T2, TRet> cb, Sql sql);
  172. IEnumerable<TRet> Query<T1, T2, T3, TRet>(Func<T1, T2, T3, TRet> cb, Sql sql);
  173. IEnumerable<TRet> Query<T1, T2, T3, T4, TRet>(Func<T1, T2, T3, T4, TRet> cb, Sql sql);
  174. List<T1> Fetch<T1, T2>(string sql, params object[] args);
  175. List<T1> Fetch<T1, T2, T3>(string sql, params object[] args);
  176. List<T1> Fetch<T1, T2, T3, T4>(string sql, params object[] args);
  177. IEnumerable<T1> Query<T1, T2>(string sql, params object[] args);
  178. IEnumerable<T1> Query<T1, T2, T3>(string sql, params object[] args);
  179. IEnumerable<T1> Query<T1, T2, T3, T4>(string sql, params object[] args);
  180. IEnumerable<TRet> Query<TRet>(Type[] types, object cb, Sql sql);
  181. List<T1> Fetch<T1, T2>(Sql sql);
  182. List<T1> Fetch<T1, T2, T3>(Sql sql);
  183. List<T1> Fetch<T1, T2, T3, T4>(Sql sql);
  184. IEnumerable<T1> Query<T1, T2>(Sql sql);
  185. IEnumerable<T1> Query<T1, T2, T3>(Sql sql);
  186. IEnumerable<T1> Query<T1, T2, T3, T4>(Sql sql);
  187. IEnumerable<T> Query<T>(string sql, params object[] args);
  188. IEnumerable<T> Query<T>(Sql sql);
  189. T SingleById<T>(object primaryKey);
  190. T SingleOrDefaultById<T>(object primaryKey);
  191. T Single<T>(string sql, params object[] args);
  192. T SingleInto<T>(T instance, string sql, params object[] args);
  193. T SingleOrDefault<T>(string sql, params object[] args);
  194. T SingleOrDefaultInto<T>(T instance, string sql, params object[] args);
  195. T First<T>(string sql, params object[] args);
  196. T FirstInto<T>(T instance, string sql, params object[] args);
  197. T FirstOrDefault<T>(string sql, params object[] args);
  198. T FirstOrDefaultInto<T>(T instance, string sql, params object[] args);
  199. T Single<T>(Sql sql);
  200. T SingleInto<T>(T instance, Sql sql);
  201. T SingleOrDefault<T>(Sql sql);
  202. T SingleOrDefaultInto<T>(T instance, Sql sql);
  203. T First<T>(Sql sql);
  204. T FirstInto<T>(T instance, Sql sql);
  205. T FirstOrDefault<T>(Sql sql);
  206. T FirstOrDefaultInto<T>(T instance, Sql sql);
  207. Dictionary<TKey, TValue> Dictionary<TKey, TValue>(Sql Sql);
  208. Dictionary<TKey, TValue> Dictionary<TKey, TValue>(string sql, params object[] args);
  209. bool Exists<T>(object primaryKey);
  210. int OneTimeCommandTimeout { get; set; }
  211. TRet FetchMultiple<T1, T2, TRet>(Func<List<T1>, List<T2>, TRet> cb, string sql, params object[] args);
  212. TRet FetchMultiple<T1, T2, T3, TRet>(Func<List<T1>, List<T2>, List<T3>, TRet> cb, string sql, params object[] args);
  213. TRet FetchMultiple<T1, T2, T3, T4, TRet>(Func<List<T1>, List<T2>, List<T3>, List<T4>, TRet> cb, string sql, params object[] args);
  214. TRet FetchMultiple<T1, T2, TRet>(Func<List<T1>, List<T2>, TRet> cb, Sql sql);
  215. TRet FetchMultiple<T1, T2, T3, TRet>(Func<List<T1>, List<T2>, List<T3>, TRet> cb, Sql sql);
  216. TRet FetchMultiple<T1, T2, T3, T4, TRet>(Func<List<T1>, List<T2>, List<T3>, List<T4>, TRet> cb, Sql sql);
  217. #if PETAPOCO_NO_DYNAMIC
  218. Database.Tuple<List<T1>, List<T2>> FetchMultiple<T1, T2>(string sql, params object[] args);
  219. Database.Tuple<List<T1>, List<T2>, List<T3>> FetchMultiple<T1, T2, T3>(string sql, params object[] args);
  220. Database.Tuple<List<T1>, List<T2>, List<T3>, List<T4>> FetchMultiple<T1, T2, T3, T4>(string sql, params object[] args);
  221. Database.Tuple<List<T1>, List<T2>> FetchMultiple <T1, T2>(Sql sql);
  222. Database.Tuple<List<T1>, List<T2>, List<T3>> FetchMultiple <T1, T2, T3>(Sql sql);
  223. Database.Tuple<List<T1>, List<T2>, List<T3>, List<T4>> FetchMultiple <T1, T2, T3, T4>(Sql sql);
  224. #else
  225. Tuple<List<T1>, List<T2>> FetchMultiple<T1, T2>(string sql, params object[] args);
  226. Tuple<List<T1>, List<T2>, List<T3>> FetchMultiple<T1, T2, T3>(string sql, params object[] args);
  227. Tuple<List<T1>, List<T2>, List<T3>, List<T4>> FetchMultiple<T1, T2, T3, T4>(string sql, params object[] args);
  228. Tuple<List<T1>, List<T2>> FetchMultiple<T1, T2>(Sql sql);
  229. Tuple<List<T1>, List<T2>, List<T3>> FetchMultiple<T1, T2, T3>(Sql sql);
  230. Tuple<List<T1>, List<T2>, List<T3>, List<T4>> FetchMultiple<T1, T2, T3, T4>(Sql sql);
  231. #endif
  232. }
  233. public interface IDatabase : IDatabaseQuery
  234. {
  235. void Dispose();
  236. IDbConnection Connection { get; }
  237. IDbTransaction Transaction { get; }
  238. IDataParameter CreateParameter();
  239. Transaction GetTransaction();
  240. Transaction GetTransaction(IsolationLevel? isolationLevel);
  241. void BeginTransaction();
  242. void BeginTransaction(IsolationLevel? isolationLevel);
  243. void AbortTransaction();
  244. void CompleteTransaction();
  245. object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco);
  246. object Insert(string tableName, string primaryKeyName, object poco);
  247. object Insert(object poco);
  248. int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue);
  249. int Update(string tableName, string primaryKeyName, object poco);
  250. int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable<string> columns);
  251. int Update(string tableName, string primaryKeyName, object poco, IEnumerable<string> columns);
  252. int Update(object poco, IEnumerable<string> columns);
  253. int Update(object poco, object primaryKeyValue, IEnumerable<string> columns);
  254. int Update(object poco);
  255. int Update(object poco, object primaryKeyValue);
  256. int Update<T>(string sql, params object[] args);
  257. int Update<T>(Sql sql);
  258. int Delete(string tableName, string primaryKeyName, object poco);
  259. int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue);
  260. int Delete(object poco);
  261. int Delete<T>(string sql, params object[] args);
  262. int Delete<T>(Sql sql);
  263. int Delete<T>(object pocoOrPrimaryKey);
  264. void Save(string tableName, string primaryKeyName, object poco);
  265. void Save(object poco);
  266. }
  267. // Database class ... this is where most of the action happens
  268. public class Database : IDisposable, IDatabase
  269. {
  270. public const string MsSqlClientProvider = "System.Data.SqlClient";
  271. public Database(IDbConnection connection) : this(connection, DBType.NotSet) {}
  272. public Database(IDbConnection connection, DBType dbType)
  273. {
  274. _sharedConnection = connection;
  275. _connectionString = connection.ConnectionString;
  276. _sharedConnectionDepth = 2; // Prevent closing external connection
  277. _dbType = dbType;
  278. CommonConstruct();
  279. }
  280. public Database(string connectionString, string providerName)
  281. {
  282. _connectionString = connectionString;
  283. _providerName = providerName;
  284. CommonConstruct();
  285. }
  286. public Database(string connectionString, DbProviderFactory provider)
  287. {
  288. _connectionString = connectionString;
  289. _factory = provider;
  290. CommonConstruct();
  291. }
  292. public Database(string connectionStringName)
  293. {
  294. // Use first?
  295. if (connectionStringName == "")
  296. connectionStringName = ConfigurationManager.ConnectionStrings[0].Name;
  297. // Work out connection string and provider name
  298. var providerName = "System.Data.SqlClient";
  299. if (ConfigurationManager.ConnectionStrings[connectionStringName] != null)
  300. {
  301. if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName))
  302. providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName;
  303. }
  304. else
  305. {
  306. throw new InvalidOperationException("Can't find a connection string with the name '" + connectionStringName + "'");
  307. }
  308. // Store factory and connection string
  309. _connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
  310. _providerName = providerName;
  311. CommonConstruct();
  312. }
  313. public enum DBType
  314. {
  315. NotSet,
  316. SqlServer,
  317. SqlServerCE,
  318. MySql,
  319. PostgreSQL,
  320. Oracle,
  321. SQLite
  322. }
  323. protected DBType _dbType = DBType.NotSet;
  324. // Common initialization
  325. private void CommonConstruct()
  326. {
  327. _transactionDepth = 0;
  328. ForceDateTimesToUtc = true;
  329. EnableAutoSelect = true;
  330. if (_providerName != null)
  331. _factory = DbProviderFactories.GetFactory(_providerName);
  332. if (_dbType == DBType.NotSet)
  333. {
  334. _dbType = DBType.SqlServer;
  335. string dbtype = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name;
  336. if (dbtype.StartsWith("MySql"))
  337. _dbType = DBType.MySql;
  338. else if (dbtype.StartsWith("SqlCe"))
  339. _dbType = DBType.SqlServerCE;
  340. else if (dbtype.StartsWith("Npgsql"))
  341. _dbType = DBType.PostgreSQL;
  342. else if (dbtype.StartsWith("Oracle"))
  343. _dbType = DBType.Oracle;
  344. else if (dbtype.StartsWith("SQLite"))
  345. _dbType = DBType.SQLite;
  346. }
  347. if (_dbType == DBType.MySql && _connectionString != null && _connectionString.IndexOf("Allow User Variables=true") >= 0)
  348. _paramPrefix = "?";
  349. if (_dbType == DBType.Oracle)
  350. _paramPrefix = ":";
  351. }
  352. // Automatically close one open shared connection
  353. public void Dispose()
  354. {
  355. // Automatically close one open connection reference
  356. // (Works with KeepConnectionAlive and manually opening a shared connection)
  357. CloseSharedConnection();
  358. }
  359. // Set to true to keep the first opened connection alive until this object is disposed
  360. public bool KeepConnectionAlive { get; set; }
  361. // Open a connection (can be nested)
  362. public void OpenSharedConnection()
  363. {
  364. if (_sharedConnectionDepth == 0)
  365. {
  366. _sharedConnection = _factory.CreateConnection();
  367. _sharedConnection.ConnectionString = _connectionString;
  368. if (_sharedConnection.State == ConnectionState.Broken)
  369. _sharedConnection.Close();
  370. if (_sharedConnection.State == ConnectionState.Closed)
  371. _sharedConnection.Open();
  372. _sharedConnection = OnConnectionOpened(_sharedConnection);
  373. if (KeepConnectionAlive)
  374. _sharedConnectionDepth++; // Make sure you call Dispose
  375. }
  376. _sharedConnectionDepth++;
  377. }
  378. // Close a previously opened connection
  379. public void CloseSharedConnection()
  380. {
  381. if (_sharedConnectionDepth > 0)
  382. {
  383. _sharedConnectionDepth--;
  384. if (_sharedConnectionDepth == 0)
  385. {
  386. OnConnectionClosing(_sharedConnection);
  387. _sharedConnection.Dispose();
  388. _sharedConnection = null;
  389. }
  390. }
  391. }
  392. public VersionExceptionHandling VersionException
  393. {
  394. get { return _versionException; }
  395. set { _versionException = value; }
  396. }
  397. // Access to our shared connection
  398. public IDbConnection Connection
  399. {
  400. get { return _sharedConnection; }
  401. }
  402. public IDbTransaction Transaction
  403. {
  404. get { return _transaction; }
  405. }
  406. public IDataParameter CreateParameter()
  407. {
  408. using (var conn = _sharedConnection ?? _factory.CreateConnection())
  409. using (var comm = conn.CreateCommand())
  410. return comm.CreateParameter();
  411. }
  412. // Helper to create a transaction scope
  413. public Transaction GetTransaction()
  414. {
  415. return GetTransaction(null);
  416. }
  417. public Transaction GetTransaction(IsolationLevel? isolationLevel)
  418. {
  419. return new Transaction(this, isolationLevel);
  420. }
  421. // Use by derived repo generated by T4 templates
  422. public virtual void OnBeginTransaction() { }
  423. public virtual void OnEndTransaction() { }
  424. public void BeginTransaction()
  425. {
  426. BeginTransaction(null);
  427. }
  428. // Start a new transaction, can be nested, every call must be
  429. // matched by a call to AbortTransaction or CompleteTransaction
  430. // Use `using (var scope=db.Transaction) { scope.Complete(); }` to ensure correct semantics
  431. public void BeginTransaction(IsolationLevel? isolationLevel)
  432. {
  433. _transactionDepth++;
  434. if (_transactionDepth == 1)
  435. {
  436. OpenSharedConnection();
  437. _transaction = isolationLevel == null ? _sharedConnection.BeginTransaction() : _sharedConnection.BeginTransaction(isolationLevel.Value);
  438. _transactionCancelled = false;
  439. OnBeginTransaction();
  440. }
  441. }
  442. // Internal helper to cleanup transaction stuff
  443. void CleanupTransaction()
  444. {
  445. OnEndTransaction();
  446. if (_transactionCancelled)
  447. _transaction.Rollback();
  448. else
  449. _transaction.Commit();
  450. _transaction.Dispose();
  451. _transaction = null;
  452. CloseSharedConnection();
  453. }
  454. // Abort the entire outer most transaction scope
  455. public void AbortTransaction()
  456. {
  457. _transactionCancelled = true;
  458. if ((--_transactionDepth) == 0)
  459. CleanupTransaction();
  460. }
  461. // Complete the transaction
  462. public void CompleteTransaction()
  463. {
  464. if ((--_transactionDepth) == 0)
  465. CleanupTransaction();
  466. }
  467. // Helper to handle named parameters from object properties
  468. static Regex rxParams = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
  469. public static string ProcessParams(string _sql, object[] args_src, List<object> args_dest)
  470. {
  471. return rxParams.Replace(_sql, m =>
  472. {
  473. string param = m.Value.Substring(1);
  474. object arg_val;
  475. int paramIndex;
  476. if (int.TryParse(param, out paramIndex))
  477. {
  478. // Numbered parameter
  479. if (paramIndex < 0 || paramIndex >= args_src.Length)
  480. throw new ArgumentOutOfRangeException(string.Format("Parameter '@{0}' specified but only {1} parameters supplied (in `{2}`)", paramIndex, args_src.Length, _sql));
  481. arg_val = args_src[paramIndex];
  482. }
  483. else
  484. {
  485. // Look for a property on one of the arguments with this name
  486. bool found = false;
  487. arg_val = null;
  488. foreach (var o in args_src)
  489. {
  490. var pi = o.GetType().GetProperty(param);
  491. if (pi != null)
  492. {
  493. arg_val = pi.GetValue(o, null);
  494. found = true;
  495. break;
  496. }
  497. }
  498. if (!found)
  499. throw new ArgumentException(string.Format("Parameter '@{0}' specified but none of the passed arguments have a property with this name (in '{1}')", param, _sql));
  500. }
  501. // Expand collections to parameter lists
  502. if ((arg_val as System.Collections.IEnumerable) != null &&
  503. (arg_val as string) == null &&
  504. (arg_val as byte[]) == null)
  505. {
  506. var sb = new StringBuilder();
  507. foreach (var i in arg_val as System.Collections.IEnumerable)
  508. {
  509. var indexOfExistingValue = args_dest.IndexOf(i);
  510. if (indexOfExistingValue >= 0)
  511. {
  512. sb.Append((sb.Length == 0 ? "@" : ",@") + indexOfExistingValue);
  513. }
  514. else
  515. {
  516. sb.Append((sb.Length == 0 ? "@" : ",@") + args_dest.Count);
  517. args_dest.Add(i);
  518. }
  519. }
  520. if (sb.Length == 0)
  521. {
  522. sb.AppendFormat("select 1 /*peta_dual*/ where 1 = 0");
  523. }
  524. return sb.ToString();
  525. }
  526. else
  527. {
  528. var indexOfExistingValue = args_dest.IndexOf(arg_val);
  529. if (indexOfExistingValue >= 0)
  530. return "@" + indexOfExistingValue;
  531. args_dest.Add(arg_val);
  532. return "@" + (args_dest.Count - 1).ToString();
  533. }
  534. }
  535. );
  536. }
  537. // Add a parameter to a DB command
  538. void AddParam(IDbCommand cmd, object item, string ParameterPrefix)
  539. {
  540. // Convert value to from poco type to db type
  541. if (Database.Mapper != null && item!=null)
  542. {
  543. var fn = Database.Mapper.GetToDbConverter(item.GetType());
  544. if (fn!=null)
  545. item = fn(item);
  546. }
  547. // Support passed in parameters
  548. var idbParam = item as IDbDataParameter;
  549. if (idbParam != null)
  550. {
  551. idbParam.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count);
  552. cmd.Parameters.Add(idbParam);
  553. return;
  554. }
  555. var p = cmd.CreateParameter();
  556. p.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count);
  557. if (item == null)
  558. {
  559. p.Value = DBNull.Value;
  560. }
  561. else
  562. {
  563. var t = item.GetType();
  564. if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int
  565. {
  566. p.Value = (int)item;
  567. }
  568. else if (t == typeof(Guid))
  569. {
  570. p.Value = item.ToString();
  571. p.DbType = DbType.String;
  572. p.Size = 40;
  573. }
  574. else if (t == typeof(string))
  575. {
  576. p.Size = Math.Max((item as string).Length + 1, 4000); // Help query plan caching by using common size
  577. p.Value = item;
  578. }
  579. else if (t == typeof(AnsiString))
  580. {
  581. // Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar
  582. p.Size = Math.Max((item as AnsiString).Value.Length + 1, 4000);
  583. p.Value = (item as AnsiString).Value;
  584. p.DbType = DbType.AnsiString;
  585. }
  586. else if (t == typeof(bool) && _dbType != DBType.PostgreSQL)
  587. {
  588. p.Value = ((bool)item) ? 1 : 0;
  589. }
  590. else if (item.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type
  591. {
  592. p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type
  593. p.Value = item;
  594. }
  595. else if (item.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type
  596. {
  597. p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type
  598. p.Value = item;
  599. }
  600. else
  601. {
  602. p.Value = item;
  603. }
  604. }
  605. cmd.Parameters.Add(p);
  606. }
  607. // Create a command
  608. static Regex rxParamsPrefix = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
  609. IDbCommand CreateCommand(IDbConnection connection, string sql, params object[] args)
  610. {
  611. // Perform parameter prefix replacements
  612. if (_paramPrefix != "@")
  613. sql = rxParamsPrefix.Replace(sql, m => _paramPrefix + m.Value.Substring(1));
  614. sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @
  615. // Create the command and add parameters
  616. IDbCommand cmd = connection.CreateCommand();
  617. cmd.Connection = connection;
  618. cmd.CommandText = sql;
  619. cmd.Transaction = _transaction;
  620. foreach (var item in args)
  621. {
  622. AddParam(cmd, item, _paramPrefix);
  623. }
  624. if (_dbType == DBType.Oracle)
  625. cmd.GetType().GetProperty("BindByName").SetValue(cmd, true, null);
  626. if (_dbType == DBType.Oracle || _dbType == DBType.MySql)
  627. cmd.CommandText = cmd.CommandText.Replace("/*peta_dual*/", "from dual");
  628. if (!String.IsNullOrEmpty(sql))
  629. DoPreExecute(cmd);
  630. return cmd;
  631. }
  632. // Override this to log/capture exceptions
  633. public virtual void OnException(Exception x)
  634. {
  635. System.Diagnostics.Debug.WriteLine(x.ToString());
  636. System.Diagnostics.Debug.WriteLine(LastCommand);
  637. }
  638. // Override this to log commands, or modify command before execution
  639. public virtual IDbConnection OnConnectionOpened(IDbConnection conn) { return conn; }
  640. public virtual void OnConnectionClosing(IDbConnection conn) { }
  641. public virtual void OnExecutingCommand(IDbCommand cmd) { }
  642. public virtual void OnExecutedCommand(IDbCommand cmd) { }
  643. // Execute a non-query command
  644. public int Execute(string sql, params object[] args)
  645. {
  646. return Execute(new Sql(sql, args));
  647. }
  648. public int Execute(Sql Sql)
  649. {
  650. var sql = Sql.SQL;
  651. var args = Sql.Arguments;
  652. try
  653. {
  654. OpenSharedConnection();
  655. try
  656. {
  657. using (var cmd = CreateCommand(_sharedConnection, sql, args))
  658. {
  659. var result = cmd.ExecuteNonQuery();
  660. OnExecutedCommand(cmd);
  661. return result;
  662. }
  663. }
  664. finally
  665. {
  666. CloseSharedConnection();
  667. }
  668. }
  669. catch (Exception x)
  670. {
  671. OnException(x);
  672. throw;
  673. }
  674. }
  675. // Execute and cast a scalar property
  676. public T ExecuteScalar<T>(string sql, params object[] args)
  677. {
  678. return ExecuteScalar<T>(new Sql(sql, args));
  679. }
  680. public T ExecuteScalar<T>(Sql Sql)
  681. {
  682. var sql = Sql.SQL;
  683. var args = Sql.Arguments;
  684. try
  685. {
  686. OpenSharedConnection();
  687. try
  688. {
  689. using (var cmd = CreateCommand(_sharedConnection, sql, args))
  690. {
  691. object val = cmd.ExecuteScalar();
  692. OnExecutedCommand(cmd);
  693. Type t = typeof(T);
  694. Type u = Nullable.GetUnderlyingType(t);
  695. if (val == null || val == DBNull.Value)
  696. return default(T);
  697. return u != null ? (T) Convert.ChangeType(val, u) : (T) Convert.ChangeType(val, t);
  698. }
  699. }
  700. finally
  701. {
  702. CloseSharedConnection();
  703. }
  704. }
  705. catch (Exception x)
  706. {
  707. OnException(x);
  708. throw;
  709. }
  710. }
  711. static Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL|EXEC)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
  712. static Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
  713. string AddSelectClause<T>(string sql)
  714. {
  715. if (sql.StartsWith(";"))
  716. return sql.Substring(1);
  717. if (!rxSelect.IsMatch(sql))
  718. {
  719. var pd = PocoData.ForType(typeof(T));
  720. var tableName = EscapeTableName(pd.TableInfo.TableName);
  721. string cols = string.Join(", ", (from c in pd.QueryColumns select EscapeSqlIdentifier(c)).ToArray());
  722. if (!rxFrom.IsMatch(sql))
  723. sql = string.Format("SELECT {0} FROM {1} {2}", cols, tableName, sql);
  724. else
  725. sql = string.Format("SELECT {0} {1}", cols, sql);
  726. }
  727. return sql;
  728. }
  729. public bool ForceDateTimesToUtc { get; set; }
  730. public bool EnableAutoSelect { get; set; }
  731. // Return a typed list of pocos
  732. public List<T> Fetch<T>(string sql, params object[] args)
  733. {
  734. return Fetch<T>(new Sql(sql, args));
  735. }
  736. public List<T> Fetch<T>(Sql sql)
  737. {
  738. return Query<T>(sql).ToList();
  739. }
  740. public List<T> Fetch<T>()
  741. {
  742. return Fetch<T>("");
  743. }
  744. static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?<!,\s+)\bFROM\b", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
  745. static Regex rxOrderBy = new Regex(@"\bORDER\s+BY\s+(?!.*?(?:\)|\s+)AS\s)(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
  746. static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
  747. public static bool SplitSqlForPaging(string sql, out string sqlCount, out string sqlSelectRemoved, out string sqlOrderBy)
  748. {
  749. sqlSelectRemoved = null;
  750. sqlCount = null;
  751. sqlOrderBy = null;
  752. // Extract the columns from "SELECT <whatever> FROM"
  753. var m = rxColumns.Match(sql);
  754. if (!m.Success)
  755. return false;
  756. // Save column list and replace with COUNT(*)
  757. Group g = m.Groups[1];
  758. sqlSelectRemoved = sql.Substring(g.Index);
  759. if (rxDistinct.IsMatch(sqlSelectRemoved))
  760. sqlCount = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length);
  761. else
  762. sqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length);
  763. // Look for an "ORDER BY <whatever>" clause
  764. m = rxOrderBy.Match(sqlCount);
  765. if (m.Success)
  766. {
  767. g = m.Groups[0];
  768. sqlOrderBy = g.ToString();
  769. sqlCount = sqlCount.Substring(0, g.Index) + sqlCount.Substring(g.Index + g.Length);
  770. }
  771. return true;
  772. }
  773. public void BuildPageQueries<T>(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage)
  774. {
  775. // Add auto select clause
  776. sql=AddSelectClause<T>(sql);
  777. // Split the SQL into the bits we need
  778. string sqlSelectRemoved, sqlOrderBy;
  779. if (!SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy))
  780. throw new Exception("Unable to parse SQL statement for paged query");
  781. if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*"))
  782. throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id");
  783. // Build the SQL for the actual final result
  784. if (_dbType == DBType.SqlServer || _dbType == DBType.Oracle)
  785. {
  786. sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, "");
  787. if (rxDistinct.IsMatch(sqlSelectRemoved))
  788. {
  789. sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner";
  790. }
  791. sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}",
  792. sqlOrderBy==null ? "ORDER BY (SELECT NULL /*peta_dual*/)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1);
  793. args = args.Concat(new object[] { skip, skip+take }).ToArray();
  794. }
  795. else if (_dbType == DBType.SqlServerCE)
  796. {
  797. sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1);
  798. args = args.Concat(new object[] { skip, take }).ToArray();
  799. }
  800. else
  801. {
  802. sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1);
  803. args = args.Concat(new object[] { take, skip }).ToArray();
  804. }
  805. }
  806. // Fetch a page
  807. public Page<T> Page<T>(long page, long itemsPerPage, string sql, params object[] args)
  808. {
  809. string sqlCount, sqlPage;
  810. BuildPageQueries<T>((page-1)*itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage);
  811. // Save the one-time command time out and use it for both queries
  812. int saveTimeout = OneTimeCommandTimeout;
  813. // Setup the paged result
  814. var result = new Page<T>();
  815. result.CurrentPage = page;
  816. result.ItemsPerPage = itemsPerPage;
  817. result.TotalItems = ExecuteScalar<long>(sqlCount, args);
  818. result.TotalPages = result.TotalItems / itemsPerPage;
  819. if ((result.TotalItems % itemsPerPage) != 0)
  820. result.TotalPages++;
  821. OneTimeCommandTimeout = saveTimeout;
  822. // Get the records
  823. result.Items = Fetch<T>(sqlPage, args);
  824. // Done
  825. return result;
  826. }
  827. public Page<T> Page<T>(long page, long itemsPerPage, Sql sql)
  828. {
  829. return Page<T>(page, itemsPerPage, sql.SQL, sql.Arguments);
  830. }
  831. public List<T> Fetch<T>(long page, long itemsPerPage, string sql, params object[] args)
  832. {
  833. return SkipTake<T>((page - 1) * itemsPerPage, itemsPerPage, sql, args);
  834. }
  835. public List<T> Fetch<T>(long page, long itemsPerPage, Sql sql)
  836. {
  837. return SkipTake<T>((page - 1) * itemsPerPage, itemsPerPage, sql.SQL, sql.Arguments);
  838. }
  839. public List<T> SkipTake<T>(long skip, long take, string sql, params object[] args)
  840. {
  841. string sqlCount, sqlPage;
  842. BuildPageQueries<T>(skip, take, sql, ref args, out sqlCount, out sqlPage);
  843. return Fetch<T>(sqlPage, args);
  844. }
  845. public List<T> SkipTake<T>(long skip, long take, Sql sql)
  846. {
  847. return SkipTake<T>(skip, take, sql.SQL, sql.Arguments);
  848. }
  849. public Dictionary<TKey, TValue> Dictionary<TKey, TValue>(Sql Sql)
  850. {
  851. return Dictionary<TKey, TValue>(Sql.SQL, Sql.Arguments);
  852. }
  853. public Dictionary<TKey, TValue> Dictionary<TKey, TValue>(string sql, params object[] args)
  854. {
  855. var newDict = new Dictionary<TKey, TValue>();
  856. bool isConverterSet = false;
  857. Func<object, object> converter1 = x => x, converter2 = x => x;
  858. foreach (var line in Query<Dictionary<string, object>>(sql, args))
  859. {
  860. object key = line.ElementAt(0).Value;
  861. object value = line.ElementAt(1).Value;
  862. if (isConverterSet == false)
  863. {
  864. converter1 = PocoData.GetConverter(ForceDateTimesToUtc, null, typeof (TKey), key.GetType()) ?? (x => x);
  865. converter2 = PocoData.GetConverter(ForceDateTimesToUtc, null, typeof (TValue), value.GetType()) ?? (x => x);
  866. isConverterSet = true;
  867. }
  868. var keyConverted = (TKey) Convert.ChangeType(converter1(key), typeof (TKey));
  869. var valueType = Nullable.GetUnderlyingType(typeof (TValue)) ?? typeof (TValue);
  870. var valConv = converter2(value);
  871. var valConverted = valConv != null ? (TValue)Convert.ChangeType(valConv, valueType) : default(TValue);
  872. if (keyConverted != null)
  873. {
  874. newDict.Add(keyConverted, valConverted);
  875. }
  876. }
  877. return newDict;
  878. }
  879. // Return an enumerable collection of pocos
  880. public IEnumerable<T> Query<T>(string sql, params object[] args)
  881. {
  882. return Query<T>(new Sql(sql, args));
  883. }
  884. public IEnumerable<T> Query<T>(Sql Sql)
  885. {
  886. return Query<T>(default(T), Sql);
  887. }
  888. private IEnumerable<T> Query<T>(T instance, Sql Sql)
  889. {
  890. var sql = Sql.SQL;
  891. var args = Sql.Arguments;
  892. if (EnableAutoSelect)
  893. sql = AddSelectClause<T>(sql);
  894. OpenSharedConnection();
  895. try
  896. {
  897. using (var cmd = CreateCommand(_sharedConnection, sql, args))
  898. {
  899. IDataReader r;
  900. var pd = PocoData.ForType(typeof(T));
  901. try
  902. {
  903. r = cmd.ExecuteReader();
  904. OnExecutedCommand(cmd);
  905. }
  906. catch (Exception x)
  907. {
  908. OnException(x);
  909. throw;
  910. }
  911. using (r)
  912. {
  913. var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, ForceDateTimesToUtc, 0, r.FieldCount, r, instance) as Func<IDataReader, T, T>;
  914. while (true)
  915. {
  916. T poco;
  917. try
  918. {
  919. if (!r.Read())
  920. yield break;
  921. poco = factory(r, instance);
  922. }
  923. catch (Exception x)
  924. {
  925. OnException(x);
  926. throw;
  927. }
  928. yield return poco;
  929. }
  930. }
  931. }
  932. }
  933. finally
  934. {
  935. CloseSharedConnection();
  936. }
  937. }
  938. // Multi Fetch
  939. public List<TRet> Fetch<T1, T2, TRet>(Func<T1, T2, TRet> cb, string sql, params object[] args) { return Query<T1, T2, TRet>(cb, sql, args).ToList(); }
  940. public List<TRet> Fetch<T1, T2, T3, TRet>(Func<T1, T2, T3, TRet> cb, string sql, params object[] args) { return Query<T1, T2, T3, TRet>(cb, sql, args).ToList(); }
  941. public List<TRet> Fetch<T1, T2, T3, T4, TRet>(Func<T1, T2, T3, T4, TRet> cb, string sql, params object[] args) { return Query<T1, T2, T3, T4, TRet>(cb, sql, args).ToList(); }
  942. // Multi Query
  943. public IEnumerable<TRet> Query<T1, T2, TRet>(Func<T1, T2, TRet> cb, string sql, params object[] args) { return Query<TRet>(new Type[] { typeof(T1), typeof(T2) }, cb, new Sql(sql, args)); }
  944. public IEnumerable<TRet> Query<T1, T2, T3, TRet>(Func<T1, T2, T3, TRet> cb, string sql, params object[] args) { return Query<TRet>(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, new Sql(sql, args)); }
  945. public IEnumerable<TRet> Query<T1, T2, T3, T4, TRet>(Func<T1, T2, T3, T4, TRet> cb, string sql, params object[] args) { return Query<TRet>(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, new Sql(sql, args)); }
  946. // Multi Fetch (SQL builder)
  947. public List<TRet> Fetch<T1, T2, TRet>(Func<T1, T2, TRet> cb, Sql sql) { return Query<T1, T2, TRet>(cb, sql).ToList(); }
  948. public List<TRet> Fetch<T1, T2, T3, TRet>(Func<T1, T2, T3, TRet> cb, Sql sql) { return Query<T1, T2, T3, TRet>(cb, sql).ToList(); }
  949. public List<TRet> Fetch<T1, T2, T3, T4, TRet>(Func<T1, T2, T3, T4, TRet> cb, Sql sql) { return Query<T1, T2, T3, T4, TRet>(cb, sql).ToList(); }
  950. // Multi Query (SQL builder)
  951. public IEnumerable<TRet> Query<T1, T2, TRet>(Func<T1, T2, TRet> cb, Sql sql) { return Query<TRet>(new Type[] { typeof(T1), typeof(T2) }, cb, sql); }
  952. public IEnumerable<TRet> Query<T1, T2, T3, TRet>(Func<T1, T2, T3, TRet> cb, Sql sql) { return Query<TRet>(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql); }
  953. public IEnumerable<TRet> Query<T1, T2, T3, T4, TRet>(Func<T1, T2, T3, T4, TRet> cb, Sql sql) { return Query<TRet>(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql); }
  954. // Multi Fetch (Simple)
  955. public List<T1> Fetch<T1, T2>(string sql, params object[] args) { return Query<T1, T2>(sql, args).ToList(); }
  956. public List<T1> Fetch<T1, T2, T3>(string sql, params object[] args) { return Query<T1, T2, T3>(sql, args).ToList(); }
  957. public List<T1> Fetch<T1, T2, T3, T4>(string sql, params object[] args) { return Query<T1, T2, T3, T4>(sql, args).ToList(); }
  958. // Multi Query (Simple)
  959. public IEnumerable<T1> Query<T1, T2>(string sql, params object[] args) { return Query<T1>(new Type[] { typeof(T1), typeof(T2) }, null, new Sql(sql, args)); }
  960. public IEnumerable<T1> Query<T1, T2, T3>(string sql, params object[] args) { return Query<T1>(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, new Sql(sql, args)); }
  961. public IEnumerable<T1> Query<T1, T2, T3, T4>(string sql, params object[] args) { return Query<T1>(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, new Sql(sql, args)); }
  962. // Multi Fetch (Simple) (SQL builder)
  963. public List<T1> Fetch<T1, T2>(Sql sql) { return Query<T1, T2>(sql).ToList(); }
  964. public List<T1> Fetch<T1, T2, T3>(Sql sql) { return Query<T1, T2, T3>(sql).ToList(); }
  965. public List<T1> Fetch<T1, T2, T3, T4>(Sql sql) { return Query<T1, T2, T3, T4>(sql).ToList(); }
  966. // Multi Query (Simple) (SQL builder)
  967. public IEnumerable<T1> Query<T1, T2>(Sql sql) { return Query<T1>(new Type[] { typeof(T1), typeof(T2) }, null, sql); }
  968. public IEnumerable<T1> Query<T1, T2, T3>(Sql sql) { return Query<T1>(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql); }
  969. public IEnumerable<T1> Query<T1, T2, T3, T4>(Sql sql) { return Query<T1>(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql); }
  970. // Automagically guess the property relationships between various POCOs and create a delegate that will set them up
  971. object GetAutoMapper(Type[] types)
  972. {
  973. // Build a key
  974. var kb = new StringBuilder();
  975. foreach (var t in types)
  976. {
  977. kb.Append(t.ToString());
  978. kb.Append(":");
  979. }
  980. var key = kb.ToString();
  981. // Check cache
  982. RWLock.EnterReadLock();
  983. try
  984. {
  985. object mapper;
  986. if (AutoMappers.TryGetValue(key, out mapper))
  987. return mapper;
  988. }
  989. finally
  990. {
  991. RWLock.ExitReadLock();
  992. }
  993. // Create it
  994. RWLock.EnterWriteLock();
  995. try
  996. {
  997. // Try again
  998. object mapper;
  999. if (AutoMappers.TryGetValue(key, out mapper))
  1000. return mapper;
  1001. // Create a method
  1002. var m = new DynamicMethod("petapoco_automapper", types[0], types, true);
  1003. var il = m.GetILGenerator();
  1004. for (int i = 1; i < types.Length; i++)
  1005. {
  1006. bool handled = false;
  1007. for (int j = i - 1; j >= 0; j--)
  1008. {
  1009. // Find the property
  1010. var candidates = from p in types[j].GetProperties() where p.PropertyType == types[i] select p;
  1011. if (candidates.Count() == 0)
  1012. continue;
  1013. if (candidates.Count() > 1)
  1014. throw new InvalidOperationException(string.Format("Can't auto join {0} as {1} has more than one property of type {0}", types[i], types[j]));
  1015. // Generate code
  1016. il.Emit(OpCodes.Ldarg_S, j);
  1017. il.Emit(OpCodes.Ldarg_S, i);
  1018. il.Emit(OpCodes.Callvirt, candidates.First().GetSetMethod(true));
  1019. handled = true;
  1020. }
  1021. if (!handled)
  1022. throw new InvalidOperationException(string.Format("Can't auto join {0}", types[i]));
  1023. }
  1024. il.Emit(OpCodes.Ldarg_0);
  1025. il.Emit(OpCodes.Ret);
  1026. // Cache it
  1027. var del = m.CreateDelegate(Expression.GetFuncType(types.Concat(types.Take(1)).ToArray()));
  1028. AutoMappers.Add(key, del);
  1029. return del;
  1030. }
  1031. finally
  1032. {
  1033. RWLock.ExitWriteLock();
  1034. }
  1035. }
  1036. // Find the split point in a result set for two different pocos and return the poco factory for the first
  1037. Delegate FindSplitPoint(Type typeThis, Type typeNext, string sql, IDataReader r, ref int pos)
  1038. {
  1039. // Last?
  1040. if (typeNext == null)
  1041. return PocoData.ForType(typeThis).GetFactory(sql, _sharedConnection.ConnectionString, ForceDateTimesToUtc, pos, r.FieldCount - pos, r, null);
  1042. // Get PocoData for the two types
  1043. PocoData pdThis = PocoData.ForType(typeThis);
  1044. PocoData pdNext = PocoData.ForType(typeNext);
  1045. // Find split point
  1046. int firstColumn = pos;
  1047. var usedColumns = new Dictionary<string, bool>();
  1048. for (; pos < r.FieldCount; pos++)
  1049. {
  1050. // Split if field name has already been used, or if the field doesn't exist in current poco but does in the next
  1051. string fieldName = r.GetName(pos);
  1052. if (usedColumns.ContainsKey(fieldName) || (!pdThis.Columns.ContainsKey(fieldName) && pdNext.Columns.ContainsKey(fieldName)))
  1053. {
  1054. return pdThis.GetFactory(sql, _sharedConnection.ConnectionString, ForceDateTimesToUtc, firstColumn, pos - firstColumn, r, null);
  1055. }
  1056. usedColumns.Add(fieldName, true);
  1057. }
  1058. throw new InvalidOperationException(string.Format("Couldn't find split point between {0} and {1}", typeThis, typeNext));
  1059. }
  1060. // Instance data used by the Multipoco factory delegate - essentially a list of the nested poco factories to call
  1061. class MultiPocoFactory
  1062. {
  1063. public List<Delegate> m_Delegates;
  1064. public Delegate GetItem(int index) { return m_Delegates[index]; }
  1065. }
  1066. // Create a multi-poco factory
  1067. Func<IDataReader, object, TRet> CreateMultiPocoFactory<TRet>(Type[] types, string sql, IDataReader r)
  1068. {
  1069. var m = new DynamicMethod("petapoco_multipoco_factory", typeof(TRet), new Type[] { typeof(MultiPocoFactory), typeof(IDataReader), typeof(object) }, typeof(MultiPocoFactory));
  1070. var il = m.GetILGenerator();
  1071. // Load the callback
  1072. il.Emit(OpCodes.Ldarg_2);
  1073. // Call each delegate
  1074. var dels = new List<Delegate>();
  1075. int pos = 0;
  1076. for (int i=0; i<types.Length; i++)
  1077. {
  1078. // Add to list of delegates to call
  1079. var del = FindSplitPoint(types[i], i + 1 < types.Length ? types[i + 1] : null, sql, r, ref pos);
  1080. dels.Add(del);
  1081. // Get the delegate
  1082. il.Emit(OpCodes.Ldarg_0); // callback,this
  1083. il.Emit(OpCodes.Ldc_I4, i); // callback,this,Index
  1084. il.Emit(OpCodes.Callvirt, typeof(MultiPocoFactory).GetMethod("GetItem")); // callback,Delegate
  1085. il.Emit(OpCodes.Ldarg_1); // callback,delegate, datareader
  1086. il.Emit(OpCodes.Ldnull); // callback,delegate, datareader,null
  1087. // Call Invoke
  1088. var tDelInvoke = del.GetType().GetMethod("Invoke");
  1089. il.Emit(OpCodes.Callvirt, tDelInvoke); // Poco left on stack
  1090. }
  1091. // By now we should have the callback and the N pocos all on the stack. Call the callback and we're done
  1092. il.Emit(OpCodes.Callvirt, Expression.GetFuncType(types.Concat(new Type[] { typeof(TRet) }).ToArray()).GetMethod("Invoke"));
  1093. il.Emit(OpCodes.Ret);
  1094. // Finish up
  1095. return (Func<IDataReader, object, TRet>)m.CreateDelegate(typeof(Func<IDataReader, object, TRet>), new MultiPocoFactory() { m_Delegates = dels });
  1096. }
  1097. // Various cached stuff
  1098. static Dictionary<string, object> MultiPocoFactories = new Dictionary<string, object>();
  1099. static Dictionary<string, object> AutoMappers = new Dictionary<string, object>();
  1100. static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim();
  1101. // Get (or create) the multi-poco factory for a…

Large files files are truncated, but you can click here to view the full file