PageRenderTime 38ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/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
  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 query
  1102. Func<IDataReader, object, TRet> GetMultiPocoFactory<TRet>(Type[] types, string sql, IDataReader r)
  1103. {
  1104. // Build a key string (this is crap, should address this at some point)
  1105. var kb = new StringBuilder();
  1106. kb.Append(typeof(TRet).ToString());
  1107. kb.Append(":");
  1108. foreach (var t in types)
  1109. {
  1110. kb.Append(":");
  1111. kb.Append(t.ToString());
  1112. }
  1113. kb.Append(":"); kb.Append(_sharedConnection.ConnectionString);
  1114. kb.Append(":"); kb.Append(ForceDateTimesToUtc);
  1115. kb.Append(":"); kb.Append(sql);
  1116. string key = kb.ToString();
  1117. // Check cache
  1118. RWLock.EnterReadLock();
  1119. try
  1120. {
  1121. object oFactory;
  1122. if (MultiPocoFactories.TryGetValue(key, out oFactory))
  1123. return (Func<IDataReader, object, TRet>)oFactory;
  1124. }
  1125. finally
  1126. {
  1127. RWLock.ExitReadLock();
  1128. }
  1129. // Cache it
  1130. RWLock.EnterWriteLock();
  1131. try
  1132. {
  1133. // Check again
  1134. object oFactory;
  1135. if (MultiPocoFactories.TryGetValue(key, out oFactory))
  1136. return (Func<IDataReader, object, TRet>)oFactory;
  1137. // Create the factory
  1138. var Factory = CreateMultiPocoFactory<TRet>(types, sql, r);
  1139. MultiPocoFactories.Add(key, Factory);
  1140. return Factory;
  1141. }
  1142. finally
  1143. {
  1144. RWLock.ExitWriteLock();
  1145. }
  1146. }
  1147. // Actual implementation of the multi-poco query
  1148. public IEnumerable<TRet> Query<TRet>(Type[] types, object cb, Sql sql)
  1149. {
  1150. OpenSharedConnection();
  1151. try
  1152. {
  1153. using (var cmd = CreateCommand(_sharedConnection, sql.SQL, sql.Arguments))
  1154. {
  1155. IDataReader r;
  1156. try
  1157. {
  1158. r = cmd.ExecuteReader();
  1159. OnExecutedCommand(cmd);
  1160. }
  1161. catch (Exception x)
  1162. {
  1163. OnException(x);
  1164. throw;
  1165. }
  1166. var factory = GetMultiPocoFactory<TRet>(types, sql.SQL, r);
  1167. if (cb == null)
  1168. cb = GetAutoMapper(types.ToArray());
  1169. bool bNeedTerminator=false;
  1170. using (r)
  1171. {
  1172. while (true)
  1173. {
  1174. TRet poco;
  1175. try
  1176. {
  1177. if (!r.Read())
  1178. break;
  1179. poco = factory(r, cb);
  1180. }
  1181. catch (Exception x)
  1182. {
  1183. OnException(x);
  1184. throw;
  1185. }
  1186. if (poco != null)
  1187. yield return poco;
  1188. else
  1189. bNeedTerminator = true;
  1190. }
  1191. if (bNeedTerminator)
  1192. {
  1193. var poco = (TRet)(cb as Delegate).DynamicInvoke(new object[types.Length]);
  1194. if (poco != null)
  1195. yield return poco;
  1196. else
  1197. yield break;
  1198. }
  1199. }
  1200. }
  1201. }
  1202. finally
  1203. {
  1204. CloseSharedConnection();
  1205. }
  1206. }
  1207. public TRet FetchMultiple<T1, T2, TRet>(Func<List<T1>, List<T2>, TRet> cb, string sql, params object[] args) { return FetchMultiple<T1, T2, DontMap, DontMap, TRet>(new[] { typeof(T1), typeof(T2) }, cb, new Sql(sql, args)); }
  1208. public TRet FetchMultiple<T1, T2, T3, TRet>(Func<List<T1>, List<T2>, List<T3>, TRet> cb, string sql, params object[] args) { return FetchMultiple<T1, T2, T3, DontMap, TRet>(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, new Sql(sql, args)); }
  1209. public TRet FetchMultiple<T1, T2, T3, T4, TRet>(Func<List<T1>, List<T2>, List<T3>, List<T4>, TRet> cb, string sql, params object[] args) { return FetchMultiple<T1, T2, T3, T4, TRet>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, new Sql(sql, args)); }
  1210. public TRet FetchMultiple<T1, T2, TRet>(Func<List<T1>, List<T2>, TRet> cb, Sql sql) { return FetchMultiple<T1, T2, DontMap, DontMap, TRet>(new[] { typeof(T1), typeof(T2) }, cb, sql); }
  1211. public TRet FetchMultiple<T1, T2, T3, TRet>(Func<List<T1>, List<T2>, List<T3>, TRet> cb, Sql sql) { return FetchMultiple<T1, T2, T3, DontMap, TRet>(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql); }
  1212. public TRet FetchMultiple<T1, T2, T3, T4, TRet>(Func<List<T1>, List<T2>, List<T3>, List<T4>, TRet> cb, Sql sql) { return FetchMultiple<T1, T2, T3, T4, TRet>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql); }
  1213. #if PETAPOCO_NO_DYNAMIC
  1214. public Tuple<List<T1>, List<T2>> FetchMultiple<T1, T2>(string sql, params object[] args) { return FetchMultiple<T1, T2, DontMap, DontMap, Tuple<List<T1>, List<T2>>>(new[] { typeof(T1), typeof(T2) }, new Func<List<T1>, List<T2>, Tuple<List<T1>, List<T2>>>((y, z) => new Tuple<List<T1>, List<T2>>(y, z)), new Sql(sql, args)); }
  1215. public Tuple<List<T1>, List<T2>, List<T3>> FetchMultiple<T1, T2, T3>(string sql, params object[] args) { return FetchMultiple<T1, T2, T3, DontMap, Tuple<List<T1>, List<T2>, List<T3>>>(new[] { typeof(T1), typeof(T2), typeof(T3) }, new Func<List<T1>, List<T2>, List<T3>, Tuple<List<T1>, List<T2>, List<T3>>>((x, y, z) => new Tuple<List<T1>, List<T2>, List<T3>>(x, y, z)), new Sql(sql, args)); }
  1216. public Tuple<List<T1>, List<T2>, List<T3>, List<T4>> FetchMultiple<T1, T2, T3, T4>(string sql, params object[] args) { return FetchMultiple<T1, T2, T3, T4, Tuple<List<T1>, List<T2>, List<T3>, List<T4>>>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, new Func<List<T1>, List<T2>, List<T3>, List<T4>, Tuple<List<T1>, List<T2>, List<T3>, List<T4>>>((w, x, y, z) => new Tuple<List<T1>, List<T2>, List<T3>, List<T4>>(w, x, y, z)), new Sql(sql, args)); }
  1217. public Tuple<List<T1>, List<T2>> FetchMultiple<T1, T2>(Sql sql) { return FetchMultiple<T1, T2, DontMap, DontMap, Tuple<List<T1>, List<T2>>>(new[] { typeof(T1), typeof(T2) }, new Func<List<T1>, List<T2>, Tuple<List<T1>, List<T2>>>((y, z) => new Tuple<List<T1>, List<T2>>(y, z)), sql); }
  1218. public Tuple<List<T1>, List<T2>, List<T3>> FetchMultiple<T1, T2, T3>(Sql sql) { return FetchMultiple<T1, T2, T3, DontMap, Tuple<List<T1>, List<T2>, List<T3>>>(new[] { typeof(T1), typeof(T2), typeof(T3) }, new Func<List<T1>, List<T2>, List<T3>, Tuple<List<T1>, List<T2>, List<T3>>>((x, y, z) => new Tuple<List<T1>, List<T2>, List<T3>>(x, y, z)), sql); }
  1219. public Tuple<List<T1>, List<T2>, List<T3>, List<T4>> FetchMultiple<T1, T2, T3, T4>(Sql sql) { return FetchMultiple<T1, T2, T3, T4, Tuple<List<T1>, List<T2>, List<T3>, List<T4>>>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, new Func<List<T1>, List<T2>, List<T3>, List<T4>, Tuple<List<T1>, List<T2>, List<T3>, List<T4>>>((w, x, y, z) => new Tuple<List<T1>, List<T2>, List<T3>, List<T4>>(w, x, y, z)), sql); }
  1220. public class Tuple<T1, T2>
  1221. {
  1222. public T1 Item1 { get; set; }
  1223. public T2 Item2 { get; set; }
  1224. public Tuple(T1 item1, T2 item2) { Item1 = item1; Item2 = item2; }
  1225. }
  1226. public class Tuple<T1, T2, T3>
  1227. {
  1228. public T1 Item1 { get; set; }
  1229. public T2 Item2 { get; set; }
  1230. public T3 Item3 { get; set; }
  1231. public Tuple(T1 item1, T2 item2, T3 item3) { Item1 = item1; Item2 = item2; Item3 = item3; }
  1232. }
  1233. public class Tuple<T1, T2, T3, T4>
  1234. {
  1235. public T1 Item1 { get; set; }
  1236. public T2 Item2 { get; set; }
  1237. public T3 Item3 { get; set; }
  1238. public T4 Item4 { get; set; }
  1239. public Tuple(T1 item1, T2 item2, T3 item3, T4 item4) { Item1 = item1; Item2 = item2; Item3 = item3; Item4 = item4; }
  1240. }
  1241. #else
  1242. public Tuple<List<T1>, List<T2>> FetchMultiple<T1, T2>(string sql, params object[] args) { return FetchMultiple<T1, T2, DontMap, DontMap, Tuple<List<T1>, List<T2>>>(new[] { typeof(T1), typeof(T2) }, new Func<List<T1>, List<T2>, Tuple<List<T1>, List<T2>>>((y, z) => new Tuple<List<T1>, List<T2>>(y, z)), new Sql(sql, args)); }
  1243. public Tuple<List<T1>, List<T2>, List<T3>> FetchMultiple<T1, T2, T3>(string sql, params object[] args) { return FetchMultiple<T1, T2, T3, DontMap, Tuple<List<T1>, List<T2>, List<T3>>>(new[] { typeof(T1), typeof(T2), typeof(T3) }, new Func<List<T1>, List<T2>, List<T3>, Tuple<List<T1>, List<T2>, List<T3>>>((x, y, z) => new Tuple<List<T1>, List<T2>, List<T3>>(x, y, z)), new Sql(sql, args)); }
  1244. public Tuple<List<T1>, List<T2>, List<T3>, List<T4>> FetchMultiple<T1, T2, T3, T4>(string sql, params object[] args) { return FetchMultiple<T1, T2, T3, T4, Tuple<List<T1>, List<T2>, List<T3>, List<T4>>>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, new Func<List<T1>, List<T2>, List<T3>, List<T4>, Tuple<List<T1>, List<T2>, List<T3>, List<T4>>>((w, x, y, z) => new Tuple<List<T1>, List<T2>, List<T3>, List<T4>>(w, x, y, z)), new Sql(sql, args)); }
  1245. public Tuple<List<T1>, List<T2>> FetchMultiple<T1, T2>(Sql sql) { return FetchMultiple<T1, T2, DontMap, DontMap, Tuple<List<T1>, List<T2>>>(new[] { typeof(T1), typeof(T2) }, new Func<List<T1>, List<T2>, Tuple<List<T1>, List<T2>>>((y, z) => new Tuple<List<T1>, List<T2>>(y, z)), sql); }
  1246. public Tuple<List<T1>, List<T2>, List<T3>> FetchMultiple<T1, T2, T3>(Sql sql) { return FetchMultiple<T1, T2, T3, DontMap, Tuple<List<T1>, List<T2>, List<T3>>>(new[] { typeof(T1), typeof(T2), typeof(T3) }, new Func<List<T1>, List<T2>, List<T3>, Tuple<List<T1>, List<T2>, List<T3>>>((x, y, z) => new Tuple<List<T1>, List<T2>, List<T3>>(x, y, z)), sql); }
  1247. public Tuple<List<T1>, List<T2>, List<T3>, List<T4>> FetchMultiple<T1, T2, T3, T4>(Sql sql) { return FetchMultiple<T1, T2, T3, T4, Tuple<List<T1>, List<T2>, List<T3>, List<T4>>>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, new Func<List<T1>, List<T2>, List<T3>, List<T4>, Tuple<List<T1>, List<T2>, List<T3>, List<T4>>>((w, x, y, z) => new Tuple<List<T1>, List<T2>, List<T3>, List<T4>>(w, x, y, z)), sql); }
  1248. #endif
  1249. public class DontMap {}
  1250. // Actual implementation of the multi query
  1251. private TRet FetchMultiple<T1, T2, T3, T4, TRet>(Type[] types, object cb, Sql Sql)
  1252. {
  1253. var sql = Sql.SQL;
  1254. var args = Sql.Arguments;
  1255. OpenSharedConnection();
  1256. try
  1257. {
  1258. using (var cmd = CreateCommand(_sharedConnection, sql, args))
  1259. {
  1260. IDataReader r;
  1261. try
  1262. {
  1263. r = cmd.ExecuteReader();
  1264. OnExecutedCommand(cmd);
  1265. }
  1266. catch (Exception x)
  1267. {
  1268. OnException(x);
  1269. throw;
  1270. }
  1271. using (r)
  1272. {
  1273. var typeIndex = 1;
  1274. var list1 = new List<T1>();
  1275. var list2 = new List<T2>();
  1276. var list3 = new List<T3>();
  1277. var list4 = new List<T4>();
  1278. do
  1279. {
  1280. if (typeIndex > types.Length)
  1281. break;
  1282. var pd = PocoData.ForType(types[typeIndex-1]);
  1283. var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, ForceDateTimesToUtc, 0, r.FieldCount, r, null);
  1284. while (true)
  1285. {
  1286. try
  1287. {
  1288. if (!r.Read())
  1289. break;
  1290. switch (typeIndex)
  1291. {
  1292. case 1:
  1293. list1.Add(((Func<IDataReader, T1, T1>)factory)(r, default(T1)));
  1294. break;
  1295. case 2:
  1296. list2.Add(((Func<IDataReader, T2, T2>)factory)(r, default(T2)));
  1297. break;
  1298. case 3:
  1299. list3.Add(((Func<IDataReader, T3, T3>)factory)(r, default(T3)));
  1300. break;
  1301. case 4:
  1302. list4.Add(((Func<IDataReader, T4, T4>)factory)(r, default(T4)));
  1303. break;
  1304. }
  1305. }
  1306. catch (Exception x)
  1307. {
  1308. OnException(x);
  1309. throw;
  1310. }
  1311. }
  1312. typeIndex++;
  1313. } while (r.NextResult());
  1314. switch (types.Length)
  1315. {
  1316. case 2:
  1317. return ((Func<List<T1>, List<T2>, TRet>)cb)(list1, list2);
  1318. case 3:
  1319. return ((Func<List<T1>, List<T2>, List<T3>, TRet>)cb)(list1, list2, list3);
  1320. case 4:
  1321. return ((Func<List<T1>, List<T2>, List<T3>, List<T4>, TRet>)cb)(list1, list2, list3, list4);
  1322. }
  1323. return default(TRet);
  1324. }
  1325. }
  1326. }
  1327. finally
  1328. {
  1329. CloseSharedConnection();
  1330. }
  1331. }
  1332. public bool Exists<T>(object primaryKey)
  1333. {
  1334. var index = 0;
  1335. var primaryKeyValuePairs = GetPrimaryKeyValues(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey, primaryKey);
  1336. return FirstOrDefault<T>(string.Format("WHERE {0}", BuildPrimaryKeySql(primaryKeyValuePairs, ref index)), primaryKeyValuePairs.Select(x => x.Value).ToArray()) != null;
  1337. }
  1338. public T SingleById<T>(object primaryKey)
  1339. {
  1340. var index = 0;
  1341. var primaryKeyValuePairs = GetPrimaryKeyValues(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey, primaryKey);
  1342. return Single<T>(string.Format("WHERE {0}", BuildPrimaryKeySql(primaryKeyValuePairs, ref index)), primaryKeyValuePairs.Select(x => x.Value).ToArray());
  1343. }
  1344. public T SingleOrDefaultById<T>(object primaryKey)
  1345. {
  1346. var index = 0;
  1347. var primaryKeyValuePairs = GetPrimaryKeyValues(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey, primaryKey);
  1348. return SingleOrDefault<T>(string.Format("WHERE {0}", BuildPrimaryKeySql(primaryKeyValuePairs, ref index)), primaryKeyValuePairs.Select(x => x.Value).ToArray());
  1349. }
  1350. public T Single<T>(string sql, params object[] args)
  1351. {
  1352. return Query<T>(sql, args).Single();
  1353. }
  1354. public T SingleInto<T>(T instance, string sql, params object[] args)
  1355. {
  1356. return Query<T>(instance, new Sql(sql, args)).Single();
  1357. }
  1358. public T SingleOrDefault<T>(string sql, params object[] args)
  1359. {
  1360. return Query<T>(sql, args).SingleOrDefault();
  1361. }
  1362. public T SingleOrDefaultInto<T>(T instance, string sql, params object[] args)
  1363. {
  1364. return Query<T>(instance, new Sql(sql, args)).SingleOrDefault();
  1365. }
  1366. public T First<T>(string sql, params object[] args)
  1367. {
  1368. return Query<T>(sql, args).First();
  1369. }
  1370. public T FirstInto<T>(T instance, string sql, params object[] args)
  1371. {
  1372. return Query<T>(instance, new Sql(sql, args)).First();
  1373. }
  1374. public T FirstOrDefault<T>(string sql, params object[] args)
  1375. {
  1376. return Query<T>(sql, args).FirstOrDefault();
  1377. }
  1378. public T FirstOrDefaultInto<T>(T instance, string sql, params object[] args)
  1379. {
  1380. return Query<T>(instance, new Sql(sql, args)).FirstOrDefault();
  1381. }
  1382. public T Single<T>(Sql sql)
  1383. {
  1384. return Query<T>(sql).Single();
  1385. }
  1386. public T SingleInto<T>(T instance, Sql sql)
  1387. {
  1388. return Query<T>(instance, sql).Single();
  1389. }
  1390. public T SingleOrDefault<T>(Sql sql)
  1391. {
  1392. return Query<T>(sql).SingleOrDefault();
  1393. }
  1394. public T SingleOrDefaultInto<T>(T instance, Sql sql)
  1395. {
  1396. return Query<T>(instance, sql).SingleOrDefault();
  1397. }
  1398. public T First<T>(Sql sql)
  1399. {
  1400. return Query<T>(sql).First();
  1401. }
  1402. public T FirstInto<T>(T instance, Sql sql)
  1403. {
  1404. return Query<T>(instance, sql).First();
  1405. }
  1406. public T FirstOrDefault<T>(Sql sql)
  1407. {
  1408. return Query<T>(sql).FirstOrDefault();
  1409. }
  1410. public T FirstOrDefaultInto<T>(T instance, Sql sql)
  1411. {
  1412. return Query<T>(instance, sql).FirstOrDefault();
  1413. }
  1414. public string EscapeTableName(string str)
  1415. {
  1416. // Assume table names with "dot" are already escaped
  1417. return str.IndexOf('.') >= 0 ? str : EscapeSqlIdentifier(str);
  1418. }
  1419. public string EscapeSqlIdentifier(string str)
  1420. {
  1421. switch (_dbType)
  1422. {
  1423. case DBType.MySql:
  1424. return string.Format("`{0}`", str);
  1425. case DBType.PostgreSQL:
  1426. return string.Format("\"{0}\"", str);
  1427. case DBType.Oracle:
  1428. return string.Format("\"{0}\"", str.ToUpperInvariant());
  1429. default:
  1430. return string.Format("[{0}]", str);
  1431. }
  1432. }
  1433. public object Insert(string tableName, string primaryKeyName, object poco)
  1434. {
  1435. return Insert(tableName, primaryKeyName, true, poco);
  1436. }
  1437. // Insert a poco into a table. If the poco has a property with the same name
  1438. // as the primary key the id of the new record is assigned to it. Either way,
  1439. // the new id is returned.
  1440. public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco)
  1441. {
  1442. try
  1443. {
  1444. OpenSharedConnection();
  1445. try
  1446. {
  1447. var pd = PocoData.ForObject(poco, primaryKeyName);
  1448. var names = new List<string>();
  1449. var values = new List<string>();
  1450. var rawvalues = new List<object>();
  1451. var index = 0;
  1452. var versionName = "";
  1453. foreach (var i in pd.Columns)
  1454. {
  1455. // Don't insert result columns
  1456. if (i.Value.ResultColumn)
  1457. continue;
  1458. // Don't insert the primary key (except under oracle where we need bring in the next sequence value)
  1459. if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, true)==0)
  1460. {
  1461. if (_dbType == DBType.Oracle && !string.IsNullOrEmpty(pd.TableInfo.SequenceName))
  1462. {
  1463. names.Add(i.Key);
  1464. values.Add(string.Format("{0}.nextval", pd.TableInfo.SequenceName));
  1465. }
  1466. continue;
  1467. }
  1468. names.Add(EscapeSqlIdentifier(i.Key));
  1469. values.Add(string.Format("{0}{1}", _paramPrefix, index++));
  1470. object val = i.Value.GetValue(poco);
  1471. if (i.Value.VersionColumn)
  1472. {
  1473. val = 1;
  1474. versionName = i.Key;
  1475. }
  1476. rawvalues.Add(val);
  1477. }
  1478. using (var cmd = CreateCommand(_sharedConnection, ""))
  1479. {
  1480. var sql = string.Empty;
  1481. if (names.Count > 0 || _dbType == DBType.MySql)
  1482. {
  1483. sql = string.Format("INSERT INTO {0} ({1}) VALUES ({2})", EscapeTableName(tableName), string.Join(",", names.ToArray()), string.Join(",", values.ToArray()));
  1484. }
  1485. else
  1486. {
  1487. sql = string.Format("INSERT INTO {0} DEFAULT VALUES", EscapeTableName(tableName));
  1488. }
  1489. cmd.CommandText = sql;
  1490. rawvalues.ForEach(x=>AddParam(cmd, x, _paramPrefix));
  1491. object id;
  1492. if (!autoIncrement)
  1493. {
  1494. DoPreExecute(cmd);
  1495. cmd.ExecuteNonQuery();
  1496. OnExecutedCommand(cmd);
  1497. id = -1;
  1498. }
  1499. else
  1500. {
  1501. switch (_dbType)
  1502. {
  1503. case DBType.SqlServerCE:
  1504. DoPreExecute(cmd);
  1505. cmd.ExecuteNonQuery();
  1506. OnExecutedCommand(cmd);
  1507. id = ExecuteScalar<object>("SELECT @@@IDENTITY AS NewID;");
  1508. break;
  1509. case DBType.SqlServer:
  1510. cmd.CommandText += ";\nSELECT SCOPE_IDENTITY() AS NewID;";
  1511. DoPreExecute(cmd);
  1512. id = cmd.ExecuteScalar();
  1513. OnExecutedCommand(cmd);
  1514. break;
  1515. case DBType.MySql:
  1516. cmd.CommandText += ";\nSELECT LAST_INSERT_ID();";
  1517. DoPreExecute(cmd);
  1518. id = cmd.ExecuteScalar();
  1519. OnExecutedCommand(cmd);
  1520. break;
  1521. case DBType.PostgreSQL:
  1522. if (primaryKeyName != null)
  1523. {
  1524. cmd.CommandText += string.Format(" returning {0} as NewID", EscapeSqlIdentifier(primaryKeyName));
  1525. DoPreExecute(cmd);
  1526. id = cmd.ExecuteScalar();
  1527. }
  1528. else
  1529. {
  1530. id = -1;
  1531. DoPreExecute(cmd);
  1532. cmd.ExecuteNonQuery();
  1533. }
  1534. OnExecutedCommand(cmd);
  1535. break;
  1536. case DBType.Oracle:
  1537. if (primaryKeyName != null)
  1538. {
  1539. cmd.CommandText += string.Format(" returning {0} into :newid", EscapeSqlIdentifier(primaryKeyName));
  1540. var param = cmd.CreateParameter();
  1541. param.ParameterName = ":newid";
  1542. param.Value = DBNull.Value;
  1543. param.Direction = ParameterDirection.ReturnValue;
  1544. param.DbType = DbType.Int64;
  1545. cmd.Parameters.Add(param);
  1546. DoPreExecute(cmd);
  1547. cmd.ExecuteNonQuery();
  1548. id = param.Value;
  1549. }
  1550. else
  1551. {
  1552. id = -1;
  1553. DoPreExecute(cmd);
  1554. cmd.ExecuteNonQuery();
  1555. }
  1556. OnExecutedCommand(cmd);
  1557. break;
  1558. case DBType.SQLite:
  1559. if (primaryKeyName != null)
  1560. {
  1561. cmd.CommandText += ";\nSELECT last_insert_rowid();";
  1562. DoPreExecute(cmd);
  1563. id = cmd.ExecuteScalar();
  1564. }
  1565. else
  1566. {
  1567. id = -1;
  1568. DoPreExecute(cmd);
  1569. cmd.ExecuteNonQuery();
  1570. }
  1571. OnExecutedCommand(cmd);
  1572. break;
  1573. default:
  1574. cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;";
  1575. DoPreExecute(cmd);
  1576. id = cmd.ExecuteScalar();
  1577. OnExecutedCommand(cmd);
  1578. break;
  1579. }
  1580. // Assign the ID back to the primary key property
  1581. if (primaryKeyName != null)
  1582. {
  1583. PocoColumn pc;
  1584. if (pd.Columns.TryGetValue(primaryKeyName, out pc))
  1585. {
  1586. pc.SetValue(poco, pc.ChangeType(id));
  1587. }
  1588. }
  1589. }
  1590. // Assign the Version column
  1591. if (!string.IsNullOrEmpty(versionName))
  1592. {
  1593. PocoColumn pc;
  1594. if (pd.Columns.TryGetValue(versionName, out pc))
  1595. {
  1596. pc.SetValue(poco, pc.ChangeType(1));
  1597. }
  1598. }
  1599. return id;
  1600. }
  1601. }
  1602. finally
  1603. {
  1604. CloseSharedConnection();
  1605. }
  1606. }
  1607. catch (Exception x)
  1608. {
  1609. OnException(x);
  1610. throw;
  1611. }
  1612. }
  1613. // Insert an annotated poco object
  1614. public object Insert(object poco)
  1615. {
  1616. var pd = PocoData.ForType(poco.GetType());
  1617. return Insert(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, poco);
  1618. }
  1619. public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue)
  1620. {
  1621. return Update(tableName, primaryKeyName, poco, primaryKeyValue, null);
  1622. }
  1623. // Update a record with values from a poco. primary key value can be either supplied or read from the poco
  1624. public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable<string> columns)
  1625. {
  1626. if (columns != null && !columns.Any())
  1627. return 0;
  1628. var sb = new StringBuilder();
  1629. var index = 0;
  1630. var rawvalues = new List<object>();
  1631. var pd = PocoData.ForObject(poco,primaryKeyName);
  1632. string versionName = null;
  1633. object versionValue = null;
  1634. var primaryKeyValuePairs = GetPrimaryKeyValues(primaryKeyName, primaryKeyValue);
  1635. foreach (var i in pd.Columns)
  1636. {
  1637. // Don't update the primary key, but grab the value if we don't have it
  1638. if (primaryKeyValue == null && primaryKeyValuePairs.ContainsKey(i.Key))
  1639. {
  1640. primaryKeyValuePairs[i.Key] = i.Value.GetValue(poco);
  1641. continue;
  1642. }
  1643. // Dont update result only columns
  1644. if (i.Value.ResultColumn)
  1645. continue;
  1646. if (!i.Value.VersionColumn && columns != null && !columns.Contains(i.Value.ColumnName, StringComparer.OrdinalIgnoreCase))
  1647. continue;
  1648. object value = i.Value.GetValue(poco);
  1649. if (i.Value.VersionColumn)
  1650. {
  1651. versionName = i.Key;
  1652. versionValue = value;
  1653. value = Convert.ToInt64(value) + 1;
  1654. }
  1655. // Build the sql
  1656. if (index > 0)
  1657. sb.Append(", ");
  1658. sb.AppendFormat("{0} = @{1}", EscapeSqlIdentifier(i.Key), index++);
  1659. rawvalues.Add(value);
  1660. }
  1661. if (columns != null && columns.Any() && sb.Length == 0)
  1662. throw new ArgumentException("There were no columns in the columns list that matched your table", "columns");
  1663. var sql = string.Format("UPDATE {0} SET {1} WHERE {2}", EscapeTableName(tableName), sb, BuildPrimaryKeySql(primaryKeyValuePairs, ref index));
  1664. rawvalues.AddRange(primaryKeyValuePairs.Select(keyValue => keyValue.Value));
  1665. if (!string.IsNullOrEmpty(versionName))
  1666. {
  1667. sql += string.Format(" AND {0} = @{1}", EscapeSqlIdentifier(versionName), index++);
  1668. rawvalues.Add(versionValue);
  1669. }
  1670. var result = Execute(sql, rawvalues.ToArray());
  1671. if (result == 0 && !string.IsNullOrEmpty(versionName) && VersionException == VersionExceptionHandling.Exception)
  1672. {
  1673. throw new DBConcurrencyException(string.Format("A Concurrency update occurred in table '{0}' for primary key value(s) = '{1}' and version = '{2}'", tableName, string.Join(",", primaryKeyValuePairs.Values.Select(x => x.ToString()).ToArray()), versionValue));
  1674. }
  1675. // Set Version
  1676. if (!string.IsNullOrEmpty(versionName))
  1677. {
  1678. PocoColumn pc;
  1679. if (pd.Columns.TryGetValue(versionName, out pc))
  1680. {
  1681. pc.SetValue(poco, Convert.ChangeType(Convert.ToInt64(versionValue) + 1, pc.PropertyInfo.PropertyType));
  1682. }
  1683. }
  1684. return result;
  1685. }
  1686. private string BuildPrimaryKeySql(Dictionary<string, object> primaryKeyValuePair, ref int index)
  1687. {
  1688. var tempIndex = index;
  1689. index += primaryKeyValuePair.Count;
  1690. return string.Join(" AND ", primaryKeyValuePair.Select((x, i) => string.Format("{0} = @{1}", EscapeSqlIdentifier(x.Key), tempIndex + i)).ToArray());
  1691. }
  1692. private Dictionary<string, object> GetPrimaryKeyValues(string primaryKeyName, object primaryKeyValue)
  1693. {
  1694. Dictionary<string, object> primaryKeyValues;
  1695. var multiplePrimaryKeysNames = primaryKeyName.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray();
  1696. if (primaryKeyValue != null)
  1697. {
  1698. if (multiplePrimaryKeysNames.Length == 1)
  1699. primaryKeyValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase) { { primaryKeyName, primaryKeyValue } };
  1700. else
  1701. primaryKeyValues = multiplePrimaryKeysNames.ToDictionary(x => x,
  1702. x => primaryKeyValue.GetType().GetProperties()
  1703. .Where(y => string.Equals(x, y.Name, StringComparison.OrdinalIgnoreCase))
  1704. .Single().GetValue(primaryKeyValue, null), StringComparer.OrdinalIgnoreCase);
  1705. }
  1706. else
  1707. {
  1708. primaryKeyValues = multiplePrimaryKeysNames.ToDictionary(x => x, x => (object)null, StringComparer.OrdinalIgnoreCase);
  1709. }
  1710. return primaryKeyValues;
  1711. }
  1712. public int Update(string tableName, string primaryKeyName, object poco)
  1713. {
  1714. return Update(tableName, primaryKeyName, poco, null);
  1715. }
  1716. public int Update(string tableName, string primaryKeyName, object poco, IEnumerable<string> columns)
  1717. {
  1718. return Update(tableName, primaryKeyName, poco, null, columns);
  1719. }
  1720. public int Update(object poco, IEnumerable<string> columns)
  1721. {
  1722. return Update(poco, null, columns);
  1723. }
  1724. public int Update(object poco)
  1725. {
  1726. return Update(poco, null, null);
  1727. }
  1728. public int Update(object poco, object primaryKeyValue)
  1729. {
  1730. return Update(poco, primaryKeyValue, null);
  1731. }
  1732. public int Update(object poco, object primaryKeyValue, IEnumerable<string> columns)
  1733. {
  1734. var pd = PocoData.ForType(poco.GetType());
  1735. return Update(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco, primaryKeyValue, columns);
  1736. }
  1737. public int Update<T>(string sql, params object[] args)
  1738. {
  1739. var pd = PocoData.ForType(typeof(T));
  1740. return Execute(string.Format("UPDATE {0} {1}", EscapeTableName(pd.TableInfo.TableName), sql), args);
  1741. }
  1742. public int Update<T>(Sql sql)
  1743. {
  1744. var pd = PocoData.ForType(typeof(T));
  1745. return Execute(new Sql(string.Format("UPDATE {0}", EscapeTableName(pd.TableInfo.TableName))).Append(sql));
  1746. }
  1747. public int Delete(string tableName, string primaryKeyName, object poco)
  1748. {
  1749. return Delete(tableName, primaryKeyName, poco, null);
  1750. }
  1751. public int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue)
  1752. {
  1753. var primaryKeyValuePairs = GetPrimaryKeyValues(primaryKeyName, primaryKeyValue);
  1754. // If primary key value not specified, pick it up from the object
  1755. if (primaryKeyValue == null)
  1756. {
  1757. var pd = PocoData.ForObject(poco, primaryKeyName);
  1758. foreach (var i in pd.Columns)
  1759. {
  1760. if (primaryKeyValuePairs.ContainsKey(i.Key))
  1761. {
  1762. primaryKeyValuePairs[i.Key] = i.Value.GetValue(poco);
  1763. }
  1764. }
  1765. }
  1766. // Do it
  1767. var index = 0;
  1768. var sql = string.Format("DELETE FROM {0} WHERE {1}", EscapeTableName(tableName), BuildPrimaryKeySql(primaryKeyValuePairs, ref index));
  1769. return Execute(sql, primaryKeyValuePairs.Select(x=>x.Value).ToArray());
  1770. }
  1771. public int Delete(object poco)
  1772. {
  1773. var pd = PocoData.ForType(poco.GetType());
  1774. return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco);
  1775. }
  1776. public int Delete<T>(object pocoOrPrimaryKey)
  1777. {
  1778. if (pocoOrPrimaryKey.GetType() == typeof(T))
  1779. return Delete(pocoOrPrimaryKey);
  1780. var pd = PocoData.ForType(typeof(T));
  1781. return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, null, pocoOrPrimaryKey);
  1782. }
  1783. public int Delete<T>(string sql, params object[] args)
  1784. {
  1785. var pd = PocoData.ForType(typeof(T));
  1786. return Execute(string.Format("DELETE FROM {0} {1}", EscapeTableName(pd.TableInfo.TableName), sql), args);
  1787. }
  1788. public int Delete<T>(Sql sql)
  1789. {
  1790. var pd = PocoData.ForType(typeof(T));
  1791. return Execute(new Sql(string.Format("DELETE FROM {0}", EscapeTableName(pd.TableInfo.TableName))).Append(sql));
  1792. }
  1793. // Check if a poco represents a new record
  1794. public bool IsNew(string primaryKeyName, object poco)
  1795. {
  1796. var pd = PocoData.ForObject(poco, primaryKeyName);
  1797. object pk;
  1798. PocoColumn pc;
  1799. if (pd.Columns.TryGetValue(primaryKeyName, out pc))
  1800. {
  1801. pk = pc.GetValue(poco);
  1802. }
  1803. #if !PETAPOCO_NO_DYNAMIC
  1804. else if (poco.GetType() == typeof(System.Dynamic.ExpandoObject))
  1805. {
  1806. return true;
  1807. }
  1808. #endif
  1809. else
  1810. {
  1811. var pi = poco.GetType().GetProperty(primaryKeyName);
  1812. if (pi == null)
  1813. throw new ArgumentException(string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName));
  1814. pk = pi.GetValue(poco, null);
  1815. }
  1816. if (pk == null)
  1817. return true;
  1818. var type = pk.GetType();
  1819. if (type.IsValueType)
  1820. {
  1821. // Common primary key types
  1822. if (type == typeof(long))
  1823. return (long)pk == default(long);
  1824. else if (type == typeof(ulong))
  1825. return (ulong)pk == default(ulong);
  1826. else if (type == typeof(int))
  1827. return (int)pk == default(int);
  1828. else if (type == typeof(uint))
  1829. return (uint)pk == default(uint);
  1830. else if (type == typeof(Guid))
  1831. return (Guid)pk == default(Guid);
  1832. // Create a default instance and compare
  1833. return pk == Activator.CreateInstance(pk.GetType());
  1834. }
  1835. else
  1836. {
  1837. return pk == null;
  1838. }
  1839. }
  1840. public bool IsNew(object poco)
  1841. {
  1842. var pd = PocoData.ForType(poco.GetType());
  1843. if (!pd.TableInfo.AutoIncrement)
  1844. throw new InvalidOperationException("IsNew() and Save() are only supported on tables with auto-increment/identity primary key columns");
  1845. return IsNew(pd.TableInfo.PrimaryKey, poco);
  1846. }
  1847. // Insert new record or Update existing record
  1848. public void Save(string tableName, string primaryKeyName, object poco)
  1849. {
  1850. if (IsNew(primaryKeyName, poco))
  1851. {
  1852. Insert(tableName, primaryKeyName, true, poco);
  1853. }
  1854. else
  1855. {
  1856. Update(tableName, primaryKeyName, poco);
  1857. }
  1858. }
  1859. public void Save(object poco)
  1860. {
  1861. var pd = PocoData.ForType(poco.GetType());
  1862. Save(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco);
  1863. }
  1864. public int CommandTimeout { get; set; }
  1865. public int OneTimeCommandTimeout { get; set; }
  1866. void DoPreExecute(IDbCommand cmd)
  1867. {
  1868. // Setup command timeout
  1869. if (OneTimeCommandTimeout != 0)
  1870. {
  1871. cmd.CommandTimeout = OneTimeCommandTimeout;
  1872. OneTimeCommandTimeout = 0;
  1873. }
  1874. else if (CommandTimeout!=0)
  1875. {
  1876. cmd.CommandTimeout = CommandTimeout;
  1877. }
  1878. // Call hook
  1879. OnExecutingCommand(cmd);
  1880. // Save it
  1881. _lastSql = cmd.CommandText;
  1882. _lastArgs = (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray();
  1883. }
  1884. public string LastSQL { get { return _lastSql; } }
  1885. public object[] LastArgs { get { return _lastArgs; } }
  1886. public string LastCommand
  1887. {
  1888. get { return FormatCommand(_lastSql, _lastArgs); }
  1889. }
  1890. public string FormatCommand(IDbCommand cmd)
  1891. {
  1892. return FormatCommand(cmd.CommandText, (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray());
  1893. }
  1894. public string FormatCommand(string sql, object[] args)
  1895. {
  1896. var sb = new StringBuilder();
  1897. if (sql == null)
  1898. return "";
  1899. sb.Append(sql);
  1900. if (args != null && args.Length > 0)
  1901. {
  1902. sb.Append("\n");
  1903. for (int i = 0; i < args.Length; i++)
  1904. {
  1905. sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, args[i].GetType().Name, args[i]);
  1906. }
  1907. sb.Remove(sb.Length - 1, 1);
  1908. }
  1909. return sb.ToString();
  1910. }
  1911. public enum VersionExceptionHandling
  1912. {
  1913. Ignore,
  1914. Exception
  1915. }
  1916. public static IMapper Mapper
  1917. {
  1918. get;
  1919. set;
  1920. }
  1921. public class PocoColumn
  1922. {
  1923. public string ColumnName;
  1924. public PropertyInfo PropertyInfo;
  1925. public bool ResultColumn;
  1926. public bool VersionColumn;
  1927. public virtual void SetValue(object target, object val) { PropertyInfo.SetValue(target, val, null); }
  1928. public virtual object GetValue(object target) { return PropertyInfo.GetValue(target, null); }
  1929. public virtual object ChangeType(object val) { return Convert.ChangeType(val, PropertyInfo.PropertyType); }
  1930. }
  1931. public class ExpandoColumn : PocoColumn
  1932. {
  1933. public override void SetValue(object target, object val) { ((IDictionary<string, object>) target)[ColumnName]=val; }
  1934. public override object GetValue(object target)
  1935. {
  1936. object val=null;
  1937. ((IDictionary<string, object>) target).TryGetValue(ColumnName, out val);
  1938. return val;
  1939. }
  1940. public override object ChangeType(object val) { return val; }
  1941. }
  1942. public static Func<Type, PocoData> PocoDataFactory = type => new PocoData(type);
  1943. public class PocoData
  1944. {
  1945. static readonly EnumMapper EnumMapper = new EnumMapper();
  1946. public static PocoData ForObject(object o, string primaryKeyName)
  1947. {
  1948. var t = o.GetType();
  1949. #if !PETAPOCO_NO_DYNAMIC
  1950. if (t == typeof(System.Dynamic.ExpandoObject))
  1951. {
  1952. var pd = new PocoData();
  1953. pd.TableInfo = new TableInfo();
  1954. pd.Columns = new Dictionary<string, PocoColumn>(StringComparer.OrdinalIgnoreCase);
  1955. pd.Columns.Add(primaryKeyName, new ExpandoColumn() { ColumnName = primaryKeyName });
  1956. pd.TableInfo.PrimaryKey = primaryKeyName;
  1957. pd.TableInfo.AutoIncrement = true;
  1958. foreach (var col in ((IDictionary<string, object>) o).Keys)
  1959. {
  1960. if (col!=primaryKeyName)
  1961. pd.Columns.Add(col, new ExpandoColumn() { ColumnName = col });
  1962. }
  1963. return pd;
  1964. }
  1965. else
  1966. #endif
  1967. return ForType(t);
  1968. }
  1969. static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim();
  1970. public static PocoData ForType(Type t)
  1971. {
  1972. #if !PETAPOCO_NO_DYNAMIC
  1973. if (t == typeof(System.Dynamic.ExpandoObject))
  1974. throw new InvalidOperationException("Can't use dynamic types with this method");
  1975. #endif
  1976. // Check cache
  1977. RWLock.EnterReadLock();
  1978. PocoData pd;
  1979. try
  1980. {
  1981. if (m_PocoDatas.TryGetValue(t, out pd))
  1982. return pd;
  1983. }
  1984. finally
  1985. {
  1986. RWLock.ExitReadLock();
  1987. }
  1988. // Cache it
  1989. RWLock.EnterWriteLock();
  1990. try
  1991. {
  1992. // Check again
  1993. if (m_PocoDatas.TryGetValue(t, out pd))
  1994. return pd;
  1995. // Create it
  1996. pd = PocoDataFactory(t);
  1997. m_PocoDatas.Add(t, pd);
  1998. }
  1999. finally
  2000. {
  2001. RWLock.ExitWriteLock();
  2002. }
  2003. return pd;
  2004. }
  2005. public PocoData()
  2006. {
  2007. }
  2008. public PocoData(Type t)
  2009. {
  2010. type = t;
  2011. TableInfo=new TableInfo();
  2012. // Get the table name
  2013. var a = t.GetCustomAttributes(typeof(TableNameAttribute), true);
  2014. TableInfo.TableName = a.Length == 0 ? t.Name : (a[0] as TableNameAttribute).Value;
  2015. // Get the primary key
  2016. a = t.GetCustomAttributes(typeof(PrimaryKeyAttribute), true);
  2017. TableInfo.PrimaryKey = a.Length == 0 ? "ID" : (a[0] as PrimaryKeyAttribute).Value;
  2018. TableInfo.SequenceName = a.Length == 0 ? null : (a[0] as PrimaryKeyAttribute).sequenceName;
  2019. TableInfo.AutoIncrement = a.Length == 0 ? true : (a[0] as PrimaryKeyAttribute).autoIncrement;
  2020. // Set autoincrement false if primary key has multiple columns
  2021. TableInfo.AutoIncrement = TableInfo.AutoIncrement ? !TableInfo.PrimaryKey.Contains(',') : TableInfo.AutoIncrement;
  2022. // Call column mapper
  2023. if (Database.Mapper != null)
  2024. Database.Mapper.GetTableInfo(t, TableInfo);
  2025. // Work out bound properties
  2026. bool ExplicitColumns = t.GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Length > 0;
  2027. Columns = new Dictionary<string, PocoColumn>(StringComparer.OrdinalIgnoreCase);
  2028. foreach (var pi in t.GetProperties())
  2029. {
  2030. // Work out if properties is to be included
  2031. var ColAttrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true);
  2032. if (ExplicitColumns)
  2033. {
  2034. if (ColAttrs.Length == 0)
  2035. continue;
  2036. }
  2037. else
  2038. {
  2039. if (pi.GetCustomAttributes(typeof(IgnoreAttribute), true).Length != 0)
  2040. continue;
  2041. }
  2042. var pc = new PocoColumn();
  2043. pc.PropertyInfo = pi;
  2044. // Work out the DB column name
  2045. if (ColAttrs.Length > 0)
  2046. {
  2047. var colattr = (ColumnAttribute)ColAttrs[0];
  2048. pc.ColumnName = colattr.Name;
  2049. if ((colattr as ResultColumnAttribute) != null)
  2050. pc.ResultColumn = true;
  2051. if ((colattr as VersionColumnAttribute) != null)
  2052. pc.VersionColumn = true;
  2053. }
  2054. if (pc.ColumnName == null)
  2055. {
  2056. pc.ColumnName = pi.Name;
  2057. if (Database.Mapper != null && !Database.Mapper.MapPropertyToColumn(pi, ref pc.ColumnName, ref pc.ResultColumn))
  2058. continue;
  2059. }
  2060. // Store it
  2061. Columns.Add(pc.ColumnName, pc);
  2062. }
  2063. // Build column list for automatic select
  2064. QueryColumns = (from c in Columns where !c.Value.ResultColumn select c.Key).ToArray();
  2065. }
  2066. static bool IsIntegralType(Type t)
  2067. {
  2068. var tc = Type.GetTypeCode(t);
  2069. return tc >= TypeCode.SByte && tc <= TypeCode.UInt64;
  2070. }
  2071. static object GetDefault(Type type)
  2072. {
  2073. if (type.IsValueType)
  2074. {
  2075. return Activator.CreateInstance(type);
  2076. }
  2077. return null;
  2078. }
  2079. // Create factory function that can convert a IDataReader record into a POCO
  2080. public Delegate GetFactory(string sql, string connString, bool ForceDateTimesToUtc, int firstColumn, int countColumns, IDataReader r, object instance)
  2081. {
  2082. // Check cache
  2083. var key = string.Format("{0}:{1}:{2}:{3}:{4}:{5}", sql, connString, ForceDateTimesToUtc, firstColumn, countColumns, instance != GetDefault(type));
  2084. RWLock.EnterReadLock();
  2085. try
  2086. {
  2087. // Have we already created it?
  2088. Delegate factory;
  2089. if (PocoFactories.TryGetValue(key, out factory))
  2090. return factory;
  2091. }
  2092. finally
  2093. {
  2094. RWLock.ExitReadLock();
  2095. }
  2096. // Take the writer lock
  2097. RWLock.EnterWriteLock();
  2098. try
  2099. {
  2100. // Check again, just in case
  2101. Delegate factory;
  2102. if (PocoFactories.TryGetValue(key, out factory))
  2103. return factory;
  2104. // Create the method
  2105. var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), type, new Type[] { typeof(IDataReader), type }, true);
  2106. var il = m.GetILGenerator();
  2107. #if !PETAPOCO_NO_DYNAMIC
  2108. if (type == typeof(object))
  2109. {
  2110. // var poco=new T()
  2111. il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj
  2112. MethodInfo fnAdd = typeof(IDictionary<string, object>).GetMethod("Add");
  2113. // Enumerate all fields generating a set assignment for the column
  2114. for (int i = firstColumn; i < firstColumn + countColumns; i++)
  2115. {
  2116. var srcType = r.GetFieldType(i);
  2117. il.Emit(OpCodes.Dup); // obj, obj
  2118. il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname
  2119. // Get the converter
  2120. Func<object, object> converter = null;
  2121. if (Database.Mapper != null)
  2122. converter = Database.Mapper.GetFromDbConverter(null, srcType);
  2123. if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime))
  2124. converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); };
  2125. // Setup stack for call to converter
  2126. AddConverterToStack(il, converter);
  2127. // r[i]
  2128. il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr
  2129. il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i
  2130. il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value
  2131. // Convert DBNull to null
  2132. il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value
  2133. il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null)
  2134. var lblNotNull = il.DefineLabel();
  2135. il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value
  2136. il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter?
  2137. if (converter!=null)
  2138. il.Emit(OpCodes.Pop); // obj, obj, fieldname,
  2139. il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null
  2140. if (converter != null)
  2141. {
  2142. var lblReady = il.DefineLabel();
  2143. il.Emit(OpCodes.Br_S, lblReady);
  2144. il.MarkLabel(lblNotNull);
  2145. il.Emit(OpCodes.Callvirt, fnInvoke);
  2146. il.MarkLabel(lblReady);
  2147. }
  2148. else
  2149. {
  2150. il.MarkLabel(lblNotNull);
  2151. }
  2152. il.Emit(OpCodes.Callvirt, fnAdd);
  2153. }
  2154. }
  2155. else
  2156. #endif
  2157. if (type.IsValueType || type == typeof(string) || type == typeof(byte[]))
  2158. {
  2159. // Do we need to install a converter?
  2160. var srcType = r.GetFieldType(0);
  2161. var converter = GetConverter(ForceDateTimesToUtc, null, srcType, type);
  2162. // "if (!rdr.IsDBNull(i))"
  2163. il.Emit(OpCodes.Ldarg_0); // rdr
  2164. il.Emit(OpCodes.Ldc_I4_0); // rdr,0
  2165. il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool
  2166. var lblCont = il.DefineLabel();
  2167. il.Emit(OpCodes.Brfalse_S, lblCont);
  2168. il.Emit(OpCodes.Ldnull); // null
  2169. var lblFin = il.DefineLabel();
  2170. il.Emit(OpCodes.Br_S, lblFin);
  2171. il.MarkLabel(lblCont);
  2172. // Setup stack for call to converter
  2173. AddConverterToStack(il, converter);
  2174. il.Emit(OpCodes.Ldarg_0); // rdr
  2175. il.Emit(OpCodes.Ldc_I4_0); // rdr,0
  2176. il.Emit(OpCodes.Callvirt, fnGetValue); // value
  2177. // Call the converter
  2178. if (converter != null)
  2179. il.Emit(OpCodes.Callvirt, fnInvoke);
  2180. il.MarkLabel(lblFin);
  2181. il.Emit(OpCodes.Unbox_Any, type); // value converted
  2182. }
  2183. else if (type == typeof(Dictionary<string, object>))
  2184. {
  2185. Func<IDataReader, object, Dictionary<string, object>> func = (reader, inst) =>
  2186. {
  2187. var dict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
  2188. for (int i = firstColumn; i < firstColumn + countColumns; i++)
  2189. {
  2190. var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
  2191. var name = reader.GetName(i);
  2192. if (!dict.ContainsKey(name))
  2193. dict.Add(name, value);
  2194. }
  2195. return dict;
  2196. };
  2197. var delegateType = typeof(Func<,,>).MakeGenericType(typeof(IDataReader), type, typeof(Dictionary<string, object>));
  2198. var localDel = Delegate.CreateDelegate(delegateType, func.Target, func.Method);
  2199. PocoFactories.Add(key, localDel);
  2200. return localDel;
  2201. }
  2202. else if (type == typeof(object[]))
  2203. {
  2204. Func<IDataReader, object, object[]> func = (reader, inst) =>
  2205. {
  2206. var obj = new object[countColumns - firstColumn];
  2207. for (int i = firstColumn; i < firstColumn + countColumns; i++)
  2208. {
  2209. var value = reader.IsDBNull(i) ? null : reader.GetValue(i);
  2210. obj[i - firstColumn] = value;
  2211. }
  2212. return obj;
  2213. };
  2214. var delegateType = typeof(Func<,,>).MakeGenericType(typeof(IDataReader), type, typeof(object[]));
  2215. var localDel = Delegate.CreateDelegate(delegateType, func.Target, func.Method);
  2216. PocoFactories.Add(key, localDel);
  2217. return localDel;
  2218. }
  2219. else
  2220. {
  2221. if (instance != null)
  2222. il.Emit(OpCodes.Ldarg_1);
  2223. else
  2224. // var poco=new T()
  2225. il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
  2226. // Enumerate all fields generating a set assignment for the column
  2227. for (int i = firstColumn; i < firstColumn + countColumns; i++)
  2228. {
  2229. // Get the PocoColumn for this db column, ignore if not known
  2230. PocoColumn pc;
  2231. if (!Columns.TryGetValue(r.GetName(i), out pc) && !Columns.TryGetValue(r.GetName(i).Replace("_", ""), out pc))
  2232. {
  2233. continue;
  2234. }
  2235. // Get the source type for this column
  2236. var srcType = r.GetFieldType(i);
  2237. var dstType = pc.PropertyInfo.PropertyType;
  2238. // "if (!rdr.IsDBNull(i))"
  2239. il.Emit(OpCodes.Ldarg_0); // poco,rdr
  2240. il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i
  2241. il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool
  2242. var lblNext = il.DefineLabel();
  2243. il.Emit(OpCodes.Brtrue_S, lblNext); // poco
  2244. il.Emit(OpCodes.Dup); // poco,poco
  2245. // Do we need to install a converter?
  2246. var converter = GetConverter(ForceDateTimesToUtc, pc, srcType, dstType);
  2247. // Fast
  2248. bool Handled = false;
  2249. if (converter == null)
  2250. {
  2251. var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) });
  2252. if (valuegetter != null
  2253. && valuegetter.ReturnType == srcType
  2254. && (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType)))
  2255. {
  2256. il.Emit(OpCodes.Ldarg_0); // *,rdr
  2257. il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
  2258. il.Emit(OpCodes.Callvirt, valuegetter); // *,value
  2259. // Convert to Nullable
  2260. if (Nullable.GetUnderlyingType(dstType) != null)
  2261. {
  2262. il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) }));
  2263. }
  2264. il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
  2265. Handled = true;
  2266. }
  2267. }
  2268. // Not so fast
  2269. if (!Handled)
  2270. {
  2271. // Setup stack for call to converter
  2272. AddConverterToStack(il, converter);
  2273. // "value = rdr.GetValue(i)"
  2274. il.Emit(OpCodes.Ldarg_0); // *,rdr
  2275. il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
  2276. il.Emit(OpCodes.Callvirt, fnGetValue); // *,value
  2277. // Call the converter
  2278. if (converter != null)
  2279. il.Emit(OpCodes.Callvirt, fnInvoke);
  2280. // Assign it
  2281. il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value
  2282. il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
  2283. }
  2284. il.MarkLabel(lblNext);
  2285. }
  2286. var fnOnLoaded = RecurseInheritedTypes<MethodInfo>(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
  2287. if (fnOnLoaded != null)
  2288. {
  2289. il.Emit(OpCodes.Dup);
  2290. il.Emit(OpCodes.Callvirt, fnOnLoaded);
  2291. }
  2292. }
  2293. il.Emit(OpCodes.Ret);
  2294. // Cache it, return it
  2295. var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type, type));
  2296. PocoFactories.Add(key, del);
  2297. return del;
  2298. }
  2299. finally
  2300. {
  2301. RWLock.ExitWriteLock();
  2302. }
  2303. }
  2304. private static void AddConverterToStack(ILGenerator il, Func<object, object> converter)
  2305. {
  2306. if (converter != null)
  2307. {
  2308. // Add the converter
  2309. int converterIndex = m_Converters.Count;
  2310. m_Converters.Add(converter);
  2311. // Generate IL to push the converter onto the stack
  2312. il.Emit(OpCodes.Ldsfld, fldConverters);
  2313. il.Emit(OpCodes.Ldc_I4, converterIndex);
  2314. il.Emit(OpCodes.Callvirt, fnListGetItem); // Converter
  2315. }
  2316. }
  2317. public static Func<object, object> GetConverter(bool forceDateTimesToUtc, PocoColumn pc, Type srcType, Type dstType)
  2318. {
  2319. Func<object, object> converter = null;
  2320. // Get converter from the mapper
  2321. if (Database.Mapper != null)
  2322. {
  2323. if (pc != null)
  2324. {
  2325. converter = Database.Mapper.GetFromDbConverter(pc.PropertyInfo, srcType);
  2326. }
  2327. else
  2328. {
  2329. var m2 = Database.Mapper as IMapper2;
  2330. if (m2 != null)
  2331. {
  2332. converter = m2.GetFromDbConverter(dstType, srcType);
  2333. }
  2334. }
  2335. }
  2336. // Standard DateTime->Utc mapper
  2337. if (forceDateTimesToUtc && converter == null && srcType == typeof(DateTime) && (dstType == typeof(DateTime) || dstType == typeof(DateTime?)))
  2338. {
  2339. converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); };
  2340. }
  2341. // Forced type conversion including integral types -> enum
  2342. if (converter == null)
  2343. {
  2344. if (dstType.IsEnum && IsIntegralType(srcType))
  2345. {
  2346. if (srcType != typeof(int))
  2347. {
  2348. converter = src => Convert.ChangeType(src, typeof (int), null);
  2349. }
  2350. }
  2351. else if (!dstType.IsAssignableFrom(srcType))
  2352. {
  2353. if (dstType.IsEnum && srcType == typeof(string))
  2354. {
  2355. converter = src => EnumMapper.EnumFromString(dstType, (string) src);
  2356. }
  2357. else
  2358. {
  2359. converter = src => Convert.ChangeType(src, dstType, null);
  2360. }
  2361. }
  2362. }
  2363. return converter;
  2364. }
  2365. static T RecurseInheritedTypes<T>(Type t, Func<Type, T> cb)
  2366. {
  2367. while (t != null)
  2368. {
  2369. T info = cb(t);
  2370. if (info != null)
  2371. return info;
  2372. t = t.BaseType;
  2373. }
  2374. return default(T);
  2375. }
  2376. static Dictionary<Type, PocoData> m_PocoDatas = new Dictionary<Type, PocoData>();
  2377. static List<Func<object, object>> m_Converters = new List<Func<object, object>>();
  2378. static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) });
  2379. static MethodInfo fnIsDBNull = typeof(IDataRecord).GetMethod("IsDBNull");
  2380. static FieldInfo fldConverters = typeof(PocoData).GetField("m_Converters", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic);
  2381. static MethodInfo fnListGetItem = typeof(List<Func<object, object>>).GetProperty("Item").GetGetMethod();
  2382. static MethodInfo fnInvoke = typeof(Func<object, object>).GetMethod("Invoke");
  2383. public Type type;
  2384. public string[] QueryColumns { get; protected set; }
  2385. public TableInfo TableInfo { get; protected set; }
  2386. public Dictionary<string, PocoColumn> Columns { get; protected set; }
  2387. Dictionary<string, Delegate> PocoFactories = new Dictionary<string, Delegate>();
  2388. }
  2389. class EnumMapper : IDisposable
  2390. {
  2391. readonly Dictionary<Type, Dictionary<string, object>> _stringsToEnums = new Dictionary<Type, Dictionary<string, object>>();
  2392. readonly Dictionary<Type, Dictionary<int, string>> _enumNumbersToStrings = new Dictionary<Type, Dictionary<int, string>>();
  2393. readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
  2394. public object EnumFromString(Type type, string value)
  2395. {
  2396. PopulateIfNotPresent(type);
  2397. return _stringsToEnums[type][value];
  2398. }
  2399. public string StringFromEnum(object theEnum)
  2400. {
  2401. Type typeOfEnum = theEnum.GetType();
  2402. PopulateIfNotPresent(typeOfEnum);
  2403. return _enumNumbersToStrings[typeOfEnum][(int)theEnum];
  2404. }
  2405. void PopulateIfNotPresent(Type type)
  2406. {
  2407. _lock.EnterUpgradeableReadLock();
  2408. try
  2409. {
  2410. if (!_stringsToEnums.ContainsKey(type))
  2411. {
  2412. _lock.EnterWriteLock();
  2413. try
  2414. {
  2415. Populate(type);
  2416. }
  2417. finally
  2418. {
  2419. _lock.ExitWriteLock();
  2420. }
  2421. }
  2422. }
  2423. finally
  2424. {
  2425. _lock.ExitUpgradeableReadLock();
  2426. }
  2427. }
  2428. void Populate(Type type)
  2429. {
  2430. Array values = Enum.GetValues(type);
  2431. _stringsToEnums[type] = new Dictionary<string, object>(values.Length);
  2432. _enumNumbersToStrings[type] = new Dictionary<int, string>(values.Length);
  2433. for (int i = 0; i < values.Length; i++)
  2434. {
  2435. object value = values.GetValue(i);
  2436. _stringsToEnums[type].Add(value.ToString(), value);
  2437. _enumNumbersToStrings[type].Add((int)value, value.ToString());
  2438. }
  2439. }
  2440. public void Dispose()
  2441. {
  2442. _lock.Dispose();
  2443. }
  2444. }
  2445. // Member variables
  2446. string _connectionString;
  2447. string _providerName;
  2448. DbProviderFactory _factory;
  2449. IDbConnection _sharedConnection;
  2450. IDbTransaction _transaction;
  2451. int _sharedConnectionDepth;
  2452. int _transactionDepth;
  2453. bool _transactionCancelled;
  2454. string _lastSql;
  2455. object[] _lastArgs;
  2456. string _paramPrefix = "@";
  2457. VersionExceptionHandling _versionException = VersionExceptionHandling.Ignore;
  2458. }
  2459. // Transaction object helps maintain transaction depth counts
  2460. public class Transaction : IDisposable
  2461. {
  2462. public Transaction(Database db) : this(db, null) { }
  2463. public Transaction(Database db, IsolationLevel? isolationLevel)
  2464. {
  2465. _db = db;
  2466. _db.BeginTransaction(isolationLevel);
  2467. }
  2468. public virtual void Complete()
  2469. {
  2470. _db.CompleteTransaction();
  2471. _db = null;
  2472. }
  2473. public void Dispose()
  2474. {
  2475. if (_db != null)
  2476. _db.AbortTransaction();
  2477. }
  2478. Database _db;
  2479. }
  2480. // Simple helper class for building SQL statments
  2481. public class Sql
  2482. {
  2483. public Sql()
  2484. {
  2485. }
  2486. public Sql(string sql, params object[] args)
  2487. {
  2488. _sql = sql;
  2489. _args = args;
  2490. }
  2491. public Sql(bool isBuilt, string sql, params object[] args)
  2492. {
  2493. _sql = sql;
  2494. _args = args;
  2495. if (isBuilt)
  2496. {
  2497. _sqlFinal = _sql;
  2498. _argsFinal = _args;
  2499. }
  2500. }
  2501. public static Sql Builder
  2502. {
  2503. get { return new Sql(); }
  2504. }
  2505. string _sql;
  2506. object[] _args;
  2507. Sql _rhs;
  2508. string _sqlFinal;
  2509. object[] _argsFinal;
  2510. private void Build()
  2511. {
  2512. // already built?
  2513. if (_sqlFinal != null)
  2514. return;
  2515. // Build it
  2516. var sb = new StringBuilder();
  2517. var args = new List<object>();
  2518. Build(sb, args, null);
  2519. _sqlFinal = sb.ToString();
  2520. _argsFinal = args.ToArray();
  2521. }
  2522. public string SQL
  2523. {
  2524. get
  2525. {
  2526. Build();
  2527. return _sqlFinal;
  2528. }
  2529. }
  2530. public object[] Arguments
  2531. {
  2532. get
  2533. {
  2534. Build();
  2535. return _argsFinal;
  2536. }
  2537. }
  2538. public Sql Append(Sql sql)
  2539. {
  2540. if (_sqlFinal != null)
  2541. _sqlFinal = null;
  2542. if (_rhs != null)
  2543. {
  2544. _rhs.Append(sql);
  2545. }
  2546. else if (_sql != null)
  2547. {
  2548. _rhs = sql;
  2549. }
  2550. else
  2551. {
  2552. _sql = sql._sql;
  2553. _args = sql._args;
  2554. _rhs = sql._rhs;
  2555. }
  2556. return this;
  2557. }
  2558. public Sql Append(string sql, params object[] args)
  2559. {
  2560. return Append(new Sql(sql, args));
  2561. }
  2562. static bool Is(Sql sql, string sqltype)
  2563. {
  2564. return sql != null && sql._sql != null && sql._sql.StartsWith(sqltype, StringComparison.InvariantCultureIgnoreCase);
  2565. }
  2566. private void Build(StringBuilder sb, List<object> args, Sql lhs)
  2567. {
  2568. if (!String.IsNullOrEmpty(_sql))
  2569. {
  2570. // Add SQL to the string
  2571. if (sb.Length > 0)
  2572. {
  2573. sb.Append("\n");
  2574. }
  2575. var sql = Database.ProcessParams(_sql, _args, args);
  2576. if (Is(lhs, "WHERE ") && Is(this, "WHERE "))
  2577. sql = "AND " + sql.Substring(6);
  2578. if (Is(lhs, "ORDER BY ") && Is(this, "ORDER BY "))
  2579. sql = ", " + sql.Substring(9);
  2580. sb.Append(sql);
  2581. }
  2582. // Now do rhs
  2583. if (_rhs != null)
  2584. _rhs.Build(sb, args, this);
  2585. }
  2586. public Sql Where(string sql, params object[] args)
  2587. {
  2588. return Append(new Sql("WHERE (" + sql + ")", args));
  2589. }
  2590. public Sql OrderBy(params object[] columns)
  2591. {
  2592. return Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray())));
  2593. }
  2594. public Sql Select(params object[] columns)
  2595. {
  2596. return Append(new Sql("SELECT " + String.Join(", ", (from x in columns select x.ToString()).ToArray())));
  2597. }
  2598. public Sql From(params object[] tables)
  2599. {
  2600. return Append(new Sql("FROM " + String.Join(", ", (from x in tables select x.ToString()).ToArray())));
  2601. }
  2602. public Sql GroupBy(params object[] columns)
  2603. {
  2604. return Append(new Sql("GROUP BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray())));
  2605. }
  2606. private SqlJoinClause Join(string JoinType, string table)
  2607. {
  2608. return new SqlJoinClause(Append(new Sql(JoinType + table)));
  2609. }
  2610. public SqlJoinClause InnerJoin(string table) { return Join("INNER JOIN ", table); }
  2611. public SqlJoinClause LeftJoin(string table) { return Join("LEFT JOIN ", table); }
  2612. public class SqlJoinClause
  2613. {
  2614. private readonly Sql _sql;
  2615. public SqlJoinClause(Sql sql)
  2616. {
  2617. _sql = sql;
  2618. }
  2619. public Sql On(string onClause, params object[] args)
  2620. {
  2621. return _sql.Append("ON " + onClause, args);
  2622. }
  2623. }
  2624. public static implicit operator Sql(SqlBuilder.Template template)
  2625. {
  2626. return new Sql(true, template.RawSql, template.Parameters);
  2627. }
  2628. }
  2629. public class SqlBuilder
  2630. {
  2631. Dictionary<string, Clauses> data = new Dictionary<string, Clauses>();
  2632. int seq;
  2633. class Clause
  2634. {
  2635. public string Sql { get; set; }
  2636. public List<object> Parameters { get; set; }
  2637. }
  2638. class Clauses : List<Clause>
  2639. {
  2640. string joiner;
  2641. string prefix;
  2642. string postfix;
  2643. public Clauses(string joiner, string prefix, string postfix)
  2644. {
  2645. this.joiner = joiner;
  2646. this.prefix = prefix;
  2647. this.postfix = postfix;
  2648. }
  2649. public string ResolveClauses(List<object> finalParams)
  2650. {
  2651. foreach (var item in this)
  2652. {
  2653. item.Sql = Database.ProcessParams(item.Sql, item.Parameters.ToArray(), finalParams);
  2654. }
  2655. return prefix + string.Join(joiner, this.Select(c => c.Sql).ToArray()) + postfix;
  2656. }
  2657. }
  2658. public class Template
  2659. {
  2660. readonly string sql;
  2661. readonly SqlBuilder builder;
  2662. private List<object> finalParams = new List<object>();
  2663. int dataSeq;
  2664. public Template(SqlBuilder builder, string sql, params object[] parameters)
  2665. {
  2666. this.sql = Database.ProcessParams(sql, parameters, finalParams);
  2667. this.builder = builder;
  2668. }
  2669. static Regex regex = new Regex(@"\/\*\*.+\*\*\/", RegexOptions.Compiled | RegexOptions.Multiline);
  2670. void ResolveSql()
  2671. {
  2672. rawSql = sql;
  2673. if (dataSeq != builder.seq)
  2674. {
  2675. foreach (var pair in builder.data)
  2676. {
  2677. rawSql = rawSql.Replace("/**" + pair.Key + "**/", pair.Value.ResolveClauses(finalParams));
  2678. }
  2679. ReplaceDefaults();
  2680. dataSeq = builder.seq;
  2681. }
  2682. if (builder.seq == 0)
  2683. {
  2684. ReplaceDefaults();
  2685. }
  2686. }
  2687. private void ReplaceDefaults()
  2688. {
  2689. foreach (var pair in builder.defaultsIfEmpty)
  2690. {
  2691. rawSql = rawSql.Replace("/**" + pair.Key + "**/", " " + pair.Value + " ");
  2692. }
  2693. // replace all that is left with empty
  2694. rawSql = regex.Replace(rawSql, "");
  2695. }
  2696. string rawSql;
  2697. public string RawSql { get { ResolveSql(); return rawSql; } }
  2698. public object[] Parameters { get { ResolveSql(); return finalParams.ToArray(); } }
  2699. }
  2700. public SqlBuilder()
  2701. {
  2702. }
  2703. public Template AddTemplate(string sql, params object[] parameters)
  2704. {
  2705. return new Template(this, sql, parameters);
  2706. }
  2707. void AddClause(string name, string sql, object[] parameters, string joiner, string prefix, string postfix)
  2708. {
  2709. Clauses clauses;
  2710. if (!data.TryGetValue(name, out clauses))
  2711. {
  2712. clauses = new Clauses(joiner, prefix, postfix);
  2713. data[name] = clauses;
  2714. }
  2715. clauses.Add(new Clause { Sql = sql, Parameters = new List<object>(parameters) });
  2716. seq++;
  2717. }
  2718. readonly Dictionary<string, string> defaultsIfEmpty = new Dictionary<string, string>
  2719. {
  2720. { "where", "1=1" },
  2721. { "select", "1" }
  2722. };
  2723. public SqlBuilder Select(params string[] columns)
  2724. {
  2725. AddClause("select", string.Join(", ", columns), new object[] { }, ", ", "", "");
  2726. return this;
  2727. }
  2728. public SqlBuilder Join(string sql, params object[] parameters)
  2729. {
  2730. AddClause("join", sql, parameters, "\nINNER JOIN ", "\nINNER JOIN ", "\n");
  2731. return this;
  2732. }
  2733. public SqlBuilder LeftJoin(string sql, params object[] parameters)
  2734. {
  2735. AddClause("leftjoin", sql, parameters, "\nLEFT JOIN ", "\nLEFT JOIN ", "\n");
  2736. return this;
  2737. }
  2738. public SqlBuilder Where(string sql, params object[] parameters)
  2739. {
  2740. AddClause("where", sql, parameters, " AND ", " ( ", " )\n");
  2741. return this;
  2742. }
  2743. public SqlBuilder OrderBy(string sql, params object[] parameters)
  2744. {
  2745. AddClause("orderby", sql, parameters, ", ", "ORDER BY ", "\n");
  2746. return this;
  2747. }
  2748. public SqlBuilder OrderByCols(params string[] columns)
  2749. {
  2750. AddClause("orderbycols", string.Join(", ", columns), new object[] { }, ", ", ", ", "");
  2751. return this;
  2752. }
  2753. public SqlBuilder GroupBy(string sql, params object[] parameters)
  2754. {
  2755. AddClause("groupby", sql, parameters, " , ", "\nGROUP BY ", "\n");
  2756. return this;
  2757. }
  2758. public SqlBuilder Having(string sql, params object[] parameters)
  2759. {
  2760. AddClause("having", sql, parameters, "\nAND ", "HAVING ", "\n");
  2761. return this;
  2762. }
  2763. }
  2764. public static class SqlExtensions
  2765. {
  2766. public static Sql ToSql(this SqlBuilder.Template template)
  2767. {
  2768. return new Sql(true, template.RawSql, template.Parameters);
  2769. }
  2770. }
  2771. }