PageRenderTime 66ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/PetaPoco/PetaPoco.cs

http://github.com/toptensoftware/PetaPoco
C# | 2329 lines | 1840 code | 298 blank | 191 comment | 339 complexity | 40ba2c0e1aae7c23f0034aea3c16481d MD5 | raw file
  1. /* PetaPoco v4.0.3 - A Tiny ORMish thing for your POCO's.
  2. * Copyright © 2011 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.Generic;
  13. using System.Linq;
  14. using System.Text;
  15. using System.Configuration;
  16. using System.Data.Common;
  17. using System.Data;
  18. using System.Text.RegularExpressions;
  19. using System.Reflection;
  20. using System.Reflection.Emit;
  21. using System.Linq.Expressions;
  22. namespace PetaPoco
  23. {
  24. // Poco's marked [Explicit] require all column properties to be marked
  25. [AttributeUsage(AttributeTargets.Class)]
  26. public class ExplicitColumnsAttribute : Attribute
  27. {
  28. }
  29. // For non-explicit pocos, causes a property to be ignored
  30. [AttributeUsage(AttributeTargets.Property)]
  31. public class IgnoreAttribute : Attribute
  32. {
  33. }
  34. // For explicit pocos, marks property as a column and optionally supplies column name
  35. [AttributeUsage(AttributeTargets.Property)]
  36. public class ColumnAttribute : Attribute
  37. {
  38. public ColumnAttribute() { }
  39. public ColumnAttribute(string name) { Name = name; }
  40. public string Name { get; set; }
  41. }
  42. // For explicit pocos, marks property as a result column and optionally supplies column name
  43. [AttributeUsage(AttributeTargets.Property)]
  44. public class ResultColumnAttribute : ColumnAttribute
  45. {
  46. public ResultColumnAttribute() { }
  47. public ResultColumnAttribute(string name) : base(name) { }
  48. }
  49. // Specify the table name of a poco
  50. [AttributeUsage(AttributeTargets.Class)]
  51. public class TableNameAttribute : Attribute
  52. {
  53. public TableNameAttribute(string tableName)
  54. {
  55. Value = tableName;
  56. }
  57. public string Value { get; private set; }
  58. }
  59. // Specific the primary key of a poco class (and optional sequence name for Oracle)
  60. [AttributeUsage(AttributeTargets.Class)]
  61. public class PrimaryKeyAttribute : Attribute
  62. {
  63. public PrimaryKeyAttribute(string primaryKey)
  64. {
  65. Value = primaryKey;
  66. autoIncrement = true;
  67. }
  68. public string Value { get; private set; }
  69. public string sequenceName { get; set; }
  70. public bool autoIncrement { get; set; }
  71. }
  72. [AttributeUsage(AttributeTargets.Property)]
  73. public class AutoJoinAttribute : Attribute
  74. {
  75. public AutoJoinAttribute() { }
  76. }
  77. // Results from paged request
  78. public class Page<T>
  79. {
  80. public long CurrentPage { get; set; }
  81. public long TotalPages { get; set; }
  82. public long TotalItems { get; set; }
  83. public long ItemsPerPage { get; set; }
  84. public List<T> Items { get; set; }
  85. public object Context { get; set; }
  86. }
  87. // Pass as parameter value to force to DBType.AnsiString
  88. public class AnsiString
  89. {
  90. public AnsiString(string str)
  91. {
  92. Value = str;
  93. }
  94. public string Value { get; private set; }
  95. }
  96. // Used by IMapper to override table bindings for an object
  97. public class TableInfo
  98. {
  99. public string TableName { get; set; }
  100. public string PrimaryKey { get; set; }
  101. public bool AutoIncrement { get; set; }
  102. public string SequenceName { get; set; }
  103. }
  104. // Optionally provide an implementation of this to Database.Mapper
  105. public interface IMapper
  106. {
  107. void GetTableInfo(Type t, TableInfo ti);
  108. bool MapPropertyToColumn(PropertyInfo pi, ref string columnName, ref bool resultColumn);
  109. Func<object, object> GetFromDbConverter(PropertyInfo pi, Type SourceType);
  110. Func<object, object> GetToDbConverter(Type SourceType);
  111. }
  112. // This will be merged with IMapper in the next major version
  113. public interface IMapper2 : IMapper
  114. {
  115. Func<object, object> GetFromDbConverter(Type DestType, Type SourceType);
  116. }
  117. // Database class ... this is where most of the action happens
  118. public class Database : IDisposable
  119. {
  120. public Database(IDbConnection connection)
  121. {
  122. _sharedConnection = connection;
  123. _connectionString = connection.ConnectionString;
  124. _sharedConnectionDepth = 2; // Prevent closing external connection
  125. CommonConstruct();
  126. }
  127. public Database(string connectionString, string providerName)
  128. {
  129. _connectionString = connectionString;
  130. _providerName = providerName;
  131. CommonConstruct();
  132. }
  133. public Database(string connectionString, DbProviderFactory provider)
  134. {
  135. _connectionString = connectionString;
  136. _factory = provider;
  137. CommonConstruct();
  138. }
  139. public Database(string connectionStringName)
  140. {
  141. // Use first?
  142. if (connectionStringName == "")
  143. connectionStringName = ConfigurationManager.ConnectionStrings[0].Name;
  144. // Work out connection string and provider name
  145. var providerName = "System.Data.SqlClient";
  146. if (ConfigurationManager.ConnectionStrings[connectionStringName] != null)
  147. {
  148. if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName))
  149. providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName;
  150. }
  151. else
  152. {
  153. throw new InvalidOperationException("Can't find a connection string with the name '" + connectionStringName + "'");
  154. }
  155. // Store factory and connection string
  156. _connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
  157. _providerName = providerName;
  158. CommonConstruct();
  159. }
  160. enum DBType
  161. {
  162. SqlServer,
  163. SqlServerCE,
  164. MySql,
  165. PostgreSQL,
  166. Oracle,
  167. SQLite
  168. }
  169. DBType _dbType = DBType.SqlServer;
  170. // Common initialization
  171. private void CommonConstruct()
  172. {
  173. _transactionDepth = 0;
  174. EnableAutoSelect = true;
  175. EnableNamedParams = true;
  176. ForceDateTimesToUtc = true;
  177. if (_providerName != null)
  178. _factory = DbProviderFactories.GetFactory(_providerName);
  179. string dbtype = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name;
  180. // Try using type name first (more reliable)
  181. if (dbtype.StartsWith("MySql")) _dbType = DBType.MySql;
  182. else if (dbtype.StartsWith("SqlCe")) _dbType = DBType.SqlServerCE;
  183. else if (dbtype.StartsWith("Npgsql")) _dbType = DBType.PostgreSQL;
  184. else if (dbtype.StartsWith("Oracle")) _dbType = DBType.Oracle;
  185. else if (dbtype.StartsWith("SQLite")) _dbType = DBType.SQLite;
  186. else if (dbtype.StartsWith("System.Data.SqlClient.")) _dbType = DBType.SqlServer;
  187. // else try with provider name
  188. else if (_providerName.IndexOf("MySql", StringComparison.InvariantCultureIgnoreCase) >= 0) _dbType = DBType.MySql;
  189. else if (_providerName.IndexOf("SqlServerCe", StringComparison.InvariantCultureIgnoreCase) >= 0) _dbType = DBType.SqlServerCE;
  190. else if (_providerName.IndexOf("Npgsql", StringComparison.InvariantCultureIgnoreCase) >= 0) _dbType = DBType.PostgreSQL;
  191. else if (_providerName.IndexOf("Oracle", StringComparison.InvariantCultureIgnoreCase) >= 0) _dbType = DBType.Oracle;
  192. else if (_providerName.IndexOf("SQLite", StringComparison.InvariantCultureIgnoreCase) >= 0) _dbType = DBType.SQLite;
  193. if (_dbType == DBType.MySql && _connectionString != null && _connectionString.IndexOf("Allow User Variables=true") >= 0)
  194. _paramPrefix = "?";
  195. if (_dbType == DBType.Oracle)
  196. _paramPrefix = ":";
  197. }
  198. // Automatically close one open shared connection
  199. public void Dispose()
  200. {
  201. // Automatically close one open connection reference
  202. // (Works with KeepConnectionAlive and manually opening a shared connection)
  203. CloseSharedConnection();
  204. }
  205. // Set to true to keep the first opened connection alive until this object is disposed
  206. public bool KeepConnectionAlive { get; set; }
  207. // Open a connection (can be nested)
  208. public void OpenSharedConnection()
  209. {
  210. if (_sharedConnectionDepth == 0)
  211. {
  212. _sharedConnection = _factory.CreateConnection();
  213. _sharedConnection.ConnectionString = _connectionString;
  214. _sharedConnection.Open();
  215. _sharedConnection = OnConnectionOpened(_sharedConnection);
  216. if (KeepConnectionAlive)
  217. _sharedConnectionDepth++; // Make sure you call Dispose
  218. }
  219. _sharedConnectionDepth++;
  220. }
  221. // Close a previously opened connection
  222. public void CloseSharedConnection()
  223. {
  224. if (_sharedConnectionDepth > 0)
  225. {
  226. _sharedConnectionDepth--;
  227. if (_sharedConnectionDepth == 0)
  228. {
  229. OnConnectionClosing(_sharedConnection);
  230. _sharedConnection.Dispose();
  231. _sharedConnection = null;
  232. }
  233. }
  234. }
  235. // Access to our shared connection
  236. public IDbConnection Connection
  237. {
  238. get { return _sharedConnection; }
  239. }
  240. // Helper to create a transaction scope
  241. public Transaction GetTransaction()
  242. {
  243. return new Transaction(this);
  244. }
  245. // Use by derived repo generated by T4 templates
  246. public virtual void OnBeginTransaction() { }
  247. public virtual void OnEndTransaction() { }
  248. // Start a new transaction, can be nested, every call must be
  249. // matched by a call to AbortTransaction or CompleteTransaction
  250. // Use `using (var scope=db.Transaction) { scope.Complete(); }` to ensure correct semantics
  251. public void BeginTransaction()
  252. {
  253. _transactionDepth++;
  254. if (_transactionDepth == 1)
  255. {
  256. OpenSharedConnection();
  257. _transaction = _sharedConnection.BeginTransaction();
  258. _transactionCancelled = false;
  259. OnBeginTransaction();
  260. }
  261. }
  262. // Internal helper to cleanup transaction stuff
  263. void CleanupTransaction()
  264. {
  265. OnEndTransaction();
  266. if (_transactionCancelled)
  267. _transaction.Rollback();
  268. else
  269. _transaction.Commit();
  270. _transaction.Dispose();
  271. _transaction = null;
  272. CloseSharedConnection();
  273. }
  274. // Abort the entire outer most transaction scope
  275. public void AbortTransaction()
  276. {
  277. _transactionCancelled = true;
  278. if ((--_transactionDepth) == 0)
  279. CleanupTransaction();
  280. }
  281. // Complete the transaction
  282. public void CompleteTransaction()
  283. {
  284. if ((--_transactionDepth) == 0)
  285. CleanupTransaction();
  286. }
  287. // Helper to handle named parameters from object properties
  288. static Regex rxParams = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
  289. public static string ProcessParams(string _sql, object[] args_src, List<object> args_dest)
  290. {
  291. return rxParams.Replace(_sql, m =>
  292. {
  293. string param = m.Value.Substring(1);
  294. object arg_val;
  295. int paramIndex;
  296. if (int.TryParse(param, out paramIndex))
  297. {
  298. // Numbered parameter
  299. if (paramIndex < 0 || paramIndex >= args_src.Length)
  300. throw new ArgumentOutOfRangeException(string.Format("Parameter '@{0}' specified but only {1} parameters supplied (in `{2}`)", paramIndex, args_src.Length, _sql));
  301. arg_val = args_src[paramIndex];
  302. }
  303. else
  304. {
  305. // Look for a property on one of the arguments with this name
  306. bool found = false;
  307. arg_val = null;
  308. foreach (var o in args_src)
  309. {
  310. var pi = o.GetType().GetProperty(param);
  311. if (pi != null)
  312. {
  313. arg_val = pi.GetValue(o, null);
  314. found = true;
  315. break;
  316. }
  317. }
  318. if (!found)
  319. throw new ArgumentException(string.Format("Parameter '@{0}' specified but none of the passed arguments have a property with this name (in '{1}')", param, _sql));
  320. }
  321. // Expand collections to parameter lists
  322. if ((arg_val as System.Collections.IEnumerable) != null &&
  323. (arg_val as string) == null &&
  324. (arg_val as byte[]) == null)
  325. {
  326. var sb = new StringBuilder();
  327. foreach (var i in arg_val as System.Collections.IEnumerable)
  328. {
  329. sb.Append((sb.Length == 0 ? "@" : ",@") + args_dest.Count.ToString());
  330. args_dest.Add(i);
  331. }
  332. return sb.ToString();
  333. }
  334. else
  335. {
  336. args_dest.Add(arg_val);
  337. return "@" + (args_dest.Count - 1).ToString();
  338. }
  339. }
  340. );
  341. }
  342. // Add a parameter to a DB command
  343. void AddParam(IDbCommand cmd, object item, string ParameterPrefix)
  344. {
  345. // Convert value to from poco type to db type
  346. if (Database.Mapper != null && item!=null)
  347. {
  348. var fn = Database.Mapper.GetToDbConverter(item.GetType());
  349. if (fn!=null)
  350. item = fn(item);
  351. }
  352. // Support passed in parameters
  353. var idbParam = item as IDbDataParameter;
  354. if (idbParam != null)
  355. {
  356. idbParam.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count);
  357. cmd.Parameters.Add(idbParam);
  358. return;
  359. }
  360. var p = cmd.CreateParameter();
  361. p.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count);
  362. if (item == null)
  363. {
  364. p.Value = DBNull.Value;
  365. }
  366. else
  367. {
  368. var t = item.GetType();
  369. if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int
  370. {
  371. p.Value = (int)item;
  372. }
  373. else if (t == typeof(Guid))
  374. {
  375. p.Value = item.ToString();
  376. p.DbType = DbType.String;
  377. p.Size = 40;
  378. }
  379. else if (t == typeof(string))
  380. {
  381. p.Size = Math.Max((item as string).Length + 1, 4000); // Help query plan caching by using common size
  382. p.Value = item;
  383. }
  384. else if (t == typeof(AnsiString))
  385. {
  386. // Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar
  387. p.Size = Math.Max((item as AnsiString).Value.Length + 1, 4000);
  388. p.Value = (item as AnsiString).Value;
  389. p.DbType = DbType.AnsiString;
  390. }
  391. else if (t == typeof(bool) && _dbType != DBType.PostgreSQL)
  392. {
  393. p.Value = ((bool)item) ? 1 : 0;
  394. }
  395. else if (item.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type
  396. {
  397. p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type
  398. p.Value = item;
  399. }
  400. else if (item.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type
  401. {
  402. p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type
  403. p.Value = item;
  404. }
  405. else
  406. {
  407. p.Value = item;
  408. }
  409. }
  410. cmd.Parameters.Add(p);
  411. }
  412. // Create a command
  413. static Regex rxParamsPrefix = new Regex(@"(?<!@)@\w+", RegexOptions.Compiled);
  414. public IDbCommand CreateCommand(IDbConnection connection, string sql, params object[] args)
  415. {
  416. // Perform named argument replacements
  417. if (EnableNamedParams)
  418. {
  419. var new_args = new List<object>();
  420. sql = ProcessParams(sql, args, new_args);
  421. args = new_args.ToArray();
  422. }
  423. // Perform parameter prefix replacements
  424. if (_paramPrefix != "@")
  425. sql = rxParamsPrefix.Replace(sql, m => _paramPrefix + m.Value.Substring(1));
  426. sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @
  427. // Create the command and add parameters
  428. IDbCommand cmd = connection.CreateCommand();
  429. cmd.Connection = connection;
  430. cmd.CommandText = sql;
  431. cmd.Transaction = _transaction;
  432. foreach (var item in args)
  433. {
  434. AddParam(cmd, item, _paramPrefix);
  435. }
  436. if (_dbType == DBType.Oracle)
  437. {
  438. cmd.GetType().GetProperty("BindByName").SetValue(cmd, true, null);
  439. }
  440. if (!String.IsNullOrEmpty(sql))
  441. DoPreExecute(cmd);
  442. return cmd;
  443. }
  444. // Override this to log/capture exceptions
  445. public virtual void OnException(Exception x)
  446. {
  447. System.Diagnostics.Debug.WriteLine(x.ToString());
  448. System.Diagnostics.Debug.WriteLine(LastCommand);
  449. }
  450. // Override this to log commands, or modify command before execution
  451. public virtual IDbConnection OnConnectionOpened(IDbConnection conn) { return conn; }
  452. public virtual void OnConnectionClosing(IDbConnection conn) { }
  453. public virtual void OnExecutingCommand(IDbCommand cmd) { }
  454. public virtual void OnExecutedCommand(IDbCommand cmd) { }
  455. // Execute a non-query command
  456. public int Execute(string sql, params object[] args)
  457. {
  458. try
  459. {
  460. OpenSharedConnection();
  461. try
  462. {
  463. using (var cmd = CreateCommand(_sharedConnection, sql, args))
  464. {
  465. var retv=cmd.ExecuteNonQuery();
  466. OnExecutedCommand(cmd);
  467. return retv;
  468. }
  469. }
  470. finally
  471. {
  472. CloseSharedConnection();
  473. }
  474. }
  475. catch (Exception x)
  476. {
  477. OnException(x);
  478. throw;
  479. }
  480. }
  481. public int Execute(Sql sql)
  482. {
  483. return Execute(sql.SQL, sql.Arguments);
  484. }
  485. // Execute and cast a scalar property
  486. public T ExecuteScalar<T>(string sql, params object[] args)
  487. {
  488. try
  489. {
  490. OpenSharedConnection();
  491. try
  492. {
  493. using (var cmd = CreateCommand(_sharedConnection, sql, args))
  494. {
  495. object val = cmd.ExecuteScalar();
  496. OnExecutedCommand(cmd);
  497. return (T)Convert.ChangeType(val, typeof(T));
  498. }
  499. }
  500. finally
  501. {
  502. CloseSharedConnection();
  503. }
  504. }
  505. catch (Exception x)
  506. {
  507. OnException(x);
  508. throw;
  509. }
  510. }
  511. public T ExecuteScalar<T>(Sql sql)
  512. {
  513. return ExecuteScalar<T>(sql.SQL, sql.Arguments);
  514. }
  515. Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
  516. Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline);
  517. string AddSelectClause<T>(string sql)
  518. {
  519. if (sql.StartsWith(";"))
  520. return sql.Substring(1);
  521. if (!rxSelect.IsMatch(sql))
  522. {
  523. var pd = PocoData.ForType(typeof(T));
  524. var tableName = EscapeTableName(pd.TableInfo.TableName);
  525. string cols = string.Join(", ", (from c in pd.QueryColumns select tableName + "." + EscapeSqlIdentifier(c)).ToArray());
  526. if (!rxFrom.IsMatch(sql))
  527. sql = string.Format("SELECT {0} FROM {1} {2}", cols, tableName, sql);
  528. else
  529. sql = string.Format("SELECT {0} {1}", cols, sql);
  530. }
  531. return sql;
  532. }
  533. public bool EnableAutoSelect { get; set; }
  534. public bool EnableNamedParams { get; set; }
  535. public bool ForceDateTimesToUtc { get; set; }
  536. // Return a typed list of pocos
  537. public List<T> Fetch<T>(string sql, params object[] args)
  538. {
  539. return Query<T>(sql, args).ToList();
  540. }
  541. public List<T> Fetch<T>(Sql sql)
  542. {
  543. return Fetch<T>(sql.SQL, sql.Arguments);
  544. }
  545. static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?<!,\s+)\bFROM\b", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
  546. static Regex rxOrderBy = new Regex(@"\bORDER\s+BY\s+(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
  547. static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
  548. public static bool SplitSqlForPaging(string sql, out string sqlCount, out string sqlSelectRemoved, out string sqlOrderBy)
  549. {
  550. sqlSelectRemoved = null;
  551. sqlCount = null;
  552. sqlOrderBy = null;
  553. // Extract the columns from "SELECT <whatever> FROM"
  554. var m = rxColumns.Match(sql);
  555. if (!m.Success)
  556. return false;
  557. // Save column list and replace with COUNT(*)
  558. Group g = m.Groups[1];
  559. sqlSelectRemoved = sql.Substring(g.Index);
  560. if (rxDistinct.IsMatch(sqlSelectRemoved))
  561. sqlCount = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length);
  562. else
  563. sqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length);
  564. // Look for an "ORDER BY <whatever>" clause
  565. m = rxOrderBy.Match(sqlCount);
  566. if (!m.Success)
  567. {
  568. sqlOrderBy = null;
  569. }
  570. else
  571. {
  572. g = m.Groups[0];
  573. sqlOrderBy = g.ToString();
  574. sqlCount = sqlCount.Substring(0, g.Index) + sqlCount.Substring(g.Index + g.Length);
  575. }
  576. return true;
  577. }
  578. public void BuildPageQueries<T>(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage)
  579. {
  580. // Add auto select clause
  581. if (EnableAutoSelect)
  582. sql = AddSelectClause<T>(sql);
  583. // Split the SQL into the bits we need
  584. string sqlSelectRemoved, sqlOrderBy;
  585. if (!SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy))
  586. throw new Exception("Unable to parse SQL statement for paged query");
  587. if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*"))
  588. throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id");
  589. // Build the SQL for the actual final result
  590. if (_dbType == DBType.SqlServer || _dbType == DBType.Oracle)
  591. {
  592. sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, "");
  593. if (rxDistinct.IsMatch(sqlSelectRemoved))
  594. {
  595. sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner";
  596. }
  597. sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}",
  598. sqlOrderBy==null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1);
  599. args = args.Concat(new object[] { skip, skip+take }).ToArray();
  600. }
  601. else if (_dbType == DBType.SqlServerCE)
  602. {
  603. sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1);
  604. args = args.Concat(new object[] { skip, take }).ToArray();
  605. }
  606. else
  607. {
  608. sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1);
  609. args = args.Concat(new object[] { take, skip }).ToArray();
  610. }
  611. }
  612. // Fetch a page
  613. public Page<T> Page<T>(long page, long itemsPerPage, string sql, params object[] args)
  614. {
  615. string sqlCount, sqlPage;
  616. BuildPageQueries<T>((page-1)*itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage);
  617. // Save the one-time command time out and use it for both queries
  618. int saveTimeout = OneTimeCommandTimeout;
  619. // Setup the paged result
  620. var result = new Page<T>();
  621. result.CurrentPage = page;
  622. result.ItemsPerPage = itemsPerPage;
  623. result.TotalItems = ExecuteScalar<long>(sqlCount, args);
  624. result.TotalPages = result.TotalItems / itemsPerPage;
  625. if ((result.TotalItems % itemsPerPage) != 0)
  626. result.TotalPages++;
  627. OneTimeCommandTimeout = saveTimeout;
  628. // Get the records
  629. result.Items = Fetch<T>(sqlPage, args);
  630. // Done
  631. return result;
  632. }
  633. public Page<T> Page<T>(long page, long itemsPerPage, Sql sql)
  634. {
  635. return Page<T>(page, itemsPerPage, sql.SQL, sql.Arguments);
  636. }
  637. public List<T> Fetch<T>(long page, long itemsPerPage, string sql, params object[] args)
  638. {
  639. return SkipTake<T>((page - 1) * itemsPerPage, itemsPerPage, sql, args);
  640. }
  641. public List<T> Fetch<T>(long page, long itemsPerPage, Sql sql)
  642. {
  643. return SkipTake<T>((page - 1) * itemsPerPage, itemsPerPage, sql.SQL, sql.Arguments);
  644. }
  645. public List<T> SkipTake<T>(long skip, long take, string sql, params object[] args)
  646. {
  647. string sqlCount, sqlPage;
  648. BuildPageQueries<T>(skip, take, sql, ref args, out sqlCount, out sqlPage);
  649. return Fetch<T>(sqlPage, args);
  650. }
  651. public List<T> SkipTake<T>(long skip, long take, Sql sql)
  652. {
  653. return SkipTake<T>(skip, take, sql.SQL, sql.Arguments);
  654. }
  655. // Return an enumerable collection of pocos
  656. public IEnumerable<T> Query<T>(string sql, params object[] args)
  657. {
  658. if (EnableAutoSelect)
  659. sql = AddSelectClause<T>(sql);
  660. OpenSharedConnection();
  661. try
  662. {
  663. using (var cmd = CreateCommand(_sharedConnection, sql, args))
  664. {
  665. IDataReader r;
  666. var pd = PocoData.ForType(typeof(T));
  667. try
  668. {
  669. r = cmd.ExecuteReader();
  670. OnExecutedCommand(cmd);
  671. }
  672. catch (Exception x)
  673. {
  674. OnException(x);
  675. throw;
  676. }
  677. var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, ForceDateTimesToUtc, 0, r.FieldCount, r) as Func<IDataReader, T>;
  678. using (r)
  679. {
  680. while (true)
  681. {
  682. T poco;
  683. try
  684. {
  685. if (!r.Read())
  686. yield break;
  687. poco = factory(r);
  688. }
  689. catch (Exception x)
  690. {
  691. OnException(x);
  692. throw;
  693. }
  694. yield return poco;
  695. }
  696. }
  697. }
  698. }
  699. finally
  700. {
  701. CloseSharedConnection();
  702. }
  703. }
  704. // Multi Fetch
  705. 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(); }
  706. 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(); }
  707. 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(); }
  708. // Multi Query
  709. 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, sql, args); }
  710. 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, sql, args); }
  711. 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, sql, args); }
  712. // Multi Fetch (SQL builder)
  713. public List<TRet> Fetch<T1, T2, TRet>(Func<T1, T2, TRet> cb, Sql sql) { return Query<T1, T2, TRet>(cb, sql.SQL, sql.Arguments).ToList(); }
  714. public List<TRet> Fetch<T1, T2, T3, TRet>(Func<T1, T2, T3, TRet> cb, Sql sql) { return Query<T1, T2, T3, TRet>(cb, sql.SQL, sql.Arguments).ToList(); }
  715. 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.SQL, sql.Arguments).ToList(); }
  716. // Multi Query (SQL builder)
  717. 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.SQL, sql.Arguments); }
  718. 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.SQL, sql.Arguments); }
  719. 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.SQL, sql.Arguments); }
  720. // Multi Fetch (Simple)
  721. public List<T1> Fetch<T1, T2>(string sql, params object[] args) { return Query<T1, T2>(sql, args).ToList(); }
  722. public List<T1> Fetch<T1, T2, T3>(string sql, params object[] args) { return Query<T1, T2, T3>(sql, args).ToList(); }
  723. public List<T1> Fetch<T1, T2, T3, T4>(string sql, params object[] args) { return Query<T1, T2, T3, T4>(sql, args).ToList(); }
  724. // Multi Query (Simple)
  725. public IEnumerable<T1> Query<T1, T2>(string sql, params object[] args) { return Query<T1>(new Type[] { typeof(T1), typeof(T2) }, null, sql, args); }
  726. public IEnumerable<T1> Query<T1, T2, T3>(string sql, params object[] args) { return Query<T1>(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); }
  727. 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, sql, args); }
  728. // Multi Fetch (Simple) (SQL builder)
  729. public List<T1> Fetch<T1, T2>(Sql sql) { return Query<T1, T2>(sql.SQL, sql.Arguments).ToList(); }
  730. public List<T1> Fetch<T1, T2, T3>(Sql sql) { return Query<T1, T2, T3>(sql.SQL, sql.Arguments).ToList(); }
  731. public List<T1> Fetch<T1, T2, T3, T4>(Sql sql) { return Query<T1, T2, T3, T4>(sql.SQL, sql.Arguments).ToList(); }
  732. // Multi Query (Simple) (SQL builder)
  733. public IEnumerable<T1> Query<T1, T2>(Sql sql) { return Query<T1>(new Type[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments); }
  734. public IEnumerable<T1> Query<T1, T2, T3>(Sql sql) { return Query<T1>(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments); }
  735. 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.SQL, sql.Arguments); }
  736. // Automagically guess the property relationships between various POCOs and create a delegate that will set them up
  737. object GetAutoMapper(Type[] types)
  738. {
  739. // Build a key
  740. var kb = new StringBuilder();
  741. foreach (var t in types)
  742. {
  743. kb.Append(t.ToString());
  744. kb.Append(":");
  745. }
  746. var key = kb.ToString();
  747. // Check cache
  748. RWLock.EnterReadLock();
  749. try
  750. {
  751. object mapper;
  752. if (AutoMappers.TryGetValue(key, out mapper))
  753. return mapper;
  754. }
  755. finally
  756. {
  757. RWLock.ExitReadLock();
  758. }
  759. // Create it
  760. RWLock.EnterWriteLock();
  761. try
  762. {
  763. // Try again
  764. object mapper;
  765. if (AutoMappers.TryGetValue(key, out mapper))
  766. return mapper;
  767. // Create a method
  768. var m = new DynamicMethod("petapoco_automapper", types[0], types, true);
  769. var il = m.GetILGenerator();
  770. for (int i = 1; i < types.Length; i++)
  771. {
  772. bool handled = false;
  773. for (int j = i - 1; j >= 0; j--)
  774. {
  775. // Find the property
  776. var candidates = from p in types[j].GetProperties() where p.PropertyType == types[i] select p;
  777. if (candidates.Count() == 0)
  778. continue;
  779. if (candidates.Count() > 1)
  780. throw new InvalidOperationException(string.Format("Can't auto join {0} as {1} has more than one property of type {0}", types[i], types[j]));
  781. // Generate code
  782. il.Emit(OpCodes.Ldarg_S, j);
  783. il.Emit(OpCodes.Ldarg_S, i);
  784. il.Emit(OpCodes.Callvirt, candidates.First().GetSetMethod(true));
  785. handled = true;
  786. }
  787. if (!handled)
  788. throw new InvalidOperationException(string.Format("Can't auto join {0}", types[i]));
  789. }
  790. il.Emit(OpCodes.Ldarg_0);
  791. il.Emit(OpCodes.Ret);
  792. // Cache it
  793. var del = m.CreateDelegate(Expression.GetFuncType(types.Concat(types.Take(1)).ToArray()));
  794. AutoMappers.Add(key, del);
  795. return del;
  796. }
  797. finally
  798. {
  799. RWLock.ExitWriteLock();
  800. }
  801. }
  802. // Find the split point in a result set for two different pocos and return the poco factory for the first
  803. Delegate FindSplitPoint(Type typeThis, Type typeNext, string sql, IDataReader r, ref int pos)
  804. {
  805. // Last?
  806. if (typeNext == null)
  807. return PocoData.ForType(typeThis).GetFactory(sql, _sharedConnection.ConnectionString, ForceDateTimesToUtc, pos, r.FieldCount - pos, r);
  808. // Get PocoData for the two types
  809. PocoData pdThis = PocoData.ForType(typeThis);
  810. PocoData pdNext = PocoData.ForType(typeNext);
  811. // Find split point
  812. int firstColumn = pos;
  813. var usedColumns = new Dictionary<string, bool>();
  814. for (; pos < r.FieldCount; pos++)
  815. {
  816. // Split if field name has already been used, or if the field doesn't exist in current poco but does in the next
  817. string fieldName = r.GetName(pos);
  818. if (usedColumns.ContainsKey(fieldName) || (!pdThis.Columns.ContainsKey(fieldName) && pdNext.Columns.ContainsKey(fieldName)))
  819. {
  820. return pdThis.GetFactory(sql, _sharedConnection.ConnectionString, ForceDateTimesToUtc, firstColumn, pos - firstColumn, r);
  821. }
  822. usedColumns.Add(fieldName, true);
  823. }
  824. throw new InvalidOperationException(string.Format("Couldn't find split point between {0} and {1}", typeThis, typeNext));
  825. }
  826. // Instance data used by the Multipoco factory delegate - essentially a list of the nested poco factories to call
  827. class MultiPocoFactory
  828. {
  829. public List<Delegate> m_Delegates;
  830. public Delegate GetItem(int index) { return m_Delegates[index]; }
  831. }
  832. // Create a multi-poco factory
  833. Func<IDataReader, object, TRet> CreateMultiPocoFactory<TRet>(Type[] types, string sql, IDataReader r)
  834. {
  835. var m = new DynamicMethod("petapoco_multipoco_factory", typeof(TRet), new Type[] { typeof(MultiPocoFactory), typeof(IDataReader), typeof(object) }, typeof(MultiPocoFactory));
  836. var il = m.GetILGenerator();
  837. // Load the callback
  838. il.Emit(OpCodes.Ldarg_2);
  839. // Call each delegate
  840. var dels = new List<Delegate>();
  841. int pos = 0;
  842. for (int i=0; i<types.Length; i++)
  843. {
  844. // Add to list of delegates to call
  845. var del = FindSplitPoint(types[i], i + 1 < types.Length ? types[i + 1] : null, sql, r, ref pos);
  846. dels.Add(del);
  847. // Get the delegate
  848. il.Emit(OpCodes.Ldarg_0); // callback,this
  849. il.Emit(OpCodes.Ldc_I4, i); // callback,this,Index
  850. il.Emit(OpCodes.Callvirt, typeof(MultiPocoFactory).GetMethod("GetItem")); // callback,Delegate
  851. il.Emit(OpCodes.Ldarg_1); // callback,delegate, datareader
  852. // Call Invoke
  853. var tDelInvoke = del.GetType().GetMethod("Invoke");
  854. il.Emit(OpCodes.Callvirt, tDelInvoke); // Poco left on stack
  855. }
  856. // By now we should have the callback and the N pocos all on the stack. Call the callback and we're done
  857. il.Emit(OpCodes.Callvirt, Expression.GetFuncType(types.Concat(new Type[] { typeof(TRet) }).ToArray()).GetMethod("Invoke"));
  858. il.Emit(OpCodes.Ret);
  859. // Finish up
  860. return (Func<IDataReader, object, TRet>)m.CreateDelegate(typeof(Func<IDataReader, object, TRet>), new MultiPocoFactory() { m_Delegates = dels });
  861. }
  862. // Various cached stuff
  863. static Dictionary<string, object> MultiPocoFactories = new Dictionary<string, object>();
  864. static Dictionary<string, object> AutoMappers = new Dictionary<string, object>();
  865. static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim();
  866. // Get (or create) the multi-poco factory for a query
  867. Func<IDataReader, object, TRet> GetMultiPocoFactory<TRet>(Type[] types, string sql, IDataReader r)
  868. {
  869. // Build a key string (this is crap, should address this at some point)
  870. var kb = new StringBuilder();
  871. kb.Append(typeof(TRet).ToString());
  872. kb.Append(":");
  873. foreach (var t in types)
  874. {
  875. kb.Append(":");
  876. kb.Append(t.ToString());
  877. }
  878. kb.Append(":"); kb.Append(_sharedConnection.ConnectionString);
  879. kb.Append(":"); kb.Append(ForceDateTimesToUtc);
  880. kb.Append(":"); kb.Append(sql);
  881. string key = kb.ToString();
  882. // Check cache
  883. RWLock.EnterReadLock();
  884. try
  885. {
  886. object oFactory;
  887. if (MultiPocoFactories.TryGetValue(key, out oFactory))
  888. return (Func<IDataReader, object, TRet>)oFactory;
  889. }
  890. finally
  891. {
  892. RWLock.ExitReadLock();
  893. }
  894. // Cache it
  895. RWLock.EnterWriteLock();
  896. try
  897. {
  898. // Check again
  899. object oFactory;
  900. if (MultiPocoFactories.TryGetValue(key, out oFactory))
  901. return (Func<IDataReader, object, TRet>)oFactory;
  902. // Create the factory
  903. var Factory = CreateMultiPocoFactory<TRet>(types, sql, r);
  904. MultiPocoFactories.Add(key, Factory);
  905. return Factory;
  906. }
  907. finally
  908. {
  909. RWLock.ExitWriteLock();
  910. }
  911. }
  912. // Actual implementation of the multi-poco query
  913. public IEnumerable<TRet> Query<TRet>(Type[] types, object cb, string sql, params object[] args)
  914. {
  915. OpenSharedConnection();
  916. try
  917. {
  918. using (var cmd = CreateCommand(_sharedConnection, sql, args))
  919. {
  920. IDataReader r;
  921. try
  922. {
  923. r = cmd.ExecuteReader();
  924. OnExecutedCommand(cmd);
  925. }
  926. catch (Exception x)
  927. {
  928. OnException(x);
  929. throw;
  930. }
  931. var factory = GetMultiPocoFactory<TRet>(types, sql, r);
  932. if (cb == null)
  933. cb = GetAutoMapper(types.ToArray());
  934. bool bNeedTerminator=false;
  935. using (r)
  936. {
  937. while (true)
  938. {
  939. TRet poco;
  940. try
  941. {
  942. if (!r.Read())
  943. break;
  944. poco = factory(r, cb);
  945. }
  946. catch (Exception x)
  947. {
  948. OnException(x);
  949. throw;
  950. }
  951. if (poco != null)
  952. yield return poco;
  953. else
  954. bNeedTerminator = true;
  955. }
  956. if (bNeedTerminator)
  957. {
  958. var poco = (TRet)(cb as Delegate).DynamicInvoke(new object[types.Length]);
  959. if (poco != null)
  960. yield return poco;
  961. else
  962. yield break;
  963. }
  964. }
  965. }
  966. }
  967. finally
  968. {
  969. CloseSharedConnection();
  970. }
  971. }
  972. public IEnumerable<T> Query<T>(Sql sql)
  973. {
  974. return Query<T>(sql.SQL, sql.Arguments);
  975. }
  976. public bool Exists<T>(object primaryKey)
  977. {
  978. return FirstOrDefault<T>(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey) != null;
  979. }
  980. public T Single<T>(object primaryKey)
  981. {
  982. return Single<T>(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey);
  983. }
  984. public T SingleOrDefault<T>(object primaryKey)
  985. {
  986. return SingleOrDefault<T>(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey);
  987. }
  988. public T Single<T>(string sql, params object[] args)
  989. {
  990. return Query<T>(sql, args).Single();
  991. }
  992. public T SingleOrDefault<T>(string sql, params object[] args)
  993. {
  994. return Query<T>(sql, args).SingleOrDefault();
  995. }
  996. public T First<T>(string sql, params object[] args)
  997. {
  998. return Query<T>(sql, args).First();
  999. }
  1000. public T FirstOrDefault<T>(string sql, params object[] args)
  1001. {
  1002. return Query<T>(sql, args).FirstOrDefault();
  1003. }
  1004. public T Single<T>(Sql sql)
  1005. {
  1006. return Query<T>(sql).Single();
  1007. }
  1008. public T SingleOrDefault<T>(Sql sql)
  1009. {
  1010. return Query<T>(sql).SingleOrDefault();
  1011. }
  1012. public T First<T>(Sql sql)
  1013. {
  1014. return Query<T>(sql).First();
  1015. }
  1016. public T FirstOrDefault<T>(Sql sql)
  1017. {
  1018. return Query<T>(sql).FirstOrDefault();
  1019. }
  1020. public string EscapeTableName(string str)
  1021. {
  1022. // Assume table names with "dot" are already escaped
  1023. return str.IndexOf('.') >= 0 ? str : EscapeSqlIdentifier(str);
  1024. }
  1025. public string EscapeSqlIdentifier(string str)
  1026. {
  1027. switch (_dbType)
  1028. {
  1029. case DBType.MySql:
  1030. return string.Format("`{0}`", str);
  1031. case DBType.PostgreSQL:
  1032. return string.Format("\"{0}\"", str);
  1033. case DBType.Oracle:
  1034. return string.Format("\"{0}\"", str.ToUpperInvariant());
  1035. default:
  1036. return string.Format("[{0}]", str);
  1037. }
  1038. }
  1039. public object Insert(string tableName, string primaryKeyName, object poco)
  1040. {
  1041. return Insert(tableName, primaryKeyName, true, poco);
  1042. }
  1043. // Insert a poco into a table. If the poco has a property with the same name
  1044. // as the primary key the id of the new record is assigned to it. Either way,
  1045. // the new id is returned.
  1046. public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco)
  1047. {
  1048. try
  1049. {
  1050. OpenSharedConnection();
  1051. try
  1052. {
  1053. using (var cmd = CreateCommand(_sharedConnection, ""))
  1054. {
  1055. var pd = PocoData.ForObject(poco, primaryKeyName);
  1056. var names = new List<string>();
  1057. var values = new List<string>();
  1058. var index = 0;
  1059. foreach (var i in pd.Columns)
  1060. {
  1061. // Don't insert result columns
  1062. if (i.Value.ResultColumn)
  1063. continue;
  1064. // Don't insert the primary key (except under oracle where we need bring in the next sequence value)
  1065. if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, true)==0)
  1066. {
  1067. if (_dbType == DBType.Oracle && !string.IsNullOrEmpty(pd.TableInfo.SequenceName))
  1068. {
  1069. names.Add(i.Key);
  1070. values.Add(string.Format("{0}.nextval", pd.TableInfo.SequenceName));
  1071. }
  1072. continue;
  1073. }
  1074. names.Add(EscapeSqlIdentifier(i.Key));
  1075. values.Add(string.Format("{0}{1}", _paramPrefix, index++));
  1076. AddParam(cmd, i.Value.GetValue(poco), _paramPrefix);
  1077. }
  1078. cmd.CommandText = string.Format("INSERT INTO {0} ({1}) VALUES ({2})",
  1079. EscapeTableName(tableName),
  1080. string.Join(",", names.ToArray()),
  1081. string.Join(",", values.ToArray())
  1082. );
  1083. if (!autoIncrement)
  1084. {
  1085. DoPreExecute(cmd);
  1086. cmd.ExecuteNonQuery();
  1087. OnExecutedCommand(cmd);
  1088. return true;
  1089. }
  1090. object id;
  1091. switch (_dbType)
  1092. {
  1093. case DBType.SqlServerCE:
  1094. DoPreExecute(cmd);
  1095. cmd.ExecuteNonQuery();
  1096. OnExecutedCommand(cmd);
  1097. id = ExecuteScalar<object>("SELECT @@@IDENTITY AS NewID;");
  1098. break;
  1099. case DBType.SqlServer:
  1100. cmd.CommandText += ";\nSELECT SCOPE_IDENTITY() AS NewID;";
  1101. DoPreExecute(cmd);
  1102. id = cmd.ExecuteScalar();
  1103. OnExecutedCommand(cmd);
  1104. break;
  1105. case DBType.PostgreSQL:
  1106. if (primaryKeyName != null)
  1107. {
  1108. cmd.CommandText += string.Format("returning {0} as NewID", EscapeSqlIdentifier(primaryKeyName));
  1109. DoPreExecute(cmd);
  1110. id = cmd.ExecuteScalar();
  1111. }
  1112. else
  1113. {
  1114. id = -1;
  1115. DoPreExecute(cmd);
  1116. cmd.ExecuteNonQuery();
  1117. }
  1118. OnExecutedCommand(cmd);
  1119. break;
  1120. case DBType.Oracle:
  1121. if (primaryKeyName != null)
  1122. {
  1123. cmd.CommandText += string.Format(" returning {0} into :newid", EscapeSqlIdentifier(primaryKeyName));
  1124. var param = cmd.CreateParameter();
  1125. param.ParameterName = ":newid";
  1126. param.Value = DBNull.Value;
  1127. param.Direction = ParameterDirection.ReturnValue;
  1128. param.DbType = DbType.Int64;
  1129. cmd.Parameters.Add(param);
  1130. DoPreExecute(cmd);
  1131. cmd.ExecuteNonQuery();
  1132. id = param.Value;
  1133. }
  1134. else
  1135. {
  1136. id = -1;
  1137. DoPreExecute(cmd);
  1138. cmd.ExecuteNonQuery();
  1139. }
  1140. OnExecutedCommand(cmd);
  1141. break;
  1142. case DBType.SQLite:
  1143. if (primaryKeyName != null)
  1144. {
  1145. cmd.CommandText += ";\nSELECT last_insert_rowid();";
  1146. DoPreExecute(cmd);
  1147. id = cmd.ExecuteScalar();
  1148. }
  1149. else
  1150. {
  1151. id = -1;
  1152. DoPreExecute(cmd);
  1153. cmd.ExecuteNonQuery();
  1154. }
  1155. OnExecutedCommand(cmd);
  1156. break;
  1157. default:
  1158. cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;";
  1159. DoPreExecute(cmd);
  1160. id = cmd.ExecuteScalar();
  1161. OnExecutedCommand(cmd);
  1162. break;
  1163. }
  1164. // Assign the ID back to the primary key property
  1165. if (primaryKeyName != null)
  1166. {
  1167. PocoColumn pc;
  1168. if (pd.Columns.TryGetValue(primaryKeyName, out pc))
  1169. {
  1170. pc.SetValue(poco, pc.ChangeType(id));
  1171. }
  1172. }
  1173. return id;
  1174. }
  1175. }
  1176. finally
  1177. {
  1178. CloseSharedConnection();
  1179. }
  1180. }
  1181. catch (Exception x)
  1182. {
  1183. OnException(x);
  1184. throw;
  1185. }
  1186. }
  1187. // Insert an annotated poco object
  1188. public object Insert(object poco)
  1189. {
  1190. var pd = PocoData.ForType(poco.GetType());
  1191. return Insert(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, poco);
  1192. }
  1193. public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue)
  1194. {
  1195. return Update(tableName, primaryKeyName, poco, primaryKeyValue, null);
  1196. }
  1197. // Update a record with values from a poco. primary key value can be either supplied or read from the poco
  1198. public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable<string> columns)
  1199. {
  1200. try
  1201. {
  1202. OpenSharedConnection();
  1203. try
  1204. {
  1205. using (var cmd = CreateCommand(_sharedConnection, ""))
  1206. {
  1207. var sb = new StringBuilder();
  1208. var index = 0;
  1209. var pd = PocoData.ForObject(poco,primaryKeyName);
  1210. if (columns == null)
  1211. {
  1212. foreach (var i in pd.Columns)
  1213. {
  1214. // Don't update the primary key, but grab the value if we don't have it
  1215. if (string.Compare(i.Key, primaryKeyName, true) == 0)
  1216. {
  1217. if (primaryKeyValue == null)
  1218. primaryKeyValue = i.Value.GetValue(poco);
  1219. continue;
  1220. }
  1221. // Dont update result only columns
  1222. if (i.Value.ResultColumn)
  1223. continue;
  1224. // Build the sql
  1225. if (index > 0)
  1226. sb.Append(", ");
  1227. sb.AppendFormat("{0} = {1}{2}", EscapeSqlIdentifier(i.Key), _paramPrefix, index++);
  1228. // Store the parameter in the command
  1229. AddParam(cmd, i.Value.GetValue(poco), _paramPrefix);
  1230. }
  1231. }
  1232. else
  1233. {
  1234. foreach (var colname in columns)
  1235. {
  1236. var pc = pd.Columns[colname];
  1237. // Build the sql
  1238. if (index > 0)
  1239. sb.Append(", ");
  1240. sb.AppendFormat("{0} = {1}{2}", EscapeSqlIdentifier(colname), _paramPrefix, index++);
  1241. // Store the parameter in the command
  1242. AddParam(cmd, pc.GetValue(poco), _paramPrefix);
  1243. }
  1244. // Grab primary key value
  1245. if (primaryKeyValue == null)
  1246. {
  1247. var pc = pd.Columns[primaryKeyName];
  1248. primaryKeyValue = pc.GetValue(poco);
  1249. }
  1250. }
  1251. cmd.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2} = {3}{4}",
  1252. EscapeTableName(tableName), sb.ToString(), EscapeSqlIdentifier(primaryKeyName), _paramPrefix, index++);
  1253. AddParam(cmd, primaryKeyValue, _paramPrefix);
  1254. DoPreExecute(cmd);
  1255. // Do it
  1256. var retv=cmd.ExecuteNonQuery();
  1257. OnExecutedCommand(cmd);
  1258. return retv;
  1259. }
  1260. }
  1261. finally
  1262. {
  1263. CloseSharedConnection();
  1264. }
  1265. }
  1266. catch (Exception x)
  1267. {
  1268. OnException(x);
  1269. throw;
  1270. }
  1271. }
  1272. public int Update(string tableName, string primaryKeyName, object poco)
  1273. {
  1274. return Update(tableName, primaryKeyName, poco, null);
  1275. }
  1276. public int Update(string tableName, string primaryKeyName, object poco, IEnumerable<string> columns)
  1277. {
  1278. return Update(tableName, primaryKeyName, poco, null, columns);
  1279. }
  1280. public int Update(object poco, IEnumerable<string> columns)
  1281. {
  1282. return Update(poco, null, columns);
  1283. }
  1284. public int Update(object poco)
  1285. {
  1286. return Update(poco, null, null);
  1287. }
  1288. public int Update(object poco, object primaryKeyValue)
  1289. {
  1290. return Update(poco, primaryKeyValue, null);
  1291. }
  1292. public int Update(object poco, object primaryKeyValue, IEnumerable<string> columns)
  1293. {
  1294. var pd = PocoData.ForType(poco.GetType());
  1295. return Update(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco, primaryKeyValue, columns);
  1296. }
  1297. public int Update<T>(string sql, params object[] args)
  1298. {
  1299. var pd = PocoData.ForType(typeof(T));
  1300. return Execute(string.Format("UPDATE {0} {1}", EscapeTableName(pd.TableInfo.TableName), sql), args);
  1301. }
  1302. public int Update<T>(Sql sql)
  1303. {
  1304. var pd = PocoData.ForType(typeof(T));
  1305. return Execute(new Sql(string.Format("UPDATE {0}", EscapeTableName(pd.TableInfo.TableName))).Append(sql));
  1306. }
  1307. public int Delete(string tableName, string primaryKeyName, object poco)
  1308. {
  1309. return Delete(tableName, primaryKeyName, poco, null);
  1310. }
  1311. public int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue)
  1312. {
  1313. // If primary key value not specified, pick it up from the object
  1314. if (primaryKeyValue == null)
  1315. {
  1316. var pd = PocoData.ForObject(poco,primaryKeyName);
  1317. PocoColumn pc;
  1318. if (pd.Columns.TryGetValue(primaryKeyName, out pc))
  1319. {
  1320. primaryKeyValue = pc.GetValue(poco);
  1321. }
  1322. }
  1323. // Do it
  1324. var sql = string.Format("DELETE FROM {0} WHERE {1}=@0", EscapeTableName(tableName), EscapeSqlIdentifier(primaryKeyName));
  1325. return Execute(sql, primaryKeyValue);
  1326. }
  1327. public int Delete(object poco)
  1328. {
  1329. var pd = PocoData.ForType(poco.GetType());
  1330. return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco);
  1331. }
  1332. public int Delete<T>(object pocoOrPrimaryKey)
  1333. {
  1334. if (pocoOrPrimaryKey.GetType() == typeof(T))
  1335. return Delete(pocoOrPrimaryKey);
  1336. var pd = PocoData.ForType(typeof(T));
  1337. return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, null, pocoOrPrimaryKey);
  1338. }
  1339. public int Delete<T>(string sql, params object[] args)
  1340. {
  1341. var pd = PocoData.ForType(typeof(T));
  1342. return Execute(string.Format("DELETE FROM {0} {1}", EscapeTableName(pd.TableInfo.TableName), sql), args);
  1343. }
  1344. public int Delete<T>(Sql sql)
  1345. {
  1346. var pd = PocoData.ForType(typeof(T));
  1347. return Execute(new Sql(string.Format("DELETE FROM {0}", EscapeTableName(pd.TableInfo.TableName))).Append(sql));
  1348. }
  1349. // Check if a poco represents a new record
  1350. public bool IsNew(string primaryKeyName, object poco)
  1351. {
  1352. var pd = PocoData.ForObject(poco, primaryKeyName);
  1353. object pk;
  1354. PocoColumn pc;
  1355. if (pd.Columns.TryGetValue(primaryKeyName, out pc))
  1356. {
  1357. pk = pc.GetValue(poco);
  1358. }
  1359. #if !PETAPOCO_NO_DYNAMIC
  1360. else if (poco.GetType() == typeof(System.Dynamic.ExpandoObject))
  1361. {
  1362. return true;
  1363. }
  1364. #endif
  1365. else
  1366. {
  1367. var pi = poco.GetType().GetProperty(primaryKeyName);
  1368. if (pi == null)
  1369. throw new ArgumentException(string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName));
  1370. pk = pi.GetValue(poco, null);
  1371. }
  1372. if (pk == null)
  1373. return true;
  1374. var type = pk.GetType();
  1375. if (type.IsValueType)
  1376. {
  1377. // Common primary key types
  1378. if (type == typeof(long))
  1379. return (long)pk == 0;
  1380. else if (type == typeof(ulong))
  1381. return (ulong)pk == 0;
  1382. else if (type == typeof(int))
  1383. return (int)pk == 0;
  1384. else if (type == typeof(uint))
  1385. return (uint)pk == 0;
  1386. // Create a default instance and compare
  1387. return pk == Activator.CreateInstance(pk.GetType());
  1388. }
  1389. else
  1390. {
  1391. return pk == null;
  1392. }
  1393. }
  1394. public bool IsNew(object poco)
  1395. {
  1396. var pd = PocoData.ForType(poco.GetType());
  1397. if (!pd.TableInfo.AutoIncrement)
  1398. throw new InvalidOperationException("IsNew() and Save() are only supported on tables with auto-increment/identity primary key columns");
  1399. return IsNew(pd.TableInfo.PrimaryKey, poco);
  1400. }
  1401. // Insert new record or Update existing record
  1402. public void Save(string tableName, string primaryKeyName, object poco)
  1403. {
  1404. if (IsNew(primaryKeyName, poco))
  1405. {
  1406. Insert(tableName, primaryKeyName, true, poco);
  1407. }
  1408. else
  1409. {
  1410. Update(tableName, primaryKeyName, poco);
  1411. }
  1412. }
  1413. public void Save(object poco)
  1414. {
  1415. var pd = PocoData.ForType(poco.GetType());
  1416. Save(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco);
  1417. }
  1418. public int CommandTimeout { get; set; }
  1419. public int OneTimeCommandTimeout { get; set; }
  1420. void DoPreExecute(IDbCommand cmd)
  1421. {
  1422. // Setup command timeout
  1423. if (OneTimeCommandTimeout != 0)
  1424. {
  1425. cmd.CommandTimeout = OneTimeCommandTimeout;
  1426. OneTimeCommandTimeout = 0;
  1427. }
  1428. else if (CommandTimeout!=0)
  1429. {
  1430. cmd.CommandTimeout = CommandTimeout;
  1431. }
  1432. // Call hook
  1433. OnExecutingCommand(cmd);
  1434. // Save it
  1435. _lastSql = cmd.CommandText;
  1436. _lastArgs = (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray();
  1437. }
  1438. public string LastSQL { get { return _lastSql; } }
  1439. public object[] LastArgs { get { return _lastArgs; } }
  1440. public string LastCommand
  1441. {
  1442. get { return FormatCommand(_lastSql, _lastArgs); }
  1443. }
  1444. public string FormatCommand(IDbCommand cmd)
  1445. {
  1446. return FormatCommand(cmd.CommandText, (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray());
  1447. }
  1448. public string FormatCommand(string sql, object[] args)
  1449. {
  1450. var sb = new StringBuilder();
  1451. if (sql == null)
  1452. return "";
  1453. sb.Append(sql);
  1454. if (args != null && args.Length > 0)
  1455. {
  1456. sb.Append("\n");
  1457. for (int i = 0; i < args.Length; i++)
  1458. {
  1459. sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, args[i].GetType().Name, args[i]);
  1460. }
  1461. sb.Remove(sb.Length - 1, 1);
  1462. }
  1463. return sb.ToString();
  1464. }
  1465. public static IMapper Mapper
  1466. {
  1467. get;
  1468. set;
  1469. }
  1470. public class PocoColumn
  1471. {
  1472. public string ColumnName;
  1473. public PropertyInfo PropertyInfo;
  1474. public bool ResultColumn;
  1475. public virtual void SetValue(object target, object val) { PropertyInfo.SetValue(target, val, null); }
  1476. public virtual object GetValue(object target) { return PropertyInfo.GetValue(target, null); }
  1477. public virtual object ChangeType(object val) { return Convert.ChangeType(val, PropertyInfo.PropertyType); }
  1478. }
  1479. public class ExpandoColumn : PocoColumn
  1480. {
  1481. public override void SetValue(object target, object val) { (target as IDictionary<string, object>)[ColumnName]=val; }
  1482. public override object GetValue(object target)
  1483. {
  1484. object val=null;
  1485. (target as IDictionary<string, object>).TryGetValue(ColumnName, out val);
  1486. return val;
  1487. }
  1488. public override object ChangeType(object val) { return val; }
  1489. }
  1490. public class PocoData
  1491. {
  1492. public static PocoData ForObject(object o, string primaryKeyName)
  1493. {
  1494. var t = o.GetType();
  1495. #if !PETAPOCO_NO_DYNAMIC
  1496. if (t == typeof(System.Dynamic.ExpandoObject))
  1497. {
  1498. var pd = new PocoData();
  1499. pd.TableInfo = new TableInfo();
  1500. pd.Columns = new Dictionary<string, PocoColumn>(StringComparer.OrdinalIgnoreCase);
  1501. pd.Columns.Add(primaryKeyName, new ExpandoColumn() { ColumnName = primaryKeyName });
  1502. pd.TableInfo.PrimaryKey = primaryKeyName;
  1503. pd.TableInfo.AutoIncrement = true;
  1504. foreach (var col in (o as IDictionary<string, object>).Keys)
  1505. {
  1506. if (col!=primaryKeyName)
  1507. pd.Columns.Add(col, new ExpandoColumn() { ColumnName = col });
  1508. }
  1509. return pd;
  1510. }
  1511. else
  1512. #endif
  1513. return ForType(t);
  1514. }
  1515. static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim();
  1516. public static PocoData ForType(Type t)
  1517. {
  1518. #if !PETAPOCO_NO_DYNAMIC
  1519. if (t == typeof(System.Dynamic.ExpandoObject))
  1520. throw new InvalidOperationException("Can't use dynamic types with this method");
  1521. #endif
  1522. // Check cache
  1523. RWLock.EnterReadLock();
  1524. PocoData pd;
  1525. try
  1526. {
  1527. if (m_PocoDatas.TryGetValue(t, out pd))
  1528. return pd;
  1529. }
  1530. finally
  1531. {
  1532. RWLock.ExitReadLock();
  1533. }
  1534. // Cache it
  1535. RWLock.EnterWriteLock();
  1536. try
  1537. {
  1538. // Check again
  1539. if (m_PocoDatas.TryGetValue(t, out pd))
  1540. return pd;
  1541. // Create it
  1542. pd = new PocoData(t);
  1543. m_PocoDatas.Add(t, pd);
  1544. }
  1545. finally
  1546. {
  1547. RWLock.ExitWriteLock();
  1548. }
  1549. return pd;
  1550. }
  1551. public PocoData()
  1552. {
  1553. }
  1554. public PocoData(Type t)
  1555. {
  1556. type = t;
  1557. TableInfo=new TableInfo();
  1558. // Get the table name
  1559. var a = t.GetCustomAttributes(typeof(TableNameAttribute), true);
  1560. TableInfo.TableName = a.Length == 0 ? t.Name : (a[0] as TableNameAttribute).Value;
  1561. // Get the primary key
  1562. a = t.GetCustomAttributes(typeof(PrimaryKeyAttribute), true);
  1563. TableInfo.PrimaryKey = a.Length == 0 ? "ID" : (a[0] as PrimaryKeyAttribute).Value;
  1564. TableInfo.SequenceName = a.Length == 0 ? null : (a[0] as PrimaryKeyAttribute).sequenceName;
  1565. TableInfo.AutoIncrement = a.Length == 0 ? false : (a[0] as PrimaryKeyAttribute).autoIncrement;
  1566. // Call column mapper
  1567. if (Database.Mapper != null)
  1568. Database.Mapper.GetTableInfo(t, TableInfo);
  1569. // Work out bound properties
  1570. bool ExplicitColumns = t.GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Length > 0;
  1571. Columns = new Dictionary<string, PocoColumn>(StringComparer.OrdinalIgnoreCase);
  1572. foreach (var pi in t.GetProperties())
  1573. {
  1574. // Work out if properties is to be included
  1575. var ColAttrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true);
  1576. if (ExplicitColumns)
  1577. {
  1578. if (ColAttrs.Length == 0)
  1579. continue;
  1580. }
  1581. else
  1582. {
  1583. if (pi.GetCustomAttributes(typeof(IgnoreAttribute), true).Length != 0)
  1584. continue;
  1585. }
  1586. var pc = new PocoColumn();
  1587. pc.PropertyInfo = pi;
  1588. // Work out the DB column name
  1589. if (ColAttrs.Length > 0)
  1590. {
  1591. var colattr = (ColumnAttribute)ColAttrs[0];
  1592. pc.ColumnName = colattr.Name;
  1593. if ((colattr as ResultColumnAttribute) != null)
  1594. pc.ResultColumn = true;
  1595. }
  1596. if (pc.ColumnName == null)
  1597. {
  1598. pc.ColumnName = pi.Name;
  1599. if (Database.Mapper != null && !Database.Mapper.MapPropertyToColumn(pi, ref pc.ColumnName, ref pc.ResultColumn))
  1600. continue;
  1601. }
  1602. // Store it
  1603. Columns.Add(pc.ColumnName, pc);
  1604. }
  1605. // Build column list for automatic select
  1606. QueryColumns = (from c in Columns where !c.Value.ResultColumn select c.Key).ToArray();
  1607. }
  1608. static bool IsIntegralType(Type t)
  1609. {
  1610. var tc = Type.GetTypeCode(t);
  1611. return tc >= TypeCode.SByte && tc <= TypeCode.UInt64;
  1612. }
  1613. // Create factory function that can convert a IDataReader record into a POCO
  1614. public Delegate GetFactory(string sql, string connString, bool ForceDateTimesToUtc, int firstColumn, int countColumns, IDataReader r)
  1615. {
  1616. // Check cache
  1617. var key = string.Format("{0}:{1}:{2}:{3}:{4}", sql, connString, ForceDateTimesToUtc, firstColumn, countColumns);
  1618. RWLock.EnterReadLock();
  1619. try
  1620. {
  1621. // Have we already created it?
  1622. Delegate factory;
  1623. if (PocoFactories.TryGetValue(key, out factory))
  1624. return factory;
  1625. }
  1626. finally
  1627. {
  1628. RWLock.ExitReadLock();
  1629. }
  1630. // Take the writer lock
  1631. RWLock.EnterWriteLock();
  1632. try
  1633. {
  1634. // Check again, just in case
  1635. Delegate factory;
  1636. if (PocoFactories.TryGetValue(key, out factory))
  1637. return factory;
  1638. // Create the method
  1639. var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), type, new Type[] { typeof(IDataReader) }, true);
  1640. var il = m.GetILGenerator();
  1641. #if !PETAPOCO_NO_DYNAMIC
  1642. if (type == typeof(object))
  1643. {
  1644. // var poco=new T()
  1645. il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj
  1646. MethodInfo fnAdd = typeof(IDictionary<string, object>).GetMethod("Add");
  1647. // Enumerate all fields generating a set assignment for the column
  1648. for (int i = firstColumn; i < firstColumn + countColumns; i++)
  1649. {
  1650. var srcType = r.GetFieldType(i);
  1651. il.Emit(OpCodes.Dup); // obj, obj
  1652. il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname
  1653. // Get the converter
  1654. Func<object, object> converter = null;
  1655. if (Database.Mapper != null)
  1656. converter = Database.Mapper.GetFromDbConverter(null, srcType);
  1657. if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime))
  1658. converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); };
  1659. // Setup stack for call to converter
  1660. AddConverterToStack(il, converter);
  1661. // r[i]
  1662. il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr
  1663. il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i
  1664. il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value
  1665. // Convert DBNull to null
  1666. il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value
  1667. il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null)
  1668. var lblNotNull = il.DefineLabel();
  1669. il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value
  1670. il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter?
  1671. if (converter != null)
  1672. il.Emit(OpCodes.Pop); // obj, obj, fieldname,
  1673. il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null
  1674. if (converter != null)
  1675. {
  1676. var lblReady = il.DefineLabel();
  1677. il.Emit(OpCodes.Br_S, lblReady);
  1678. il.MarkLabel(lblNotNull);
  1679. il.Emit(OpCodes.Callvirt, fnInvoke);
  1680. il.MarkLabel(lblReady);
  1681. }
  1682. else
  1683. {
  1684. il.MarkLabel(lblNotNull);
  1685. }
  1686. il.Emit(OpCodes.Callvirt, fnAdd);
  1687. }
  1688. }
  1689. else
  1690. #endif
  1691. if (type.IsValueType || type == typeof(string) || type == typeof(byte[]))
  1692. {
  1693. // Do we need to install a converter?
  1694. var srcType = r.GetFieldType(0);
  1695. var converter = GetConverter(ForceDateTimesToUtc, null, srcType, type);
  1696. // "if (!rdr.IsDBNull(i))"
  1697. il.Emit(OpCodes.Ldarg_0); // rdr
  1698. il.Emit(OpCodes.Ldc_I4_0); // rdr,0
  1699. il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool
  1700. var lblCont = il.DefineLabel();
  1701. il.Emit(OpCodes.Brfalse_S, lblCont);
  1702. il.Emit(OpCodes.Ldnull); // null
  1703. var lblFin = il.DefineLabel();
  1704. il.Emit(OpCodes.Br_S, lblFin);
  1705. il.MarkLabel(lblCont);
  1706. // Setup stack for call to converter
  1707. AddConverterToStack(il, converter);
  1708. il.Emit(OpCodes.Ldarg_0); // rdr
  1709. il.Emit(OpCodes.Ldc_I4_0); // rdr,0
  1710. il.Emit(OpCodes.Callvirt, fnGetValue); // value
  1711. // Call the converter
  1712. if (converter != null)
  1713. il.Emit(OpCodes.Callvirt, fnInvoke);
  1714. il.MarkLabel(lblFin);
  1715. il.Emit(OpCodes.Unbox_Any, type); // value converted
  1716. }
  1717. else
  1718. {
  1719. // var poco=new T()
  1720. il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
  1721. // Enumerate all fields generating a set assignment for the column
  1722. for (int i = firstColumn; i < firstColumn + countColumns; i++)
  1723. {
  1724. // Get the PocoColumn for this db column, ignore if not known
  1725. PocoColumn pc;
  1726. if (!Columns.TryGetValue(r.GetName(i), out pc))
  1727. continue;
  1728. // Get the source type for this column
  1729. var srcType = r.GetFieldType(i);
  1730. var dstType = pc.PropertyInfo.PropertyType;
  1731. // "if (!rdr.IsDBNull(i))"
  1732. il.Emit(OpCodes.Ldarg_0); // poco,rdr
  1733. il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i
  1734. il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool
  1735. var lblNext = il.DefineLabel();
  1736. il.Emit(OpCodes.Brtrue_S, lblNext); // poco
  1737. il.Emit(OpCodes.Dup); // poco,poco
  1738. // Do we need to install a converter?
  1739. var converter = GetConverter(ForceDateTimesToUtc, pc, srcType, dstType);
  1740. // Fast
  1741. bool Handled = false;
  1742. if (converter == null)
  1743. {
  1744. var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) });
  1745. if (valuegetter != null
  1746. && valuegetter.ReturnType == srcType
  1747. && (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType)))
  1748. {
  1749. il.Emit(OpCodes.Ldarg_0); // *,rdr
  1750. il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
  1751. il.Emit(OpCodes.Callvirt, valuegetter); // *,value
  1752. // Convert to Nullable
  1753. if (Nullable.GetUnderlyingType(dstType) != null)
  1754. {
  1755. il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) }));
  1756. }
  1757. il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
  1758. Handled = true;
  1759. }
  1760. }
  1761. // Not so fast
  1762. if (!Handled)
  1763. {
  1764. // Setup stack for call to converter
  1765. AddConverterToStack(il, converter);
  1766. // "value = rdr.GetValue(i)"
  1767. il.Emit(OpCodes.Ldarg_0); // *,rdr
  1768. il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
  1769. il.Emit(OpCodes.Callvirt, fnGetValue); // *,value
  1770. // Call the converter
  1771. if (converter != null)
  1772. il.Emit(OpCodes.Callvirt, fnInvoke);
  1773. // Assign it
  1774. il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value
  1775. il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
  1776. }
  1777. il.MarkLabel(lblNext);
  1778. }
  1779. var fnOnLoaded = RecurseInheritedTypes<MethodInfo>(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
  1780. if (fnOnLoaded != null)
  1781. {
  1782. il.Emit(OpCodes.Dup);
  1783. il.Emit(OpCodes.Callvirt, fnOnLoaded);
  1784. }
  1785. }
  1786. il.Emit(OpCodes.Ret);
  1787. // Cache it, return it
  1788. var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type));
  1789. PocoFactories.Add(key, del);
  1790. return del;
  1791. }
  1792. finally
  1793. {
  1794. RWLock.ExitWriteLock();
  1795. }
  1796. }
  1797. private static void AddConverterToStack(ILGenerator il, Func<object, object> converter)
  1798. {
  1799. if (converter != null)
  1800. {
  1801. // Add the converter
  1802. int converterIndex = m_Converters.Count;
  1803. m_Converters.Add(converter);
  1804. // Generate IL to push the converter onto the stack
  1805. il.Emit(OpCodes.Ldsfld, fldConverters);
  1806. il.Emit(OpCodes.Ldc_I4, converterIndex);
  1807. il.Emit(OpCodes.Callvirt, fnListGetItem); // Converter
  1808. }
  1809. }
  1810. private static Func<object, object> GetConverter(bool forceDateTimesToUtc, PocoColumn pc, Type srcType, Type dstType)
  1811. {
  1812. Func<object, object> converter = null;
  1813. // Get converter from the mapper
  1814. if (Database.Mapper != null)
  1815. {
  1816. if (pc != null)
  1817. {
  1818. converter = Database.Mapper.GetFromDbConverter(pc.PropertyInfo, srcType);
  1819. }
  1820. else
  1821. {
  1822. var m2 = Database.Mapper as IMapper2;
  1823. if (m2 != null)
  1824. {
  1825. converter = m2.GetFromDbConverter(dstType, srcType);
  1826. }
  1827. }
  1828. }
  1829. // Standard DateTime->Utc mapper
  1830. if (forceDateTimesToUtc && converter == null && srcType == typeof(DateTime) && (dstType == typeof(DateTime) || dstType == typeof(DateTime?)))
  1831. {
  1832. converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); };
  1833. }
  1834. // Forced type conversion including integral types -> enum
  1835. if (converter == null)
  1836. {
  1837. if (dstType.IsEnum && IsIntegralType(srcType))
  1838. {
  1839. if (srcType != typeof(int))
  1840. {
  1841. converter = delegate(object src) { return Convert.ChangeType(src, typeof(int), null); };
  1842. }
  1843. }
  1844. else if (!dstType.IsAssignableFrom(srcType))
  1845. {
  1846. converter = delegate(object src) { return Convert.ChangeType(src, dstType, null); };
  1847. }
  1848. }
  1849. return converter;
  1850. }
  1851. static T RecurseInheritedTypes<T>(Type t, Func<Type, T> cb)
  1852. {
  1853. while (t != null)
  1854. {
  1855. T info = cb(t);
  1856. if (info != null)
  1857. return info;
  1858. t = t.BaseType;
  1859. }
  1860. return default(T);
  1861. }
  1862. static Dictionary<Type, PocoData> m_PocoDatas = new Dictionary<Type, PocoData>();
  1863. static List<Func<object, object>> m_Converters = new List<Func<object, object>>();
  1864. static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) });
  1865. static MethodInfo fnIsDBNull = typeof(IDataRecord).GetMethod("IsDBNull");
  1866. static FieldInfo fldConverters = typeof(PocoData).GetField("m_Converters", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic);
  1867. static MethodInfo fnListGetItem = typeof(List<Func<object, object>>).GetProperty("Item").GetGetMethod();
  1868. static MethodInfo fnInvoke = typeof(Func<object, object>).GetMethod("Invoke");
  1869. public Type type;
  1870. public string[] QueryColumns { get; private set; }
  1871. public TableInfo TableInfo { get; private set; }
  1872. public Dictionary<string, PocoColumn> Columns { get; private set; }
  1873. Dictionary<string, Delegate> PocoFactories = new Dictionary<string, Delegate>();
  1874. }
  1875. // Member variables
  1876. string _connectionString;
  1877. string _providerName;
  1878. DbProviderFactory _factory;
  1879. IDbConnection _sharedConnection;
  1880. IDbTransaction _transaction;
  1881. int _sharedConnectionDepth;
  1882. int _transactionDepth;
  1883. bool _transactionCancelled;
  1884. string _lastSql;
  1885. object[] _lastArgs;
  1886. string _paramPrefix = "@";
  1887. }
  1888. // Transaction object helps maintain transaction depth counts
  1889. public class Transaction : IDisposable
  1890. {
  1891. public Transaction(Database db)
  1892. {
  1893. _db = db;
  1894. _db.BeginTransaction();
  1895. }
  1896. public virtual void Complete()
  1897. {
  1898. _db.CompleteTransaction();
  1899. _db = null;
  1900. }
  1901. public void Dispose()
  1902. {
  1903. if (_db != null)
  1904. _db.AbortTransaction();
  1905. }
  1906. Database _db;
  1907. }
  1908. // Simple helper class for building SQL statments
  1909. public class Sql
  1910. {
  1911. public Sql()
  1912. {
  1913. }
  1914. public Sql(string sql, params object[] args)
  1915. {
  1916. _sql = sql;
  1917. _args = args;
  1918. }
  1919. public static Sql Builder
  1920. {
  1921. get { return new Sql(); }
  1922. }
  1923. string _sql;
  1924. object[] _args;
  1925. Sql _rhs;
  1926. string _sqlFinal;
  1927. object[] _argsFinal;
  1928. private void Build()
  1929. {
  1930. // already built?
  1931. if (_sqlFinal != null)
  1932. return;
  1933. // Build it
  1934. var sb = new StringBuilder();
  1935. var args = new List<object>();
  1936. Build(sb, args, null);
  1937. _sqlFinal = sb.ToString();
  1938. _argsFinal = args.ToArray();
  1939. }
  1940. public string SQL
  1941. {
  1942. get
  1943. {
  1944. Build();
  1945. return _sqlFinal;
  1946. }
  1947. }
  1948. public object[] Arguments
  1949. {
  1950. get
  1951. {
  1952. Build();
  1953. return _argsFinal;
  1954. }
  1955. }
  1956. public Sql Append(Sql sql)
  1957. {
  1958. if (_rhs != null)
  1959. _rhs.Append(sql);
  1960. else
  1961. _rhs = sql;
  1962. return this;
  1963. }
  1964. public Sql Append(string sql, params object[] args)
  1965. {
  1966. return Append(new Sql(sql, args));
  1967. }
  1968. static bool Is(Sql sql, string sqltype)
  1969. {
  1970. return sql != null && sql._sql != null && sql._sql.StartsWith(sqltype, StringComparison.InvariantCultureIgnoreCase);
  1971. }
  1972. private void Build(StringBuilder sb, List<object> args, Sql lhs)
  1973. {
  1974. if (!String.IsNullOrEmpty(_sql))
  1975. {
  1976. // Add SQL to the string
  1977. if (sb.Length > 0)
  1978. {
  1979. sb.Append("\n");
  1980. }
  1981. var sql = Database.ProcessParams(_sql, _args, args);
  1982. if (Is(lhs, "WHERE ") && Is(this, "WHERE "))
  1983. sql = "AND " + sql.Substring(6);
  1984. if (Is(lhs, "ORDER BY ") && Is(this, "ORDER BY "))
  1985. sql = ", " + sql.Substring(9);
  1986. sb.Append(sql);
  1987. }
  1988. // Now do rhs
  1989. if (_rhs != null)
  1990. _rhs.Build(sb, args, this);
  1991. }
  1992. public Sql Where(string sql, params object[] args)
  1993. {
  1994. return Append(new Sql("WHERE (" + sql + ")", args));
  1995. }
  1996. public Sql OrderBy(params object[] columns)
  1997. {
  1998. return Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray())));
  1999. }
  2000. public Sql Select(params object[] columns)
  2001. {
  2002. return Append(new Sql("SELECT " + String.Join(", ", (from x in columns select x.ToString()).ToArray())));
  2003. }
  2004. public Sql From(params object[] tables)
  2005. {
  2006. return Append(new Sql("FROM " + String.Join(", ", (from x in tables select x.ToString()).ToArray())));
  2007. }
  2008. public Sql GroupBy(params object[] columns)
  2009. {
  2010. return Append(new Sql("GROUP BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray())));
  2011. }
  2012. private SqlJoinClause Join(string JoinType, string table)
  2013. {
  2014. return new SqlJoinClause(Append(new Sql(JoinType + table)));
  2015. }
  2016. public SqlJoinClause InnerJoin(string table) { return Join("INNER JOIN ", table); }
  2017. public SqlJoinClause LeftJoin(string table) { return Join("LEFT JOIN ", table); }
  2018. public class SqlJoinClause
  2019. {
  2020. private readonly Sql _sql;
  2021. public SqlJoinClause(Sql sql)
  2022. {
  2023. _sql = sql;
  2024. }
  2025. public Sql On(string onClause, params object[] args)
  2026. {
  2027. return _sql.Append("ON " + onClause, args);
  2028. }
  2029. }
  2030. }
  2031. }