/* PetaPoco - A Tiny ORMish thing for your POCO's.
* Copyright © 2011-2012 Topten Software. All Rights Reserved.
*
* Apache License 2.0 - http://www.toptensoftware.com/petapoco/license
*
* Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for
* use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice
* and Adam Schroder (@schotime) for lots of suggestions, improvements and Oracle support
*/
// Define PETAPOCO_NO_DYNAMIC in your project settings on .NET 3.5
// This file was built by merging separate C# source files into one.
// DO NOT EDIT THIS FILE - go back to the originals
using PetaPoco.DatabaseTypes;
using PetaPoco.Internal;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Globalization;
using Sony.Filtr.Database;
using MySqlConnector;
using MoreLinq;
namespace PetaPoco
{
///
/// The main PetaPoco Database class. You can either use this class directly, or derive from it.
///
public class Database : IDisposable
{
#region Constructors
///
/// Construct a database using a supplied IDbConnection
///
/// The IDbConnection to use
///
/// The supplied IDbConnection will not be closed/disposed by PetaPoco - that remains
/// the responsibility of the caller.
///
public Database(IDbConnection connection)
{
_sharedConnection = connection;
_connectionString = connection.ConnectionString;
_sharedConnectionDepth = 2; // Prevent closing external connection
CommonConstruct();
}
///
/// 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.
///
public Database(string connectionString, string providerName)
{
_connectionString = connectionString;
_providerName = providerName;
CommonConstruct();
}
///
/// Construct a Database using a supplied connection string and a DbProviderFactory
///
/// The connection string to use
/// The DbProviderFactory to use for instantiating IDbConnection's
public Database(string connectionString, DbProviderFactory provider)
{
DbProviderFactories.RegisterFactory("MySqlConnector.MySqlConnectorFactory", MySqlConnector.MySqlConnectorFactory.Instance);
_connectionString = connectionString;
_factory = provider;
CommonConstruct();
}
///
/// 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)
{
DbProviderFactories.RegisterFactory("MySqlConnector.MySqlConnectorFactory", MySqlConnector.MySqlConnectorFactory.Instance);
// Use first?
if (connectionStringName == "")
connectionStringName = DatabaseHandler.GetConnectionStringByName("dbPetaPoco");
// Work out connection string and provider name
var providerName = "MySqlConnector.MySqlConnectorFactory";
// Store factory and connection string
_connectionString = DatabaseHandler.GetConnectionStringByName(connectionStringName);
_providerName = providerName;
CommonConstruct();
}
///
/// Provides common initialization for the various constructors
///
private void CommonConstruct()
{
// Reset
_transactionDepth = 0;
EnableAutoSelect = true;
EnableNamedParams = true;
// If a provider name was supplied, get the IDbProviderFactory for it
if (_providerName != null)
_factory = DbProviderFactories.GetFactory(_providerName);
// Resolve the DB Type
string DBTypeName = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name;
_dbType = DatabaseType.Resolve(DBTypeName, _providerName);
// What character is used for delimiting parameters in SQL
_paramPrefix = _dbType.GetParameterPrefix(_connectionString);
}
#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
///
public bool KeepConnectionAlive
{
get;
set;
}
///
/// Open a connection that will be used for all subsequent queries.
///
///
/// Calls to Open/CloseSharedConnection are reference counted and should be balanced
///
public void OpenSharedConnection()
{
if (_sharedConnectionDepth == 0)
{
_sharedConnection = _factory.CreateConnection();
_sharedConnection.ConnectionString = _connectionString;
if (_sharedConnection.State == ConnectionState.Broken)
_sharedConnection.Close();
if (_sharedConnection.State == ConnectionState.Closed)
_sharedConnection.Open();
_sharedConnection = OnConnectionOpened(_sharedConnection);
if (KeepConnectionAlive)
_sharedConnectionDepth++; // Make sure you call Dispose
}
_sharedConnectionDepth++;
}
///
/// Releases the shared connection
///
public void CloseSharedConnection()
{
if (_sharedConnectionDepth > 0)
{
_sharedConnectionDepth--;
if (_sharedConnectionDepth == 0)
{
OnConnectionClosing(_sharedConnection);
_sharedConnection.Dispose();
_sharedConnection = null;
}
}
}
///
/// Provides access to the currently open shared connection (or null if none)
///
public IDbConnection Connection
{
get { return _sharedConnection; }
}
#endregion
#region Transaction Management
// Helper to create a transaction scope
///
/// 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.
///
public ITransaction GetTransaction(IsolationLevel isolationLevel = IsolationLevel.Unspecified)
{
return new Transaction(this, isolationLevel);
}
///
/// 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()
{
}
///
/// Called when a transaction ends.
///
public virtual void OnEndTransaction()
{
}
///
/// Starts a transaction scope, see GetTransaction() for recommended usage
///
public void BeginTransaction(IsolationLevel isolationLevel)
{
_transactionDepth++;
if (_transactionDepth == 1)
{
OpenSharedConnection();
_transaction = _sharedConnection.BeginTransaction(isolationLevel);
_transactionCancelled = false;
OnBeginTransaction();
}
}
///
/// Internal helper to cleanup transaction
///
void CleanupTransaction()
{
OnEndTransaction();
if (_transactionCancelled)
_transaction.Rollback();
else
_transaction.Commit();
_transaction.Dispose();
_transaction = null;
CloseSharedConnection();
}
///
/// Aborts the entire outer most transaction scope
///
///
/// Called automatically by Transaction.Dispose()
/// if the transaction wasn't completed.
///
public void AbortTransaction()
{
_transactionCancelled = true;
if ((--_transactionDepth) == 0)
CleanupTransaction();
}
///
/// Marks the current transaction scope as complete.
///
public void CompleteTransaction()
{
if ((--_transactionDepth) == 0)
CleanupTransaction();
}
#endregion
#region Command Management
///
/// 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)
{
// Convert value to from poco type to db type
if (pi != null)
{
var mapper = Mappers.GetMapper(pi.DeclaringType);
var fn = mapper.GetToDbConverter(pi);
if (fn != null)
value = fn(value);
}
// Support passed in parameters
var idbParam = value as IDbDataParameter;
if (idbParam != null)
{
idbParam.ParameterName = string.Format("{0}{1}", _paramPrefix, cmd.Parameters.Count);
cmd.Parameters.Add(idbParam);
return;
}
// Create the parameter
var p = cmd.CreateParameter();
p.ParameterName = string.Format("{0}{1}", _paramPrefix, cmd.Parameters.Count);
// Assign the parmeter value
if (value == null)
{
p.Value = DBNull.Value;
}
else
{
// 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
{
p.Value = (int)value;
}
else if (t == typeof(Guid))
{
p.Value = value.ToString();
p.DbType = DbType.String;
p.Size = 40;
}
else if (t == typeof(string))
{
// 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.Value = value;
}
else if (t == typeof(AnsiString))
{
// Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar
p.Size = Math.Max((value as AnsiString).Value.Length + 1, 4000);
p.Value = (value as AnsiString).Value;
p.DbType = DbType.AnsiString;
}
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.Value = value;
}
else if (value.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type
{
p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type
p.Value = value;
}
else
{
p.Value = value;
}
}
// Add to the collection
cmd.Parameters.Add(p);
}
// Create a command
static Regex rxParamsPrefix = new Regex(@"(?();
sql = ParametersHelper.ProcessParams(sql, args, new_args);
args = new_args.ToArray();
}
// Perform parameter prefix replacements
if (_paramPrefix != "@")
sql = rxParamsPrefix.Replace(sql, m => _paramPrefix + m.Value.Substring(1));
sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @
// Create the command and add parameters
IDbCommand cmd = connection.CreateCommand();
cmd.Connection = connection;
cmd.CommandText = sql;
cmd.Transaction = _transaction;
foreach (var item in args)
{
AddParam(cmd, item, null);
}
// Notify the DB type
_dbType.PreExecute(cmd);
// Call logging
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.
///
/// 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);
return true;
}
///
/// Called when DB connection opened
///
/// The newly opened IDbConnection
/// The same or a replacement IDbConnection
///
/// Override this method to provide custom logging of opening connection, or
/// to provide a proxy IDbConnection.
///
public virtual IDbConnection OnConnectionOpened(IDbConnection conn)
{
return conn;
}
///
/// Called when DB connection closed
///
/// The soon to be closed IDBConnection
public virtual 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
///
public virtual void OnExecutingCommand(IDbCommand cmd)
{
}
///
/// Called on completion of command execution
///
/// The IDbCommand that finished executing
public virtual void OnExecutedCommand(IDbCommand cmd)
{
}
#endregion
#region operation: Execute
///
/// Executes a non-query command
///
/// The SQL statement to execute
/// Arguments to any embedded parameters in the SQL
/// The number of rows affected
public int Execute(string sql, params object[] args)
{
try
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, sql, args))
{
var retv = cmd.ExecuteNonQuery();
OnExecutedCommand(cmd);
return retv;
}
}
finally
{
CloseSharedConnection();
}
}
catch (Exception x)
{
if (OnException(x))
throw;
return -1;
}
}
///
/// Executes a non-query command
///
/// An SQL builder object representing the query and it's arguments
/// The number of rows affected
public int Execute(Sql sql)
{
return Execute(sql.SQL, sql.Arguments);
}
#endregion
#region operation: ExecuteScalar
///
/// 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
public T ExecuteScalar(string sql, params object[] args)
{
try
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, sql, args))
{
object val = cmd.ExecuteScalar();
OnExecutedCommand(cmd);
// Handle nullable types
Type u = Nullable.GetUnderlyingType(typeof(T));
if (u != null && val == null)
return default(T);
return (T)Convert.ChangeType(val, u == null ? typeof(T) : u);
}
}
finally
{
CloseSharedConnection();
}
}
catch (Exception x)
{
if (OnException(x))
throw;
return default(T);
}
}
///
/// 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
public T ExecuteScalar(Sql sql)
{
return ExecuteScalar(sql.SQL, sql.Arguments);
}
#endregion
#region operation: Fetch
///
/// 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
public List Fetch(string sql, params object[] args)
{
return Query(sql, args).ToList();
}
///
/// 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 List Fetch(Sql sql)
{
return Fetch(sql.SQL, sql.Arguments);
}
#endregion
#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
///
/// The Type representing a row in the result set
/// The number of rows to skip before the start of the page
/// The number of rows in the page
/// The original SQL select statement
/// 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)
{
// Add auto select clause
if (EnableAutoSelect)
sql = AutoSelectHelper.AddSelectClause(_dbType, sql);
// Split the SQL
PagingHelper.SQLParts parts;
if (!PagingHelper.SplitSQL(sql, out parts))
throw new Exception("Unable to parse SQL statement for paged query");
sqlPage = _dbType.BuildPageQuery(skip, take, parts, ref args);
sqlCount = parts.sqlCount;
}
///
/// 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.
///
public Page Page(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;
// Setup the paged result
var result = new Page
{
CurrentPage = page,
ItemsPerPage = itemsPerPage,
TotalItems = ExecuteScalar(sqlCount, countArgs)
};
result.TotalPages = result.TotalItems / itemsPerPage;
if ((result.TotalItems % itemsPerPage) != 0)
result.TotalPages++;
OneTimeCommandTimeout = saveTimeout;
// Get the records
result.Items = Fetch(sqlPage, pageArgs);
// Done
return result;
}
///
/// 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.
///
public Page Page(long page, long itemsPerPage, string sql, params object[] args)
{
string sqlCount, sqlPage;
BuildPageQueries((page - 1) * itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage);
return Page(page, itemsPerPage, sqlCount, args, sqlPage, 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.
///
public Page Page(long page, long itemsPerPage, Sql sql)
{
return Page(page, itemsPerPage, sql.SQL, sql.Arguments);
}
///
/// 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.
///
public Page Page(long page, long itemsPerPage, Sql sqlCount, Sql sqlPage)
{
return Page(page, itemsPerPage, sqlCount.SQL, sqlCount.Arguments, sqlPage.SQL, sqlPage.Arguments);
}
#endregion
#region operation: Fetch (page)
///
/// 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.
///
public List Fetch(long page, long itemsPerPage, string sql, params object[] args)
{
return SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql, 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.
///
public List Fetch(long page, long itemsPerPage, Sql sql)
{
return SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql.SQL, sql.Arguments);
}
#endregion
#region operation: SkipTake
///
/// 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.
///
public List SkipTake(long skip, long take, string sql, params object[] args)
{
string sqlCount, sqlPage;
BuildPageQueries(skip, take, sql, ref args, out sqlCount, out sqlPage);
return Fetch(sqlPage, 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.
///
public List SkipTake(long skip, long take, Sql sql)
{
return SkipTake(skip, take, sql.SQL, sql.Arguments);
}
#endregion
#region operation: Query
///
/// Runs an SQL query, returning the results as an IEnumerable collection
///
/// The Type representing a row in the result set
/// The SQL query
/// Arguments to any embedded parameters in the SQL statement
/// An enumerable collection of result records
///
/// 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 rather than an IEnumerable.
///
public IEnumerable Query(string sql, params object[] args)
{
if (EnableAutoSelect)
sql = AutoSelectHelper.AddSelectClause(_dbType, sql);
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, sql, args))
{
IDataReader r;
var pd = PocoData.ForType(typeof(T));
try
{
r = cmd.ExecuteReader();
OnExecutedCommand(cmd);
}
catch (Exception x)
{
if (OnException(x))
throw;
yield break;
}
var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, r.FieldCount, r) as Func;
using (r)
{
while (true)
{
T poco;
try
{
if (!r.Read())
yield break;
poco = factory(r);
}
catch (Exception x)
{
if (OnException(x))
throw;
yield break;
}
yield return poco;
}
}
}
}
finally
{
CloseSharedConnection();
}
}
///
/// Runs an SQL query, returning the results as an IEnumerable collection
///
/// The Type representing a row in the result set
/// An SQL builder object representing the base SQL query and it's arguments
/// An enumerable collection of result records
///
/// 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 rather than an IEnumerable.
///
public IEnumerable Query(Sql sql)
{
return Query(sql.SQL, sql.Arguments);
}
#endregion
#region operation: Exists
///
/// 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.
public bool Exists(string sqlCondition, params object[] args)
{
var poco = PocoData.ForType(typeof(T)).TableInfo;
return ExecuteScalar(string.Format(_dbType.GetExistsSql(), poco.TableName, sqlCondition), args) != 0;
}
///
/// 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 bool Exists(object primaryKey)
{
return Exists(string.Format("{0}=@0", _dbType.EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey);
}
#endregion
#region operation: linq style (Exists, Single, SingleOrDefault etc...)
///
/// 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.
///
public T Single(object primaryKey)
{
return Single(string.Format("WHERE {0}=@0", _dbType.EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), 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.
///
public T SingleOrDefault(object primaryKey)
{
return SingleOrDefault(string.Format("WHERE {0}=@0", _dbType.EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), 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
///
public T Single(string sql, params object[] args)
{
return Query(sql, args).Single();
}
///
/// 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 T SingleOrDefault(string sql, params object[] args)
{
return Query(sql, args).SingleOrDefault();
}
///
/// 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 T First(string sql, params object[] args)
{
return Query(sql, args).First();
}
///
/// 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 T FirstOrDefault(string sql, params object[] args)
{
return Query(sql, args).FirstOrDefault();
}
///
/// 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
///
public T Single(Sql sql)
{
return Query(sql).Single();
}
///
/// 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 T SingleOrDefault(Sql sql)
{
return Query(sql).SingleOrDefault();
}
///
/// 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 T First(Sql sql)
{
return Query(sql).First();
}
///
/// 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 T FirstOrDefault(Sql sql)
{
return Query(sql).FirstOrDefault();
}
#endregion
#region operation: Insert
///
/// 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
public object Insert(string tableName, string primaryKeyName, object poco)
{
return Insert(tableName, primaryKeyName, true, 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.
public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco)
{
try
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, ""))
{
var pd = PocoData.ForObject(poco, primaryKeyName);
var names = new List();
var values = new List();
var index = 0;
foreach (var i in pd.Columns)
{
// Don't insert result columns
if (i.Value.ResultColumn)
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)
{
// Setup auto increment expression
string autoIncExpression = _dbType.GetAutoIncrementExpression(pd.TableInfo);
if (autoIncExpression != null)
{
names.Add(i.Key);
values.Add(autoIncExpression);
}
continue;
}
names.Add(_dbType.EscapeSqlIdentifier(i.Key));
values.Add(string.Format("{0}{1}", _paramPrefix, index++));
AddParam(cmd, i.Value.GetValue(poco), i.Value.PropertyInfo);
}
string outputClause = String.Empty;
if (autoIncrement)
{
outputClause = _dbType.GetInsertOutputClause(primaryKeyName);
}
cmd.CommandText = string.Format("INSERT INTO {0} ({1}){2} VALUES ({3})",
_dbType.EscapeTableName(tableName),
string.Join(",", names.ToArray()),
outputClause,
string.Join(",", values.ToArray())
);
if (!autoIncrement)
{
DoPreExecute(cmd);
cmd.ExecuteNonQuery();
OnExecutedCommand(cmd);
PocoColumn pkColumn;
if (primaryKeyName != null && pd.Columns.TryGetValue(primaryKeyName, out pkColumn))
return pkColumn.GetValue(poco);
else
return null;
}
object id = _dbType.ExecuteInsert(this, cmd, primaryKeyName);
// Assign the ID back to the primary key property
if (primaryKeyName != null)
{
PocoColumn pc;
if (pd.Columns.TryGetValue(primaryKeyName, out pc))
{
pc.SetValue(poco, pc.ChangeType(id));
}
}
return id;
}
}
finally
{
CloseSharedConnection();
}
}
catch (Exception x)
{
if (OnException(x))
throw;
return null;
}
}
///
/// 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
public object Insert(object poco)
{
var pd = PocoData.ForType(poco.GetType());
return Insert(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, poco);
}
public void BulkInsert(IEnumerable collection, int? maxBatch = null, bool ignoreExisting = false)
{
try
{
OpenSharedConnection();
using (var cmd = CreateCommand(_sharedConnection, ""))
{
var pd = PocoData.ForType(typeof(T));
var tableName = _dbType.EscapeTableName(pd.TableInfo.TableName);
string cols = string.Join(", ",
(from c in pd.QueryColumns select tableName + "." + _dbType.EscapeSqlIdentifier(c)).ToArray());
//TODO: Transactions. ResultColumn. AutoIncrement handling?
if (maxBatch == null)
{
BulkInsertInternal(collection, pd, cmd, tableName, cols, ignoreExisting);
}
else
{
foreach (var batch in collection.Batch(maxBatch.Value))
{
BulkInsertInternal(batch, pd, cmd, tableName, cols, ignoreExisting);
}
}
}
}
finally
{
CloseSharedConnection();
}
}
public async Task BulkInsertAsync(IEnumerable collection, int? maxBatch = null, bool ignoreExisting = false)
{
try
{
OpenSharedConnection();
var pd = PocoData.ForType(typeof(T));
var tableName = _dbType.EscapeTableName(pd.TableInfo.TableName);
string cols = string.Join(", ",
(from c in pd.QueryColumns select tableName + "." + _dbType.EscapeSqlIdentifier(c)).ToArray());
//TODO: Transactions. ResultColumn. AutoIncrement handling?
if (maxBatch == null)
{
using (var cmd = CreateCommand(_sharedConnection, ""))
{
await BulkInsertInternalAsync(collection, pd, cmd, tableName, cols, ignoreExisting);
}
}
else
{
foreach (var batch in collection.Batch(maxBatch.Value))
{
using (var cmd = CreateCommand(_sharedConnection, ""))
{
await BulkInsertInternalAsync(batch, pd, cmd, tableName, cols, ignoreExisting);
}
}
}
}
finally
{
CloseSharedConnection();
}
}
private void BulkInsertInternal(IEnumerable batch, PocoData pd, IDbCommand cmd, string tableName, string cols, bool ignoreExisting = false)
{
var pocoValues = new List();
var index = 0;
foreach (var poco in batch)
{
var values = new List();
foreach (var i in pd.Columns)
{
values.Add(string.Format("{0}{1}", _paramPrefix, index++));
AddParam(cmd, i.Value.GetValue(poco), i.Value.PropertyInfo);
}
pocoValues.Add("(" + string.Join(",", values.ToArray()) + ")");
}
string sql;
if (ignoreExisting)
{
sql = string.Format("INSERT IGNORE INTO {0} ({1}) VALUES {2}", tableName, cols, string.Join(", ", pocoValues));
}
else
{
sql = string.Format("INSERT INTO {0} ({1}) VALUES {2}", tableName, cols, string.Join(", ", pocoValues));
}
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
private async Task BulkInsertInternalAsync(IEnumerable batch, PocoData pd, IDbCommand cmd, string tableName, string cols, bool ignoreExisting = false)
{
var pocoValues = new List();
var index = 0;
foreach (var poco in batch)
{
var values = new List();
foreach (var i in pd.Columns)
{
values.Add(string.Format("{0}{1}", _paramPrefix, index++));
AddParam(cmd, i.Value.GetValue(poco), i.Value.PropertyInfo);
}
pocoValues.Add("(" + string.Join(",", values.ToArray()) + ")");
}
string sql;
if (ignoreExisting)
{
sql = string.Format("INSERT IGNORE INTO {0} ({1}) VALUES {2}", tableName, cols, string.Join(", ", pocoValues));
}
else
{
sql = string.Format("INSERT INTO {0} ({1}) VALUES {2}", tableName, cols, string.Join(", ", pocoValues));
}
cmd.CommandText = sql;
if (cmd is MySqlCommand)
{
await ((MySqlCommand)cmd).ExecuteNonQueryAsync();
}
else
{
cmd.ExecuteNonQuery();
}
}
#endregion
#region operation: Update
///
/// 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
public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue)
{
return Update(tableName, primaryKeyName, poco, primaryKeyValue, null);
}
///
/// 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
public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns)
{
try
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, ""))
{
var sb = new StringBuilder();
var index = 0;
var pd = PocoData.ForObject(poco, primaryKeyName);
if (columns == null)
{
foreach (var i in pd.Columns)
{
// Don't update the primary key, but grab the value if we don't have it
if (string.Compare(i.Key, primaryKeyName, true) == 0)
{
if (primaryKeyValue == null)
primaryKeyValue = i.Value.GetValue(poco);
continue;
}
// Dont update result only columns
if (i.Value.ResultColumn)
continue;
// Build the sql
if (index > 0)
sb.Append(", ");
sb.AppendFormat("{0} = {1}{2}", _dbType.EscapeSqlIdentifier(i.Key), _paramPrefix, index++);
// Store the parameter in the command
AddParam(cmd, i.Value.GetValue(poco), i.Value.PropertyInfo);
}
}
else
{
foreach (var colname in columns)
{
var pc = pd.Columns[colname];
// Build the sql
if (index > 0)
sb.Append(", ");
sb.AppendFormat("{0} = {1}{2}", _dbType.EscapeSqlIdentifier(colname), _paramPrefix, index++);
// Store the parameter in the command
AddParam(cmd, pc.GetValue(poco), pc.PropertyInfo);
}
// Grab primary key value
if (primaryKeyValue == null)
{
var pc = pd.Columns[primaryKeyName];
primaryKeyValue = pc.GetValue(poco);
}
}
// Find the property info for the primary key
PropertyInfo pkpi = null;
if (primaryKeyName != null)
{
pkpi = pd.Columns[primaryKeyName].PropertyInfo;
}
cmd.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2} = {3}{4}",
_dbType.EscapeTableName(tableName), sb.ToString(), _dbType.EscapeSqlIdentifier(primaryKeyName), _paramPrefix, index++);
AddParam(cmd, primaryKeyValue, pkpi);
DoPreExecute(cmd);
// Do it
var retv = cmd.ExecuteNonQuery();
OnExecutedCommand(cmd);
return retv;
}
}
finally
{
CloseSharedConnection();
}
}
catch (Exception x)
{
if (OnException(x))
throw;
return -1;
}
}
///
/// 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
public int Update(string tableName, string primaryKeyName, object poco)
{
return Update(tableName, primaryKeyName, poco, null);
}
///
/// 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
public int Update(string tableName, string primaryKeyName, object poco, IEnumerable columns)
{
return Update(tableName, primaryKeyName, poco, null, 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
public int Update(object poco, IEnumerable columns)
{
return Update(poco, null, columns);
}
///
/// Performs an SQL update
///
/// The POCO object that specifies the column values to be updated
/// The number of affected rows
public int Update(object poco)
{
return Update(poco, null, null);
}
///
/// 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
public int Update(object poco, object primaryKeyValue)
{
return Update(poco, primaryKeyValue, null);
}
///
/// 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
public int Update(object poco, object primaryKeyValue, IEnumerable columns)
{
var pd = PocoData.ForType(poco.GetType());
return Update(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco, primaryKeyValue, 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
public int Update(string sql, params object[] args)
{
var pd = PocoData.ForType(typeof(T));
return Execute(string.Format("UPDATE {0} {1}", _dbType.EscapeTableName(pd.TableInfo.TableName), sql), 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
public int Update(Sql sql)
{
var pd = PocoData.ForType(typeof(T));
return Execute(new Sql(string.Format("UPDATE {0}", _dbType.EscapeTableName(pd.TableInfo.TableName))).Append(sql));
}
#endregion
#region operation: 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
/// The number of rows affected
public int Delete(string tableName, string primaryKeyName, object poco)
{
return Delete(tableName, primaryKeyName, poco, null);
}
///
/// 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
public int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue)
{
// If primary key value not specified, pick it up from the object
if (primaryKeyValue == null)
{
var pd = PocoData.ForObject(poco, primaryKeyName);
PocoColumn pc;
if (pd.Columns.TryGetValue(primaryKeyName, out pc))
{
primaryKeyValue = pc.GetValue(poco);
}
}
// Do it
var sql = string.Format("DELETE FROM {0} WHERE {1}=@0", _dbType.EscapeTableName(tableName), _dbType.EscapeSqlIdentifier(primaryKeyName));
return Execute(sql, 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
public int Delete(object poco)
{
var pd = PocoData.ForType(poco.GetType());
return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, 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
///
public int Delete(object pocoOrPrimaryKey)
{
if (pocoOrPrimaryKey.GetType() == typeof(T))
return Delete(pocoOrPrimaryKey);
var pd = PocoData.ForType(typeof(T));
return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, null, 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
public int Delete(string sql, params object[] args)
{
var pd = PocoData.ForType(typeof(T));
return Execute(string.Format("DELETE FROM {0} {1}", _dbType.EscapeTableName(pd.TableInfo.TableName), sql), 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
public int Delete(Sql sql)
{
var pd = PocoData.ForType(typeof(T));
return Execute(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
///
/// 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(string primaryKeyName, object poco)
{
var pd = PocoData.ForObject(poco, primaryKeyName);
object pk;
PocoColumn pc;
if (pd.Columns.TryGetValue(primaryKeyName, out pc))
{
pk = pc.GetValue(poco);
}
#if !PETAPOCO_NO_DYNAMIC
else if (poco.GetType() == typeof(System.Dynamic.ExpandoObject))
{
return true;
}
#endif
else
{
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));
pk = pi.GetValue(poco, null);
}
if (pk == null)
return true;
var type = pk.GetType();
if (type.IsValueType)
{
// Common primary key types
if (type == typeof(long))
return (long)pk == default(long);
else if (type == typeof(ulong))
return (ulong)pk == default(ulong);
else if (type == typeof(int))
return (int)pk == default(int);
else if (type == typeof(uint))
return (uint)pk == default(uint);
else 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;
}
}
///
/// 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 void Save(string tableName, string primaryKeyName, object poco)
{
if (IsNew(primaryKeyName, poco))
{
Insert(tableName, primaryKeyName, true, poco);
}
else
{
Update(tableName, primaryKeyName, poco);
}
}
///
/// Saves a POCO by either performing either an SQL Insert or SQL Update
///
/// The POCO object to be saved
public void Save(object poco)
{
var pd = PocoData.ForType(poco.GetType());
Save(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 List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); }
///
/// 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 List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); }
///
/// 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 List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); }
///
/// Perform a multi-poco query
///
/// The first POCO type
/// The second POCO type
/// The type of objects in the returned IEnumerable
/// 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 an IEnumerable
public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql, args); }
///
/// Perform a multi-poco query
///
/// The first POCO type
/// The second POCO type
/// The third POCO type
/// The type of objects in the returned IEnumerable
/// 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 an IEnumerable
public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, args); }
///
/// 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 in the returned IEnumerable
/// 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 an IEnumerable
public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, args); }
///
/// 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 List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); }
///
/// 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 List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); }
///
/// 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 List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); }
///
/// Perform a multi-poco query
///
/// The first POCO type
/// The second POCO type
/// The type of objects in the returned IEnumerable
/// 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 an IEnumerable
public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments); }
///
/// Perform a multi-poco query
///
/// The first POCO type
/// The second POCO type
/// The third POCO type
/// The type of objects in the returned IEnumerable
/// 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 an IEnumerable
public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments); }
///
/// 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 in the returned IEnumerable
/// 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 an IEnumerable
public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments); }
///
/// 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 List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); }
///
/// 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 List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); }
///
/// 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 List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); }
///
/// 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
/// A collection of POCO's as an IEnumerable
public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql, args); }
///
/// 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
/// A collection of POCO's as an IEnumerable
public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); }
///
/// 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
/// A collection of POCO's as an IEnumerable
public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); }
///
/// 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 List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); }
///
/// 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 List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); }
///
/// 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 List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); }
///
/// Perform a multi-poco query
///
/// 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 an IEnumerable
public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments); }
///
/// 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
/// A collection of POCO's as an IEnumerable
public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments); }
///
/// 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
/// A collection of POCO's as an IEnumerable
public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments); }
///
/// Performs a multi-poco query
///
/// The type of objects in the returned IEnumerable
/// 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 an IEnumerable
public IEnumerable Query(Type[] types, object cb, string sql, params object[] args)
{
OpenSharedConnection();
try
{
using (var cmd = CreateCommand(_sharedConnection, sql, args))
{
IDataReader r;
try
{
r = cmd.ExecuteReader();
OnExecutedCommand(cmd);
}
catch (Exception x)
{
if (OnException(x))
throw;
yield break;
}
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 (!r.Read())
break;
poco = factory(r, cb);
}
catch (Exception x)
{
if (OnException(x))
throw;
yield break;
}
if (poco != null)
yield return poco;
else
bNeedTerminator = true;
}
if (bNeedTerminator)
{
var poco = (TRet)(cb as Delegate).DynamicInvoke(new object[types.Length]);
if (poco != null)
yield return poco;
else
yield break;
}
}
}
}
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
///
/// 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 (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;
IDbConnection _sharedConnection;
IDbTransaction _transaction;
int _sharedConnectionDepth;
int _transactionDepth;
bool _transactionCancelled;
string _lastSql;
object[] _lastArgs;
string _paramPrefix;
#endregion
#region Internal operations
internal void ExecuteNonQueryHelper(IDbCommand cmd)
{
DoPreExecute(cmd);
cmd.ExecuteNonQuery();
OnExecutedCommand(cmd);
}
internal object ExecuteScalarHelper(IDbCommand cmd)
{
DoPreExecute(cmd);
object r = cmd.ExecuteScalar();
OnExecutedCommand(cmd);
return r;
}
internal void DoPreExecute(IDbCommand 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
private const string _bulkLoaderTerminator = "=|+";
public async Task ImportBulkFileLoaderByBatchesAsync(
IEnumerable pocos,
MySqlBulkLoaderConflictOption conflictionOption = MySqlBulkLoaderConflictOption.Ignore,
int batchSize = 20_000,
Action successAction = null,
Action failAction = null)
{
var pd = PocoData.ForType(typeof(T));
string[] files = pocos.Batch(batchSize).Select((batch, i) => WriteBulkLoaderFileToDisk(batch, pd, i)).ToArray();
try
{
OpenSharedConnection();
var mysqlConnection = _sharedConnection as MySqlConnection;
if (mysqlConnection == null)
{
throw new ArgumentException("BulkFileLoaderAsync can only be used with MySqlConnections");
}
await BulkLoadFilesAsync(pd, mysqlConnection, files, conflictionOption, successAction, failAction);
}
finally
{
CloseSharedConnection();
}
}
public async Task ImportBulkFileLoaderAsync(IEnumerable pocos, MySqlBulkLoaderConflictOption conflictionOption = MySqlBulkLoaderConflictOption.Ignore)
{
var pd = PocoData.ForType(typeof(T));
var filePath = WriteBulkLoaderFileToDisk(pocos, pd, 0);
try
{
OpenSharedConnection();
var mysqlConnection = _sharedConnection as MySqlConnection;
if (mysqlConnection == null)
{
throw new ArgumentException("BulkFileLoaderAsync can only be used with MySqlConnections");
}
await BulkLoadFileAsync(pd, filePath, mysqlConnection, conflictionOption, 1, 1);
}
finally
{
CloseSharedConnection();
}
}
private static async Task BulkLoadFilesAsync(
PocoData pd,
MySqlConnection connection,
string[] files,
MySqlBulkLoaderConflictOption conflictionOption = MySqlBulkLoaderConflictOption.Ignore,
Action successAction = null,
Action failAction = null)
{
var transaction = connection.BeginTransaction();
try
{
for(int i = 0; i < files.Length; ++i)
{
await BulkLoadFileAsync(pd, files[i], connection, conflictionOption, i, files.Length, successAction, failAction);
}
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
private static async Task BulkLoadFileAsync(
PocoData pd,
string filePath,
MySqlConnection connection,
MySqlBulkLoaderConflictOption conflictionOption,
int currentIndex,
int total,
Action successAction = null,
Action failAction = null,
int retryCounter = 1)
{
try
{
var bulkLoader = new MySqlBulkLoader(connection);
bulkLoader.Local = true;
bulkLoader.NumberOfLinesToSkip = 1;
bulkLoader.FileName = filePath;
bulkLoader.TableName = pd.TableInfo.TableName;
bulkLoader.FieldTerminator = _bulkLoaderTerminator;
bulkLoader.Columns.AddRange(pd.Columns.Select(p => p.Key).ToList());
bulkLoader.LineTerminator = Environment.NewLine;
bulkLoader.ConflictOption = conflictionOption;
bulkLoader.CharacterSet = "utf8mb4";
await bulkLoader.LoadAsync();
if (successAction != null)
{
successAction(currentIndex, total);
}
}
catch (Exception ex)
{
if (failAction != null)
{
failAction(ex.Message, ex.StackTrace, currentIndex, total);
}
if (retryCounter > 0)
{
await BulkLoadFileAsync(pd, filePath, connection, conflictionOption, currentIndex, total, successAction, failAction, --retryCounter);
}
}
finally
{
if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
}
public void ImportBulkFileLoader(IEnumerable pocos, MySqlBulkLoaderConflictOption conflictionOption)
{
var pd = PocoData.ForType(typeof(T));
var filePath = WriteBulkLoaderFileToDisk(pocos, pd, 0);
try
{
OpenSharedConnection();
var mysqlConnection = _sharedConnection as MySqlConnection;
if (mysqlConnection == null)
{
throw new ArgumentException("BulkFileLoader can only be used with MySqlConnections");
}
var bulkLoader = new MySqlBulkLoader(mysqlConnection);
bulkLoader.Local = true;
bulkLoader.NumberOfLinesToSkip = 1;
bulkLoader.FileName = filePath;
bulkLoader.TableName = pd.TableInfo.TableName;
bulkLoader.FieldTerminator = _bulkLoaderTerminator;
bulkLoader.Columns.AddRange(pd.Columns.Select(p => p.Key).ToList());
bulkLoader.LineTerminator = Environment.NewLine;
bulkLoader.ConflictOption = conflictionOption;
bulkLoader.CharacterSet = "utf8mb4";
bulkLoader.Load();
}
finally
{
if (File.Exists(filePath))
{
File.Delete(filePath);
}
CloseSharedConnection();
}
}
private string WriteBulkLoaderFileToDisk(IEnumerable pocos, PocoData pd, int index)
{
var filePath = $"BulkFileLoaderAsync-{pd.TableInfo.TableName}-{index}-{Guid.NewGuid()}.csv";
using (var sw = new StreamWriter(filePath, true, Encoding.UTF8))
{
sw.WriteLine(); //Empty line
}
var lines = pocos.Select(p => string.Join(_bulkLoaderTerminator, pd.Columns.Select(c => GetColumnValue(c, p))));
File.AppendAllLines(filePath, lines);
return filePath;
}
private string GetColumnValue(KeyValuePair column, T poco)
{
var value = column.Value.GetValue(poco);
if (value is DateTime)
{
return GetBulkImportString((DateTime)value);
}
if (value is NodaTime.LocalDate)
{
return ((NodaTime.LocalDate)value).ToString("yyyy'-'MM'-'dd", _bulkImportCultureInfo);
}
if (value == null)
{
return "\\N";
}
if (value is double)
{
return ((double)value).ToString(defaultNumberFormat);
}
if (value is float)
{
return ((float)value).ToString(defaultNumberFormat);
}
return value?.ToString();
}
private readonly CultureInfo _bulkImportCultureInfo = CultureInfo.GetCultureInfo("sv-se");
private NumberFormatInfo defaultNumberFormat = new NumberFormatInfo()
{
NumberDecimalSeparator = "."
};
private string GetBulkImportString(DateTime date)
{
return date.ToString(_bulkImportCultureInfo);
}
}
/*
Thanks to Adam Schroder (@schotime) for this.
This extra file provides an implementation of DbProviderFactory for early versions of the Oracle
drivers that don't include include it. For later versions of Oracle, the standard OracleProviderFactory
class should work fine
Uses reflection to load Oracle.DataAccess assembly and in-turn create connections and commands
Currently untested.
Usage:
new PetaPoco.Database("", new PetaPoco.OracleProvider())
Or in your app/web config (be sure to change ASSEMBLYNAME to the name of your
assembly containing OracleProvider.cs)
*/
public class OracleProvider : DbProviderFactory
{
private const string _assemblyName = "Oracle.DataAccess";
private const string _connectionTypeName = "Oracle.DataAccess.Client.OracleConnection";
private const string _commandTypeName = "Oracle.DataAccess.Client.OracleCommand";
private static Type _connectionType;
private static Type _commandType;
// Required for DbProviderFactories.GetFactory() to work.
public static OracleProvider Instance = new OracleProvider();
public OracleProvider()
{
_connectionType = TypeFromAssembly(_connectionTypeName, _assemblyName);
_commandType = TypeFromAssembly(_commandTypeName, _assemblyName);
if (_connectionType == null)
throw new InvalidOperationException("Can't find Connection type: " + _connectionTypeName);
}
public override DbConnection CreateConnection()
{
return (DbConnection)Activator.CreateInstance(_connectionType);
}
public override DbCommand CreateCommand()
{
DbCommand command = (DbCommand)Activator.CreateInstance(_commandType);
var oracleCommandBindByName = _commandType.GetProperty("BindByName");
oracleCommandBindByName.SetValue(command, true, null);
return command;
}
public static Type TypeFromAssembly(string typeName, string assemblyName)
{
try
{
// Try to get the type from an already loaded assembly
Type type = Type.GetType(typeName);
if (type != null)
{
return type;
}
if (assemblyName == null)
{
// No assembly was specified for the type, so just fail
string message = "Could not load type " + typeName + ". Possible cause: no assembly name specified.";
throw new TypeLoadException(message);
}
Assembly assembly = Assembly.Load(assemblyName);
if (assembly == null)
{
throw new InvalidOperationException("Can't find assembly: " + assemblyName);
}
type = assembly.GetType(typeName);
if (type == null)
{
return null;
}
return type;
}
catch (Exception)
{
return null;
}
}
}
///
/// For explicit poco properties, marks the property as a column and optionally
/// supplies the DB column name.
///
[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : Attribute
{
public ColumnAttribute()
{
ForceToUtc = false;
}
public ColumnAttribute(string Name)
{
this.Name = Name;
ForceToUtc = false;
}
public string Name
{
get;
set;
}
public bool ForceToUtc
{
get;
set;
}
}
///
/// Poco classes marked with the Explicit attribute require all column properties to
/// be marked with the Column attribute
///
[AttributeUsage(AttributeTargets.Class)]
public class ExplicitColumnsAttribute : Attribute
{
}
///
/// Use the Ignore attribute on POCO class properties that shouldn't be mapped
/// by PetaPoco.
///
[AttributeUsage(AttributeTargets.Property)]
public class IgnoreAttribute : Attribute
{
}
///
/// Specifies the primary key column of a poco class, whether the column is auto incrementing
/// and the sequence name for Oracle sequence columns.
///
[AttributeUsage(AttributeTargets.Class)]
public class PrimaryKeyAttribute : Attribute
{
public PrimaryKeyAttribute(string primaryKey)
{
Value = primaryKey;
autoIncrement = true;
}
public string Value
{
get;
private set;
}
public string sequenceName
{
get;
set;
}
public bool autoIncrement
{
get;
set;
}
}
///
/// Marks a poco property as a result only column that is populated in queries
/// but not used for updates or inserts.
///
[AttributeUsage(AttributeTargets.Property)]
public class ResultColumnAttribute : ColumnAttribute
{
public ResultColumnAttribute()
{
}
public ResultColumnAttribute(string name)
: base(name)
{
}
}
///
/// Sets the DB table name to be used for a Poco class.
///
[AttributeUsage(AttributeTargets.Class)]
public class TableNameAttribute : Attribute
{
public TableNameAttribute(string tableName)
{
Value = tableName;
}
public string Value
{
get;
private set;
}
}
///
/// Wrap strings in an instance of this class to force use of DBType.AnsiString
///
public class AnsiString
{
///
/// Constructs an AnsiString
///
/// The C# string to be converted to ANSI before being passed to the DB
public AnsiString(string str)
{
Value = str;
}
///
/// The string value
///
public string Value
{
get;
private set;
}
}
///
/// Hold information about a column in the database.
///
///
/// Typically ColumnInfo is automatically populated from the attributes on a POCO object and it's properties. It can
/// however also be returned from the IMapper interface to provide your owning bindings between the DB and your POCOs.
///
public class ColumnInfo
{
///
/// The SQL name of the column
///
public string ColumnName
{
get;
set;
}
///
/// True if this column returns a calculated value from the database and shouldn't be used in Insert and Update operations.
///
public bool ResultColumn
{
get;
set;
}
///
/// True if time and date values returned through this column should be forced to UTC DateTimeKind. (no conversion is applied - the Kind of the DateTime property
/// is simply set to DateTimeKind.Utc instead of DateTimeKind.Unknown.
///
public bool ForceToUtc
{
get;
set;
}
///
/// Creates and populates a ColumnInfo from the attributes of a POCO property.
///
/// The property whose column info is required
/// A ColumnInfo instance
public static ColumnInfo FromProperty(PropertyInfo pi)
{
// Check if declaring poco has [Explicit] attribute
bool ExplicitColumns = pi.DeclaringType.GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Length > 0;
// Check for [Column]/[Ignore] Attributes
var ColAttrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true);
if (ExplicitColumns)
{
if (ColAttrs.Length == 0)
return null;
}
else
{
if (pi.GetCustomAttributes(typeof(IgnoreAttribute), true).Length != 0)
return null;
}
ColumnInfo ci = new ColumnInfo();
// Read attribute
if (ColAttrs.Length > 0)
{
var colattr = (ColumnAttribute)ColAttrs[0];
ci.ColumnName = colattr.Name == null ? pi.Name : colattr.Name;
ci.ForceToUtc = colattr.ForceToUtc;
if ((colattr as ResultColumnAttribute) != null)
ci.ResultColumn = true;
}
else
{
ci.ColumnName = pi.Name;
ci.ForceToUtc = false;
ci.ResultColumn = false;
}
return ci;
}
}
///
/// IMapper provides a way to hook into PetaPoco's Database to POCO mapping mechanism to either
/// customize or completely replace it.
///
///
/// To use this functionality, instantiate a class that implements IMapper and then pass it to
/// PetaPoco through the static method Mappers.Register()
///
public interface IMapper
{
///
/// Get information about the table associated with a POCO class
///
///
/// A TableInfo instance
///
/// This method must return a valid TableInfo.
/// To create a TableInfo from a POCO's attributes, use TableInfo.FromPoco
///
TableInfo GetTableInfo(Type pocoType);
///
/// Get information about the column associated with a property of a POCO
///
/// The PropertyInfo of the property being queried
/// A reference to a ColumnInfo instance, or null to ignore this property
///
/// To create a ColumnInfo from a property's attributes, use PropertyInfo.FromProperty
///
ColumnInfo GetColumnInfo(PropertyInfo pocoProperty);
///
/// Supply a function to convert a database value to the correct property value
///
/// The target property
/// The type of data returned by the DB
/// A Func that can do the conversion, or null for no conversion
Func