diff --git a/AsyncPoco/AsyncPoco.cs b/AsyncPoco/AsyncPoco.cs index 208ba1e5..489b0b5e 100644 --- a/AsyncPoco/AsyncPoco.cs +++ b/AsyncPoco/AsyncPoco.cs @@ -24,6 +24,8 @@ using System.Configuration; using System.Data; using System.Data.Common; +using System.Diagnostics; +using System.Dynamic; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -36,34 +38,62 @@ namespace AsyncPoco { /// - /// The main Database class. You can either use this class directly, or derive from it. + /// The main Database class. You can either use this class directly, or derive from it. /// - public class Database : IDisposable + public class Database : IDatabase { + private static IEqualityComparer _columnComparer = StringComparer.InvariantCultureIgnoreCase; + public static IEqualityComparer ColumnComparer + { + get { return _columnComparer; } + set + { + _columnComparer = value; + MultiPocoFactory.FieldNameComparer = value; + PocoData.ColumnComparer = value; + EnumMapper.FieldComparer = value; + } + } + + #region IDisposable + + /// + /// Automatically close one open shared connection + /// + public void Dispose() + { + // Automatically close one open connection reference + // (Works with KeepConnectionAlive and manually opening a shared connection) + CloseSharedConnection(); + } + + #endregion + #region Constructors + /// - /// Construct a database using a supplied DbConnection + /// Construct a database using a supplied DbConnection /// /// The DbConnection to use /// - /// The supplied DbConnection will not be closed/disposed by PetaPoco - that remains - /// the responsibility of the caller. + /// The supplied DbConnection will not be closed/disposed by PetaPoco - that remains + /// the responsibility of the caller. /// public Database(DbConnection connection) { _sharedConnection = connection; _connectionString = connection.ConnectionString; - _sharedConnectionDepth = 2; // Prevent closing external connection + _sharedConnectionDepth = 2; // Prevent closing external connection CommonConstruct(); } /// - /// Construct a database using a supplied connections string and optionally a provider name + /// Construct a database using a supplied connections string and optionally a provider name /// /// The DB connection string /// The name of the DB provider to use /// - /// PetaPoco will automatically close and dispose any connections it creates. + /// PetaPoco will automatically close and dispose any connections it creates. /// public Database(string connectionString, string providerName) { @@ -73,7 +103,7 @@ public Database(string connectionString, string providerName) } /// - /// Construct a Database using a supplied connection string and a DbProviderFactory + /// Construct a Database using a supplied connection string and a DbProviderFactory /// /// The connection string to use /// The DbProviderFactory to use for instantiating DbConnection's @@ -85,8 +115,8 @@ public Database(string connectionString, DbProviderFactory provider) } /// - /// Construct a Database using a supplied connectionString Name. The actual connection string and provider will be - /// read from app/web.config. + /// Construct a Database using a supplied connectionString Name. The actual connection string and provider will be + /// read from app/web.config. /// /// The name of the connection public Database(string connectionStringName) @@ -114,7 +144,7 @@ public Database(string connectionStringName) } /// - /// Provides common initialization for the various constructors + /// Provides common initialization for the various constructors /// private void CommonConstruct() { @@ -128,7 +158,7 @@ private void CommonConstruct() _factory = DbProviderFactories.GetFactory(_providerName); // Resolve the DB Type - string DBTypeName = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; + var DBTypeName = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; _dbType = DatabaseType.Resolve(DBTypeName, _providerName); // What character is used for delimiting parameters in SQL @@ -137,33 +167,18 @@ private void CommonConstruct() #endregion - #region IDisposable - /// - /// Automatically close one open shared connection - /// - public void Dispose() - { - // Automatically close one open connection reference - // (Works with KeepConnectionAlive and manually opening a shared connection) - CloseSharedConnection(); - } - #endregion - #region Connection Management + /// - /// When set to true the first opened connection is kept alive until this object is disposed + /// When set to true the first opened connection is kept alive until this object is disposed /// - public bool KeepConnectionAlive - { - get; - set; - } + public bool KeepConnectionAlive { get; set; } /// - /// Open a connection that will be used for all subsequent queries. + /// Open a connection that will be used for all subsequent queries. /// /// - /// Calls to Open/CloseSharedConnection are reference counted and should be balanced + /// Calls to Open/CloseSharedConnection are reference counted and should be balanced /// public virtual async Task OpenSharedConnectionAsync() { @@ -181,13 +196,13 @@ public virtual async Task OpenSharedConnectionAsync() _sharedConnection = OnConnectionOpened(_sharedConnection); if (KeepConnectionAlive) - _sharedConnectionDepth++; // Make sure you call Dispose + _sharedConnectionDepth++; // Make sure you call Dispose } _sharedConnectionDepth++; } /// - /// Releases the shared connection + /// Releases the shared connection /// public void CloseSharedConnection() { @@ -204,7 +219,7 @@ public void CloseSharedConnection() } /// - /// Provides access to the currently open shared connection (or null if none) + /// Provides access to the currently open shared connection (or null if none) /// public IDbConnection Connection { @@ -214,28 +229,25 @@ public IDbConnection Connection #endregion #region Transaction Management + // Helper to create a transaction scope /// - /// Starts or continues a transaction. + /// Starts or continues a transaction. /// /// An ITransaction reference that must be Completed or disposed /// - /// This method makes management of calls to Begin/End/CompleteTransaction easier. - /// - /// The usage pattern for this should be: - /// - /// using (var tx = db.GetTransaction()) - /// { - /// // Do stuff - /// db.Update(...); - /// + /// This method makes management of calls to Begin/End/CompleteTransaction easier. + /// The usage pattern for this should be: + /// using (var tx = db.GetTransaction()) + /// { + /// // Do stuff + /// db.Update(...); /// // Mark the transaction as complete /// tx.Complete(); - /// } - /// - /// Transactions can be nested but they must all be completed otherwise the entire - /// transaction is aborted. + /// } + /// Transactions can be nested but they must all be completed otherwise the entire + /// transaction is aborted. /// public Task GetTransactionAsync() { @@ -243,22 +255,22 @@ public Task GetTransactionAsync() } /// - /// Called when a transaction starts. Overridden by the T4 template generated database - /// classes to ensure the same DB instance is used throughout the transaction. + /// Called when a transaction starts. Overridden by the T4 template generated database + /// classes to ensure the same DB instance is used throughout the transaction. /// - public virtual void OnBeginTransaction() - { + public virtual void OnBeginTransaction() + { } /// - /// Called when a transaction ends. + /// Called when a transaction ends. /// - public virtual void OnEndTransaction() - { + public virtual void OnEndTransaction() + { } /// - /// Starts a transaction scope, see GetTransaction() for recommended usage + /// Starts a transaction scope, see GetTransaction() for recommended usage /// public virtual async Task BeginTransactionAsync() { @@ -271,11 +283,10 @@ public virtual async Task BeginTransactionAsync() _transactionCancelled = false; OnBeginTransaction(); } - } /// - /// Internal helper to cleanup transaction + /// Internal helper to cleanup transaction /// protected virtual void CleanupTransaction() { @@ -293,38 +304,39 @@ protected virtual void CleanupTransaction() } /// - /// Aborts the entire outer most transaction scope + /// Aborts the entire outer most transaction scope /// /// - /// Called automatically by Transaction.Dispose() - /// if the transaction wasn't completed. + /// Called automatically by Transaction.Dispose() + /// if the transaction wasn't completed. /// public void AbortTransaction() { _transactionCancelled = true; - if ((--_transactionDepth) == 0) + if (--_transactionDepth == 0) CleanupTransaction(); } /// - /// Marks the current transaction scope as complete. + /// Marks the current transaction scope as complete. /// public void CompleteTransaction() { - if ((--_transactionDepth) == 0) + if (--_transactionDepth == 0) CleanupTransaction(); } #endregion #region Command Management + /// - /// Add a parameter to a DB command + /// Add a parameter to a DB command /// /// A reference to the IDbCommand to which the parameter is to be added /// The value to assign to the parameter /// Optional, a reference to the property info of the POCO property from which the value is coming. - void AddParam(IDbCommand cmd, object value, PropertyInfo pi) + private void AddParam(IDbCommand cmd, object value, PropertyInfo pi) { // Convert value to from poco type to db type if (pi != null) @@ -359,7 +371,7 @@ void AddParam(IDbCommand cmd, object value, PropertyInfo pi) value = _dbType.MapParameterValue(value); var t = value.GetType(); - if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int + if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int { p.Value = (int)value; } @@ -373,9 +385,9 @@ void AddParam(IDbCommand cmd, object value, PropertyInfo pi) { // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. Set before attempting to set Size, or Size will always max out at 4000 if ((value as string).Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter") - p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); + p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); - p.Size = Math.Max((value as string).Length + 1, 4000); // Help query plan caching by using common size + p.Size = Math.Max((value as string).Length + 1, 4000); // Help query plan caching by using common size p.Value = value; } else if (t == typeof(AnsiString)) @@ -387,7 +399,8 @@ void AddParam(IDbCommand cmd, object value, PropertyInfo pi) } else if (value.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type + p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); + //geography is the equivalent SQL Server Type p.Value = value; } @@ -407,7 +420,8 @@ void AddParam(IDbCommand cmd, object value, PropertyInfo pi) } // Create a command - static Regex rxParamsPrefix = new Regex(@"(? _paramPrefix + m.Value.Substring(1)); - sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @ + sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @ // Create the command and add parameters var cmd = connection.CreateCommand(); @@ -437,74 +451,76 @@ public DbCommand CreateCommand(DbConnection connection, string sql, params objec _dbType.PreExecute(cmd); // Call logging - if (!String.IsNullOrEmpty(sql)) + if (!string.IsNullOrEmpty(sql)) DoPreExecute(cmd); return cmd; } + #endregion #region Exception Reporting and Logging /// - /// Called if an exception occurs during processing of a DB operation. Override to provide custom logging/handling. + /// Called if an exception occurs during processing of a DB operation. Override to provide custom logging/handling. /// /// The exception instance /// True to re-throw the exception, false to suppress it public virtual bool OnException(Exception x) { - System.Diagnostics.Debug.WriteLine(x.ToString()); - System.Diagnostics.Debug.WriteLine(LastCommand); + Debug.WriteLine(x.ToString()); + Debug.WriteLine(LastCommand); return true; } /// - /// Called when DB connection opened + /// Called when DB connection opened /// /// The newly opened DbConnection /// The same or a replacement DbConnection /// - /// Override this method to provide custom logging of opening connection, or - /// to provide a proxy DbConnection. + /// Override this method to provide custom logging of opening connection, or + /// to provide a proxy DbConnection. /// - public virtual DbConnection OnConnectionOpened(DbConnection conn) - { - return conn; + public virtual DbConnection OnConnectionOpened(DbConnection conn) + { + return conn; } /// - /// Called when DB connection closed + /// Called when DB connection closed /// /// The soon to be closed IDBConnection - public virtual void OnConnectionClosing(IDbConnection conn) - { + public virtual void OnConnectionClosing(IDbConnection conn) + { } /// - /// Called just before an DB command is executed + /// Called just before an DB command is executed /// /// The command to be executed /// - /// Override this method to provide custom logging of commands and/or - /// modification of the IDbCommand before it's executed + /// Override this method to provide custom logging of commands and/or + /// modification of the IDbCommand before it's executed /// - public virtual void OnExecutingCommand(IDbCommand cmd) - { + public virtual void OnExecutingCommand(IDbCommand cmd) + { } /// - /// Called on completion of command execution + /// Called on completion of command execution /// /// The IDbCommand that finished executing - public virtual void OnExecutedCommand(IDbCommand cmd) - { + public virtual void OnExecutedCommand(IDbCommand cmd) + { } #endregion #region operation: Execute + /// - /// Executes a non-query command + /// Executes a non-query command /// /// The SQL statement to execute /// Arguments to any embedded parameters in the SQL @@ -537,7 +553,7 @@ public virtual async Task ExecuteAsync(string sql, params object[] args) } /// - /// Executes a non-query command + /// Executes a non-query command /// /// An SQL builder object representing the query and it's arguments /// The number of rows affected @@ -551,7 +567,7 @@ public Task ExecuteAsync(Sql sql) #region operation: ExecuteScalarAsync /// - /// Executes a query and return the first column of the first row in the result set. + /// Executes a query and return the first column of the first row in the result set. /// /// The type that the result value should be cast to /// The SQL query to execute @@ -566,12 +582,12 @@ public virtual async Task ExecuteScalarAsync(string sql, params object[] a { using (var cmd = CreateCommand(_sharedConnection, sql, args)) { - object val = await cmd.ExecuteScalarAsync(); + var val = await cmd.ExecuteScalarAsync(); OnExecutedCommand(cmd); // Handle nullable types - Type u = Nullable.GetUnderlyingType(typeof(T)); - if (u != null && val == null) + var u = Nullable.GetUnderlyingType(typeof(T)); + if (u != null && val == null) return default(T); return (T)Convert.ChangeType(val, u ?? typeof(T)); @@ -591,7 +607,7 @@ public virtual async Task ExecuteScalarAsync(string sql, params object[] a } /// - /// Executes a query and return the first column of the first row in the result set. + /// Executes a query and return the first column of the first row in the result set. /// /// The type that the result value should be cast to /// An SQL builder object representing the query and it's arguments @@ -606,7 +622,7 @@ public Task ExecuteScalarAsync(Sql sql) #region operation: Fetch /// - /// Runs a query and returns the result set as a typed list + /// Runs a query and returns the result set as a typed list /// /// The Type representing a row in the result set /// The SQL query to execute @@ -620,12 +636,12 @@ public async Task> FetchAsync(string sql, params object[] args) } /// - /// Runs a query and returns the result set as a typed list + /// Runs a query and returns the result set as a typed list /// /// The Type representing a row in the result set /// An SQL builder object representing the query and it's arguments /// A List holding the results of the query - public Task> FetchAsync(Sql sql) + public Task> FetchAsync(Sql sql) { return FetchAsync(sql.SQL, sql.Arguments); } @@ -635,8 +651,8 @@ public Task> FetchAsync(Sql sql) #region operation: Page /// - /// Starting with a regular SELECT statement, derives the SQL statements required to query a - /// DB for a page of records and the total number of records + /// Starting with a regular SELECT statement, derives the SQL statements required to query a + /// DB for a page of records and the total number of records /// /// The Type representing a row in the result set /// The number of rows to skip before the start of the page @@ -645,7 +661,8 @@ public Task> FetchAsync(Sql sql) /// Arguments to any embedded parameters in the SQL /// Outputs the SQL statement to query for the total number of matching rows /// Outputs the SQL statement to retrieve a single page of matching rows - void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) + private void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, + out string sqlPage) { // Add auto select clause if (EnableAutoSelect) @@ -661,7 +678,7 @@ void BuildPageQueries(long skip, long take, string sql, ref object[] args, ou } /// - /// Retrieves a page of records and the total number of available records + /// Retrieves a page of records and the total number of available records /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -672,10 +689,12 @@ void BuildPageQueries(long skip, long take, string sql, ref object[] args, ou /// Arguments to any embedded parameters in the sqlPage statement /// A Page of results /// - /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. - /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. + /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. + /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page + /// object. /// - public async Task> PageAsync(long page, long itemsPerPage, string sqlCount, object[] countArgs, string sqlPage, object[] pageArgs) + public async Task> PageAsync(long page, long itemsPerPage, string sqlCount, object[] countArgs, + string sqlPage, object[] pageArgs) { // Save the one-time command time out and use it for both queries var saveTimeout = OneTimeCommandTimeout; @@ -689,7 +708,7 @@ public async Task> PageAsync(long page, long itemsPerPage, string sql }; result.TotalPages = result.TotalItems / itemsPerPage; - if ((result.TotalItems % itemsPerPage) != 0) + if (result.TotalItems % itemsPerPage != 0) result.TotalPages++; OneTimeCommandTimeout = saveTimeout; @@ -701,9 +720,8 @@ public async Task> PageAsync(long page, long itemsPerPage, string sql return result; } - /// - /// Retrieves a page of records and the total number of available records + /// Retrieves a page of records and the total number of available records /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -712,19 +730,19 @@ public async Task> PageAsync(long page, long itemsPerPage, string sql /// Arguments to any embedded parameters in the SQL statement /// A Page of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. It will also execute a second query to retrieve the - /// total number of records in the result set. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. It will also execute a second query to retrieve the + /// total number of records in the result set. /// - public Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args) + public Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args) { string sqlCount, sqlPage; - BuildPageQueries((page-1)*itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage); + BuildPageQueries((page - 1) * itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage); return PageAsync(page, itemsPerPage, sqlCount, args, sqlPage, args); } /// - /// Retrieves a page of records and the total number of available records + /// Retrieves a page of records and the total number of available records /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -732,9 +750,9 @@ public Task> PageAsync(long page, long itemsPerPage, string sql, para /// An SQL builder object representing the base SQL query and it's arguments /// A Page of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. It will also execute a second query to retrieve the - /// total number of records in the result set. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. It will also execute a second query to retrieve the + /// total number of records in the result set. /// public Task> PageAsync(long page, long itemsPerPage, Sql sql) { @@ -742,7 +760,7 @@ public Task> PageAsync(long page, long itemsPerPage, Sql sql) } /// - /// Retrieves a page of records and the total number of available records + /// Retrieves a page of records and the total number of available records /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -751,8 +769,9 @@ public Task> PageAsync(long page, long itemsPerPage, Sql sql) /// An SQL builder object representing the SQL to retrieve a single page of results /// A Page of results /// - /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. - /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. + /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. + /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page + /// object. /// public Task> PageAsync(long page, long itemsPerPage, Sql sqlCount, Sql sqlPage) { @@ -764,7 +783,7 @@ public Task> PageAsync(long page, long itemsPerPage, Sql sqlCount, Sq #region operation: Fetch (page) /// - /// Retrieves a page of records (without the total count) + /// Retrieves a page of records (without the total count) /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -773,8 +792,8 @@ public Task> PageAsync(long page, long itemsPerPage, Sql sqlCount, Sq /// Arguments to any embedded parameters in the SQL statement /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. /// public Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args) { @@ -782,7 +801,7 @@ public Task> FetchAsync(long page, long itemsPerPage, string sql, par } /// - /// Retrieves a page of records (without the total count) + /// Retrieves a page of records (without the total count) /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -790,8 +809,8 @@ public Task> FetchAsync(long page, long itemsPerPage, string sql, par /// An SQL builder object representing the base SQL query and it's arguments /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. /// public Task> FetchAsync(long page, long itemsPerPage, Sql sql) { @@ -803,7 +822,7 @@ public Task> FetchAsync(long page, long itemsPerPage, Sql sql) #region operation: SkipTakeAsync /// - /// Retrieves a range of records from result set + /// Retrieves a range of records from result set /// /// The Type representing a row in the result set /// The number of rows at the start of the result set to skip over @@ -812,8 +831,8 @@ public Task> FetchAsync(long page, long itemsPerPage, Sql sql) /// Arguments to any embedded parameters in the SQL statement /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified range. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified range. /// public Task> SkipTakeAsync(long skip, long take, string sql, params object[] args) { @@ -823,7 +842,7 @@ public Task> SkipTakeAsync(long skip, long take, string sql, params o } /// - /// Retrieves a range of records from result set + /// Retrieves a range of records from result set /// /// The Type representing a row in the result set /// The number of rows at the start of the result set to skip over @@ -831,79 +850,84 @@ public Task> SkipTakeAsync(long skip, long take, string sql, params o /// An SQL builder object representing the base SQL query and it's arguments /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified range. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified range. /// public Task> SkipTakeAsync(long skip, long take, Sql sql) { return SkipTakeAsync(skip, take, sql.SQL, sql.Arguments); } + #endregion #region operation: Query /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// The SQL query /// Callback to process each result /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public Task QueryAsync(string sql, Action action) { + public Task QueryAsync(string sql, Action action) + { return QueryAsync(sql, null, action); } /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// The SQL query /// Callback to process each result, return false to stop iterating /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public Task QueryAsync(string sql, Func func) { + public Task QueryAsync(string sql, Func func) + { return QueryAsync(sql, null, func); } /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// Callback to process each result /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public Task QueryAsync(string sql, object[] args, Action action) { - return QueryAsync(sql, args, v => { + public Task QueryAsync(string sql, object[] args, Action action) + { + return QueryAsync(sql, args, v => + { action(v); return true; }); } /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// Callback to process each result, return false to stop iterating /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public virtual async Task QueryAsync(string sql, object[] args, Func func) + public virtual async Task QueryAsync(string sql, object[] args, Func func) { if (EnableAutoSelect) sql = AutoSelectHelper.AddSelectClause(_dbType, sql); @@ -927,8 +951,10 @@ public virtual async Task QueryAsync(string sql, object[] args, Func return; } - var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, r.FieldCount, r) as Func; - using (r) { + var factory = + pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, r.FieldCount, r) as Func; + using (r) + { var keepGoing = true; while (keepGoing) { @@ -960,33 +986,33 @@ public virtual async Task QueryAsync(string sql, object[] args, Func } /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// An SQL builder object representing the base SQL query and it's arguments /// Callback to process each result /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public Task QueryAsync(Sql sql, Action action) + public Task QueryAsync(Sql sql, Action action) { return QueryAsync(sql.SQL, sql.Arguments, action); } /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// An SQL builder object representing the base SQL query and it's arguments /// Callback to process each result, return false to stop iterating /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public Task QueryAsync(Sql sql, Func func) + public Task QueryAsync(Sql sql, Func func) { return QueryAsync(sql.SQL, sql.Arguments, func); } @@ -996,7 +1022,7 @@ public Task QueryAsync(Sql sql, Func func) #region operation: Exists /// - /// Checks for the existance of a row matching the specified condition + /// Checks for the existance of a row matching the specified condition /// /// The Type representing the table being queried /// The SQL expression to be tested for (ie: the WHERE expression) @@ -1010,12 +1036,13 @@ public async Task ExistsAsync(string sqlCondition, params object[] args } /// - /// Checks for the existance of a row with the specified primary key value. + /// Checks for the existance of a row with the specified primary key value. /// /// The Type representing the table being queried /// The primary key value to look for /// True if a record with the specified primary key value exists. - public Task ExistsAsync(object primaryKey) { + public Task ExistsAsync(object primaryKey) + { var index = 0; var pk = GetPrimaryKeyValues(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey, primaryKey); return ExistsAsync(BuildPrimaryKeySql(pk, ref index), pk.Select(x => x.Value).ToArray()); @@ -1026,15 +1053,15 @@ public Task ExistsAsync(object primaryKey) { #region operation: linq style (Exists, Single, SingleOrDefault etc...) /// - /// Returns the record with the specified primary key value + /// Returns the record with the specified primary key value /// /// The Type representing a row in the result set /// The primary key value of the record to fetch /// The single record matching the specified primary key value /// - /// Throws an exception if there are zero or more than one record with the specified primary key value. + /// Throws an exception if there are zero or more than one record with the specified primary key value. /// - public Task SingleAsync(object primaryKey) + public Task SingleAsync(object primaryKey) { var index = 0; var pk = GetPrimaryKeyValues(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey, primaryKey); @@ -1042,15 +1069,15 @@ public Task SingleAsync(object primaryKey) } /// - /// Returns the record with the specified primary key value, or the default value if not found + /// Returns the record with the specified primary key value, or the default value if not found /// /// The Type representing a row in the result set /// The primary key value of the record to fetch /// The single record matching the specified primary key value /// - /// If there are no records with the specified primary key value, default(T) (typically null) is returned. + /// If there are no records with the specified primary key value, default(T) (typically null) is returned. /// - public Task SingleOrDefaultAsync(object primaryKey) + public Task SingleOrDefaultAsync(object primaryKey) { var index = 0; var pk = GetPrimaryKeyValues(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey, primaryKey); @@ -1058,44 +1085,46 @@ public Task SingleOrDefaultAsync(object primaryKey) } /// - /// Runs a query that should always return a single row. + /// Runs a query that should always return a single row. /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// The single record matching the specified primary key value /// - /// Throws an exception if there are zero or more than one matching record + /// Throws an exception if there are zero or more than one matching record /// public async Task SingleAsync(string sql, params object[] args) { var count = 0; - T poco = default(T); - await QueryAsync(sql, args, v => { + var poco = default(T); + await QueryAsync(sql, args, v => + { poco = v; count++; return count <= 2; }); if (count == 0) throw new InvalidOperationException("Sequence contains no elements."); - else if (count > 1) + if (count > 1) throw new InvalidOperationException("Sequence contains more than one element."); return poco; } /// - /// Runs a query that should always return either a single row, or no rows + /// Runs a query that should always return either a single row, or no rows /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// The single record matching the specified primary key value, or default(T) if no matching rows - public async Task SingleOrDefaultAsync(string sql, params object[] args) + public async Task SingleOrDefaultAsync(string sql, params object[] args) { var count = 0; - T poco = default(T); - await QueryAsync(sql, args, v => { + var poco = default(T); + await QueryAsync(sql, args, v => + { poco = v; count++; return count <= 2; @@ -1107,17 +1136,18 @@ await QueryAsync(sql, args, v => { } /// - /// Runs a query that should always return at least one return + /// Runs a query that should always return at least one return /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// The first record in the result set - public async Task FirstAsync(string sql, params object[] args) + public async Task FirstAsync(string sql, params object[] args) { var gotIt = false; - T poco = default(T); - await QueryAsync(sql, args, v => { + var poco = default(T); + await QueryAsync(sql, args, v => + { poco = v; gotIt = true; return false; @@ -1129,16 +1159,17 @@ await QueryAsync(sql, args, v => { } /// - /// Runs a query and returns the first record, or the default value if no matching records + /// Runs a query and returns the first record, or the default value if no matching records /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// The first record in the result set, or default(T) if no matching rows - public async Task FirstOrDefaultAsync(string sql, params object[] args) + public async Task FirstOrDefaultAsync(string sql, params object[] args) { - T poco = default(T); - await QueryAsync(sql, args, v => { + var poco = default(T); + await QueryAsync(sql, args, v => + { poco = v; return false; }); @@ -1146,13 +1177,13 @@ await QueryAsync(sql, args, v => { } /// - /// Runs a query that should always return a single row. + /// Runs a query that should always return a single row. /// /// The Type representing a row in the result set /// An SQL builder object representing the query and it's arguments /// The single record matching the specified primary key value /// - /// Throws an exception if there are zero or more than one matching record + /// Throws an exception if there are zero or more than one matching record /// public Task SingleAsync(Sql sql) { @@ -1160,43 +1191,44 @@ public Task SingleAsync(Sql sql) } /// - /// Runs a query that should always return either a single row, or no rows + /// Runs a query that should always return either a single row, or no rows /// /// The Type representing a row in the result set /// An SQL builder object representing the query and it's arguments /// The single record matching the specified primary key value, or default(T) if no matching rows - public Task SingleOrDefaultAsync(Sql sql) + public Task SingleOrDefaultAsync(Sql sql) { return SingleOrDefaultAsync(sql.SQL, sql.Arguments); } /// - /// Runs a query that should always return at least one return + /// Runs a query that should always return at least one return /// /// The Type representing a row in the result set /// An SQL builder object representing the query and it's arguments /// The first record in the result set - public Task FirstAsync(Sql sql) + public Task FirstAsync(Sql sql) { return FirstAsync(sql.SQL, sql.Arguments); } /// - /// Runs a query and returns the first record, or the default value if no matching records + /// Runs a query and returns the first record, or the default value if no matching records /// /// The Type representing a row in the result set /// An SQL builder object representing the query and it's arguments /// The first record in the result set, or default(T) if no matching rows - public Task FirstOrDefaultAsync(Sql sql) + public Task FirstOrDefaultAsync(Sql sql) { return FirstOrDefaultAsync(sql.SQL, sql.Arguments); } + #endregion #region operation: Insert /// - /// Performs an SQL Insert + /// Performs an SQL Insert /// /// The name of the table to insert into /// The name of the primary key column of the table @@ -1207,19 +1239,19 @@ public Task InsertAsync(string tableName, string primaryKeyName, object return InsertAsync(tableName, primaryKeyName, true, poco); } - - /// - /// Performs an SQL Insert + /// Performs an SQL Insert /// /// The name of the table to insert into /// The name of the primary key column of the table /// True if the primary key is automatically allocated by the DB /// The POCO object that specifies the column values to be inserted /// The auto allocated primary key of the new record, or null for non-auto-increment tables - /// Inserts a poco into a table. If the poco has a property with the same name - /// as the primary key the id of the new record is assigned to it. Either way, - /// the new id is returned. + /// + /// Inserts a poco into a table. If the poco has a property with the same name + /// as the primary key the id of the new record is assigned to it. Either way, + /// the new id is returned. + /// public virtual async Task InsertAsync(string tableName, string primaryKeyName, bool autoIncrement, object poco) { try @@ -1244,11 +1276,11 @@ public virtual async Task InsertAsync(string tableName, string primaryKe continue; // Don't insert the primary key (except under oracle where we need bring in the next sequence value) - if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, true)==0) + if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, true) == 0) { // Setup auto increment expression - string autoIncExpression = _dbType.GetAutoIncrementExpression(pd.TableInfo); - if (autoIncExpression!=null) + var autoIncExpression = _dbType.GetAutoIncrementExpression(pd.TableInfo); + if (autoIncExpression != null) { names.Add(i.Key); values.Add(autoIncExpression); @@ -1261,7 +1293,7 @@ public virtual async Task InsertAsync(string tableName, string primaryKe AddParam(cmd, i.Value.GetValue(poco), i.Value.PropertyInfo); } - string outputClause = String.Empty; + var outputClause = string.Empty; if (autoIncrement) { outputClause = _dbType.GetInsertOutputClause(primaryKeyName); @@ -1269,11 +1301,11 @@ public virtual async Task InsertAsync(string tableName, string primaryKe cmd.CommandText = string.Format("INSERT INTO {0} ({1}){2} VALUES ({3})", - _dbType.EscapeTableName(tableName), - string.Join(",", names.ToArray()), - outputClause, - string.Join(",", values.ToArray()) - ); + _dbType.EscapeTableName(tableName), + string.Join(",", names.ToArray()), + outputClause, + string.Join(",", values.ToArray()) + ); if (!autoIncrement) { @@ -1289,8 +1321,7 @@ public virtual async Task InsertAsync(string tableName, string primaryKe } - object id = await _dbType.ExecuteInsertAsync(this, cmd, primaryKeyName); - + var id = await _dbType.ExecuteInsertAsync(this, cmd, primaryKeyName); // Assign the ID back to the primary key property if (primaryKeyName != null) @@ -1319,12 +1350,14 @@ public virtual async Task InsertAsync(string tableName, string primaryKe } /// - /// Performs an SQL Insert + /// Performs an SQL Insert /// /// The POCO object that specifies the column values to be inserted /// The auto allocated primary key of the new record, or null for non-auto-increment tables - /// The name of the table, it's primary key and whether it's an auto-allocated primary key are retrieved - /// from the POCO's attributes + /// + /// The name of the table, it's primary key and whether it's an auto-allocated primary key are retrieved + /// from the POCO's attributes + /// public Task InsertAsync(object poco) { var pd = PocoData.ForType(poco.GetType()); @@ -1336,7 +1369,7 @@ public Task InsertAsync(object poco) #region operation: Update /// - /// Performs an SQL update + /// Performs an SQL update /// /// The name of the table to update /// The name of the primary key column of the table @@ -1349,7 +1382,7 @@ public Task UpdateAsync(string tableName, string primaryKeyName, object poc } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The name of the table to update /// The name of the primary key column of the table @@ -1357,7 +1390,8 @@ public Task UpdateAsync(string tableName, string primaryKeyName, object poc /// The primary key of the record to be updated /// The column names of the columns to be updated, or null for all /// The number of affected rows - public virtual async Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) + public virtual async Task UpdateAsync(string tableName, string primaryKeyName, object poco, + object primaryKeyValue, IEnumerable columns) { try { @@ -1369,7 +1403,7 @@ public virtual async Task UpdateAsync(string tableName, string primaryKeyNa { var sb = new StringBuilder(); var index = 0; - var pd = PocoData.ForObject(poco,primaryKeyName); + var pd = PocoData.ForObject(poco, primaryKeyName); var primaryKeyValuePairs = GetPrimaryKeyValues(primaryKeyName, primaryKeyValue); if (columns == null) @@ -1417,11 +1451,12 @@ public virtual async Task UpdateAsync(string tableName, string primaryKeyNa } cmd.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2}", - _dbType.EscapeTableName(tableName), - sb, + _dbType.EscapeTableName(tableName), + sb, BuildPrimaryKeySql(primaryKeyValuePairs, ref index)); - foreach (var keyValue in primaryKeyValuePairs) { + foreach (var keyValue in primaryKeyValuePairs) + { var pi = pd.Columns.ContainsKey(keyValue.Key) ? pd.Columns[keyValue.Key].PropertyInfo : null; AddParam(cmd, keyValue.Value, pi); } @@ -1448,7 +1483,7 @@ public virtual async Task UpdateAsync(string tableName, string primaryKeyNa } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The name of the table to update /// The name of the primary key column of the table @@ -1460,7 +1495,7 @@ public Task UpdateAsync(string tableName, string primaryKeyName, object poc } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The name of the table to update /// The name of the primary key column of the table @@ -1473,7 +1508,7 @@ public Task UpdateAsync(string tableName, string primaryKeyName, object poc } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO object that specifies the column values to be updated /// The column names of the columns to be updated, or null for all @@ -1484,7 +1519,7 @@ public Task UpdateAsync(object poco, IEnumerable columns) } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO object that specifies the column values to be updated /// The number of affected rows @@ -1494,7 +1529,7 @@ public Task UpdateAsync(object poco) } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO object that specifies the column values to be updated /// The primary key of the record to be updated @@ -1505,7 +1540,7 @@ public Task UpdateAsync(object poco, object primaryKeyValue) } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO object that specifies the column values to be updated /// The primary key of the record to be updated @@ -1518,7 +1553,7 @@ public Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO class who's attributes specify the name of the table to update /// The SQL update and condition clause (ie: everything after "UPDATE tablename" @@ -1531,22 +1566,26 @@ public Task UpdateAsync(string sql, params object[] args) } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO class who's attributes specify the name of the table to update - /// An SQL builder object representing the SQL update and condition clause (ie: everything after "UPDATE tablename" + /// + /// An SQL builder object representing the SQL update and condition clause (ie: everything after "UPDATE + /// tablename" + /// /// The number of affected rows public Task UpdateAsync(Sql sql) { var pd = PocoData.ForType(typeof(T)); return ExecuteAsync(new Sql(string.Format("UPDATE {0}", _dbType.EscapeTableName(pd.TableInfo.TableName))).Append(sql)); } + #endregion #region operation: Delete /// - /// Performs and SQL Delete + /// Performs and SQL Delete /// /// The name of the table to delete from /// The name of the primary key column @@ -1558,22 +1597,31 @@ public Task DeleteAsync(string tableName, string primaryKeyName, object poc } /// - /// Performs and SQL Delete + /// Performs and SQL Delete /// /// The name of the table to delete from /// The name of the primary key column - /// The POCO object whose primary key value will be used to delete the row (or null to use the supplied primary key value) - /// The value of the primary key identifing the record to be deleted (or null, or get this value from the POCO instance) + /// + /// The POCO object whose primary key value will be used to delete the row (or null to use the supplied + /// primary key value) + /// + /// + /// The value of the primary key identifing the record to be deleted (or null, or get this + /// value from the POCO instance) + /// /// The number of rows affected public virtual Task DeleteAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue) { var primaryKeyValuePairs = GetPrimaryKeyValues(primaryKeyName, primaryKeyValue); - if (poco != null) { + if (poco != null) + { // If primary key value not specified, pick it up from the object var pd = PocoData.ForObject(poco, primaryKeyName); - foreach (var i in pd.Columns) { - if (primaryKeyValue == null && primaryKeyValuePairs.ContainsKey(i.Key)) { + foreach (var i in pd.Columns) + { + if (primaryKeyValue == null && primaryKeyValuePairs.ContainsKey(i.Key)) + { primaryKeyValuePairs[i.Key] = i.Value.GetValue(poco); } } @@ -1586,7 +1634,7 @@ public virtual Task DeleteAsync(string tableName, string primaryKeyName, ob } /// - /// Performs an SQL Delete + /// Performs an SQL Delete /// /// The POCO object specifying the table name and primary key value of the row to be deleted /// The number of rows affected @@ -1597,7 +1645,7 @@ public Task DeleteAsync(object poco) } /// - /// Performs an SQL Delete + /// Performs an SQL Delete /// /// The POCO class whose attributes identify the table and primary key to be used in the delete /// The value of the primary key of the row to delete @@ -1611,7 +1659,7 @@ public Task DeleteAsync(object pocoOrPrimaryKey) } /// - /// Performs an SQL Delete + /// Performs an SQL Delete /// /// The POCO class who's attributes specify the name of the table to delete from /// The SQL condition clause identifying the row to delete (ie: everything after "DELETE FROM tablename" @@ -1624,22 +1672,27 @@ public Task DeleteAsync(string sql, params object[] args) } /// - /// Performs an SQL Delete + /// Performs an SQL Delete /// /// The POCO class who's attributes specify the name of the table to delete from - /// An SQL builder object representing the SQL condition clause identifying the row to delete (ie: everything after "UPDATE tablename" + /// + /// An SQL builder object representing the SQL condition clause identifying the row to delete (ie: + /// everything after "UPDATE tablename" + /// /// The number of affected rows public Task DeleteAsync(Sql sql) { var pd = PocoData.ForType(typeof(T)); - return ExecuteAsync(new Sql(string.Format("DELETE FROM {0}", _dbType.EscapeTableName(pd.TableInfo.TableName))).Append(sql)); + return + ExecuteAsync(new Sql(string.Format("DELETE FROM {0}", _dbType.EscapeTableName(pd.TableInfo.TableName))).Append(sql)); } + #endregion #region operation: IsNew /// - /// Check if a poco represents a new row + /// Check if a poco represents a new row /// /// The name of the primary key column /// The object instance whose "newness" is to be tested @@ -1655,7 +1708,7 @@ public bool IsNew(string primaryKeyName, object poco) pk = pc.GetValue(poco); } #if !PETAPOCO_NO_DYNAMIC - else if (poco.GetType() == typeof(System.Dynamic.ExpandoObject)) + else if (poco.GetType() == typeof(ExpandoObject)) { return true; } @@ -1664,7 +1717,8 @@ public bool IsNew(string primaryKeyName, object poco) { var pi = poco.GetType().GetProperty(primaryKeyName); if (pi == null) - throw new ArgumentException(string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName)); + throw new ArgumentException( + string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName)); pk = pi.GetValue(poco, null); } @@ -1678,70 +1732,1429 @@ public bool IsNew(string primaryKeyName, object poco) // Common primary key types if (type == typeof(long)) return (long)pk == default(long); - else if (type == typeof(ulong)) + if (type == typeof(ulong)) return (ulong)pk == default(ulong); - else if (type == typeof(int)) + if (type == typeof(int)) return (int)pk == default(int); - else if (type == typeof(uint)) + if (type == typeof(uint)) return (uint)pk == default(uint); - else if (type == typeof(Guid)) + if (type == typeof(Guid)) return (Guid)pk == default(Guid); - // Create a default instance and compare - return pk == Activator.CreateInstance(pk.GetType()); - } - else - { - return pk == null; - } - } + // Create a default instance and compare + return pk == Activator.CreateInstance(pk.GetType()); + } + return pk == null; + } + + /// + /// Check if a poco represents a new row + /// + /// The object instance whose "newness" is to be tested + /// True if the POCO represents a record already in the database + /// This method simply tests if the POCO's primary key column property has been set to something non-zero. + public bool IsNew(object poco) + { + var pd = PocoData.ForType(poco.GetType()); + if (!pd.TableInfo.AutoIncrement) + throw new InvalidOperationException( + "IsNew() and Save() are only supported on tables with auto-increment/identity primary key columns"); + return IsNew(pd.TableInfo.PrimaryKey, poco); + } + + #endregion + + #region operation: Save + + /// + /// Saves a POCO by either performing either an SQL Insert or SQL Update + /// + /// The name of the table to be updated + /// The name of the primary key column + /// The POCO object to be saved + public Task SaveAsync(string tableName, string primaryKeyName, object poco) + { + if (IsNew(primaryKeyName, poco)) + { + return InsertAsync(tableName, primaryKeyName, true, poco); + } + return UpdateAsync(tableName, primaryKeyName, poco); + } + + /// + /// Saves a POCO by either performing either an SQL Insert or SQL Update + /// + /// The POCO object to be saved + public Task SaveAsync(object poco) + { + var pd = PocoData.ForType(poco.GetType()); + return SaveAsync(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); + } + + #endregion + + #region operation: Multi-Poco Query/Fetch + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + public Task> FetchAsync(Func cb, string sql, params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2) }, cb, sql, args); + } + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + public Task> FetchAsync(Func cb, string sql, params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, args); + } + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + public Task> FetchAsync(Func cb, string sql, + params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, args); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + public Task QueryAsync(Func cb, string sql, object[] args, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2) }, cb, sql, args, action); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + public Task QueryAsync(Func cb, string sql, object[] args, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, args, action); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + public Task QueryAsync(Func cb, string sql, object[] args, + Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, args, action); + } + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + public Task> FetchAsync(Func cb, Sql sql) + { + return FetchAsync(new[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments); + } + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + public Task> FetchAsync(Func cb, Sql sql) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments); + } + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + public Task> FetchAsync(Func cb, Sql sql) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + public Task QueryAsync(Func cb, Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments, action); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + public Task QueryAsync(Func cb, Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments, action); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + public Task QueryAsync(Func cb, Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments, action); + } + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + public Task> FetchAsync(string sql, params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2) }, null, sql, args); + } + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + public Task> FetchAsync(string sql, params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); + } + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + public Task> FetchAsync(string sql, params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + public Task QueryAsync(string sql, object[] args, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2) }, null, sql, args, action); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + public Task QueryAsync(string sql, object[] args, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args, action); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + public Task QueryAsync(string sql, object[] args, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args, action); + } + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + public Task> FetchAsync(Sql sql) + { + return FetchAsync(sql.SQL, sql.Arguments); + } + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + public Task> FetchAsync(Sql sql) + { + return FetchAsync(sql.SQL, sql.Arguments); + } + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + public Task> FetchAsync(Sql sql) + { + return FetchAsync(sql.SQL, sql.Arguments); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + public Task QueryAsync(Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments, action); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + public Task QueryAsync(Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments, action); + } + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + public Task QueryAsync(Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments, action); + } + + /// + /// Perform a multi-poco fetch + /// + /// The type of objects to pass to the action + /// An array of Types representing the POCO types of the returned result set. + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + public async Task> FetchAsync(Type[] types, object cb, string sql, params object[] args) + { + var list = new List(); + await QueryAsync(types, cb, sql, args, list.Add); + return list; + } + + /// + /// Performs a multi-poco query + /// + /// The type of objects to pass to the action + /// An array of Types representing the POCO types of the returned result set. + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + public virtual async Task QueryAsync(Type[] types, object cb, string sql, object[] args, Action action) + { + await OpenSharedConnectionAsync(); + try + { + using (var cmd = CreateCommand(_sharedConnection, sql, args)) + { + DbDataReader r; + try + { + r = await cmd.ExecuteReaderAsync(); + OnExecutedCommand(cmd); + } + catch (Exception x) + { + if (OnException(x)) + throw; + return; + } + var factory = MultiPocoFactory.GetFactory(types, _sharedConnection.ConnectionString, sql, r); + if (cb == null) + cb = MultiPocoFactory.GetAutoMapper(types.ToArray()); + var bNeedTerminator = false; + using (r) + { + while (true) + { + TRet poco; + try + { + if (!await r.ReadAsync()) + break; + poco = factory(r, cb); + } + catch (Exception x) + { + if (OnException(x)) + throw; + return; + } + + if (poco != null) + action(poco); + else + bNeedTerminator = true; + } + if (bNeedTerminator) + { + var poco = (TRet)(cb as Delegate).DynamicInvoke(new object[types.Length]); + if (poco != null) + action(poco); + } + } + } + } + finally + { + CloseSharedConnection(); + } + } + + #endregion + + #region Last Command + + /// + /// Retrieves the SQL of the last executed statement + /// + public string LastSQL { get; private set; } + + /// + /// Retrieves the arguments to the last execute statement + /// + public object[] LastArgs { get; private set; } + + /// + /// Returns a formatted string describing the last executed SQL statement and it's argument values + /// + public string LastCommand + { + get { return FormatCommand(LastSQL, LastArgs); } + } + + #endregion + + #region FormatCommand + + /// + /// Formats the contents of a DB command for display + /// + /// + /// + public string FormatCommand(IDbCommand cmd) + { + return FormatCommand(cmd.CommandText, + (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray()); + } + + /// + /// Formats an SQL query and it's arguments for display + /// + /// + /// + /// + public string FormatCommand(string sql, object[] args) + { + var sb = new StringBuilder(); + if (sql == null) + return ""; + sb.Append(sql); + if (args != null && args.Length > 0) + { + sb.Append("\n"); + for (var i = 0; i < args.Length; i++) + { + sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, args[i].GetType().Name, args[i]); + } + sb.Remove(sb.Length - 1, 1); + } + return sb.ToString(); + } + + #endregion + + #region Public Properties + + /* + public static IMapper Mapper + { + get; + set; + } */ + + /// + /// When set to true, PetaPoco will automatically create the "SELECT columns" part of any query that looks like it + /// needs it + /// + public bool EnableAutoSelect { get; set; } + + /// + /// When set to true, parameters can be named ?myparam and populated from properties of the passed in argument values. + /// + public bool EnableNamedParams { get; set; } + + /// + /// Sets the timeout value for all SQL statements. + /// + public int CommandTimeout { get; set; } + + /// + /// Sets the timeout value for the next (and only next) SQL statement + /// + public int OneTimeCommandTimeout { get; set; } + + #endregion + + #region Member Fields + + // Member variables + internal DatabaseType _dbType; + private readonly string _connectionString; + private readonly string _providerName; + private DbProviderFactory _factory; + private DbConnection _sharedConnection; + private DbTransaction _transaction; + private int _sharedConnectionDepth; + private int _transactionDepth; + private bool _transactionCancelled; + private string _paramPrefix; + + #endregion + + #region Internal operations + + internal async Task ExecuteNonQueryHelperAsync(DbCommand cmd) + { + DoPreExecute(cmd); + await cmd.ExecuteNonQueryAsync(); + OnExecutedCommand(cmd); + } + + internal async Task ExecuteScalarHelperAsync(DbCommand cmd) + { + DoPreExecute(cmd); + var r = await cmd.ExecuteScalarAsync(); + OnExecutedCommand(cmd); + return r; + } + + internal void DoPreExecute(DbCommand cmd) + { + // Setup command timeout + if (OneTimeCommandTimeout != 0) + { + cmd.CommandTimeout = OneTimeCommandTimeout; + OneTimeCommandTimeout = 0; + } + else if (CommandTimeout != 0) + { + cmd.CommandTimeout = CommandTimeout; + } + + // Call hook + OnExecutingCommand(cmd); + + // Save it + LastSQL = cmd.CommandText; + LastArgs = (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray(); + } + + #endregion + + #region Composite primary key support + + private Dictionary GetPrimaryKeyValues(string primaryKeyName, object primaryKeyValue) + { + Dictionary primaryKeyValues; + + var multiplePrimaryKeysNames = + primaryKeyName.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); + if (primaryKeyValue != null) + { + if (multiplePrimaryKeysNames.Length == 1) + { + primaryKeyValues = new Dictionary(ColumnComparer) + { + {primaryKeyName, primaryKeyValue} + }; + } + else + { + var dict = primaryKeyValue as Dictionary; + primaryKeyValues = dict ?? + multiplePrimaryKeysNames.ToDictionary(x => x, + x => + primaryKeyValue.GetType() + .GetProperties() + .Single(y => string.Equals(x, y.Name, StringComparison.OrdinalIgnoreCase)) + .GetValue(primaryKeyValue, null), ColumnComparer); + } + } + else + { + primaryKeyValues = multiplePrimaryKeysNames.ToDictionary(x => x, x => (object)null, + StringComparer.OrdinalIgnoreCase); + } + + return primaryKeyValues; + } + + private string BuildPrimaryKeySql(Dictionary primaryKeyValuePair, ref int index) + { + var tempIndex = index; + index += primaryKeyValuePair.Count; + return string.Join(" AND ", + primaryKeyValuePair.Select( + (x, i) => + x.Value == null || x.Value == DBNull.Value + ? string.Format("{0} IS NULL", _dbType.EscapeSqlIdentifier(x.Key)) + : string.Format("{0} = @{1}", _dbType.EscapeSqlIdentifier(x.Key), tempIndex + i)).ToArray()); + } + + #endregion + } + + public interface IDatabase : IDisposable + { + /// + /// When set to true the first opened connection is kept alive until this object is disposed + /// + bool KeepConnectionAlive { get; set; } + + /// + /// Provides access to the currently open shared connection (or null if none) + /// + IDbConnection Connection { get; } + + /// + /// Retrieves the SQL of the last executed statement + /// + string LastSQL { get; } + + /// + /// Retrieves the arguments to the last execute statement + /// + object[] LastArgs { get; } + + /// + /// Returns a formatted string describing the last executed SQL statement and it's argument values + /// + string LastCommand { get; } + + /// + /// When set to true, PetaPoco will automatically create the "SELECT columns" part of any query that looks like it needs it + /// + bool EnableAutoSelect { get; set; } + + /// + /// When set to true, parameters can be named ?myparam and populated from properties of the passed in argument values. + /// + bool EnableNamedParams { get; set; } + + /// + /// Sets the timeout value for all SQL statements. + /// + int CommandTimeout { get; set; } + + /// + /// Sets the timeout value for the next (and only next) SQL statement + /// + int OneTimeCommandTimeout { get; set; } + + /// + /// Open a connection that will be used for all subsequent queries. + /// + /// + /// Calls to Open/CloseSharedConnection are reference counted and should be balanced + /// + Task OpenSharedConnectionAsync(); + + /// + /// Releases the shared connection + /// + void CloseSharedConnection(); + + /// + /// Starts or continues a transaction. + /// + /// An ITransaction reference that must be Completed or disposed + /// + /// This method makes management of calls to Begin/End/CompleteTransaction easier. + /// + /// The usage pattern for this should be: + /// + /// using (var tx = db.GetTransaction()) + /// { + /// // Do stuff + /// db.Update(...); + /// + /// // Mark the transaction as complete + /// tx.Complete(); + /// } + /// + /// Transactions can be nested but they must all be completed otherwise the entire + /// transaction is aborted. + /// + Task GetTransactionAsync(); + + /// + /// Called when a transaction starts. Overridden by the T4 template generated database + /// classes to ensure the same DB instance is used throughout the transaction. + /// + void OnBeginTransaction(); + + /// + /// Called when a transaction ends. + /// + void OnEndTransaction(); + + /// + /// Starts a transaction scope, see GetTransaction() for recommended usage + /// + Task BeginTransactionAsync(); + + /// + /// Aborts the entire outer most transaction scope + /// + /// + /// Called automatically by Transaction.Dispose() + /// if the transaction wasn't completed. + /// + void AbortTransaction(); + + /// + /// Marks the current transaction scope as complete. + /// + void CompleteTransaction(); + + DbCommand CreateCommand(DbConnection connection, string sql, params object[] args); + + /// + /// Called if an exception occurs during processing of a DB operation. Override to provide custom logging/handling. + /// + /// The exception instance + /// True to re-throw the exception, false to suppress it + bool OnException(Exception x); + + /// + /// Called when DB connection opened + /// + /// The newly opened DbConnection + /// The same or a replacement DbConnection + /// + /// Override this method to provide custom logging of opening connection, or + /// to provide a proxy DbConnection. + /// + DbConnection OnConnectionOpened(DbConnection conn); + + /// + /// Called when DB connection closed + /// + /// The soon to be closed IDBConnection + void OnConnectionClosing(IDbConnection conn); + + /// + /// Called just before an DB command is executed + /// + /// The command to be executed + /// + /// Override this method to provide custom logging of commands and/or + /// modification of the IDbCommand before it's executed + /// + void OnExecutingCommand(IDbCommand cmd); + + /// + /// Called on completion of command execution + /// + /// The IDbCommand that finished executing + void OnExecutedCommand(IDbCommand cmd); + + /// + /// Executes a non-query command + /// + /// The SQL statement to execute + /// Arguments to any embedded parameters in the SQL + /// The number of rows affected + Task ExecuteAsync(string sql, params object[] args); + + /// + /// Executes a non-query command + /// + /// An SQL builder object representing the query and it's arguments + /// The number of rows affected + Task ExecuteAsync(Sql sql); + + /// + /// Executes a query and return the first column of the first row in the result set. + /// + /// The type that the result value should be cast to + /// The SQL query to execute + /// Arguments to any embedded parameters in the SQL + /// The scalar value cast to T + Task ExecuteScalarAsync(string sql, params object[] args); + + /// + /// Executes a query and return the first column of the first row in the result set. + /// + /// The type that the result value should be cast to + /// An SQL builder object representing the query and it's arguments + /// The scalar value cast to T + Task ExecuteScalarAsync(Sql sql); + + /// + /// Runs a query and returns the result set as a typed list + /// + /// The Type representing a row in the result set + /// The SQL query to execute + /// Arguments to any embedded parameters in the SQL + /// A List holding the results of the query + Task> FetchAsync(string sql, params object[] args); + + /// + /// Runs a query and returns the result set as a typed list + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the query and it's arguments + /// A List holding the results of the query + Task> FetchAsync(Sql sql); + + /// + /// Retrieves a page of records and the total number of available records + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// The SQL to retrieve the total number of records + /// Arguments to any embedded parameters in the sqlCount statement + /// The SQL To retrieve a single page of results + /// Arguments to any embedded parameters in the sqlPage statement + /// A Page of results + /// + /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. + /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. + /// + Task> PageAsync(long page, long itemsPerPage, string sqlCount, object[] countArgs, string sqlPage, object[] pageArgs); + + /// + /// Retrieves a page of records and the total number of available records + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// The base SQL query + /// Arguments to any embedded parameters in the SQL statement + /// A Page of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. It will also execute a second query to retrieve the + /// total number of records in the result set. + /// + Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args); + + /// + /// Retrieves a page of records and the total number of available records + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// An SQL builder object representing the base SQL query and it's arguments + /// A Page of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. It will also execute a second query to retrieve the + /// total number of records in the result set. + /// + Task> PageAsync(long page, long itemsPerPage, Sql sql); + + /// + /// Retrieves a page of records and the total number of available records + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// An SQL builder object representing the SQL to retrieve the total number of records + /// An SQL builder object representing the SQL to retrieve a single page of results + /// A Page of results + /// + /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. + /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. + /// + Task> PageAsync(long page, long itemsPerPage, Sql sqlCount, Sql sqlPage); + + /// + /// Retrieves a page of records (without the total count) + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// The base SQL query + /// Arguments to any embedded parameters in the SQL statement + /// A List of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. + /// + Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args); + + /// + /// Retrieves a page of records (without the total count) + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// An SQL builder object representing the base SQL query and it's arguments + /// A List of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. + /// + Task> FetchAsync(long page, long itemsPerPage, Sql sql); + + /// + /// Retrieves a range of records from result set + /// + /// The Type representing a row in the result set + /// The number of rows at the start of the result set to skip over + /// The number of rows to retrieve + /// The base SQL query + /// Arguments to any embedded parameters in the SQL statement + /// A List of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified range. + /// + Task> SkipTakeAsync(long skip, long take, string sql, params object[] args); + + /// + /// Retrieves a range of records from result set + /// + /// The Type representing a row in the result set + /// The number of rows at the start of the result set to skip over + /// The number of rows to retrieve + /// An SQL builder object representing the base SQL query and it's arguments + /// A List of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified range. + /// + Task> SkipTakeAsync(long skip, long take, Sql sql); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// The SQL query + /// Callback to process each result + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(string sql, Action action); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// The SQL query + /// Callback to process each result, return false to stop iterating + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(string sql, Func func); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// Callback to process each result + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(string sql, object[] args, Action action); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// Callback to process each result, return false to stop iterating + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(string sql, object[] args, Func func); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the base SQL query and it's arguments + /// Callback to process each result + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(Sql sql, Action action); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the base SQL query and it's arguments + /// Callback to process each result, return false to stop iterating + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(Sql sql, Func func); + + /// + /// Checks for the existance of a row matching the specified condition + /// + /// The Type representing the table being queried + /// The SQL expression to be tested for (ie: the WHERE expression) + /// Arguments to any embedded parameters in the SQL statement + /// True if a record matching the condition is found. + Task ExistsAsync(string sqlCondition, params object[] args); + + /// + /// Checks for the existance of a row with the specified primary key value. + /// + /// The Type representing the table being queried + /// The primary key value to look for + /// True if a record with the specified primary key value exists. + Task ExistsAsync(object primaryKey); + + /// + /// Returns the record with the specified primary key value + /// + /// The Type representing a row in the result set + /// The primary key value of the record to fetch + /// The single record matching the specified primary key value + /// + /// Throws an exception if there are zero or more than one record with the specified primary key value. + /// + Task SingleAsync(object primaryKey); + + /// + /// Returns the record with the specified primary key value, or the default value if not found + /// + /// The Type representing a row in the result set + /// The primary key value of the record to fetch + /// The single record matching the specified primary key value + /// + /// If there are no records with the specified primary key value, default(T) (typically null) is returned. + /// + Task SingleOrDefaultAsync(object primaryKey); + + /// + /// Runs a query that should always return a single row. + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// The single record matching the specified primary key value + /// + /// Throws an exception if there are zero or more than one matching record + /// + Task SingleAsync(string sql, params object[] args); + + /// + /// Runs a query that should always return either a single row, or no rows + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// The single record matching the specified primary key value, or default(T) if no matching rows + Task SingleOrDefaultAsync(string sql, params object[] args); + + /// + /// Runs a query that should always return at least one return + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// The first record in the result set + Task FirstAsync(string sql, params object[] args); + + /// + /// Runs a query and returns the first record, or the default value if no matching records + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// The first record in the result set, or default(T) if no matching rows + Task FirstOrDefaultAsync(string sql, params object[] args); + + /// + /// Runs a query that should always return a single row. + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the query and it's arguments + /// The single record matching the specified primary key value + /// + /// Throws an exception if there are zero or more than one matching record + /// + Task SingleAsync(Sql sql); + + /// + /// Runs a query that should always return either a single row, or no rows + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the query and it's arguments + /// The single record matching the specified primary key value, or default(T) if no matching rows + Task SingleOrDefaultAsync(Sql sql); + + /// + /// Runs a query that should always return at least one return + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the query and it's arguments + /// The first record in the result set + Task FirstAsync(Sql sql); + + /// + /// Runs a query and returns the first record, or the default value if no matching records + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the query and it's arguments + /// The first record in the result set, or default(T) if no matching rows + Task FirstOrDefaultAsync(Sql sql); + + /// + /// Performs an SQL Insert + /// + /// The name of the table to insert into + /// The name of the primary key column of the table + /// The POCO object that specifies the column values to be inserted + /// The auto allocated primary key of the new record + Task InsertAsync(string tableName, string primaryKeyName, object poco); + + /// + /// Performs an SQL Insert + /// + /// The name of the table to insert into + /// The name of the primary key column of the table + /// True if the primary key is automatically allocated by the DB + /// The POCO object that specifies the column values to be inserted + /// The auto allocated primary key of the new record, or null for non-auto-increment tables + /// Inserts a poco into a table. If the poco has a property with the same name + /// as the primary key the id of the new record is assigned to it. Either way, + /// the new id is returned. + Task InsertAsync(string tableName, string primaryKeyName, bool autoIncrement, object poco); + + /// + /// Performs an SQL Insert + /// + /// The POCO object that specifies the column values to be inserted + /// The auto allocated primary key of the new record, or null for non-auto-increment tables + /// The name of the table, it's primary key and whether it's an auto-allocated primary key are retrieved + /// from the POCO's attributes + Task InsertAsync(object poco); + + /// + /// Performs an SQL update + /// + /// The name of the table to update + /// The name of the primary key column of the table + /// The POCO object that specifies the column values to be updated + /// The primary key of the record to be updated + /// The number of affected records + Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue); + + /// + /// Performs an SQL update + /// + /// The name of the table to update + /// The name of the primary key column of the table + /// The POCO object that specifies the column values to be updated + /// The primary key of the record to be updated + /// The column names of the columns to be updated, or null for all + /// The number of affected rows + Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns); + + /// + /// Performs an SQL update + /// + /// The name of the table to update + /// The name of the primary key column of the table + /// The POCO object that specifies the column values to be updated + /// The number of affected rows + Task UpdateAsync(string tableName, string primaryKeyName, object poco); + + /// + /// Performs an SQL update + /// + /// The name of the table to update + /// The name of the primary key column of the table + /// The POCO object that specifies the column values to be updated + /// The column names of the columns to be updated, or null for all + /// The number of affected rows + Task UpdateAsync(string tableName, string primaryKeyName, object poco, IEnumerable columns); + + /// + /// Performs an SQL update + /// + /// The POCO object that specifies the column values to be updated + /// The column names of the columns to be updated, or null for all + /// The number of affected rows + Task UpdateAsync(object poco, IEnumerable columns); + + /// + /// Performs an SQL update + /// + /// The POCO object that specifies the column values to be updated + /// The number of affected rows + Task UpdateAsync(object poco); + + /// + /// Performs an SQL update + /// + /// The POCO object that specifies the column values to be updated + /// The primary key of the record to be updated + /// The number of affected rows + Task UpdateAsync(object poco, object primaryKeyValue); + + /// + /// Performs an SQL update + /// + /// The POCO object that specifies the column values to be updated + /// The primary key of the record to be updated + /// The column names of the columns to be updated, or null for all + /// The number of affected rows + Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable columns); + + /// + /// Performs an SQL update + /// + /// The POCO class who's attributes specify the name of the table to update + /// The SQL update and condition clause (ie: everything after "UPDATE tablename" + /// Arguments to any embedded parameters in the SQL + /// The number of affected rows + Task UpdateAsync(string sql, params object[] args); + + /// + /// Performs an SQL update + /// + /// The POCO class who's attributes specify the name of the table to update + /// An SQL builder object representing the SQL update and condition clause (ie: everything after "UPDATE tablename" + /// The number of affected rows + Task UpdateAsync(Sql sql); + + /// + /// Performs and SQL Delete + /// + /// The name of the table to delete from + /// The name of the primary key column + /// The POCO object whose primary key value will be used to delete the row + /// The number of rows affected + Task DeleteAsync(string tableName, string primaryKeyName, object poco); + + /// + /// Performs and SQL Delete + /// + /// The name of the table to delete from + /// The name of the primary key column + /// The POCO object whose primary key value will be used to delete the row (or null to use the supplied primary key value) + /// The value of the primary key identifing the record to be deleted (or null, or get this value from the POCO instance) + /// The number of rows affected + Task DeleteAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue); + + /// + /// Performs an SQL Delete + /// + /// The POCO object specifying the table name and primary key value of the row to be deleted + /// The number of rows affected + Task DeleteAsync(object poco); + + /// + /// Performs an SQL Delete + /// + /// The POCO class whose attributes identify the table and primary key to be used in the delete + /// The value of the primary key of the row to delete + /// + Task DeleteAsync(object pocoOrPrimaryKey); + + /// + /// Performs an SQL Delete + /// + /// The POCO class who's attributes specify the name of the table to delete from + /// The SQL condition clause identifying the row to delete (ie: everything after "DELETE FROM tablename" + /// Arguments to any embedded parameters in the SQL + /// The number of affected rows + Task DeleteAsync(string sql, params object[] args); + + /// + /// Performs an SQL Delete + /// + /// The POCO class who's attributes specify the name of the table to delete from + /// An SQL builder object representing the SQL condition clause identifying the row to delete (ie: everything after "UPDATE tablename" + /// The number of affected rows + Task DeleteAsync(Sql sql); /// /// Check if a poco represents a new row /// + /// The name of the primary key column /// The object instance whose "newness" is to be tested /// True if the POCO represents a record already in the database /// This method simply tests if the POCO's primary key column property has been set to something non-zero. - public bool IsNew(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - if (!pd.TableInfo.AutoIncrement) - throw new InvalidOperationException("IsNew() and Save() are only supported on tables with auto-increment/identity primary key columns"); - return IsNew(pd.TableInfo.PrimaryKey, poco); - } - #endregion + bool IsNew(string primaryKeyName, object poco); + + /// + /// Check if a poco represents a new row + /// + /// The object instance whose "newness" is to be tested + /// True if the POCO represents a record already in the database + /// This method simply tests if the POCO's primary key column property has been set to something non-zero. + bool IsNew(object poco); - #region operation: Save /// /// Saves a POCO by either performing either an SQL Insert or SQL Update /// /// The name of the table to be updated /// The name of the primary key column /// The POCO object to be saved - public Task SaveAsync(string tableName, string primaryKeyName, object poco) - { - if (IsNew(primaryKeyName, poco)) - { - return InsertAsync(tableName, primaryKeyName, true, poco); - } - else - { - return UpdateAsync(tableName, primaryKeyName, poco); - } - } + Task SaveAsync(string tableName, string primaryKeyName, object poco); /// /// Saves a POCO by either performing either an SQL Insert or SQL Update /// /// The POCO object to be saved - public Task SaveAsync(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - return SaveAsync(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); - } - #endregion + Task SaveAsync(object poco); - #region operation: Multi-Poco Query/Fetch /// /// Perform a multi-poco fetch /// @@ -1752,7 +3165,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2) }, cb, sql, args); } + Task> FetchAsync(Func cb, string sql, params object[] args); /// /// Perform a multi-poco fetch @@ -1765,7 +3178,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, args); } + Task> FetchAsync(Func cb, string sql, params object[] args); /// /// Perform a multi-poco fetch @@ -1779,7 +3192,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, args); } + Task> FetchAsync(Func cb, string sql, params object[] args); /// /// Perform a multi-poco query @@ -1791,7 +3204,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(Func cb, string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2) }, cb, sql, args, action); } + Task QueryAsync(Func cb, string sql, object[] args, Action action); /// /// Perform a multi-poco query @@ -1804,7 +3217,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(Func cb, string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, args, action); } + Task QueryAsync(Func cb, string sql, object[] args, Action action); /// /// Perform a multi-poco query @@ -1818,7 +3231,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(Func cb, string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, args, action); } + Task QueryAsync(Func cb, string sql, object[] args, Action action); /// /// Perform a multi-poco fetch @@ -1829,7 +3242,7 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, Sql sql) { return FetchAsync(new[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments); } + Task> FetchAsync(Func cb, Sql sql); /// /// Perform a multi-poco fetch @@ -1841,7 +3254,7 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, Sql sql) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments); } + Task> FetchAsync(Func cb, Sql sql); /// /// Perform a multi-poco fetch @@ -1854,7 +3267,7 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, Sql sql) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments); } + Task> FetchAsync(Func cb, Sql sql); /// /// Perform a multi-poco query @@ -1865,7 +3278,7 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Func cb, Sql sql, Action action) { return QueryAsync(new[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments, action); } + Task QueryAsync(Func cb, Sql sql, Action action); /// /// Perform a multi-poco query @@ -1877,7 +3290,7 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Func cb, Sql sql, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments, action); } + Task QueryAsync(Func cb, Sql sql, Action action); /// /// Perform a multi-poco query @@ -1890,7 +3303,7 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Func cb, Sql sql, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments, action); } + Task QueryAsync(Func cb, Sql sql, Action action); /// /// Perform a multi-poco fetch @@ -1900,7 +3313,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2) }, null, sql, args); } + Task> FetchAsync(string sql, params object[] args); /// /// Perform a multi-poco fetch @@ -1911,7 +3324,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); } + Task> FetchAsync(string sql, params object[] args); /// /// Perform a multi-poco fetch @@ -1923,7 +3336,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); } + Task> FetchAsync(string sql, params object[] args); /// /// Perform a multi-poco query @@ -1933,7 +3346,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2) }, null, sql, args, action); } + Task QueryAsync(string sql, object[] args, Action action); /// /// Perform a multi-poco query @@ -1944,7 +3357,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args, action); } + Task QueryAsync(string sql, object[] args, Action action); /// /// Perform a multi-poco query @@ -1956,7 +3369,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args, action); } + Task QueryAsync(string sql, object[] args, Action action); /// /// Perform a multi-poco fetch @@ -1965,7 +3378,7 @@ public Task SaveAsync(object poco) /// The second POCO type /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Sql sql) { return FetchAsync(sql.SQL, sql.Arguments); } + Task> FetchAsync(Sql sql); /// /// Perform a multi-poco fetch @@ -1975,7 +3388,7 @@ public Task SaveAsync(object poco) /// The third POCO type /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Sql sql) { return FetchAsync(sql.SQL, sql.Arguments); } + Task> FetchAsync(Sql sql); /// /// Perform a multi-poco fetch @@ -1986,7 +3399,7 @@ public Task SaveAsync(object poco) /// The fourth POCO type /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Sql sql) { return FetchAsync(sql.SQL, sql.Arguments); } + Task> FetchAsync(Sql sql); /// /// Perform a multi-poco query @@ -1995,7 +3408,7 @@ public Task SaveAsync(object poco) /// The second POCO type /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Sql sql, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments, action); } + Task QueryAsync(Sql sql, Action action); /// /// Perform a multi-poco query @@ -2005,7 +3418,7 @@ public Task SaveAsync(object poco) /// The third POCO type /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Sql sql, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments, action); } + Task QueryAsync(Sql sql, Action action); /// /// Perform a multi-poco query @@ -2016,7 +3429,7 @@ public Task SaveAsync(object poco) /// The fourth POCO type /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Sql sql, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments, action); } + Task QueryAsync(Sql sql, Action action); /// /// Perform a multi-poco fetch @@ -2027,11 +3440,7 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public async Task> FetchAsync(Type[] types, object cb, string sql, params object[] args) { - var list = new List(); - await QueryAsync(types, cb, sql, args, list.Add); - return list; - } + Task> FetchAsync(Type[] types, object cb, string sql, params object[] args); /// /// Performs a multi-poco query @@ -2042,102 +3451,14 @@ public async Task> FetchAsync(Type[] types, object cb, string s /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public virtual async Task QueryAsync(Type[] types, object cb, string sql, object[] args, Action action) - { - await OpenSharedConnectionAsync(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - DbDataReader r; - try - { - r = await cmd.ExecuteReaderAsync(); - OnExecutedCommand(cmd); - } - catch (Exception x) - { - if (OnException(x)) - throw; - return; - } - var factory = MultiPocoFactory.GetFactory(types, _sharedConnection.ConnectionString, sql, r); - if (cb == null) - cb = MultiPocoFactory.GetAutoMapper(types.ToArray()); - bool bNeedTerminator = false; - using (r) - { - while (true) - { - TRet poco; - try - { - if (!await r.ReadAsync()) - break; - poco = factory(r, cb); - } - catch (Exception x) - { - if (OnException(x)) - throw; - return; - } - - if (poco != null) - action(poco); - else - bNeedTerminator = true; - } - if (bNeedTerminator) - { - var poco = (TRet)(cb as Delegate).DynamicInvoke(new object[types.Length]); - if (poco != null) - action(poco); - } - } - } - } - finally - { - CloseSharedConnection(); - } - } - - #endregion - - #region Last Command - - /// - /// Retrieves the SQL of the last executed statement - /// - public string LastSQL { get { return _lastSql; } } - - /// - /// Retrieves the arguments to the last execute statement - /// - public object[] LastArgs { get { return _lastArgs; } } - - - /// - /// Returns a formatted string describing the last executed SQL statement and it's argument values - /// - public string LastCommand - { - get { return FormatCommand(_lastSql, _lastArgs); } - } - #endregion - - #region FormatCommand + Task QueryAsync(Type[] types, object cb, string sql, object[] args, Action action); /// /// Formats the contents of a DB command for display /// /// /// - public string FormatCommand(IDbCommand cmd) - { - return FormatCommand(cmd.CommandText, (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray()); - } + string FormatCommand(IDbCommand cmd); /// /// Formats an SQL query and it's arguments for display @@ -2145,156 +3466,9 @@ public string FormatCommand(IDbCommand cmd) /// /// /// - public string FormatCommand(string sql, object[] args) - { - var sb = new StringBuilder(); - if (sql == null) - return ""; - sb.Append(sql); - if (args != null && args.Length > 0) - { - sb.Append("\n"); - for (int i = 0; i < args.Length; i++) - { - sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, args[i].GetType().Name, args[i]); - } - sb.Remove(sb.Length - 1, 1); - } - return sb.ToString(); - } - #endregion - - #region Public Properties - - /* - public static IMapper Mapper - { - get; - set; - } */ - - /// - /// When set to true, PetaPoco will automatically create the "SELECT columns" part of any query that looks like it needs it - /// - public bool EnableAutoSelect - { - get; - set; - } - - /// - /// When set to true, parameters can be named ?myparam and populated from properties of the passed in argument values. - /// - public bool EnableNamedParams - { - get; - set; - } - - /// - /// Sets the timeout value for all SQL statements. - /// - public int CommandTimeout - { - get; - set; - } - - /// - /// Sets the timeout value for the next (and only next) SQL statement - /// - public int OneTimeCommandTimeout - { - get; - set; - } - #endregion - - #region Member Fields - // Member variables - internal DatabaseType _dbType; - string _connectionString; - string _providerName; - DbProviderFactory _factory; - DbConnection _sharedConnection; - DbTransaction _transaction; - int _sharedConnectionDepth; - int _transactionDepth; - bool _transactionCancelled; - string _lastSql; - object[] _lastArgs; - string _paramPrefix; - #endregion - - #region Internal operations - internal async Task ExecuteNonQueryHelperAsync(DbCommand cmd) - { - DoPreExecute(cmd); - await cmd.ExecuteNonQueryAsync(); - OnExecutedCommand(cmd); - } - - internal async Task ExecuteScalarHelperAsync(DbCommand cmd) - { - DoPreExecute(cmd); - object r = await cmd.ExecuteScalarAsync(); - OnExecutedCommand(cmd); - return r; - } - - internal void DoPreExecute(DbCommand cmd) - { - // Setup command timeout - if (OneTimeCommandTimeout != 0) - { - cmd.CommandTimeout = OneTimeCommandTimeout; - OneTimeCommandTimeout = 0; - } - else if (CommandTimeout != 0) - { - cmd.CommandTimeout = CommandTimeout; - } - - // Call hook - OnExecutingCommand(cmd); - - // Save it - _lastSql = cmd.CommandText; - _lastArgs = (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray(); - } - - #endregion - - #region Composite primary key support - private Dictionary GetPrimaryKeyValues(string primaryKeyName, object primaryKeyValue) { - Dictionary primaryKeyValues; - - var multiplePrimaryKeysNames = primaryKeyName.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); - if (primaryKeyValue != null) { - if (multiplePrimaryKeysNames.Length == 1) { - primaryKeyValues = new Dictionary(StringComparer.OrdinalIgnoreCase) { { primaryKeyName, primaryKeyValue } }; - } - else { - var dict = primaryKeyValue as Dictionary; - primaryKeyValues = dict ?? multiplePrimaryKeysNames.ToDictionary(x => x, x => primaryKeyValue.GetType().GetProperties().Single(y => string.Equals(x, y.Name, StringComparison.OrdinalIgnoreCase)).GetValue(primaryKeyValue, null), StringComparer.OrdinalIgnoreCase); - } - } - else { - primaryKeyValues = multiplePrimaryKeysNames.ToDictionary(x => x, x => (object)null, StringComparer.OrdinalIgnoreCase); - } - - return primaryKeyValues; - } - - private string BuildPrimaryKeySql(Dictionary primaryKeyValuePair, ref int index) { - var tempIndex = index; - index += primaryKeyValuePair.Count; - return string.Join(" AND ", primaryKeyValuePair.Select((x, i) => x.Value == null || x.Value == DBNull.Value ? string.Format("{0} IS NULL", _dbType.EscapeSqlIdentifier(x.Key)) : string.Format("{0} = @{1}", _dbType.EscapeSqlIdentifier(x.Key), tempIndex + i)).ToArray()); - } - #endregion + string FormatCommand(string sql, object[] args); } - /* Thanks to Adam Schroder (@schotime) for this. @@ -3469,6 +4643,8 @@ public override object GetValue(object target) class MultiPocoFactory { + public static IEqualityComparer FieldNameComparer { get; set; } = StringComparer.InvariantCultureIgnoreCase; + // Instance data used by the Multipoco factory delegate - essentially a list of the nested poco factories to call List _delegates; public Delegate GetItem(int index) { return _delegates[index]; } @@ -3530,7 +4706,7 @@ static Delegate FindSplitPoint(Type typeThis, Type typeNext, string ConnectionSt // Find split point int firstColumn = pos; - var usedColumns = new Dictionary(); + var usedColumns = new Dictionary(FieldNameComparer); for (; pos < r.FieldCount; pos++) { // Split if field name has already been used, or if the field doesn't exist in current poco but does in the next @@ -3621,6 +4797,8 @@ internal class PocoColumn class PocoData { + public static IEqualityComparer ColumnComparer { get;set;}=StringComparer.InvariantCultureIgnoreCase; + public static PocoData ForObject(object o, string primaryKeyName) { var t = o.GetType(); @@ -3629,7 +4807,7 @@ public static PocoData ForObject(object o, string primaryKeyName) { var pd = new PocoData(); pd.TableInfo = new TableInfo(); - pd.Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); + pd.Columns = new Dictionary(ColumnComparer); pd.Columns.Add(primaryKeyName, new ExpandoColumn() { ColumnName = primaryKeyName }); pd.TableInfo.PrimaryKey = primaryKeyName; pd.TableInfo.AutoIncrement = true; @@ -3670,7 +4848,7 @@ public PocoData(Type t) TableInfo = mapper.GetTableInfo(t); // Work out bound properties - Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); + Columns = new Dictionary(ColumnComparer); foreach (var pi in t.GetProperties()) { ColumnInfo ci = mapper.GetColumnInfo(pi); @@ -3723,7 +4901,6 @@ public Delegate GetFactory(string sql, string connString, int firstColumn, int c #if !PETAPOCO_NO_DYNAMIC if (type == typeof(object)) { - // var poco=new T() il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add"); @@ -4151,6 +5328,8 @@ public void Flush() internal static class EnumMapper { + public static IEqualityComparer FieldComparer { get; set; } = StringComparer.InvariantCultureIgnoreCase; + public static object EnumFromString(Type enumType, string value) { if (!enumType.IsEnum) { @@ -4161,7 +5340,7 @@ public static object EnumFromString(Type enumType, string value) { var values = Enum.GetValues(enumType); - var newmap = new Dictionary(values.Length, StringComparer.InvariantCultureIgnoreCase); + var newmap = new Dictionary(values.Length, FieldComparer); foreach (var v in values) { diff --git a/AsyncPoco/AsyncPoco.csproj b/AsyncPoco/AsyncPoco.csproj index ad2c0684..d06ac87b 100644 --- a/AsyncPoco/AsyncPoco.csproj +++ b/AsyncPoco/AsyncPoco.csproj @@ -76,6 +76,7 @@ + diff --git a/AsyncPoco/Core/MultiPocoFactory.cs b/AsyncPoco/Core/MultiPocoFactory.cs index 54f0d8f2..a54886cb 100644 --- a/AsyncPoco/Core/MultiPocoFactory.cs +++ b/AsyncPoco/Core/MultiPocoFactory.cs @@ -14,6 +14,8 @@ namespace AsyncPoco.Internal { class MultiPocoFactory { + public static IEqualityComparer FieldNameComparer { get; set; } = StringComparer.InvariantCultureIgnoreCase; + // Instance data used by the Multipoco factory delegate - essentially a list of the nested poco factories to call List _delegates; public Delegate GetItem(int index) { return _delegates[index]; } @@ -75,7 +77,7 @@ static Delegate FindSplitPoint(Type typeThis, Type typeNext, string ConnectionSt // Find split point int firstColumn = pos; - var usedColumns = new Dictionary(); + var usedColumns = new Dictionary(FieldNameComparer); for (; pos < r.FieldCount; pos++) { // Split if field name has already been used, or if the field doesn't exist in current poco but does in the next diff --git a/AsyncPoco/Core/PocoData.cs b/AsyncPoco/Core/PocoData.cs index b7d5ffe0..01a9d6d4 100644 --- a/AsyncPoco/Core/PocoData.cs +++ b/AsyncPoco/Core/PocoData.cs @@ -14,6 +14,8 @@ namespace AsyncPoco.Internal { class PocoData { + public static IEqualityComparer ColumnComparer { get;set;}=StringComparer.InvariantCultureIgnoreCase; + public static PocoData ForObject(object o, string primaryKeyName) { var t = o.GetType(); @@ -22,7 +24,7 @@ public static PocoData ForObject(object o, string primaryKeyName) { var pd = new PocoData(); pd.TableInfo = new TableInfo(); - pd.Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); + pd.Columns = new Dictionary(ColumnComparer); pd.Columns.Add(primaryKeyName, new ExpandoColumn() { ColumnName = primaryKeyName }); pd.TableInfo.PrimaryKey = primaryKeyName; pd.TableInfo.AutoIncrement = true; @@ -63,7 +65,7 @@ public PocoData(Type t) TableInfo = mapper.GetTableInfo(t); // Work out bound properties - Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); + Columns = new Dictionary(ColumnComparer); foreach (var pi in t.GetProperties()) { ColumnInfo ci = mapper.GetColumnInfo(pi); @@ -116,7 +118,6 @@ public Delegate GetFactory(string sql, string connString, int firstColumn, int c #if !PETAPOCO_NO_DYNAMIC if (type == typeof(object)) { - // var poco=new T() il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add"); @@ -251,7 +252,7 @@ public Delegate GetFactory(string sql, string connString, int firstColumn, int c { il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) })); } - + il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco Handled = true; } diff --git a/AsyncPoco/Database.cs b/AsyncPoco/Database.cs index 317a1db4..e73703c5 100644 --- a/AsyncPoco/Database.cs +++ b/AsyncPoco/Database.cs @@ -14,48 +14,77 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Configuration; -using System.Data.Common; using System.Data; -using System.Text.RegularExpressions; +using System.Data.Common; +using System.Diagnostics; +using System.Dynamic; +using System.Linq; using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using AsyncPoco.Internal; - namespace AsyncPoco { /// - /// The main Database class. You can either use this class directly, or derive from it. + /// The main Database class. You can either use this class directly, or derive from it. /// - public class Database : IDisposable + public class Database : IDatabase { + private static IEqualityComparer _columnComparer = StringComparer.InvariantCultureIgnoreCase; + public static IEqualityComparer ColumnComparer + { + get { return _columnComparer; } + set + { + _columnComparer = value; + MultiPocoFactory.FieldNameComparer = value; + PocoData.ColumnComparer = value; + EnumMapper.FieldComparer = value; + } + } + + #region IDisposable + + /// + /// Automatically close one open shared connection + /// + public void Dispose() + { + // Automatically close one open connection reference + // (Works with KeepConnectionAlive and manually opening a shared connection) + CloseSharedConnection(); + } + + #endregion + #region Constructors + /// - /// Construct a database using a supplied DbConnection + /// Construct a database using a supplied DbConnection /// /// The DbConnection to use /// - /// The supplied DbConnection will not be closed/disposed by PetaPoco - that remains - /// the responsibility of the caller. + /// The supplied DbConnection will not be closed/disposed by PetaPoco - that remains + /// the responsibility of the caller. /// public Database(DbConnection connection) { _sharedConnection = connection; _connectionString = connection.ConnectionString; - _sharedConnectionDepth = 2; // Prevent closing external connection + _sharedConnectionDepth = 2; // Prevent closing external connection CommonConstruct(); } /// - /// Construct a database using a supplied connections string and optionally a provider name + /// Construct a database using a supplied connections string and optionally a provider name /// /// The DB connection string /// The name of the DB provider to use /// - /// PetaPoco will automatically close and dispose any connections it creates. + /// PetaPoco will automatically close and dispose any connections it creates. /// public Database(string connectionString, string providerName) { @@ -65,7 +94,7 @@ public Database(string connectionString, string providerName) } /// - /// Construct a Database using a supplied connection string and a DbProviderFactory + /// Construct a Database using a supplied connection string and a DbProviderFactory /// /// The connection string to use /// The DbProviderFactory to use for instantiating DbConnection's @@ -77,8 +106,8 @@ public Database(string connectionString, DbProviderFactory provider) } /// - /// Construct a Database using a supplied connectionString Name. The actual connection string and provider will be - /// read from app/web.config. + /// Construct a Database using a supplied connectionString Name. The actual connection string and provider will be + /// read from app/web.config. /// /// The name of the connection public Database(string connectionStringName) @@ -106,7 +135,7 @@ public Database(string connectionStringName) } /// - /// Provides common initialization for the various constructors + /// Provides common initialization for the various constructors /// private void CommonConstruct() { @@ -120,7 +149,7 @@ private void CommonConstruct() _factory = DbProviderFactories.GetFactory(_providerName); // Resolve the DB Type - string DBTypeName = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; + var DBTypeName = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; _dbType = DatabaseType.Resolve(DBTypeName, _providerName); // What character is used for delimiting parameters in SQL @@ -129,33 +158,18 @@ private void CommonConstruct() #endregion - #region IDisposable - /// - /// Automatically close one open shared connection - /// - public void Dispose() - { - // Automatically close one open connection reference - // (Works with KeepConnectionAlive and manually opening a shared connection) - CloseSharedConnection(); - } - #endregion - #region Connection Management + /// - /// When set to true the first opened connection is kept alive until this object is disposed + /// When set to true the first opened connection is kept alive until this object is disposed /// - public bool KeepConnectionAlive - { - get; - set; - } + public bool KeepConnectionAlive { get; set; } /// - /// Open a connection that will be used for all subsequent queries. + /// Open a connection that will be used for all subsequent queries. /// /// - /// Calls to Open/CloseSharedConnection are reference counted and should be balanced + /// Calls to Open/CloseSharedConnection are reference counted and should be balanced /// public virtual async Task OpenSharedConnectionAsync() { @@ -173,13 +187,13 @@ public virtual async Task OpenSharedConnectionAsync() _sharedConnection = OnConnectionOpened(_sharedConnection); if (KeepConnectionAlive) - _sharedConnectionDepth++; // Make sure you call Dispose + _sharedConnectionDepth++; // Make sure you call Dispose } _sharedConnectionDepth++; } /// - /// Releases the shared connection + /// Releases the shared connection /// public void CloseSharedConnection() { @@ -196,7 +210,7 @@ public void CloseSharedConnection() } /// - /// Provides access to the currently open shared connection (or null if none) + /// Provides access to the currently open shared connection (or null if none) /// public IDbConnection Connection { @@ -206,28 +220,25 @@ public IDbConnection Connection #endregion #region Transaction Management + // Helper to create a transaction scope /// - /// Starts or continues a transaction. + /// Starts or continues a transaction. /// /// An ITransaction reference that must be Completed or disposed /// - /// This method makes management of calls to Begin/End/CompleteTransaction easier. - /// - /// The usage pattern for this should be: - /// - /// using (var tx = db.GetTransaction()) - /// { - /// // Do stuff - /// db.Update(...); - /// + /// This method makes management of calls to Begin/End/CompleteTransaction easier. + /// The usage pattern for this should be: + /// using (var tx = db.GetTransaction()) + /// { + /// // Do stuff + /// db.Update(...); /// // Mark the transaction as complete /// tx.Complete(); - /// } - /// - /// Transactions can be nested but they must all be completed otherwise the entire - /// transaction is aborted. + /// } + /// Transactions can be nested but they must all be completed otherwise the entire + /// transaction is aborted. /// public Task GetTransactionAsync() { @@ -235,22 +246,22 @@ public Task GetTransactionAsync() } /// - /// Called when a transaction starts. Overridden by the T4 template generated database - /// classes to ensure the same DB instance is used throughout the transaction. + /// Called when a transaction starts. Overridden by the T4 template generated database + /// classes to ensure the same DB instance is used throughout the transaction. /// - public virtual void OnBeginTransaction() - { + public virtual void OnBeginTransaction() + { } /// - /// Called when a transaction ends. + /// Called when a transaction ends. /// - public virtual void OnEndTransaction() - { + public virtual void OnEndTransaction() + { } /// - /// Starts a transaction scope, see GetTransaction() for recommended usage + /// Starts a transaction scope, see GetTransaction() for recommended usage /// public virtual async Task BeginTransactionAsync() { @@ -263,11 +274,10 @@ public virtual async Task BeginTransactionAsync() _transactionCancelled = false; OnBeginTransaction(); } - } /// - /// Internal helper to cleanup transaction + /// Internal helper to cleanup transaction /// protected virtual void CleanupTransaction() { @@ -285,38 +295,39 @@ protected virtual void CleanupTransaction() } /// - /// Aborts the entire outer most transaction scope + /// Aborts the entire outer most transaction scope /// /// - /// Called automatically by Transaction.Dispose() - /// if the transaction wasn't completed. + /// Called automatically by Transaction.Dispose() + /// if the transaction wasn't completed. /// public void AbortTransaction() { _transactionCancelled = true; - if ((--_transactionDepth) == 0) + if (--_transactionDepth == 0) CleanupTransaction(); } /// - /// Marks the current transaction scope as complete. + /// Marks the current transaction scope as complete. /// public void CompleteTransaction() { - if ((--_transactionDepth) == 0) + if (--_transactionDepth == 0) CleanupTransaction(); } #endregion #region Command Management + /// - /// Add a parameter to a DB command + /// Add a parameter to a DB command /// /// A reference to the IDbCommand to which the parameter is to be added /// The value to assign to the parameter /// Optional, a reference to the property info of the POCO property from which the value is coming. - void AddParam(IDbCommand cmd, object value, PropertyInfo pi) + private void AddParam(IDbCommand cmd, object value, PropertyInfo pi) { // Convert value to from poco type to db type if (pi != null) @@ -349,9 +360,9 @@ void AddParam(IDbCommand cmd, object value, PropertyInfo pi) { // Give the database type first crack at converting to DB required type value = _dbType.MapParameterValue(value); - + var t = value.GetType(); - if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int + if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int { p.Value = (int)value; } @@ -365,9 +376,9 @@ void AddParam(IDbCommand cmd, object value, PropertyInfo pi) { // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. Set before attempting to set Size, or Size will always max out at 4000 if ((value as string).Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter") - p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); - - p.Size = Math.Max((value as string).Length + 1, 4000); // Help query plan caching by using common size + p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); + + p.Size = Math.Max((value as string).Length + 1, 4000); // Help query plan caching by using common size p.Value = value; } else if (t == typeof(AnsiString)) @@ -379,7 +390,8 @@ void AddParam(IDbCommand cmd, object value, PropertyInfo pi) } else if (value.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type + p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); + //geography is the equivalent SQL Server Type p.Value = value; } @@ -399,7 +411,8 @@ void AddParam(IDbCommand cmd, object value, PropertyInfo pi) } // Create a command - static Regex rxParamsPrefix = new Regex(@"(? _paramPrefix + m.Value.Substring(1)); - sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @ + sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @ // Create the command and add parameters var cmd = connection.CreateCommand(); @@ -429,74 +442,76 @@ public DbCommand CreateCommand(DbConnection connection, string sql, params objec _dbType.PreExecute(cmd); // Call logging - if (!String.IsNullOrEmpty(sql)) + if (!string.IsNullOrEmpty(sql)) DoPreExecute(cmd); return cmd; } + #endregion #region Exception Reporting and Logging /// - /// Called if an exception occurs during processing of a DB operation. Override to provide custom logging/handling. + /// Called if an exception occurs during processing of a DB operation. Override to provide custom logging/handling. /// /// The exception instance /// True to re-throw the exception, false to suppress it public virtual bool OnException(Exception x) { - System.Diagnostics.Debug.WriteLine(x.ToString()); - System.Diagnostics.Debug.WriteLine(LastCommand); + Debug.WriteLine(x.ToString()); + Debug.WriteLine(LastCommand); return true; } /// - /// Called when DB connection opened + /// Called when DB connection opened /// /// The newly opened DbConnection /// The same or a replacement DbConnection /// - /// Override this method to provide custom logging of opening connection, or - /// to provide a proxy DbConnection. + /// Override this method to provide custom logging of opening connection, or + /// to provide a proxy DbConnection. /// - public virtual DbConnection OnConnectionOpened(DbConnection conn) - { - return conn; + public virtual DbConnection OnConnectionOpened(DbConnection conn) + { + return conn; } /// - /// Called when DB connection closed + /// Called when DB connection closed /// /// The soon to be closed IDBConnection - public virtual void OnConnectionClosing(IDbConnection conn) - { + public virtual void OnConnectionClosing(IDbConnection conn) + { } - + /// - /// Called just before an DB command is executed + /// Called just before an DB command is executed /// /// The command to be executed /// - /// Override this method to provide custom logging of commands and/or - /// modification of the IDbCommand before it's executed + /// Override this method to provide custom logging of commands and/or + /// modification of the IDbCommand before it's executed /// - public virtual void OnExecutingCommand(IDbCommand cmd) - { + public virtual void OnExecutingCommand(IDbCommand cmd) + { } /// - /// Called on completion of command execution + /// Called on completion of command execution /// /// The IDbCommand that finished executing - public virtual void OnExecutedCommand(IDbCommand cmd) - { + public virtual void OnExecutedCommand(IDbCommand cmd) + { } #endregion #region operation: Execute + /// - /// Executes a non-query command + /// Executes a non-query command /// /// The SQL statement to execute /// Arguments to any embedded parameters in the SQL @@ -529,7 +544,7 @@ public virtual async Task ExecuteAsync(string sql, params object[] args) } /// - /// Executes a non-query command + /// Executes a non-query command /// /// An SQL builder object representing the query and it's arguments /// The number of rows affected @@ -543,7 +558,7 @@ public Task ExecuteAsync(Sql sql) #region operation: ExecuteScalarAsync /// - /// Executes a query and return the first column of the first row in the result set. + /// Executes a query and return the first column of the first row in the result set. /// /// The type that the result value should be cast to /// The SQL query to execute @@ -558,12 +573,12 @@ public virtual async Task ExecuteScalarAsync(string sql, params object[] a { using (var cmd = CreateCommand(_sharedConnection, sql, args)) { - object val = await cmd.ExecuteScalarAsync(); + var val = await cmd.ExecuteScalarAsync(); OnExecutedCommand(cmd); // Handle nullable types - Type u = Nullable.GetUnderlyingType(typeof(T)); - if (u != null && val == null) + var u = Nullable.GetUnderlyingType(typeof(T)); + if (u != null && val == null) return default(T); return (T)Convert.ChangeType(val, u ?? typeof(T)); @@ -583,7 +598,7 @@ public virtual async Task ExecuteScalarAsync(string sql, params object[] a } /// - /// Executes a query and return the first column of the first row in the result set. + /// Executes a query and return the first column of the first row in the result set. /// /// The type that the result value should be cast to /// An SQL builder object representing the query and it's arguments @@ -598,7 +613,7 @@ public Task ExecuteScalarAsync(Sql sql) #region operation: Fetch /// - /// Runs a query and returns the result set as a typed list + /// Runs a query and returns the result set as a typed list /// /// The Type representing a row in the result set /// The SQL query to execute @@ -612,12 +627,12 @@ public async Task> FetchAsync(string sql, params object[] args) } /// - /// Runs a query and returns the result set as a typed list + /// Runs a query and returns the result set as a typed list /// /// The Type representing a row in the result set /// An SQL builder object representing the query and it's arguments /// A List holding the results of the query - public Task> FetchAsync(Sql sql) + public Task> FetchAsync(Sql sql) { return FetchAsync(sql.SQL, sql.Arguments); } @@ -627,8 +642,8 @@ public Task> FetchAsync(Sql sql) #region operation: Page /// - /// Starting with a regular SELECT statement, derives the SQL statements required to query a - /// DB for a page of records and the total number of records + /// Starting with a regular SELECT statement, derives the SQL statements required to query a + /// DB for a page of records and the total number of records /// /// The Type representing a row in the result set /// The number of rows to skip before the start of the page @@ -637,7 +652,8 @@ public Task> FetchAsync(Sql sql) /// Arguments to any embedded parameters in the SQL /// Outputs the SQL statement to query for the total number of matching rows /// Outputs the SQL statement to retrieve a single page of matching rows - void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) + private void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, + out string sqlPage) { // Add auto select clause if (EnableAutoSelect) @@ -653,7 +669,7 @@ void BuildPageQueries(long skip, long take, string sql, ref object[] args, ou } /// - /// Retrieves a page of records and the total number of available records + /// Retrieves a page of records and the total number of available records /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -664,10 +680,12 @@ void BuildPageQueries(long skip, long take, string sql, ref object[] args, ou /// Arguments to any embedded parameters in the sqlPage statement /// A Page of results /// - /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. - /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. + /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. + /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page + /// object. /// - public async Task> PageAsync(long page, long itemsPerPage, string sqlCount, object[] countArgs, string sqlPage, object[] pageArgs) + public async Task> PageAsync(long page, long itemsPerPage, string sqlCount, object[] countArgs, + string sqlPage, object[] pageArgs) { // Save the one-time command time out and use it for both queries var saveTimeout = OneTimeCommandTimeout; @@ -681,7 +699,7 @@ public async Task> PageAsync(long page, long itemsPerPage, string sql }; result.TotalPages = result.TotalItems / itemsPerPage; - if ((result.TotalItems % itemsPerPage) != 0) + if (result.TotalItems % itemsPerPage != 0) result.TotalPages++; OneTimeCommandTimeout = saveTimeout; @@ -692,10 +710,9 @@ public async Task> PageAsync(long page, long itemsPerPage, string sql // Done return result; } - - + /// - /// Retrieves a page of records and the total number of available records + /// Retrieves a page of records and the total number of available records /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -704,19 +721,19 @@ public async Task> PageAsync(long page, long itemsPerPage, string sql /// Arguments to any embedded parameters in the SQL statement /// A Page of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. It will also execute a second query to retrieve the - /// total number of records in the result set. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. It will also execute a second query to retrieve the + /// total number of records in the result set. /// - public Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args) + public Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args) { string sqlCount, sqlPage; - BuildPageQueries((page-1)*itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage); + BuildPageQueries((page - 1) * itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage); return PageAsync(page, itemsPerPage, sqlCount, args, sqlPage, args); } /// - /// Retrieves a page of records and the total number of available records + /// Retrieves a page of records and the total number of available records /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -724,9 +741,9 @@ public Task> PageAsync(long page, long itemsPerPage, string sql, para /// An SQL builder object representing the base SQL query and it's arguments /// A Page of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. It will also execute a second query to retrieve the - /// total number of records in the result set. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. It will also execute a second query to retrieve the + /// total number of records in the result set. /// public Task> PageAsync(long page, long itemsPerPage, Sql sql) { @@ -734,7 +751,7 @@ public Task> PageAsync(long page, long itemsPerPage, Sql sql) } /// - /// Retrieves a page of records and the total number of available records + /// Retrieves a page of records and the total number of available records /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -743,8 +760,9 @@ public Task> PageAsync(long page, long itemsPerPage, Sql sql) /// An SQL builder object representing the SQL to retrieve a single page of results /// A Page of results /// - /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. - /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. + /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. + /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page + /// object. /// public Task> PageAsync(long page, long itemsPerPage, Sql sqlCount, Sql sqlPage) { @@ -756,7 +774,7 @@ public Task> PageAsync(long page, long itemsPerPage, Sql sqlCount, Sq #region operation: Fetch (page) /// - /// Retrieves a page of records (without the total count) + /// Retrieves a page of records (without the total count) /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -765,8 +783,8 @@ public Task> PageAsync(long page, long itemsPerPage, Sql sqlCount, Sq /// Arguments to any embedded parameters in the SQL statement /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. /// public Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args) { @@ -774,7 +792,7 @@ public Task> FetchAsync(long page, long itemsPerPage, string sql, par } /// - /// Retrieves a page of records (without the total count) + /// Retrieves a page of records (without the total count) /// /// The Type representing a row in the result set /// The 1 based page number to retrieve @@ -782,8 +800,8 @@ public Task> FetchAsync(long page, long itemsPerPage, string sql, par /// An SQL builder object representing the base SQL query and it's arguments /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified page. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. /// public Task> FetchAsync(long page, long itemsPerPage, Sql sql) { @@ -795,7 +813,7 @@ public Task> FetchAsync(long page, long itemsPerPage, Sql sql) #region operation: SkipTakeAsync /// - /// Retrieves a range of records from result set + /// Retrieves a range of records from result set /// /// The Type representing a row in the result set /// The number of rows at the start of the result set to skip over @@ -804,8 +822,8 @@ public Task> FetchAsync(long page, long itemsPerPage, Sql sql) /// Arguments to any embedded parameters in the SQL statement /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified range. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified range. /// public Task> SkipTakeAsync(long skip, long take, string sql, params object[] args) { @@ -815,7 +833,7 @@ public Task> SkipTakeAsync(long skip, long take, string sql, params o } /// - /// Retrieves a range of records from result set + /// Retrieves a range of records from result set /// /// The Type representing a row in the result set /// The number of rows at the start of the result set to skip over @@ -823,79 +841,84 @@ public Task> SkipTakeAsync(long skip, long take, string sql, params o /// An SQL builder object representing the base SQL query and it's arguments /// A List of results /// - /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the - /// records for the specified range. + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified range. /// public Task> SkipTakeAsync(long skip, long take, Sql sql) { return SkipTakeAsync(skip, take, sql.SQL, sql.Arguments); } + #endregion #region operation: Query /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// The SQL query /// Callback to process each result /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public Task QueryAsync(string sql, Action action) { + public Task QueryAsync(string sql, Action action) + { return QueryAsync(sql, null, action); } /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// The SQL query /// Callback to process each result, return false to stop iterating /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public Task QueryAsync(string sql, Func func) { + public Task QueryAsync(string sql, Func func) + { return QueryAsync(sql, null, func); } /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// Callback to process each result /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public Task QueryAsync(string sql, object[] args, Action action) { - return QueryAsync(sql, args, v => { + public Task QueryAsync(string sql, object[] args, Action action) + { + return QueryAsync(sql, args, v => + { action(v); return true; }); } /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// Callback to process each result, return false to stop iterating /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public virtual async Task QueryAsync(string sql, object[] args, Func func) + public virtual async Task QueryAsync(string sql, object[] args, Func func) { if (EnableAutoSelect) sql = AutoSelectHelper.AddSelectClause(_dbType, sql); @@ -919,8 +942,10 @@ public virtual async Task QueryAsync(string sql, object[] args, Func return; } - var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, r.FieldCount, r) as Func; - using (r) { + var factory = + pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, r.FieldCount, r) as Func; + using (r) + { var keepGoing = true; while (keepGoing) { @@ -952,33 +977,33 @@ public virtual async Task QueryAsync(string sql, object[] args, Func } /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// An SQL builder object representing the base SQL query and it's arguments /// Callback to process each result /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public Task QueryAsync(Sql sql, Action action) + public Task QueryAsync(Sql sql, Action action) { return QueryAsync(sql.SQL, sql.Arguments, action); } /// - /// Runs an SQL query, asynchronously passing each result to a callback + /// Runs an SQL query, asynchronously passing each result to a callback /// /// The Type representing a row in the result set /// An SQL builder object representing the base SQL query and it's arguments /// Callback to process each result, return false to stop iterating /// - /// For some DB providers, care should be taken to not start a new Query before finishing with - /// and disposing the previous one. In cases where this is an issue, consider using Fetch which - /// returns the results as a List. + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. /// - public Task QueryAsync(Sql sql, Func func) + public Task QueryAsync(Sql sql, Func func) { return QueryAsync(sql.SQL, sql.Arguments, func); } @@ -988,7 +1013,7 @@ public Task QueryAsync(Sql sql, Func func) #region operation: Exists /// - /// Checks for the existance of a row matching the specified condition + /// Checks for the existance of a row matching the specified condition /// /// The Type representing the table being queried /// The SQL expression to be tested for (ie: the WHERE expression) @@ -1002,12 +1027,13 @@ public async Task ExistsAsync(string sqlCondition, params object[] args } /// - /// Checks for the existance of a row with the specified primary key value. + /// Checks for the existance of a row with the specified primary key value. /// /// The Type representing the table being queried /// The primary key value to look for /// True if a record with the specified primary key value exists. - public Task ExistsAsync(object primaryKey) { + public Task ExistsAsync(object primaryKey) + { var index = 0; var pk = GetPrimaryKeyValues(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey, primaryKey); return ExistsAsync(BuildPrimaryKeySql(pk, ref index), pk.Select(x => x.Value).ToArray()); @@ -1018,15 +1044,15 @@ public Task ExistsAsync(object primaryKey) { #region operation: linq style (Exists, Single, SingleOrDefault etc...) /// - /// Returns the record with the specified primary key value + /// Returns the record with the specified primary key value /// /// The Type representing a row in the result set /// The primary key value of the record to fetch /// The single record matching the specified primary key value /// - /// Throws an exception if there are zero or more than one record with the specified primary key value. + /// Throws an exception if there are zero or more than one record with the specified primary key value. /// - public Task SingleAsync(object primaryKey) + public Task SingleAsync(object primaryKey) { var index = 0; var pk = GetPrimaryKeyValues(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey, primaryKey); @@ -1034,15 +1060,15 @@ public Task SingleAsync(object primaryKey) } /// - /// Returns the record with the specified primary key value, or the default value if not found + /// Returns the record with the specified primary key value, or the default value if not found /// /// The Type representing a row in the result set /// The primary key value of the record to fetch /// The single record matching the specified primary key value /// - /// If there are no records with the specified primary key value, default(T) (typically null) is returned. + /// If there are no records with the specified primary key value, default(T) (typically null) is returned. /// - public Task SingleOrDefaultAsync(object primaryKey) + public Task SingleOrDefaultAsync(object primaryKey) { var index = 0; var pk = GetPrimaryKeyValues(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey, primaryKey); @@ -1050,44 +1076,46 @@ public Task SingleOrDefaultAsync(object primaryKey) } /// - /// Runs a query that should always return a single row. + /// Runs a query that should always return a single row. /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// The single record matching the specified primary key value /// - /// Throws an exception if there are zero or more than one matching record + /// Throws an exception if there are zero or more than one matching record /// public async Task SingleAsync(string sql, params object[] args) { var count = 0; - T poco = default(T); - await QueryAsync(sql, args, v => { + var poco = default(T); + await QueryAsync(sql, args, v => + { poco = v; count++; return count <= 2; }); if (count == 0) throw new InvalidOperationException("Sequence contains no elements."); - else if (count > 1) + if (count > 1) throw new InvalidOperationException("Sequence contains more than one element."); return poco; } /// - /// Runs a query that should always return either a single row, or no rows + /// Runs a query that should always return either a single row, or no rows /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// The single record matching the specified primary key value, or default(T) if no matching rows - public async Task SingleOrDefaultAsync(string sql, params object[] args) + public async Task SingleOrDefaultAsync(string sql, params object[] args) { var count = 0; - T poco = default(T); - await QueryAsync(sql, args, v => { + var poco = default(T); + await QueryAsync(sql, args, v => + { poco = v; count++; return count <= 2; @@ -1099,17 +1127,18 @@ await QueryAsync(sql, args, v => { } /// - /// Runs a query that should always return at least one return + /// Runs a query that should always return at least one return /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// The first record in the result set - public async Task FirstAsync(string sql, params object[] args) + public async Task FirstAsync(string sql, params object[] args) { var gotIt = false; - T poco = default(T); - await QueryAsync(sql, args, v => { + var poco = default(T); + await QueryAsync(sql, args, v => + { poco = v; gotIt = true; return false; @@ -1121,16 +1150,17 @@ await QueryAsync(sql, args, v => { } /// - /// Runs a query and returns the first record, or the default value if no matching records + /// Runs a query and returns the first record, or the default value if no matching records /// /// The Type representing a row in the result set /// The SQL query /// Arguments to any embedded parameters in the SQL statement /// The first record in the result set, or default(T) if no matching rows - public async Task FirstOrDefaultAsync(string sql, params object[] args) + public async Task FirstOrDefaultAsync(string sql, params object[] args) { - T poco = default(T); - await QueryAsync(sql, args, v => { + var poco = default(T); + await QueryAsync(sql, args, v => + { poco = v; return false; }); @@ -1138,13 +1168,13 @@ await QueryAsync(sql, args, v => { } /// - /// Runs a query that should always return a single row. + /// Runs a query that should always return a single row. /// /// The Type representing a row in the result set /// An SQL builder object representing the query and it's arguments /// The single record matching the specified primary key value /// - /// Throws an exception if there are zero or more than one matching record + /// Throws an exception if there are zero or more than one matching record /// public Task SingleAsync(Sql sql) { @@ -1152,43 +1182,44 @@ public Task SingleAsync(Sql sql) } /// - /// Runs a query that should always return either a single row, or no rows + /// Runs a query that should always return either a single row, or no rows /// /// The Type representing a row in the result set /// An SQL builder object representing the query and it's arguments /// The single record matching the specified primary key value, or default(T) if no matching rows - public Task SingleOrDefaultAsync(Sql sql) + public Task SingleOrDefaultAsync(Sql sql) { return SingleOrDefaultAsync(sql.SQL, sql.Arguments); } /// - /// Runs a query that should always return at least one return + /// Runs a query that should always return at least one return /// /// The Type representing a row in the result set /// An SQL builder object representing the query and it's arguments /// The first record in the result set - public Task FirstAsync(Sql sql) + public Task FirstAsync(Sql sql) { return FirstAsync(sql.SQL, sql.Arguments); } /// - /// Runs a query and returns the first record, or the default value if no matching records + /// Runs a query and returns the first record, or the default value if no matching records /// /// The Type representing a row in the result set /// An SQL builder object representing the query and it's arguments /// The first record in the result set, or default(T) if no matching rows - public Task FirstOrDefaultAsync(Sql sql) + public Task FirstOrDefaultAsync(Sql sql) { return FirstOrDefaultAsync(sql.SQL, sql.Arguments); } + #endregion #region operation: Insert /// - /// Performs an SQL Insert + /// Performs an SQL Insert /// /// The name of the table to insert into /// The name of the primary key column of the table @@ -1199,19 +1230,19 @@ public Task InsertAsync(string tableName, string primaryKeyName, object return InsertAsync(tableName, primaryKeyName, true, poco); } - - /// - /// Performs an SQL Insert + /// Performs an SQL Insert /// /// The name of the table to insert into /// The name of the primary key column of the table /// True if the primary key is automatically allocated by the DB /// The POCO object that specifies the column values to be inserted /// The auto allocated primary key of the new record, or null for non-auto-increment tables - /// Inserts a poco into a table. If the poco has a property with the same name - /// as the primary key the id of the new record is assigned to it. Either way, - /// the new id is returned. + /// + /// Inserts a poco into a table. If the poco has a property with the same name + /// as the primary key the id of the new record is assigned to it. Either way, + /// the new id is returned. + /// public virtual async Task InsertAsync(string tableName, string primaryKeyName, bool autoIncrement, object poco) { try @@ -1236,11 +1267,11 @@ public virtual async Task InsertAsync(string tableName, string primaryKe continue; // Don't insert the primary key (except under oracle where we need bring in the next sequence value) - if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, true)==0) + if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, true) == 0) { // Setup auto increment expression - string autoIncExpression = _dbType.GetAutoIncrementExpression(pd.TableInfo); - if (autoIncExpression!=null) + var autoIncExpression = _dbType.GetAutoIncrementExpression(pd.TableInfo); + if (autoIncExpression != null) { names.Add(i.Key); values.Add(autoIncExpression); @@ -1253,7 +1284,7 @@ public virtual async Task InsertAsync(string tableName, string primaryKe AddParam(cmd, i.Value.GetValue(poco), i.Value.PropertyInfo); } - string outputClause = String.Empty; + var outputClause = string.Empty; if (autoIncrement) { outputClause = _dbType.GetInsertOutputClause(primaryKeyName); @@ -1261,11 +1292,11 @@ public virtual async Task InsertAsync(string tableName, string primaryKe cmd.CommandText = string.Format("INSERT INTO {0} ({1}){2} VALUES ({3})", - _dbType.EscapeTableName(tableName), - string.Join(",", names.ToArray()), - outputClause, - string.Join(",", values.ToArray()) - ); + _dbType.EscapeTableName(tableName), + string.Join(",", names.ToArray()), + outputClause, + string.Join(",", values.ToArray()) + ); if (!autoIncrement) { @@ -1281,8 +1312,7 @@ public virtual async Task InsertAsync(string tableName, string primaryKe } - object id = await _dbType.ExecuteInsertAsync(this, cmd, primaryKeyName); - + var id = await _dbType.ExecuteInsertAsync(this, cmd, primaryKeyName); // Assign the ID back to the primary key property if (primaryKeyName != null) @@ -1311,12 +1341,14 @@ public virtual async Task InsertAsync(string tableName, string primaryKe } /// - /// Performs an SQL Insert + /// Performs an SQL Insert /// /// The POCO object that specifies the column values to be inserted /// The auto allocated primary key of the new record, or null for non-auto-increment tables - /// The name of the table, it's primary key and whether it's an auto-allocated primary key are retrieved - /// from the POCO's attributes + /// + /// The name of the table, it's primary key and whether it's an auto-allocated primary key are retrieved + /// from the POCO's attributes + /// public Task InsertAsync(object poco) { var pd = PocoData.ForType(poco.GetType()); @@ -1328,7 +1360,7 @@ public Task InsertAsync(object poco) #region operation: Update /// - /// Performs an SQL update + /// Performs an SQL update /// /// The name of the table to update /// The name of the primary key column of the table @@ -1341,7 +1373,7 @@ public Task UpdateAsync(string tableName, string primaryKeyName, object poc } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The name of the table to update /// The name of the primary key column of the table @@ -1349,7 +1381,8 @@ public Task UpdateAsync(string tableName, string primaryKeyName, object poc /// The primary key of the record to be updated /// The column names of the columns to be updated, or null for all /// The number of affected rows - public virtual async Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) + public virtual async Task UpdateAsync(string tableName, string primaryKeyName, object poco, + object primaryKeyValue, IEnumerable columns) { try { @@ -1361,7 +1394,7 @@ public virtual async Task UpdateAsync(string tableName, string primaryKeyNa { var sb = new StringBuilder(); var index = 0; - var pd = PocoData.ForObject(poco,primaryKeyName); + var pd = PocoData.ForObject(poco, primaryKeyName); var primaryKeyValuePairs = GetPrimaryKeyValues(primaryKeyName, primaryKeyValue); if (columns == null) @@ -1409,11 +1442,12 @@ public virtual async Task UpdateAsync(string tableName, string primaryKeyNa } cmd.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2}", - _dbType.EscapeTableName(tableName), - sb, + _dbType.EscapeTableName(tableName), + sb, BuildPrimaryKeySql(primaryKeyValuePairs, ref index)); - foreach (var keyValue in primaryKeyValuePairs) { + foreach (var keyValue in primaryKeyValuePairs) + { var pi = pd.Columns.ContainsKey(keyValue.Key) ? pd.Columns[keyValue.Key].PropertyInfo : null; AddParam(cmd, keyValue.Value, pi); } @@ -1440,7 +1474,7 @@ public virtual async Task UpdateAsync(string tableName, string primaryKeyNa } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The name of the table to update /// The name of the primary key column of the table @@ -1452,7 +1486,7 @@ public Task UpdateAsync(string tableName, string primaryKeyName, object poc } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The name of the table to update /// The name of the primary key column of the table @@ -1465,7 +1499,7 @@ public Task UpdateAsync(string tableName, string primaryKeyName, object poc } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO object that specifies the column values to be updated /// The column names of the columns to be updated, or null for all @@ -1476,7 +1510,7 @@ public Task UpdateAsync(object poco, IEnumerable columns) } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO object that specifies the column values to be updated /// The number of affected rows @@ -1486,7 +1520,7 @@ public Task UpdateAsync(object poco) } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO object that specifies the column values to be updated /// The primary key of the record to be updated @@ -1497,7 +1531,7 @@ public Task UpdateAsync(object poco, object primaryKeyValue) } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO object that specifies the column values to be updated /// The primary key of the record to be updated @@ -1510,7 +1544,7 @@ public Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO class who's attributes specify the name of the table to update /// The SQL update and condition clause (ie: everything after "UPDATE tablename" @@ -1523,22 +1557,26 @@ public Task UpdateAsync(string sql, params object[] args) } /// - /// Performs an SQL update + /// Performs an SQL update /// /// The POCO class who's attributes specify the name of the table to update - /// An SQL builder object representing the SQL update and condition clause (ie: everything after "UPDATE tablename" + /// + /// An SQL builder object representing the SQL update and condition clause (ie: everything after "UPDATE + /// tablename" + /// /// The number of affected rows public Task UpdateAsync(Sql sql) { var pd = PocoData.ForType(typeof(T)); return ExecuteAsync(new Sql(string.Format("UPDATE {0}", _dbType.EscapeTableName(pd.TableInfo.TableName))).Append(sql)); } + #endregion #region operation: Delete /// - /// Performs and SQL Delete + /// Performs and SQL Delete /// /// The name of the table to delete from /// The name of the primary key column @@ -1550,22 +1588,31 @@ public Task DeleteAsync(string tableName, string primaryKeyName, object poc } /// - /// Performs and SQL Delete + /// Performs and SQL Delete /// /// The name of the table to delete from /// The name of the primary key column - /// The POCO object whose primary key value will be used to delete the row (or null to use the supplied primary key value) - /// The value of the primary key identifing the record to be deleted (or null, or get this value from the POCO instance) + /// + /// The POCO object whose primary key value will be used to delete the row (or null to use the supplied + /// primary key value) + /// + /// + /// The value of the primary key identifing the record to be deleted (or null, or get this + /// value from the POCO instance) + /// /// The number of rows affected public virtual Task DeleteAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue) { var primaryKeyValuePairs = GetPrimaryKeyValues(primaryKeyName, primaryKeyValue); - if (poco != null) { + if (poco != null) + { // If primary key value not specified, pick it up from the object var pd = PocoData.ForObject(poco, primaryKeyName); - foreach (var i in pd.Columns) { - if (primaryKeyValue == null && primaryKeyValuePairs.ContainsKey(i.Key)) { + foreach (var i in pd.Columns) + { + if (primaryKeyValue == null && primaryKeyValuePairs.ContainsKey(i.Key)) + { primaryKeyValuePairs[i.Key] = i.Value.GetValue(poco); } } @@ -1578,7 +1625,7 @@ public virtual Task DeleteAsync(string tableName, string primaryKeyName, ob } /// - /// Performs an SQL Delete + /// Performs an SQL Delete /// /// The POCO object specifying the table name and primary key value of the row to be deleted /// The number of rows affected @@ -1589,7 +1636,7 @@ public Task DeleteAsync(object poco) } /// - /// Performs an SQL Delete + /// Performs an SQL Delete /// /// The POCO class whose attributes identify the table and primary key to be used in the delete /// The value of the primary key of the row to delete @@ -1603,7 +1650,7 @@ public Task DeleteAsync(object pocoOrPrimaryKey) } /// - /// Performs an SQL Delete + /// Performs an SQL Delete /// /// The POCO class who's attributes specify the name of the table to delete from /// The SQL condition clause identifying the row to delete (ie: everything after "DELETE FROM tablename" @@ -1616,22 +1663,27 @@ public Task DeleteAsync(string sql, params object[] args) } /// - /// Performs an SQL Delete + /// Performs an SQL Delete /// /// The POCO class who's attributes specify the name of the table to delete from - /// An SQL builder object representing the SQL condition clause identifying the row to delete (ie: everything after "UPDATE tablename" + /// + /// An SQL builder object representing the SQL condition clause identifying the row to delete (ie: + /// everything after "UPDATE tablename" + /// /// The number of affected rows public Task DeleteAsync(Sql sql) { var pd = PocoData.ForType(typeof(T)); - return ExecuteAsync(new Sql(string.Format("DELETE FROM {0}", _dbType.EscapeTableName(pd.TableInfo.TableName))).Append(sql)); + return + ExecuteAsync(new Sql(string.Format("DELETE FROM {0}", _dbType.EscapeTableName(pd.TableInfo.TableName))).Append(sql)); } + #endregion #region operation: IsNew /// - /// Check if a poco represents a new row + /// Check if a poco represents a new row /// /// The name of the primary key column /// The object instance whose "newness" is to be tested @@ -1647,7 +1699,7 @@ public bool IsNew(string primaryKeyName, object poco) pk = pc.GetValue(poco); } #if !PETAPOCO_NO_DYNAMIC - else if (poco.GetType() == typeof(System.Dynamic.ExpandoObject)) + else if (poco.GetType() == typeof(ExpandoObject)) { return true; } @@ -1656,7 +1708,8 @@ public bool IsNew(string primaryKeyName, object poco) { var pi = poco.GetType().GetProperty(primaryKeyName); if (pi == null) - throw new ArgumentException(string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName)); + throw new ArgumentException( + string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName)); pk = pi.GetValue(poco, null); } @@ -1670,26 +1723,23 @@ public bool IsNew(string primaryKeyName, object poco) // Common primary key types if (type == typeof(long)) return (long)pk == default(long); - else if (type == typeof(ulong)) + if (type == typeof(ulong)) return (ulong)pk == default(ulong); - else if (type == typeof(int)) + if (type == typeof(int)) return (int)pk == default(int); - else if (type == typeof(uint)) + if (type == typeof(uint)) return (uint)pk == default(uint); - else if (type == typeof(Guid)) + if (type == typeof(Guid)) return (Guid)pk == default(Guid); // Create a default instance and compare return pk == Activator.CreateInstance(pk.GetType()); } - else - { - return pk == null; - } + return pk == null; } /// - /// Check if a poco represents a new row + /// Check if a poco represents a new row /// /// The object instance whose "newness" is to be tested /// True if the POCO represents a record already in the database @@ -1698,14 +1748,17 @@ public bool IsNew(object poco) { var pd = PocoData.ForType(poco.GetType()); if (!pd.TableInfo.AutoIncrement) - throw new InvalidOperationException("IsNew() and Save() are only supported on tables with auto-increment/identity primary key columns"); + throw new InvalidOperationException( + "IsNew() and Save() are only supported on tables with auto-increment/identity primary key columns"); return IsNew(pd.TableInfo.PrimaryKey, poco); } + #endregion #region operation: Save + /// - /// Saves a POCO by either performing either an SQL Insert or SQL Update + /// Saves a POCO by either performing either an SQL Insert or SQL Update /// /// The name of the table to be updated /// The name of the primary key column @@ -1716,14 +1769,11 @@ public Task SaveAsync(string tableName, string primaryKeyName, object poco) { return InsertAsync(tableName, primaryKeyName, true, poco); } - else - { - return UpdateAsync(tableName, primaryKeyName, poco); - } + return UpdateAsync(tableName, primaryKeyName, poco); } /// - /// Saves a POCO by either performing either an SQL Insert or SQL Update + /// Saves a POCO by either performing either an SQL Insert or SQL Update /// /// The POCO object to be saved public Task SaveAsync(object poco) @@ -1731,11 +1781,13 @@ public Task SaveAsync(object poco) var pd = PocoData.ForType(poco.GetType()); return SaveAsync(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); } + #endregion #region operation: Multi-Poco Query/Fetch + /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type @@ -1744,10 +1796,13 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2) }, cb, sql, args); } + public Task> FetchAsync(Func cb, string sql, params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2) }, cb, sql, args); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type @@ -1757,10 +1812,13 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, args); } + public Task> FetchAsync(Func cb, string sql, params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, args); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type @@ -1771,10 +1829,14 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, args); } + public Task> FetchAsync(Func cb, string sql, + params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, args); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type @@ -1783,10 +1845,13 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(Func cb, string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2) }, cb, sql, args, action); } + public Task QueryAsync(Func cb, string sql, object[] args, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2) }, cb, sql, args, action); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type @@ -1796,10 +1861,13 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(Func cb, string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, args, action); } + public Task QueryAsync(Func cb, string sql, object[] args, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, args, action); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type @@ -1810,10 +1878,14 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(Func cb, string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, args, action); } + public Task QueryAsync(Func cb, string sql, object[] args, + Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, args, action); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type @@ -1821,10 +1893,13 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, Sql sql) { return FetchAsync(new[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments); } + public Task> FetchAsync(Func cb, Sql sql) + { + return FetchAsync(new[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type @@ -1833,10 +1908,13 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, Sql sql) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments); } + public Task> FetchAsync(Func cb, Sql sql) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type @@ -1846,10 +1924,13 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Func cb, Sql sql) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments); } + public Task> FetchAsync(Func cb, Sql sql) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type @@ -1857,10 +1938,13 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Func cb, Sql sql, Action action) { return QueryAsync(new[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments, action); } + public Task QueryAsync(Func cb, Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments, action); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type @@ -1869,10 +1953,13 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Func cb, Sql sql, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments, action); } + public Task QueryAsync(Func cb, Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments, action); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type @@ -1882,20 +1969,26 @@ public Task SaveAsync(object poco) /// A callback function to connect the POCO instances, or null to automatically guess the relationships /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Func cb, Sql sql, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments, action); } + public Task QueryAsync(Func cb, Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments, action); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2) }, null, sql, args); } + public Task> FetchAsync(string sql, params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2) }, null, sql, args); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type @@ -1903,10 +1996,13 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); } + public Task> FetchAsync(string sql, params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type @@ -1915,20 +2011,26 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public Task> FetchAsync(string sql, params object[] args) { return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); } + public Task> FetchAsync(string sql, params object[] args) + { + return FetchAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2) }, null, sql, args, action); } + public Task QueryAsync(string sql, object[] args, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2) }, null, sql, args, action); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type @@ -1936,10 +2038,13 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args, action); } + public Task QueryAsync(string sql, object[] args, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args, action); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type @@ -1948,29 +2053,38 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// Callback to process each result - public Task QueryAsync(string sql, object[] args, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args, action); } + public Task QueryAsync(string sql, object[] args, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args, action); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Sql sql) { return FetchAsync(sql.SQL, sql.Arguments); } + public Task> FetchAsync(Sql sql) + { + return FetchAsync(sql.SQL, sql.Arguments); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type /// The third POCO type /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Sql sql) { return FetchAsync(sql.SQL, sql.Arguments); } + public Task> FetchAsync(Sql sql) + { + return FetchAsync(sql.SQL, sql.Arguments); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The first POCO type /// The second POCO type @@ -1978,29 +2092,38 @@ public Task SaveAsync(object poco) /// The fourth POCO type /// An SQL builder object representing the query and it's arguments /// A collection of POCO's as a List - public Task> FetchAsync(Sql sql) { return FetchAsync(sql.SQL, sql.Arguments); } + public Task> FetchAsync(Sql sql) + { + return FetchAsync(sql.SQL, sql.Arguments); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Sql sql, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments, action); } + public Task QueryAsync(Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments, action); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type /// The third POCO type /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Sql sql, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments, action); } + public Task QueryAsync(Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments, action); + } /// - /// Perform a multi-poco query + /// Perform a multi-poco query /// /// The first POCO type /// The second POCO type @@ -2008,10 +2131,13 @@ public Task SaveAsync(object poco) /// The fourth POCO type /// An SQL builder object representing the query and it's arguments /// Callback to process each result - public Task QueryAsync(Sql sql, Action action) { return QueryAsync(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments, action); } + public Task QueryAsync(Sql sql, Action action) + { + return QueryAsync(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments, action); + } /// - /// Perform a multi-poco fetch + /// Perform a multi-poco fetch /// /// The type of objects to pass to the action /// An array of Types representing the POCO types of the returned result set. @@ -2019,14 +2145,15 @@ public Task SaveAsync(object poco) /// The SQL query to be executed /// Arguments to any embedded parameters in the SQL /// A collection of POCO's as a List - public async Task> FetchAsync(Type[] types, object cb, string sql, params object[] args) { + public async Task> FetchAsync(Type[] types, object cb, string sql, params object[] args) + { var list = new List(); await QueryAsync(types, cb, sql, args, list.Add); return list; } - + /// - /// Performs a multi-poco query + /// Performs a multi-poco query /// /// The type of objects to pass to the action /// An array of Types representing the POCO types of the returned result set. @@ -2056,7 +2183,7 @@ public virtual async Task QueryAsync(Type[] types, object cb, string sql, var factory = MultiPocoFactory.GetFactory(types, _sharedConnection.ConnectionString, sql, r); if (cb == null) cb = MultiPocoFactory.GetAutoMapper(types.ToArray()); - bool bNeedTerminator = false; + var bNeedTerminator = false; using (r) { while (true) @@ -2100,39 +2227,40 @@ public virtual async Task QueryAsync(Type[] types, object cb, string sql, #region Last Command /// - /// Retrieves the SQL of the last executed statement + /// Retrieves the SQL of the last executed statement /// - public string LastSQL { get { return _lastSql; } } + public string LastSQL { get; private set; } /// - /// Retrieves the arguments to the last execute statement + /// Retrieves the arguments to the last execute statement /// - public object[] LastArgs { get { return _lastArgs; } } - + public object[] LastArgs { get; private set; } /// - /// Returns a formatted string describing the last executed SQL statement and it's argument values + /// Returns a formatted string describing the last executed SQL statement and it's argument values /// public string LastCommand { - get { return FormatCommand(_lastSql, _lastArgs); } + get { return FormatCommand(LastSQL, LastArgs); } } + #endregion #region FormatCommand /// - /// Formats the contents of a DB command for display + /// Formats the contents of a DB command for display /// /// /// public string FormatCommand(IDbCommand cmd) { - return FormatCommand(cmd.CommandText, (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray()); + return FormatCommand(cmd.CommandText, + (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray()); } /// - /// Formats an SQL query and it's arguments for display + /// Formats an SQL query and it's arguments for display /// /// /// @@ -2146,7 +2274,7 @@ public string FormatCommand(string sql, object[] args) if (args != null && args.Length > 0) { sb.Append("\n"); - for (int i = 0; i < args.Length; i++) + for (var i = 0; i < args.Length; i++) { sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, args[i].GetType().Name, args[i]); } @@ -2154,6 +2282,7 @@ public string FormatCommand(string sql, object[] args) } return sb.ToString(); } + #endregion #region Public Properties @@ -2166,59 +2295,46 @@ public static IMapper Mapper } */ /// - /// When set to true, PetaPoco will automatically create the "SELECT columns" part of any query that looks like it needs it + /// When set to true, PetaPoco will automatically create the "SELECT columns" part of any query that looks like it + /// needs it /// - public bool EnableAutoSelect - { - get; - set; - } - + public bool EnableAutoSelect { get; set; } + /// - /// When set to true, parameters can be named ?myparam and populated from properties of the passed in argument values. + /// When set to true, parameters can be named ?myparam and populated from properties of the passed in argument values. /// - public bool EnableNamedParams - { - get; - set; - } + public bool EnableNamedParams { get; set; } /// - /// Sets the timeout value for all SQL statements. + /// Sets the timeout value for all SQL statements. /// - public int CommandTimeout - { - get; - set; - } + public int CommandTimeout { get; set; } /// - /// Sets the timeout value for the next (and only next) SQL statement + /// Sets the timeout value for the next (and only next) SQL statement /// - public int OneTimeCommandTimeout - { - get; - set; - } + public int OneTimeCommandTimeout { get; set; } + #endregion #region Member Fields + // Member variables internal DatabaseType _dbType; - string _connectionString; - string _providerName; - DbProviderFactory _factory; - DbConnection _sharedConnection; - DbTransaction _transaction; - int _sharedConnectionDepth; - int _transactionDepth; - bool _transactionCancelled; - string _lastSql; - object[] _lastArgs; - string _paramPrefix; + private readonly string _connectionString; + private readonly string _providerName; + private DbProviderFactory _factory; + private DbConnection _sharedConnection; + private DbTransaction _transaction; + private int _sharedConnectionDepth; + private int _transactionDepth; + private bool _transactionCancelled; + private string _paramPrefix; + #endregion #region Internal operations + internal async Task ExecuteNonQueryHelperAsync(DbCommand cmd) { DoPreExecute(cmd); @@ -2229,7 +2345,7 @@ internal async Task ExecuteNonQueryHelperAsync(DbCommand cmd) internal async Task ExecuteScalarHelperAsync(DbCommand cmd) { DoPreExecute(cmd); - object r = await cmd.ExecuteScalarAsync(); + var r = await cmd.ExecuteScalarAsync(); OnExecutedCommand(cmd); return r; } @@ -2251,39 +2367,62 @@ internal void DoPreExecute(DbCommand cmd) OnExecutingCommand(cmd); // Save it - _lastSql = cmd.CommandText; - _lastArgs = (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray(); + LastSQL = cmd.CommandText; + LastArgs = (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray(); } #endregion #region Composite primary key support - private Dictionary GetPrimaryKeyValues(string primaryKeyName, object primaryKeyValue) { + + private Dictionary GetPrimaryKeyValues(string primaryKeyName, object primaryKeyValue) + { Dictionary primaryKeyValues; - var multiplePrimaryKeysNames = primaryKeyName.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); - if (primaryKeyValue != null) { - if (multiplePrimaryKeysNames.Length == 1) { - primaryKeyValues = new Dictionary(StringComparer.OrdinalIgnoreCase) { { primaryKeyName, primaryKeyValue } }; + var multiplePrimaryKeysNames = + primaryKeyName.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); + if (primaryKeyValue != null) + { + if (multiplePrimaryKeysNames.Length == 1) + { + primaryKeyValues = new Dictionary(ColumnComparer) + { + {primaryKeyName, primaryKeyValue} + }; } - else { + else + { var dict = primaryKeyValue as Dictionary; - primaryKeyValues = dict ?? multiplePrimaryKeysNames.ToDictionary(x => x, x => primaryKeyValue.GetType().GetProperties().Single(y => string.Equals(x, y.Name, StringComparison.OrdinalIgnoreCase)).GetValue(primaryKeyValue, null), StringComparer.OrdinalIgnoreCase); + primaryKeyValues = dict ?? + multiplePrimaryKeysNames.ToDictionary(x => x, + x => + primaryKeyValue.GetType() + .GetProperties() + .Single(y => string.Equals(x, y.Name, StringComparison.OrdinalIgnoreCase)) + .GetValue(primaryKeyValue, null), ColumnComparer); } } - else { - primaryKeyValues = multiplePrimaryKeysNames.ToDictionary(x => x, x => (object)null, StringComparer.OrdinalIgnoreCase); + else + { + primaryKeyValues = multiplePrimaryKeysNames.ToDictionary(x => x, x => (object)null, + StringComparer.OrdinalIgnoreCase); } return primaryKeyValues; } - private string BuildPrimaryKeySql(Dictionary primaryKeyValuePair, ref int index) { + private string BuildPrimaryKeySql(Dictionary primaryKeyValuePair, ref int index) + { var tempIndex = index; index += primaryKeyValuePair.Count; - return string.Join(" AND ", primaryKeyValuePair.Select((x, i) => x.Value == null || x.Value == DBNull.Value ? string.Format("{0} IS NULL", _dbType.EscapeSqlIdentifier(x.Key)) : string.Format("{0} = @{1}", _dbType.EscapeSqlIdentifier(x.Key), tempIndex + i)).ToArray()); + return string.Join(" AND ", + primaryKeyValuePair.Select( + (x, i) => + x.Value == null || x.Value == DBNull.Value + ? string.Format("{0} IS NULL", _dbType.EscapeSqlIdentifier(x.Key)) + : string.Format("{0} = @{1}", _dbType.EscapeSqlIdentifier(x.Key), tempIndex + i)).ToArray()); } + #endregion } - -} +} \ No newline at end of file diff --git a/AsyncPoco/IDatabase.cs b/AsyncPoco/IDatabase.cs new file mode 100644 index 00000000..f0a19b9d --- /dev/null +++ b/AsyncPoco/IDatabase.cs @@ -0,0 +1,1042 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Threading.Tasks; + +namespace AsyncPoco +{ + public interface IDatabase : IDisposable + { + /// + /// When set to true the first opened connection is kept alive until this object is disposed + /// + bool KeepConnectionAlive { get; set; } + + /// + /// Provides access to the currently open shared connection (or null if none) + /// + IDbConnection Connection { get; } + + /// + /// Retrieves the SQL of the last executed statement + /// + string LastSQL { get; } + + /// + /// Retrieves the arguments to the last execute statement + /// + object[] LastArgs { get; } + + /// + /// Returns a formatted string describing the last executed SQL statement and it's argument values + /// + string LastCommand { get; } + + /// + /// When set to true, PetaPoco will automatically create the "SELECT columns" part of any query that looks like it needs it + /// + bool EnableAutoSelect { get; set; } + + /// + /// When set to true, parameters can be named ?myparam and populated from properties of the passed in argument values. + /// + bool EnableNamedParams { get; set; } + + /// + /// Sets the timeout value for all SQL statements. + /// + int CommandTimeout { get; set; } + + /// + /// Sets the timeout value for the next (and only next) SQL statement + /// + int OneTimeCommandTimeout { get; set; } + + /// + /// Open a connection that will be used for all subsequent queries. + /// + /// + /// Calls to Open/CloseSharedConnection are reference counted and should be balanced + /// + Task OpenSharedConnectionAsync(); + + /// + /// Releases the shared connection + /// + void CloseSharedConnection(); + + /// + /// Starts or continues a transaction. + /// + /// An ITransaction reference that must be Completed or disposed + /// + /// This method makes management of calls to Begin/End/CompleteTransaction easier. + /// + /// The usage pattern for this should be: + /// + /// using (var tx = db.GetTransaction()) + /// { + /// // Do stuff + /// db.Update(...); + /// + /// // Mark the transaction as complete + /// tx.Complete(); + /// } + /// + /// Transactions can be nested but they must all be completed otherwise the entire + /// transaction is aborted. + /// + Task GetTransactionAsync(); + + /// + /// Called when a transaction starts. Overridden by the T4 template generated database + /// classes to ensure the same DB instance is used throughout the transaction. + /// + void OnBeginTransaction(); + + /// + /// Called when a transaction ends. + /// + void OnEndTransaction(); + + /// + /// Starts a transaction scope, see GetTransaction() for recommended usage + /// + Task BeginTransactionAsync(); + + /// + /// Aborts the entire outer most transaction scope + /// + /// + /// Called automatically by Transaction.Dispose() + /// if the transaction wasn't completed. + /// + void AbortTransaction(); + + /// + /// Marks the current transaction scope as complete. + /// + void CompleteTransaction(); + + DbCommand CreateCommand(DbConnection connection, string sql, params object[] args); + + /// + /// Called if an exception occurs during processing of a DB operation. Override to provide custom logging/handling. + /// + /// The exception instance + /// True to re-throw the exception, false to suppress it + bool OnException(Exception x); + + /// + /// Called when DB connection opened + /// + /// The newly opened DbConnection + /// The same or a replacement DbConnection + /// + /// Override this method to provide custom logging of opening connection, or + /// to provide a proxy DbConnection. + /// + DbConnection OnConnectionOpened(DbConnection conn); + + /// + /// Called when DB connection closed + /// + /// The soon to be closed IDBConnection + void OnConnectionClosing(IDbConnection conn); + + /// + /// Called just before an DB command is executed + /// + /// The command to be executed + /// + /// Override this method to provide custom logging of commands and/or + /// modification of the IDbCommand before it's executed + /// + void OnExecutingCommand(IDbCommand cmd); + + /// + /// Called on completion of command execution + /// + /// The IDbCommand that finished executing + void OnExecutedCommand(IDbCommand cmd); + + /// + /// Executes a non-query command + /// + /// The SQL statement to execute + /// Arguments to any embedded parameters in the SQL + /// The number of rows affected + Task ExecuteAsync(string sql, params object[] args); + + /// + /// Executes a non-query command + /// + /// An SQL builder object representing the query and it's arguments + /// The number of rows affected + Task ExecuteAsync(Sql sql); + + /// + /// Executes a query and return the first column of the first row in the result set. + /// + /// The type that the result value should be cast to + /// The SQL query to execute + /// Arguments to any embedded parameters in the SQL + /// The scalar value cast to T + Task ExecuteScalarAsync(string sql, params object[] args); + + /// + /// Executes a query and return the first column of the first row in the result set. + /// + /// The type that the result value should be cast to + /// An SQL builder object representing the query and it's arguments + /// The scalar value cast to T + Task ExecuteScalarAsync(Sql sql); + + /// + /// Runs a query and returns the result set as a typed list + /// + /// The Type representing a row in the result set + /// The SQL query to execute + /// Arguments to any embedded parameters in the SQL + /// A List holding the results of the query + Task> FetchAsync(string sql, params object[] args); + + /// + /// Runs a query and returns the result set as a typed list + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the query and it's arguments + /// A List holding the results of the query + Task> FetchAsync(Sql sql); + + /// + /// Retrieves a page of records and the total number of available records + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// The SQL to retrieve the total number of records + /// Arguments to any embedded parameters in the sqlCount statement + /// The SQL To retrieve a single page of results + /// Arguments to any embedded parameters in the sqlPage statement + /// A Page of results + /// + /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. + /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. + /// + Task> PageAsync(long page, long itemsPerPage, string sqlCount, object[] countArgs, string sqlPage, object[] pageArgs); + + /// + /// Retrieves a page of records and the total number of available records + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// The base SQL query + /// Arguments to any embedded parameters in the SQL statement + /// A Page of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. It will also execute a second query to retrieve the + /// total number of records in the result set. + /// + Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args); + + /// + /// Retrieves a page of records and the total number of available records + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// An SQL builder object representing the base SQL query and it's arguments + /// A Page of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. It will also execute a second query to retrieve the + /// total number of records in the result set. + /// + Task> PageAsync(long page, long itemsPerPage, Sql sql); + + /// + /// Retrieves a page of records and the total number of available records + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// An SQL builder object representing the SQL to retrieve the total number of records + /// An SQL builder object representing the SQL to retrieve a single page of results + /// A Page of results + /// + /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. + /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. + /// + Task> PageAsync(long page, long itemsPerPage, Sql sqlCount, Sql sqlPage); + + /// + /// Retrieves a page of records (without the total count) + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// The base SQL query + /// Arguments to any embedded parameters in the SQL statement + /// A List of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. + /// + Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args); + + /// + /// Retrieves a page of records (without the total count) + /// + /// The Type representing a row in the result set + /// The 1 based page number to retrieve + /// The number of records per page + /// An SQL builder object representing the base SQL query and it's arguments + /// A List of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified page. + /// + Task> FetchAsync(long page, long itemsPerPage, Sql sql); + + /// + /// Retrieves a range of records from result set + /// + /// The Type representing a row in the result set + /// The number of rows at the start of the result set to skip over + /// The number of rows to retrieve + /// The base SQL query + /// Arguments to any embedded parameters in the SQL statement + /// A List of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified range. + /// + Task> SkipTakeAsync(long skip, long take, string sql, params object[] args); + + /// + /// Retrieves a range of records from result set + /// + /// The Type representing a row in the result set + /// The number of rows at the start of the result set to skip over + /// The number of rows to retrieve + /// An SQL builder object representing the base SQL query and it's arguments + /// A List of results + /// + /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the + /// records for the specified range. + /// + Task> SkipTakeAsync(long skip, long take, Sql sql); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// The SQL query + /// Callback to process each result + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(string sql, Action action); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// The SQL query + /// Callback to process each result, return false to stop iterating + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(string sql, Func func); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// Callback to process each result + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(string sql, object[] args, Action action); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// Callback to process each result, return false to stop iterating + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(string sql, object[] args, Func func); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the base SQL query and it's arguments + /// Callback to process each result + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(Sql sql, Action action); + + /// + /// Runs an SQL query, asynchronously passing each result to a callback + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the base SQL query and it's arguments + /// Callback to process each result, return false to stop iterating + /// + /// For some DB providers, care should be taken to not start a new Query before finishing with + /// and disposing the previous one. In cases where this is an issue, consider using Fetch which + /// returns the results as a List. + /// + Task QueryAsync(Sql sql, Func func); + + /// + /// Checks for the existance of a row matching the specified condition + /// + /// The Type representing the table being queried + /// The SQL expression to be tested for (ie: the WHERE expression) + /// Arguments to any embedded parameters in the SQL statement + /// True if a record matching the condition is found. + Task ExistsAsync(string sqlCondition, params object[] args); + + /// + /// Checks for the existance of a row with the specified primary key value. + /// + /// The Type representing the table being queried + /// The primary key value to look for + /// True if a record with the specified primary key value exists. + Task ExistsAsync(object primaryKey); + + /// + /// Returns the record with the specified primary key value + /// + /// The Type representing a row in the result set + /// The primary key value of the record to fetch + /// The single record matching the specified primary key value + /// + /// Throws an exception if there are zero or more than one record with the specified primary key value. + /// + Task SingleAsync(object primaryKey); + + /// + /// Returns the record with the specified primary key value, or the default value if not found + /// + /// The Type representing a row in the result set + /// The primary key value of the record to fetch + /// The single record matching the specified primary key value + /// + /// If there are no records with the specified primary key value, default(T) (typically null) is returned. + /// + Task SingleOrDefaultAsync(object primaryKey); + + /// + /// Runs a query that should always return a single row. + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// The single record matching the specified primary key value + /// + /// Throws an exception if there are zero or more than one matching record + /// + Task SingleAsync(string sql, params object[] args); + + /// + /// Runs a query that should always return either a single row, or no rows + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// The single record matching the specified primary key value, or default(T) if no matching rows + Task SingleOrDefaultAsync(string sql, params object[] args); + + /// + /// Runs a query that should always return at least one return + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// The first record in the result set + Task FirstAsync(string sql, params object[] args); + + /// + /// Runs a query and returns the first record, or the default value if no matching records + /// + /// The Type representing a row in the result set + /// The SQL query + /// Arguments to any embedded parameters in the SQL statement + /// The first record in the result set, or default(T) if no matching rows + Task FirstOrDefaultAsync(string sql, params object[] args); + + /// + /// Runs a query that should always return a single row. + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the query and it's arguments + /// The single record matching the specified primary key value + /// + /// Throws an exception if there are zero or more than one matching record + /// + Task SingleAsync(Sql sql); + + /// + /// Runs a query that should always return either a single row, or no rows + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the query and it's arguments + /// The single record matching the specified primary key value, or default(T) if no matching rows + Task SingleOrDefaultAsync(Sql sql); + + /// + /// Runs a query that should always return at least one return + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the query and it's arguments + /// The first record in the result set + Task FirstAsync(Sql sql); + + /// + /// Runs a query and returns the first record, or the default value if no matching records + /// + /// The Type representing a row in the result set + /// An SQL builder object representing the query and it's arguments + /// The first record in the result set, or default(T) if no matching rows + Task FirstOrDefaultAsync(Sql sql); + + /// + /// Performs an SQL Insert + /// + /// The name of the table to insert into + /// The name of the primary key column of the table + /// The POCO object that specifies the column values to be inserted + /// The auto allocated primary key of the new record + Task InsertAsync(string tableName, string primaryKeyName, object poco); + + /// + /// Performs an SQL Insert + /// + /// The name of the table to insert into + /// The name of the primary key column of the table + /// True if the primary key is automatically allocated by the DB + /// The POCO object that specifies the column values to be inserted + /// The auto allocated primary key of the new record, or null for non-auto-increment tables + /// Inserts a poco into a table. If the poco has a property with the same name + /// as the primary key the id of the new record is assigned to it. Either way, + /// the new id is returned. + Task InsertAsync(string tableName, string primaryKeyName, bool autoIncrement, object poco); + + /// + /// Performs an SQL Insert + /// + /// The POCO object that specifies the column values to be inserted + /// The auto allocated primary key of the new record, or null for non-auto-increment tables + /// The name of the table, it's primary key and whether it's an auto-allocated primary key are retrieved + /// from the POCO's attributes + Task InsertAsync(object poco); + + /// + /// Performs an SQL update + /// + /// The name of the table to update + /// The name of the primary key column of the table + /// The POCO object that specifies the column values to be updated + /// The primary key of the record to be updated + /// The number of affected records + Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue); + + /// + /// Performs an SQL update + /// + /// The name of the table to update + /// The name of the primary key column of the table + /// The POCO object that specifies the column values to be updated + /// The primary key of the record to be updated + /// The column names of the columns to be updated, or null for all + /// The number of affected rows + Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns); + + /// + /// Performs an SQL update + /// + /// The name of the table to update + /// The name of the primary key column of the table + /// The POCO object that specifies the column values to be updated + /// The number of affected rows + Task UpdateAsync(string tableName, string primaryKeyName, object poco); + + /// + /// Performs an SQL update + /// + /// The name of the table to update + /// The name of the primary key column of the table + /// The POCO object that specifies the column values to be updated + /// The column names of the columns to be updated, or null for all + /// The number of affected rows + Task UpdateAsync(string tableName, string primaryKeyName, object poco, IEnumerable columns); + + /// + /// Performs an SQL update + /// + /// The POCO object that specifies the column values to be updated + /// The column names of the columns to be updated, or null for all + /// The number of affected rows + Task UpdateAsync(object poco, IEnumerable columns); + + /// + /// Performs an SQL update + /// + /// The POCO object that specifies the column values to be updated + /// The number of affected rows + Task UpdateAsync(object poco); + + /// + /// Performs an SQL update + /// + /// The POCO object that specifies the column values to be updated + /// The primary key of the record to be updated + /// The number of affected rows + Task UpdateAsync(object poco, object primaryKeyValue); + + /// + /// Performs an SQL update + /// + /// The POCO object that specifies the column values to be updated + /// The primary key of the record to be updated + /// The column names of the columns to be updated, or null for all + /// The number of affected rows + Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable columns); + + /// + /// Performs an SQL update + /// + /// The POCO class who's attributes specify the name of the table to update + /// The SQL update and condition clause (ie: everything after "UPDATE tablename" + /// Arguments to any embedded parameters in the SQL + /// The number of affected rows + Task UpdateAsync(string sql, params object[] args); + + /// + /// Performs an SQL update + /// + /// The POCO class who's attributes specify the name of the table to update + /// An SQL builder object representing the SQL update and condition clause (ie: everything after "UPDATE tablename" + /// The number of affected rows + Task UpdateAsync(Sql sql); + + /// + /// Performs and SQL Delete + /// + /// The name of the table to delete from + /// The name of the primary key column + /// The POCO object whose primary key value will be used to delete the row + /// The number of rows affected + Task DeleteAsync(string tableName, string primaryKeyName, object poco); + + /// + /// Performs and SQL Delete + /// + /// The name of the table to delete from + /// The name of the primary key column + /// The POCO object whose primary key value will be used to delete the row (or null to use the supplied primary key value) + /// The value of the primary key identifing the record to be deleted (or null, or get this value from the POCO instance) + /// The number of rows affected + Task DeleteAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue); + + /// + /// Performs an SQL Delete + /// + /// The POCO object specifying the table name and primary key value of the row to be deleted + /// The number of rows affected + Task DeleteAsync(object poco); + + /// + /// Performs an SQL Delete + /// + /// The POCO class whose attributes identify the table and primary key to be used in the delete + /// The value of the primary key of the row to delete + /// + Task DeleteAsync(object pocoOrPrimaryKey); + + /// + /// Performs an SQL Delete + /// + /// The POCO class who's attributes specify the name of the table to delete from + /// The SQL condition clause identifying the row to delete (ie: everything after "DELETE FROM tablename" + /// Arguments to any embedded parameters in the SQL + /// The number of affected rows + Task DeleteAsync(string sql, params object[] args); + + /// + /// Performs an SQL Delete + /// + /// The POCO class who's attributes specify the name of the table to delete from + /// An SQL builder object representing the SQL condition clause identifying the row to delete (ie: everything after "UPDATE tablename" + /// The number of affected rows + Task DeleteAsync(Sql sql); + + /// + /// Check if a poco represents a new row + /// + /// The name of the primary key column + /// The object instance whose "newness" is to be tested + /// True if the POCO represents a record already in the database + /// This method simply tests if the POCO's primary key column property has been set to something non-zero. + bool IsNew(string primaryKeyName, object poco); + + /// + /// Check if a poco represents a new row + /// + /// The object instance whose "newness" is to be tested + /// True if the POCO represents a record already in the database + /// This method simply tests if the POCO's primary key column property has been set to something non-zero. + bool IsNew(object poco); + + /// + /// Saves a POCO by either performing either an SQL Insert or SQL Update + /// + /// The name of the table to be updated + /// The name of the primary key column + /// The POCO object to be saved + Task SaveAsync(string tableName, string primaryKeyName, object poco); + + /// + /// Saves a POCO by either performing either an SQL Insert or SQL Update + /// + /// The POCO object to be saved + Task SaveAsync(object poco); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + Task> FetchAsync(Func cb, string sql, params object[] args); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + Task> FetchAsync(Func cb, string sql, params object[] args); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + Task> FetchAsync(Func cb, string sql, params object[] args); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + Task QueryAsync(Func cb, string sql, object[] args, Action action); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + Task QueryAsync(Func cb, string sql, object[] args, Action action); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + Task QueryAsync(Func cb, string sql, object[] args, Action action); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + Task> FetchAsync(Func cb, Sql sql); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + Task> FetchAsync(Func cb, Sql sql); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The returned list POCO type + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + Task> FetchAsync(Func cb, Sql sql); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + Task QueryAsync(Func cb, Sql sql, Action action); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + Task QueryAsync(Func cb, Sql sql, Action action); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The type of objects passed to the action callback + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + Task QueryAsync(Func cb, Sql sql, Action action); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + Task> FetchAsync(string sql, params object[] args); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + Task> FetchAsync(string sql, params object[] args); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + Task> FetchAsync(string sql, params object[] args); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + Task QueryAsync(string sql, object[] args, Action action); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + Task QueryAsync(string sql, object[] args, Action action); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + Task QueryAsync(string sql, object[] args, Action action); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + Task> FetchAsync(Sql sql); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + Task> FetchAsync(Sql sql); + + /// + /// Perform a multi-poco fetch + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// An SQL builder object representing the query and it's arguments + /// A collection of POCO's as a List + Task> FetchAsync(Sql sql); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + Task QueryAsync(Sql sql, Action action); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + Task QueryAsync(Sql sql, Action action); + + /// + /// Perform a multi-poco query + /// + /// The first POCO type + /// The second POCO type + /// The third POCO type + /// The fourth POCO type + /// An SQL builder object representing the query and it's arguments + /// Callback to process each result + Task QueryAsync(Sql sql, Action action); + + /// + /// Perform a multi-poco fetch + /// + /// The type of objects to pass to the action + /// An array of Types representing the POCO types of the returned result set. + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// A collection of POCO's as a List + Task> FetchAsync(Type[] types, object cb, string sql, params object[] args); + + /// + /// Performs a multi-poco query + /// + /// The type of objects to pass to the action + /// An array of Types representing the POCO types of the returned result set. + /// A callback function to connect the POCO instances, or null to automatically guess the relationships + /// The SQL query to be executed + /// Arguments to any embedded parameters in the SQL + /// Callback to process each result + Task QueryAsync(Type[] types, object cb, string sql, object[] args, Action action); + + /// + /// Formats the contents of a DB command for display + /// + /// + /// + string FormatCommand(IDbCommand cmd); + + /// + /// Formats an SQL query and it's arguments for display + /// + /// + /// + /// + string FormatCommand(string sql, object[] args); + } +} \ No newline at end of file diff --git a/AsyncPoco/T4 Templates/AsyncPoco.Generator.ttinclude b/AsyncPoco/T4 Templates/AsyncPoco.Generator.ttinclude index c5084d5f..85ddad38 100644 --- a/AsyncPoco/T4 Templates/AsyncPoco.Generator.ttinclude +++ b/AsyncPoco/T4 Templates/AsyncPoco.Generator.ttinclude @@ -108,7 +108,7 @@ namespace <#=Namespace #> private Dictionary ModifiedColumns; private void OnLoaded() { - ModifiedColumns = new Dictionary(); + ModifiedColumns = new Dictionary(ColumnComparer); } protected void MarkColumnModified(string column_name) { diff --git a/AsyncPoco/Utilities/EnumMapper.cs b/AsyncPoco/Utilities/EnumMapper.cs index 119fd45c..cbd1f2e0 100644 --- a/AsyncPoco/Utilities/EnumMapper.cs +++ b/AsyncPoco/Utilities/EnumMapper.cs @@ -7,6 +7,8 @@ namespace AsyncPoco.Internal { internal static class EnumMapper { + public static IEqualityComparer FieldComparer { get; set; } = StringComparer.InvariantCultureIgnoreCase; + public static object EnumFromString(Type enumType, string value) { if (!enumType.IsEnum) { @@ -17,7 +19,7 @@ public static object EnumFromString(Type enumType, string value) { var values = Enum.GetValues(enumType); - var newmap = new Dictionary(values.Length, StringComparer.InvariantCultureIgnoreCase); + var newmap = new Dictionary(values.Length, FieldComparer); foreach (var v in values) {