diff --git a/AmalgamationTool/AmalgamationTool.csproj b/AmalgamationTool/AmalgamationTool.csproj
new file mode 100644
index 0000000..76fb7a7
--- /dev/null
+++ b/AmalgamationTool/AmalgamationTool.csproj
@@ -0,0 +1,58 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {A64D2052-D0CD-488E-BF05-E5952615D926}
+ Exe
+ Properties
+ AmalgamationTool
+ AmalgamationTool
+ v4.0
+ Client
+ 512
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs
new file mode 100644
index 0000000..825a45d
--- /dev/null
+++ b/AmalgamationTool/DynamORM.Amalgamation.cs
@@ -0,0 +1,10406 @@
+/*
+ * DynamORM - Dynamic Object-Relational Mapping library.
+ * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * See: http://opensource.org/licenses/bsd-license.php
+ *
+ * Supported preprocessor flags:
+ * * DYNAMORM_OMMIT_OLDSYNTAX - Remove dynamic table functionality
+ * * DYNAMORM_OMMIT_GENERICEXECUTION - Remove generic execution functionality
+ * * DYNAMORM_OMMIT_TRYPARSE - Remove TryParse helpers (also applies DYNAMORM_OMMIT_GENERICEXECUTION)
+*/
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Data;
+using System.Data.Common;
+using System.Dynamic;
+using System.IO;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Text;
+using DynamORM.Builders;
+using DynamORM.Builders.Extensions;
+using DynamORM.Builders.Implementation;
+using DynamORM.Helpers;
+using DynamORM.Helpers.Dynamics;
+using DynamORM.Mapper;
+
+[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "This is a generated file which generates all the necessary support classes.")]
+[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1403:FileMayOnlyContainASingleNamespace", Justification = "This is a generated file which generates all the necessary support classes.")]
+
+namespace DynamORM
+{
+ /// Small utility class to manage single columns.
+ public class DynamicColumn
+ {
+ #region Enums
+
+ /// Order By Order.
+ public enum SortOrder
+ {
+ /// Ascending order.
+ Asc,
+
+ /// Descending order.
+ Desc
+ }
+
+ /// Dynamic query operators.
+ public enum CompareOperator
+ {
+ /// Equals operator (default).
+ Eq,
+
+ /// Not equal operator.
+ Not,
+
+ /// Like operator.
+ Like,
+
+ /// Not like operator.
+ NotLike,
+
+ /// In operator.
+ In,
+
+ /// Less than operator.
+ Lt,
+
+ /// Less or equal operator.
+ Lte,
+
+ /// Greater than operator.
+ Gt,
+
+ /// Greater or equal operator.
+ Gte,
+
+ /// Between two values.
+ Between,
+ }
+
+ #endregion Enums
+
+ #region Constructors
+
+ /// Initializes a new instance of the class.
+ public DynamicColumn()
+ {
+ }
+
+ /// Initializes a new instance of the class.
+ /// Constructor provided for easier object creation in queries.
+ /// Name of column to set.
+ public DynamicColumn(string columnName)
+ : this()
+ {
+ ColumnName = columnName;
+ }
+
+ /// Initializes a new instance of the class.
+ /// Constructor provided for easier object creation in queries.
+ /// Name of column to set.
+ /// Compare column to value(s) operator.
+ /// Parameter value(s).
+ public DynamicColumn(string columnName, CompareOperator oper, object value)
+ : this(columnName)
+ {
+ Operator = oper;
+ Value = value;
+ }
+
+ #endregion Constructors
+
+ #region Properties
+
+ /// Gets or sets column name.
+ public string ColumnName { get; set; }
+
+ /// Gets or sets column alias.
+ /// Select specific.
+ public string Alias { get; set; }
+
+ /// Gets or sets aggregate function used on column.
+ /// Select specific.
+ public string Aggregate { get; set; }
+
+ /// Gets or sets order direction.
+ public SortOrder Order { get; set; }
+
+ /// Gets or sets value for parameters.
+ public object Value { get; set; }
+
+ /// Gets or sets condition operator.
+ public CompareOperator Operator { get; set; }
+
+ /// Gets or sets a value indicating whether this condition will be treated as or condition.
+ public bool Or { get; set; }
+
+ /// Gets or sets a value indicating whether start new block in where statement.
+ public bool BeginBlock { get; set; }
+
+ /// Gets or sets a value indicating whether end existing block in where statement.
+ public bool EndBlock { get; set; }
+
+ /// Gets or sets a value indicating whether set parameters for null values.
+ public bool? VirtualColumn { get; set; }
+
+ /// Gets or sets schema representation of a column.
+ /// Workaround to providers issues which sometimes pass wrong
+ /// data o schema. For example decimal has precision of 255 in SQL
+ /// server.
+ public DynamicSchemaColumn? Schema { get; set; }
+
+ #endregion Properties
+
+ #region Query creation helpers
+
+ #region Operators
+
+ private DynamicColumn SetOperatorAndValue(CompareOperator c, object v)
+ {
+ Operator = c;
+ Value = v;
+
+ return this;
+ }
+
+ /// Helper method setting
+ /// to and
+ /// to provided value .
+ /// Value of parameter to set.
+ /// Returns self.
+ public DynamicColumn Eq(object value)
+ {
+ return SetOperatorAndValue(CompareOperator.Eq, value);
+ }
+
+ /// Helper method setting
+ /// to and
+ /// to provided value .
+ /// Value of parameter to set.
+ /// Returns self.
+ public DynamicColumn Not(object value)
+ {
+ return SetOperatorAndValue(CompareOperator.Not, value);
+ }
+
+ /// Helper method setting
+ /// to and
+ /// to provided value .
+ /// Value of parameter to set.
+ /// Returns self.
+ public DynamicColumn Like(object value)
+ {
+ return SetOperatorAndValue(CompareOperator.Like, value);
+ }
+
+ /// Helper method setting
+ /// to and
+ /// to provided value .
+ /// Value of parameter to set.
+ /// Returns self.
+ public DynamicColumn NotLike(object value)
+ {
+ return SetOperatorAndValue(CompareOperator.NotLike, value);
+ }
+
+ /// Helper method setting
+ /// to and
+ /// to provided value .
+ /// Value of parameter to set.
+ /// Returns self.
+ public DynamicColumn Greater(object value)
+ {
+ return SetOperatorAndValue(CompareOperator.Gt, value);
+ }
+
+ /// Helper method setting
+ /// to and
+ /// to provided value .
+ /// Value of parameter to set.
+ /// Returns self.
+ public DynamicColumn Less(object value)
+ {
+ return SetOperatorAndValue(CompareOperator.Lt, value);
+ }
+
+ /// Helper method setting
+ /// to and
+ /// to provided value .
+ /// Value of parameter to set.
+ /// Returns self.
+ public DynamicColumn GreaterOrEqual(object value)
+ {
+ return SetOperatorAndValue(CompareOperator.Gte, value);
+ }
+
+ /// Helper method setting
+ /// to and
+ /// to provided value .
+ /// Value of parameter to set.
+ /// Returns self.
+ public DynamicColumn LessOrEqual(object value)
+ {
+ return SetOperatorAndValue(CompareOperator.Lte, value);
+ }
+
+ /// Helper method setting
+ /// to and
+ /// to provided values.
+ /// Value of from parameter to set.
+ /// Value of to parameter to set.
+ /// Returns self.
+ public DynamicColumn Between(object from, object to)
+ {
+ return SetOperatorAndValue(CompareOperator.Between, new[] { from, to });
+ }
+
+ /// Helper method setting
+ /// to and
+ /// to provided values.
+ /// Values of parameters to set.
+ /// Returns self.
+ public DynamicColumn In(IEnumerable values)
+ {
+ return SetOperatorAndValue(CompareOperator.In, values);
+ }
+
+ /// Helper method setting
+ /// to and
+ /// to provided values.
+ /// Values of parameters to set.
+ /// Returns self.
+ public DynamicColumn In(params object[] values)
+ {
+ if (values.Length == 1 && (values[0].GetType().IsCollection() || values[0] is IEnumerable))
+ return SetOperatorAndValue(CompareOperator.In, values[0]);
+
+ return SetOperatorAndValue(CompareOperator.In, values);
+ }
+
+ #endregion Operators
+
+ #region Order
+
+ /// Helper method setting
+ /// to ..
+ /// Returns self.
+ public DynamicColumn Asc()
+ {
+ Order = SortOrder.Asc;
+ return this;
+ }
+
+ /// Helper method setting
+ /// to ..
+ /// Returns self.
+ public DynamicColumn Desc()
+ {
+ Order = SortOrder.Desc;
+ return this;
+ }
+
+ #endregion Order
+
+ #region Other
+
+ /// Helper method setting
+ ///
+ /// to provided name .
+ /// Name to set.
+ /// Returns self.
+ public DynamicColumn SetName(string name)
+ {
+ ColumnName = name;
+ return this;
+ }
+
+ /// Helper method setting
+ ///
+ /// to provided alias .
+ /// Alias to set.
+ /// Returns self.
+ public DynamicColumn SetAlias(string alias)
+ {
+ Alias = alias;
+ return this;
+ }
+
+ /// Helper method setting
+ ///
+ /// to provided aggregate .
+ /// Aggregate to set.
+ /// Returns self.
+ public DynamicColumn SetAggregate(string aggregate)
+ {
+ Aggregate = aggregate;
+ return this;
+ }
+
+ /// Sets the begin block flag.
+ /// If set to true [begin].
+ /// Returns self.
+ public DynamicColumn SetBeginBlock(bool begin = true)
+ {
+ BeginBlock = begin;
+ return this;
+ }
+
+ /// Sets the end block flag.
+ /// If set to true [end].
+ /// Returns self.
+ public DynamicColumn SetEndBlock(bool end = true)
+ {
+ EndBlock = end;
+ return this;
+ }
+
+ /// Sets the or flag.
+ /// If set to true [or].
+ /// Returns self.
+ public DynamicColumn SetOr(bool or = true)
+ {
+ Or = or;
+ return this;
+ }
+
+ /// Sets the virtual column.
+ /// Set virtual column value.
+ /// Returns self.
+ public DynamicColumn SetVirtualColumn(bool? virt)
+ {
+ VirtualColumn = virt;
+ return this;
+ }
+
+ #endregion Other
+
+ #endregion Query creation helpers
+
+ #region Parsing
+
+ /// Parse column for select query.
+ /// Column format consist of Column Name , Alias and
+ /// Aggregate function in this order separated by ': '.
+ /// Column string.
+ /// Instance of .
+ public static DynamicColumn ParseSelectColumn(string column)
+ {
+ // Split column description
+ var parts = column.Split(':');
+
+ if (parts.Length > 0)
+ {
+ DynamicColumn ret = new DynamicColumn() { ColumnName = parts[0] };
+
+ if (parts.Length > 1)
+ ret.Alias = parts[1];
+
+ if (parts.Length > 2)
+ ret.Aggregate = parts[2];
+
+ return ret;
+ }
+
+ return null;
+ }
+
+ /// Parse column for order by in query.
+ /// Column format consist of Column Name and
+ /// Direction in this order separated by ': '.
+ /// Column string.
+ /// Instance of .
+ public static DynamicColumn ParseOrderByColumn(string column)
+ {
+ // Split column description
+ var parts = column.Split(':');
+
+ if (parts.Length > 0)
+ {
+ DynamicColumn ret = new DynamicColumn() { ColumnName = parts[0] };
+
+ if (parts.Length > 1)
+ ret.Order = parts[1].ToLower() == "d" || parts[1].ToLower() == "desc" ? SortOrder.Desc : SortOrder.Asc;
+
+ if (parts.Length > 2)
+ ret.Alias = parts[2];
+
+ return ret;
+ }
+
+ return null;
+ }
+
+ #endregion Parsing
+
+ #region ToSQL
+
+ internal string ToSQLSelectColumn(DynamicDatabase db)
+ {
+ StringBuilder sb = new StringBuilder();
+ ToSQLSelectColumn(db, sb);
+ return sb.ToString();
+ }
+
+ internal void ToSQLSelectColumn(DynamicDatabase db, StringBuilder sb)
+ {
+ string column = ColumnName == "*" ? "*" : ColumnName;
+
+ if (column != "*" &&
+ (column.IndexOf(db.LeftDecorator) == -1 || column.IndexOf(db.RightDecorator) == -1) &&
+ (column.IndexOf('(') == -1 || column.IndexOf(')') == -1))
+ column = db.DecorateName(column);
+
+ string alias = Alias;
+
+ if (!string.IsNullOrEmpty(Aggregate))
+ {
+ sb.AppendFormat("{0}({1})", Aggregate, column);
+
+ alias = string.IsNullOrEmpty(alias) ?
+ ColumnName == "*" ? Guid.NewGuid().ToString() : ColumnName :
+ alias;
+ }
+ else
+ sb.Append(column);
+
+ if (!string.IsNullOrEmpty(alias))
+ sb.AppendFormat(" AS {0}", alias);
+ }
+
+ internal string ToSQLGroupByColumn(DynamicDatabase db)
+ {
+ StringBuilder sb = new StringBuilder();
+ ToSQLGroupByColumn(db, sb);
+ return sb.ToString();
+ }
+
+ internal void ToSQLGroupByColumn(DynamicDatabase db, StringBuilder sb)
+ {
+ sb.Append(db.DecorateName(ColumnName));
+ }
+
+ internal string ToSQLOrderByColumn(DynamicDatabase db)
+ {
+ StringBuilder sb = new StringBuilder();
+ ToSQLOrderByColumn(db, sb);
+ return sb.ToString();
+ }
+
+ internal void ToSQLOrderByColumn(DynamicDatabase db, StringBuilder sb)
+ {
+ if (!string.IsNullOrEmpty(Alias))
+ sb.Append(Alias);
+ else
+ sb.Append(db.DecorateName(ColumnName));
+
+ sb.AppendFormat(" {0}", Order.ToString().ToUpper());
+ }
+
+ #endregion ToSQL
+ }
+
+ /// Helper class to easy manage command.
+ public class DynamicCommand : IDbCommand, IExtendedDisposable
+ {
+ private IDbCommand _command;
+ private int? _commandTimeout = null;
+ private DynamicConnection _con;
+ private DynamicDatabase _db;
+ ////private long _poolStamp = 0;
+
+ /// Initializes a new instance of the class.
+ /// The connection.
+ /// The database manager.
+ internal DynamicCommand(DynamicConnection con, DynamicDatabase db)
+ {
+ IsDisposed = false;
+ _con = con;
+ _db = db;
+
+ lock (_db.SyncLock)
+ {
+ if (!_db.CommandsPool.ContainsKey(_con.Connection))
+ throw new InvalidOperationException("Can't create command using disposed connection.");
+ else
+ {
+ _command = _con.Connection.CreateCommand();
+ _db.CommandsPool[_con.Connection].Add(this);
+ }
+ }
+ }
+
+ /// Initializes a new instance of the class.
+ /// The database manager.
+ /// Used internally to create command without context.
+ internal DynamicCommand(DynamicDatabase db)
+ {
+ IsDisposed = false;
+ _db = db;
+ _command = db.Provider.CreateCommand();
+ }
+
+ /// Prepare command for execution.
+ /// Returns edited instance.
+ internal IDbCommand PrepareForExecution()
+ {
+ // TODO: Fix that
+ ////if (_poolStamp < _db.PoolStamp)
+ {
+ _command.CommandTimeout = _commandTimeout ?? _db.CommandTimeout ?? _command.CommandTimeout;
+
+ if (_db.TransactionPool[_command.Connection].Count > 0)
+ _command.Transaction = _db.TransactionPool[_command.Connection].Peek();
+ else
+ _command.Transaction = null;
+
+ ////_poolStamp = _db.PoolStamp;
+ }
+
+ return _db.DumpCommands ? _command.Dump(Console.Out) : _command;
+ }
+
+ #region IDbCommand Members
+
+ ///
+ /// Attempts to cancels the execution of an .
+ ///
+ public void Cancel()
+ {
+ _command.Cancel();
+ }
+
+ ///
+ /// Gets or sets the text command to run against the data source.
+ ///
+ /// The text command to execute. The default value is an empty string ("").
+ public string CommandText { get { return _command.CommandText; } set { _command.CommandText = value; } }
+
+ ///
+ /// Gets or sets the wait time before terminating the attempt to execute a command and generating an error.
+ ///
+ /// The time (in seconds) to wait for the command to execute. The default value is 30 seconds.
+ /// The property value assigned is less than 0.
+ public int CommandTimeout { get { return _commandTimeout ?? _command.CommandTimeout; } set { _commandTimeout = value; } }
+
+ /// Gets or sets how the property is interpreted.
+ public CommandType CommandType { get { return _command.CommandType; } set { _command.CommandType = value; } }
+
+ /// Gets or sets the
+ /// used by this instance of the .
+ /// The connection to the data source.
+ public IDbConnection Connection
+ {
+ get { return _con; }
+
+ set
+ {
+ _con = value as DynamicConnection;
+
+ if (_con != null)
+ {
+ ////_poolStamp = 0;
+ _command.Connection = _con.Connection;
+ }
+ else if (value == null)
+ {
+ _command.Transaction = null;
+ _command.Connection = null;
+ }
+ else
+ throw new InvalidOperationException("Can't assign direct IDbConnection implementation. This property accepts only DynamORM implementation of IDbConnection.");
+ }
+ }
+
+ /// Creates a new instance of an
+ /// object.
+ /// An object.
+ public IDbDataParameter CreateParameter()
+ {
+ return _command.CreateParameter();
+ }
+
+ /// Executes an SQL statement against the Connection object of a
+ /// data provider, and returns the number of rows affected.
+ /// The number of rows affected.
+ public int ExecuteNonQuery()
+ {
+ try
+ {
+ return PrepareForExecution().ExecuteNonQuery();
+ }
+ catch (Exception ex)
+ {
+ throw new DynamicQueryException(ex, this);
+ }
+ }
+
+ /// Executes the
+ /// against the ,
+ /// and builds an using one
+ /// of the values.
+ /// One of the
+ /// values.
+ /// An object.
+ public IDataReader ExecuteReader(CommandBehavior behavior)
+ {
+ try
+ {
+ return PrepareForExecution().ExecuteReader(behavior);
+ }
+ catch (Exception ex)
+ {
+ throw new DynamicQueryException(ex, this);
+ }
+ }
+
+ /// Executes the
+ /// against the and
+ /// builds an .
+ /// An object.
+ public IDataReader ExecuteReader()
+ {
+ try
+ {
+ return PrepareForExecution().ExecuteReader();
+ }
+ catch (Exception ex)
+ {
+ throw new DynamicQueryException(ex, this);
+ }
+ }
+
+ /// Executes the query, and returns the first column of the
+ /// first row in the result set returned by the query. Extra columns or
+ /// rows are ignored.
+ /// The first column of the first row in the result set.
+ public object ExecuteScalar()
+ {
+ try
+ {
+ return PrepareForExecution().ExecuteScalar();
+ }
+ catch (Exception ex)
+ {
+ throw new DynamicQueryException(ex, this);
+ }
+ }
+
+ /// Gets the .
+ public IDataParameterCollection Parameters
+ {
+ get { return _command.Parameters; }
+ }
+
+ /// Creates a prepared (or compiled) version of the command on the data source.
+ public void Prepare()
+ {
+ try
+ {
+ _command.Prepare();
+ }
+ catch (Exception ex)
+ {
+ throw new DynamicQueryException("Error preparing command.", ex, this);
+ }
+ }
+
+ /// Gets or sets the transaction within which the Command
+ /// object of a data provider executes.
+ /// It's does nothing, transaction is peeked from transaction
+ /// pool of a connection. This is only a dummy.
+ public IDbTransaction Transaction { get { return null; } set { } }
+
+ /// Gets or sets how command results are applied to the
+ /// when used by the
+ /// method of a .
+ /// One of the values. The default is
+ /// Both unless the command is automatically generated. Then the default is None.
+ /// The value entered was not one of the
+ /// values.
+ public UpdateRowSource UpdatedRowSource { get { return _command.UpdatedRowSource; } set { _command.UpdatedRowSource = value; } }
+
+ #endregion IDbCommand Members
+
+ #region IExtendedDisposable Members
+
+ /// Performs application-defined tasks associated with
+ /// freeing, releasing, or resetting unmanaged resources.
+ public void Dispose()
+ {
+ lock (_db.SyncLock)
+ {
+ if (_con != null)
+ {
+ var pool = _db.CommandsPool.TryGetValue(_con.Connection);
+
+ if (pool != null && pool.Contains(this))
+ pool.Remove(this);
+ }
+
+ IsDisposed = true;
+
+ _command.Dispose();
+ }
+ }
+
+ /// Gets a value indicating whether this instance is disposed.
+ public bool IsDisposed { get; private set; }
+
+ #endregion IExtendedDisposable Members
+ }
+
+ /// Connection wrapper.
+ /// This class is only connection holder, connection is managed by
+ /// instance.
+ public class DynamicConnection : IDbConnection, IExtendedDisposable
+ {
+ private DynamicDatabase _db;
+ private bool _singleTransaction;
+
+ /// Gets underlying connection.
+ internal IDbConnection Connection { get; private set; }
+
+ /// Initializes a new instance of the class.
+ /// Database connection manager.
+ /// Active connection.
+ /// Are we using single transaction mode? I so... act correctly.
+ internal DynamicConnection(DynamicDatabase db, IDbConnection con, bool singleTransaction)
+ {
+ IsDisposed = false;
+ _db = db;
+ Connection = con;
+ _singleTransaction = singleTransaction;
+ }
+
+ /// Begins a database transaction.
+ /// One of the values.
+ /// This action is invoked when transaction is disposed.
+ /// Returns representation.
+ internal DynamicTransaction BeginTransaction(IsolationLevel? il, Action disposed)
+ {
+ return new DynamicTransaction(_db, this, _singleTransaction, il, disposed);
+ }
+
+ #region IDbConnection Members
+
+ /// Creates and returns a Command object associated with the connection.
+ /// A Command object associated with the connection.
+ public IDbCommand CreateCommand()
+ {
+ return new DynamicCommand(this, _db);
+ }
+
+ /// Begins a database transaction.
+ /// Returns representation.
+ public IDbTransaction BeginTransaction()
+ {
+ return BeginTransaction(null, null);
+ }
+
+ /// Begins a database transaction with the specified
+ /// value.
+ /// One of the values.
+ /// Returns representation.
+ public IDbTransaction BeginTransaction(IsolationLevel il)
+ {
+ return BeginTransaction(il, null);
+ }
+
+ /// Changes the current database for an open Connection object.
+ /// The name of the database to use in place of the current database.
+ /// This operation is not supported in DynamORM . and will throw .
+ /// Thrown always.
+ public void ChangeDatabase(string databaseName)
+ {
+ throw new NotSupportedException("This operation is not supported in DynamORM.");
+ }
+
+ /// Opens a database connection with the settings specified by
+ /// the ConnectionString property of the provider-specific
+ /// Connection object.
+ /// Does nothing. handles
+ /// opening connections.
+ public void Open()
+ {
+ }
+
+ /// Closes the connection to the database.
+ /// Does nothing. handles
+ /// closing connections. Only way to close it is to dispose connection.
+ /// It will close if this is multi connection configuration, otherwise
+ /// it will stay open until is not
+ /// disposed.
+ public void Close()
+ {
+ }
+
+ /// Gets or sets the string used to open a database.
+ /// Changing connection string operation is not supported in DynamORM .
+ /// and will throw .
+ /// Thrown always when set is attempted.
+ public string ConnectionString
+ {
+ get { return Connection.ConnectionString; }
+ set { throw new NotSupportedException("This operation is not supported in DynamORM."); }
+ }
+
+ /// Gets the time to wait while trying to establish a connection
+ /// before terminating the attempt and generating an error.
+ public int ConnectionTimeout
+ {
+ get { return Connection.ConnectionTimeout; }
+ }
+
+ /// Gets the name of the current database or the database
+ /// to be used after a connection is opened.
+ public string Database
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// Gets the current state of the connection.
+ public ConnectionState State
+ {
+ get { return Connection.State; }
+ }
+
+ #endregion IDbConnection Members
+
+ #region IExtendedDisposable Members
+
+ /// Performs application-defined tasks associated with freeing,
+ /// releasing, or resetting unmanaged resources.
+ public void Dispose()
+ {
+ _db.Close(Connection);
+ IsDisposed = true;
+ }
+
+ /// Gets a value indicating whether this instance is disposed.
+ public bool IsDisposed { get; private set; }
+
+ #endregion IExtendedDisposable Members
+ }
+
+ /// Dynamic database is a class responsible for managing database.
+ public class DynamicDatabase : IExtendedDisposable
+ {
+ #region Internal fields and properties
+
+ private DbProviderFactory _provider;
+ private string _connectionString;
+ private bool _singleConnection;
+ private bool _singleTransaction;
+ private string _leftDecorator = "\"";
+ private string _rightDecorator = "\"";
+ private bool _leftDecoratorIsInInvalidMembersChars = true;
+ private bool _rightDecoratorIsInInvalidMembersChars = true;
+ private string _parameterFormat = "@{0}";
+ private int? _commandTimeout = null;
+ private long _poolStamp = 0;
+
+ private DynamicConnection _tempConn = null;
+
+ /// Provides lock object for this database instance.
+ internal readonly object SyncLock = new object();
+
+ /// Gets or sets timestamp of last transaction pool or configuration change.
+ /// This property is used to allow commands to determine if
+ /// they need to update transaction object or not.
+ internal long PoolStamp
+ {
+ get
+ {
+ long r = 0;
+
+ lock (SyncLock)
+ r = _poolStamp;
+
+ return r;
+ }
+
+ set
+ {
+ lock (SyncLock)
+ _poolStamp = value;
+ }
+ }
+
+ /// Gets pool of connections and transactions.
+ internal Dictionary> TransactionPool { get; private set; }
+
+ /// Gets pool of connections and commands.
+ /// Pool should contain dynamic commands instead of native ones.
+ internal Dictionary> CommandsPool { get; private set; }
+
+ /// Gets schema columns cache.
+ internal Dictionary> Schema { get; private set; }
+
+#if !DYNAMORM_OMMIT_OLDSYNTAX
+
+ /// Gets tables cache for this database instance.
+ internal Dictionary TablesCache { get; private set; }
+
+#endif
+
+ #endregion Internal fields and properties
+
+ #region Properties and Constructors
+
+ /// Gets database options.
+ public DynamicDatabaseOptions Options { get; private set; }
+
+ /// Gets or sets command timeout.
+ public int? CommandTimeout { get { return _commandTimeout; } set { _commandTimeout = value; _poolStamp = DateTime.Now.Ticks; } }
+
+ /// Gets the database provider.
+ public DbProviderFactory Provider { get { return _provider; } }
+
+ /// Gets or sets a value indicating whether
+ /// dump commands to console or not.
+ public bool DumpCommands { get; set; }
+
+ /// Initializes a new instance of the class.
+ /// Database provider by name.
+ /// Connection string to provided database.
+ /// Connection options.
+ public DynamicDatabase(string provider, string connectionString, DynamicDatabaseOptions options)
+ : this(DbProviderFactories.GetFactory(provider), connectionString, options)
+ {
+ }
+
+ /// Initializes a new instance of the class.
+ /// Database provider.
+ /// Connection string to provided database.
+ /// Connection options.
+ public DynamicDatabase(DbProviderFactory provider, string connectionString, DynamicDatabaseOptions options)
+ {
+ IsDisposed = false;
+ _provider = provider;
+
+ InitCommon(connectionString, options);
+ }
+
+ /// Initializes a new instance of the class.
+ /// Active database connection.
+ /// Connection options. required.
+ public DynamicDatabase(IDbConnection connection, DynamicDatabaseOptions options)
+ {
+ IsDisposed = false;
+ InitCommon(connection.ConnectionString, options);
+ TransactionPool.Add(connection, new Stack());
+
+ if (!_singleConnection)
+ throw new InvalidOperationException("This constructor accepts only connections with DynamicDatabaseOptions.SingleConnection option.");
+ }
+
+ private void InitCommon(string connectionString, DynamicDatabaseOptions options)
+ {
+ _connectionString = connectionString;
+ Options = options;
+
+ _singleConnection = (options & DynamicDatabaseOptions.SingleConnection) == DynamicDatabaseOptions.SingleConnection;
+ _singleTransaction = (options & DynamicDatabaseOptions.SingleTransaction) == DynamicDatabaseOptions.SingleTransaction;
+ DumpCommands = (options & DynamicDatabaseOptions.DumpCommands) == DynamicDatabaseOptions.DumpCommands;
+
+ TransactionPool = new Dictionary>();
+ CommandsPool = new Dictionary>();
+ Schema = new Dictionary>();
+#if !DYNAMORM_OMMIT_OLDSYNTAX
+ TablesCache = new Dictionary();
+#endif
+ }
+
+ #endregion Properties and Constructors
+
+ #region Table
+
+#if !DYNAMORM_OMMIT_OLDSYNTAX
+
+ /// Gets dynamic table which is a simple ORM using dynamic objects.
+ /// The action with instance of as parameter.
+ /// Table name.
+ /// Override keys in schema.
+ /// Owner of the table.
+ public void Table(Action action, string table = "", string[] keys = null, string owner = "")
+ {
+ using (dynamic t = Table(table, keys, owner))
+ action(t);
+ }
+
+ /// Gets dynamic table which is a simple ORM using dynamic objects.
+ /// Type used to determine table name.
+ /// The action with instance of as parameter.
+ /// Override keys in schema.
+ public void Table(Action action, string[] keys = null)
+ {
+ using (dynamic t = Table(keys))
+ action(t);
+ }
+
+ /// Gets dynamic table which is a simple ORM using dynamic objects.
+ /// Table name.
+ /// Override keys in schema.
+ /// Owner of the table.
+ /// Instance of .
+ public dynamic Table(string table = "", string[] keys = null, string owner = "")
+ {
+ string key = string.Concat(
+ table == null ? string.Empty : table,
+ keys == null ? string.Empty : string.Join("_|_", keys));
+
+ DynamicTable dt = null;
+ lock (SyncLock)
+ dt = TablesCache.TryGetValue(key) ??
+ TablesCache.AddAndPassValue(key,
+ new DynamicTable(this, table, owner, keys));
+
+ return dt;
+ }
+
+ /// Gets dynamic table which is a simple ORM using dynamic objects.
+ /// Type used to determine table name.
+ /// Override keys in schema.
+ /// Instance of .
+ public dynamic Table(string[] keys = null)
+ {
+ Type table = typeof(T);
+ string key = string.Concat(
+ table.FullName,
+ keys == null ? string.Empty : string.Join("_|_", keys));
+
+ DynamicTable dt = null;
+ lock (SyncLock)
+ dt = TablesCache.TryGetValue(key) ??
+ TablesCache.AddAndPassValue(key,
+ new DynamicTable(this, table, keys));
+
+ return dt;
+ }
+
+ /// Removes cached table.
+ /// Disposed dynamic table.
+ internal void RemoveFromCache(DynamicTable dynamicTable)
+ {
+ foreach (var item in TablesCache.Where(kvp => kvp.Value == dynamicTable).ToList())
+ TablesCache.Remove(item.Key);
+ }
+
+#endif
+
+ #endregion Table
+
+ #region From/Insert/Update/Delete
+
+ ///
+ /// Adds to the FROM clause the contents obtained by parsing the dynamic lambda expressions given. The supported
+ /// formats are:
+ /// - Resolve to a string: x => "owner.Table AS Alias", where the alias part is optional.
+ /// - Resolve to an expression: x => x.owner.Table.As( x.Alias ), where the alias part is optional.
+ /// - Generic expression: x => x( expression ).As( x.Alias ), where the alias part is mandatory. In this
+ /// case the alias is not annotated.
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ public virtual IDynamicSelectQueryBuilder From(Func fn)
+ {
+ return new DynamicSelectQueryBuilder(this).From(fn);
+ }
+
+ /// Adds to the FROM clause using .
+ /// Type which can be represented in database.
+ /// This instance to permit chaining.
+ public virtual IDynamicSelectQueryBuilder From()
+ {
+ return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)));
+ }
+
+ /// Adds to the FROM clause using .
+ /// Type which can be represented in database.
+ /// This instance to permit chaining.
+ public virtual IDynamicSelectQueryBuilder From(Type t)
+ {
+ return new DynamicSelectQueryBuilder(this).From(x => x(t));
+ }
+
+ ///
+ /// Adds to the INSERT INTO clause the contents obtained by parsing the dynamic lambda expressions given. The supported
+ /// formats are:
+ /// - Resolve to a string: x => "owner.Table".
+ /// - Resolve to a type: x => typeof(SomeClass).
+ /// - Resolve to an expression: x => x.owner.Table.
+ /// - Generic expression: x => x( expression ). Expression can
+ /// be or .
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ public virtual IDynamicInsertQueryBuilder Insert(Func func)
+ {
+ return new DynamicInsertQueryBuilder(this).Table(func);
+ }
+
+ /// Adds to the INSERT INTO clause using .
+ /// Type which can be represented in database.
+ /// This instance to permit chaining.
+ public virtual IDynamicInsertQueryBuilder Insert()
+ {
+ return new DynamicInsertQueryBuilder(this).Table(typeof(T));
+ }
+
+ /// Adds to the INSERT INTO clause using .
+ /// Type which can be represented in database.
+ /// This instance to permit chaining.
+ public virtual IDynamicInsertQueryBuilder Insert(Type t)
+ {
+ return new DynamicInsertQueryBuilder(this).Table(t);
+ }
+
+ /// Bulk insert objects into database.
+ /// Type of objects to insert.
+ /// Enumerable containing instances of objects to insert.
+ /// Number of inserted rows.
+ public virtual int Insert(IEnumerable e) where T : class
+ {
+ int affected = 0;
+ var mapper = DynamicMapperCache.GetMapper(typeof(T));
+
+ if (mapper != null)
+ {
+ using (var con = Open())
+ using (var tra = con.BeginTransaction())
+ using (var cmd = con.CreateCommand())
+ {
+ try
+ {
+ var parameters = new Dictionary();
+
+ if (!string.IsNullOrEmpty(mapper.InsertCommandText))
+ {
+ cmd.CommandText = mapper.InsertCommandText;
+
+ foreach (var col in mapper.ColumnsMap.Values
+ .Where(di => !di.Ignore && di.InsertCommandParameter != null)
+ .OrderBy(di => di.InsertCommandParameter.Ordinal))
+ {
+ var para = cmd.CreateParameter();
+ para.ParameterName = col.InsertCommandParameter.Name;
+ para.DbType = col.InsertCommandParameter.Type;
+ cmd.Parameters.Add(para);
+
+ parameters[para] = col;
+ }
+ }
+ else
+ {
+ DynamicPropertyInvoker currentprop = null;
+ var temp = new Dictionary();
+ int ord = 0;
+
+ var ib = Insert()
+ .SetVirtualMode(true)
+ .CreateTemporaryParameterAction(p => temp[p.Name] = currentprop)
+ .CreateParameterAction((p, cp) =>
+ {
+ parameters[cp] = temp[p.Name];
+ parameters[cp].InsertCommandParameter = new DynamicPropertyInvoker.ParameterSpec
+ {
+ Name = cp.ParameterName,
+ Type = cp.DbType,
+ Ordinal = ord++,
+ };
+ });
+
+ foreach (var prop in mapper.PropertyMap)
+ if (!mapper.Ignored.Contains(prop.Key))
+ {
+ var col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key;
+ currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower());
+
+ if (currentprop.Ignore)
+ continue;
+
+ if (currentprop.Get != null)
+ ib.Insert(col, null);
+ }
+
+ ib.FillCommand(cmd);
+
+ // Cache command
+ mapper.InsertCommandText = cmd.CommandText;
+ }
+
+ foreach (var o in e)
+ {
+ foreach (var m in parameters)
+ m.Key.Value = m.Value.Get(o);
+
+ affected += cmd.ExecuteNonQuery();
+ }
+
+ tra.Commit();
+ }
+ catch (Exception ex)
+ {
+ if (tra != null)
+ tra.Rollback();
+
+ affected = 0;
+
+ var problematicCommand = new StringBuilder();
+ cmd.Dump(problematicCommand);
+
+ throw new InvalidOperationException(problematicCommand.ToString(), ex);
+ }
+ }
+ }
+
+ return affected;
+ }
+
+ ///
+ /// Adds to the UPDATE clause the contents obtained by parsing the dynamic lambda expressions given. The supported
+ /// formats are:
+ /// - Resolve to a string: x => "owner.Table".
+ /// - Resolve to a type: x => typeof(SomeClass).
+ /// - Resolve to an expression: x => x.owner.Table.
+ /// - Generic expression: x => x( expression ). Expression can
+ /// be or .
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ public virtual IDynamicUpdateQueryBuilder Update(Func func)
+ {
+ return new DynamicUpdateQueryBuilder(this).Table(func);
+ }
+
+ /// Adds to the UPDATE clause using .
+ /// Type which can be represented in database.
+ /// This instance to permit chaining.
+ public virtual IDynamicUpdateQueryBuilder Update()
+ {
+ return new DynamicUpdateQueryBuilder(this).Table(typeof(T));
+ }
+
+ /// Adds to the UPDATE clause using .
+ /// Type which can be represented in database.
+ /// This instance to permit chaining.
+ public virtual IDynamicUpdateQueryBuilder Update(Type t)
+ {
+ return new DynamicUpdateQueryBuilder(this).Table(t);
+ }
+
+ /// Bulk update objects in database.
+ /// Type of objects to update.
+ /// Enumerable containing instances of objects to update.
+ /// Number of updated rows.
+ public virtual int Update(IEnumerable e) where T : class
+ {
+ int affected = 0;
+ var mapper = DynamicMapperCache.GetMapper(typeof(T));
+
+ if (mapper != null)
+ {
+ using (var con = Open())
+ using (var tra = con.BeginTransaction())
+ using (var cmd = con.CreateCommand())
+ {
+ try
+ {
+ var parameters = new Dictionary();
+
+ if (!string.IsNullOrEmpty(mapper.UpdateCommandText))
+ {
+ cmd.CommandText = mapper.UpdateCommandText;
+
+ foreach (var col in mapper.ColumnsMap.Values
+ .Where(di => !di.Ignore && di.UpdateCommandParameter != null)
+ .OrderBy(di => di.UpdateCommandParameter.Ordinal))
+ {
+ var para = cmd.CreateParameter();
+ para.ParameterName = col.UpdateCommandParameter.Name;
+ para.DbType = col.UpdateCommandParameter.Type;
+ cmd.Parameters.Add(para);
+
+ parameters[para] = col;
+ }
+ }
+ else
+ {
+ DynamicPropertyInvoker currentprop = null;
+ var temp = new Dictionary();
+ int ord = 0;
+
+ var ib = Update()
+ .SetVirtualMode(true)
+ .CreateTemporaryParameterAction(p => temp[p.Name] = currentprop)
+ .CreateParameterAction((p, cp) =>
+ {
+ parameters[cp] = temp[p.Name];
+ parameters[cp].UpdateCommandParameter = new DynamicPropertyInvoker.ParameterSpec
+ {
+ Name = cp.ParameterName,
+ Type = cp.DbType,
+ Ordinal = ord++,
+ };
+ });
+
+ foreach (var prop in mapper.PropertyMap)
+ if (!mapper.Ignored.Contains(prop.Key))
+ {
+ var col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key;
+ currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower());
+
+ if (currentprop.Ignore)
+ continue;
+
+ if (currentprop.Get != null)
+ {
+ if (currentprop.Column != null && currentprop.Column.IsKey)
+ ib.Where(col, null);
+ else
+ ib.Values(col, null);
+ }
+ }
+
+ ib.FillCommand(cmd);
+
+ // Cache command
+ mapper.UpdateCommandText = cmd.CommandText;
+ }
+
+ foreach (var o in e)
+ {
+ foreach (var m in parameters)
+ m.Key.Value = m.Value.Get(o);
+
+ affected += cmd.ExecuteNonQuery();
+ }
+
+ tra.Commit();
+ }
+ catch (Exception ex)
+ {
+ if (tra != null)
+ tra.Rollback();
+
+ affected = 0;
+
+ var problematicCommand = new StringBuilder();
+ cmd.Dump(problematicCommand);
+
+ throw new InvalidOperationException(problematicCommand.ToString(), ex);
+ }
+ }
+ }
+
+ return affected;
+ }
+
+ /// Bulk update or insert objects into database.
+ /// Type of objects to update or insert.
+ /// Enumerable containing instances of objects to update or insert.
+ /// Number of updated or inserted rows.
+ public virtual int UpdateOrInsert(IEnumerable e) where T : class
+ {
+ int affected = 0;
+ var mapper = DynamicMapperCache.GetMapper(typeof(T));
+
+ if (mapper != null)
+ {
+ using (var con = Open())
+ using (var tra = con.BeginTransaction())
+ using (var cmdUp = con.CreateCommand())
+ using (var cmdIn = con.CreateCommand())
+ {
+ try
+ {
+ #region Update
+
+ var parametersUp = new Dictionary();
+
+ if (!string.IsNullOrEmpty(mapper.UpdateCommandText))
+ {
+ cmdUp.CommandText = mapper.UpdateCommandText;
+
+ foreach (var col in mapper.ColumnsMap.Values
+ .Where(di => !di.Ignore && di.UpdateCommandParameter != null)
+ .OrderBy(di => di.UpdateCommandParameter.Ordinal))
+ {
+ var para = cmdUp.CreateParameter();
+ para.ParameterName = col.UpdateCommandParameter.Name;
+ para.DbType = col.UpdateCommandParameter.Type;
+ cmdUp.Parameters.Add(para);
+
+ parametersUp[para] = col;
+ }
+ }
+ else
+ {
+ DynamicPropertyInvoker currentprop = null;
+ var temp = new Dictionary();
+ int ord = 0;
+
+ var ib = Update()
+ .SetVirtualMode(true)
+ .CreateTemporaryParameterAction(p => temp[p.Name] = currentprop)
+ .CreateParameterAction((p, cp) =>
+ {
+ parametersUp[cp] = temp[p.Name];
+ parametersUp[cp].UpdateCommandParameter = new DynamicPropertyInvoker.ParameterSpec
+ {
+ Name = cp.ParameterName,
+ Type = cp.DbType,
+ Ordinal = ord++,
+ };
+ });
+
+ foreach (var prop in mapper.PropertyMap)
+ if (!mapper.Ignored.Contains(prop.Key))
+ {
+ var col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key;
+ currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower());
+
+ if (currentprop.Ignore)
+ continue;
+
+ if (currentprop.Get != null)
+ {
+ if (currentprop.Column != null && currentprop.Column.IsKey)
+ ib.Where(col, null);
+ else
+ ib.Values(col, null);
+ }
+ }
+
+ ib.FillCommand(cmdUp);
+
+ // Cache command
+ mapper.UpdateCommandText = cmdUp.CommandText;
+ }
+
+ #endregion Update
+
+ #region Insert
+
+ var parametersIn = new Dictionary();
+
+ if (!string.IsNullOrEmpty(mapper.InsertCommandText))
+ {
+ cmdIn.CommandText = mapper.InsertCommandText;
+
+ foreach (var col in mapper.ColumnsMap.Values
+ .Where(di => !di.Ignore && di.InsertCommandParameter != null)
+ .OrderBy(di => di.InsertCommandParameter.Ordinal))
+ {
+ var para = cmdIn.CreateParameter();
+ para.ParameterName = col.InsertCommandParameter.Name;
+ para.DbType = col.InsertCommandParameter.Type;
+ cmdIn.Parameters.Add(para);
+
+ parametersIn[para] = col;
+ }
+ }
+ else
+ {
+ DynamicPropertyInvoker currentprop = null;
+ var temp = new Dictionary();
+ int ord = 0;
+
+ var ib = Insert()
+ .SetVirtualMode(true)
+ .CreateTemporaryParameterAction(p => temp[p.Name] = currentprop)
+ .CreateParameterAction((p, cp) =>
+ {
+ parametersIn[cp] = temp[p.Name];
+ parametersIn[cp].InsertCommandParameter = new DynamicPropertyInvoker.ParameterSpec
+ {
+ Name = cp.ParameterName,
+ Type = cp.DbType,
+ Ordinal = ord++,
+ };
+ });
+
+ foreach (var prop in mapper.PropertyMap)
+ if (!mapper.Ignored.Contains(prop.Key))
+ {
+ var col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key;
+ currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower());
+
+ if (currentprop.Ignore)
+ continue;
+
+ if (currentprop.Get != null)
+ ib.Insert(col, null);
+ }
+
+ ib.FillCommand(cmdIn);
+
+ // Cache command
+ mapper.InsertCommandText = cmdIn.CommandText;
+ }
+
+ #endregion Insert
+
+ foreach (var o in e)
+ {
+ foreach (var m in parametersUp)
+ m.Key.Value = m.Value.Get(o);
+
+ int a = cmdUp.ExecuteNonQuery();
+ if (a == 0)
+ {
+ foreach (var m in parametersIn)
+ m.Key.Value = m.Value.Get(o);
+
+ a = cmdIn.ExecuteNonQuery();
+ }
+
+ affected += a;
+ }
+
+ tra.Commit();
+ }
+ catch (Exception ex)
+ {
+ if (tra != null)
+ tra.Rollback();
+
+ affected = 0;
+
+ var problematicCommand = new StringBuilder();
+ cmdUp.Dump(problematicCommand);
+
+ throw new InvalidOperationException(problematicCommand.ToString(), ex);
+ }
+ }
+ }
+
+ return affected;
+ }
+
+ ///
+ /// Adds to the DELETE FROM clause the contents obtained by parsing the dynamic lambda expressions given. The supported
+ /// formats are:
+ /// - Resolve to a string: x => "owner.Table".
+ /// - Resolve to a type: x => typeof(SomeClass).
+ /// - Resolve to an expression: x => x.owner.Table.
+ /// - Generic expression: x => x( expression ). Expression can
+ /// be or .
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ public virtual IDynamicDeleteQueryBuilder Delete(Func func)
+ {
+ return new DynamicDeleteQueryBuilder(this).Table(func);
+ }
+
+ /// Adds to the DELETE FROM clause using .
+ /// Type which can be represented in database.
+ /// This instance to permit chaining.
+ public virtual IDynamicDeleteQueryBuilder Delete()
+ {
+ return new DynamicDeleteQueryBuilder(this).Table(typeof(T));
+ }
+
+ /// Bulk delete objects in database.
+ /// Type of objects to delete.
+ /// Enumerable containing instances of objects to delete.
+ /// Number of deleted rows.
+ public virtual int Delete(IEnumerable e) where T : class
+ {
+ int affected = 0;
+ var mapper = DynamicMapperCache.GetMapper(typeof(T));
+
+ if (mapper != null)
+ {
+ using (var con = Open())
+ using (var tra = con.BeginTransaction())
+ using (var cmd = con.CreateCommand())
+ {
+ try
+ {
+ var parameters = new Dictionary();
+
+ if (!string.IsNullOrEmpty(mapper.DeleteCommandText))
+ {
+ cmd.CommandText = mapper.DeleteCommandText;
+
+ foreach (var col in mapper.ColumnsMap.Values
+ .Where(di => !di.Ignore && di.DeleteCommandParameter != null)
+ .OrderBy(di => di.DeleteCommandParameter.Ordinal))
+ {
+ var para = cmd.CreateParameter();
+ para.ParameterName = col.DeleteCommandParameter.Name;
+ para.DbType = col.DeleteCommandParameter.Type;
+ cmd.Parameters.Add(para);
+
+ parameters[para] = col;
+ }
+ }
+ else
+ {
+ DynamicPropertyInvoker currentprop = null;
+ var temp = new Dictionary();
+ int ord = 0;
+
+ var ib = Delete()
+ .SetVirtualMode(true)
+ .CreateTemporaryParameterAction(p => temp[p.Name] = currentprop)
+ .CreateParameterAction((p, cp) =>
+ {
+ parameters[cp] = temp[p.Name];
+ parameters[cp].DeleteCommandParameter = new DynamicPropertyInvoker.ParameterSpec
+ {
+ Name = cp.ParameterName,
+ Type = cp.DbType,
+ Ordinal = ord++,
+ };
+ });
+
+ foreach (var prop in mapper.PropertyMap)
+ if (!mapper.Ignored.Contains(prop.Key))
+ {
+ var col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key;
+ currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower());
+
+ if (currentprop.Ignore)
+ continue;
+
+ if (currentprop.Get != null)
+ {
+ if (currentprop.Column != null && currentprop.Column.IsKey)
+ ib.Where(col, null);
+ }
+ }
+
+ ib.FillCommand(cmd);
+
+ // Cache command
+ mapper.DeleteCommandText = cmd.CommandText;
+ }
+
+ foreach (var o in e)
+ {
+ foreach (var m in parameters)
+ m.Key.Value = m.Value.Get(o);
+
+ affected += cmd.ExecuteNonQuery();
+ }
+
+ tra.Commit();
+ }
+ catch (Exception ex)
+ {
+ if (tra != null)
+ tra.Rollback();
+
+ affected = 0;
+
+ var problematicCommand = new StringBuilder();
+ cmd.Dump(problematicCommand);
+
+ throw new InvalidOperationException(problematicCommand.ToString(), ex);
+ }
+ }
+ }
+
+ return affected;
+ }
+
+ #endregion From/Insert/Update/Delete
+
+ #region Schema
+
+ /// Builds table cache if necessary and returns it.
+ /// Name of table for which build schema.
+ /// Owner of table for which build schema.
+ /// Table schema.
+ public Dictionary GetSchema(string table, string owner = null)
+ {
+ Dictionary schema = null;
+
+ lock (SyncLock)
+ schema = Schema.TryGetValue(table.ToLower()) ??
+ BuildAndCacheSchema(table, null, owner);
+
+ return schema;
+ }
+
+ /// Builds table cache if necessary and returns it.
+ /// Type of table for which build schema.
+ /// Table schema or null if type was anonymous.
+ public Dictionary GetSchema()
+ {
+ if (typeof(T).IsAnonymous())
+ return null;
+
+ Dictionary schema = null;
+
+ lock (SyncLock)
+ schema = Schema.TryGetValue(typeof(T).GetType().FullName) ??
+ BuildAndCacheSchema(null, DynamicMapperCache.GetMapper());
+
+ return schema;
+ }
+
+ /// Builds table cache if necessary and returns it.
+ /// Type of table for which build schema.
+ /// Table schema or null if type was anonymous.
+ public Dictionary GetSchema(Type table)
+ {
+ if (table == null || table.IsAnonymous() || table.IsValueType)
+ return null;
+
+ Dictionary schema = null;
+
+ lock (SyncLock)
+ schema = Schema.TryGetValue(table.FullName) ??
+ BuildAndCacheSchema(null, DynamicMapperCache.GetMapper(table));
+
+ return schema;
+ }
+
+ /// Get schema describing objects from reader.
+ /// Table from which extract column info.
+ /// Owner of table from which extract column info.
+ /// List of objects .
+ /// If your database doesn't get those values in upper case (like most of the databases) you should override this method.
+ protected virtual IEnumerable ReadSchema(string table, string owner)
+ {
+ using (var con = Open())
+ using (var cmd = con.CreateCommand())
+ {
+ using (var rdr = cmd
+ .SetCommand(string.Format("SELECT * FROM {0}{1} WHERE 1 = 0",
+ !string.IsNullOrEmpty(owner) ? string.Format("{0}.", DecorateName(owner)) : string.Empty,
+ DecorateName(table)))
+ .ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo))
+ foreach (DataRow col in rdr.GetSchemaTable().Rows)
+ {
+ var c = col.RowToDynamicUpper();
+
+ yield return new DynamicSchemaColumn
+ {
+ Name = c.COLUMNNAME,
+ Type = DynamicExtensions.TypeMap.TryGetNullable((Type)c.DATATYPE) ?? DbType.String,
+ IsKey = c.ISKEY ?? false,
+ IsUnique = c.ISUNIQUE ?? false,
+ Size = (int)(c.COLUMNSIZE ?? 0),
+ Precision = (byte)(c.NUMERICPRECISION ?? 0),
+ Scale = (byte)(c.NUMERICSCALE ?? 0)
+ };
+ }
+ }
+ }
+
+ private Dictionary BuildAndCacheSchema(string tableName, DynamicTypeMap mapper, string owner = null)
+ {
+ Dictionary schema = null;
+
+ if (mapper != null)
+ tableName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ?
+ mapper.Type.Name : mapper.Table.Name;
+
+ bool databaseSchemaSupport = !string.IsNullOrEmpty(tableName) &&
+ (Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema;
+ bool mapperSchema = mapper != null && mapper.Table != null && (mapper.Table.Override || !databaseSchemaSupport);
+
+ #region Database schema
+
+ if (databaseSchemaSupport && !Schema.ContainsKey(tableName.ToLower()))
+ {
+ schema = ReadSchema(tableName, owner)
+ .ToDictionary(k => k.Name.ToLower(), k => k);
+
+ Schema[tableName.ToLower()] = schema;
+ }
+
+ #endregion Database schema
+
+ #region Type schema
+
+ if (mapperSchema && !Schema.ContainsKey(mapper.Type.FullName))
+ {
+ // TODO: Ged rid of this monster below...
+ if (databaseSchemaSupport)
+ {
+ #region Merge with db schema
+
+ schema = mapper.ColumnsMap.ToDictionary(k => k.Key, (v) =>
+ {
+ DynamicSchemaColumn? col = Schema[tableName.ToLower()].TryGetNullable(v.Key);
+
+ return new DynamicSchemaColumn
+ {
+ Name = DynamicExtensions.Coalesce(
+ v.Value.Column == null || string.IsNullOrEmpty(v.Value.Column.Name) ? null : v.Value.Column.Name,
+ col.HasValue && !string.IsNullOrEmpty(col.Value.Name) ? col.Value.Name : null,
+ v.Value.Name),
+ IsKey = DynamicExtensions.CoalesceNullable(
+ v.Value.Column != null ? v.Value.Column.IsKey : false,
+ col.HasValue ? col.Value.IsKey : false).Value,
+ Type = DynamicExtensions.CoalesceNullable(
+ v.Value.Column != null ? v.Value.Column.Type : null,
+ col.HasValue ? col.Value.Type : DbType.String).Value,
+ IsUnique = DynamicExtensions.CoalesceNullable(
+ v.Value.Column != null ? v.Value.Column.IsUnique : null,
+ col.HasValue ? col.Value.IsUnique : false).Value,
+ Size = DynamicExtensions.CoalesceNullable(
+ v.Value.Column != null ? v.Value.Column.Size : null,
+ col.HasValue ? col.Value.Size : 0).Value,
+ Precision = DynamicExtensions.CoalesceNullable(
+ v.Value.Column != null ? v.Value.Column.Precision : null,
+ col.HasValue ? col.Value.Precision : (byte)0).Value,
+ Scale = DynamicExtensions.CoalesceNullable(
+ v.Value.Column != null ? v.Value.Column.Scale : null,
+ col.HasValue ? col.Value.Scale : (byte)0).Value,
+ };
+ });
+
+ #endregion Merge with db schema
+ }
+ else
+ {
+ #region MapEnumerable based only on type
+
+ schema = mapper.ColumnsMap.ToDictionary(k => k.Key,
+ v => new DynamicSchemaColumn
+ {
+ Name = DynamicExtensions.Coalesce(v.Value.Column == null || string.IsNullOrEmpty(v.Value.Column.Name) ? null : v.Value.Column.Name, v.Value.Name),
+ IsKey = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.IsKey : false, false).Value,
+ Type = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Type : null, DbType.String).Value,
+ IsUnique = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.IsUnique : null, false).Value,
+ Size = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Size : null, 0).Value,
+ Precision = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Precision : null, 0).Value,
+ Scale = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Scale : null, 0).Value,
+ });
+
+ #endregion MapEnumerable based only on type
+ }
+ }
+
+ if (mapper != null && schema != null)
+ Schema[mapper.Type.FullName] = schema;
+
+ #endregion Type schema
+
+ return schema;
+ }
+
+ #endregion Schema
+
+ #region Decorators
+
+ /// Gets or sets left side decorator for database objects.
+ public string LeftDecorator
+ {
+ get { return _leftDecorator; }
+ set
+ {
+ _leftDecorator = value;
+ _leftDecoratorIsInInvalidMembersChars =
+ _leftDecorator.Length == 1 && StringExtensions.InvalidMemberChars.Contains(_leftDecorator[0]);
+ }
+ }
+
+ /// Gets or sets right side decorator for database objects.
+ public string RightDecorator
+ {
+ get { return _rightDecorator; }
+ set
+ {
+ _rightDecorator = value;
+ _rightDecoratorIsInInvalidMembersChars =
+ _rightDecorator.Length == 1 && StringExtensions.InvalidMemberChars.Contains(_rightDecorator[0]);
+ }
+ }
+
+ /// Gets or sets parameter name format.
+ public string ParameterFormat { get { return _parameterFormat; } set { _parameterFormat = value; } }
+
+ /// Decorate string representing name of database object.
+ /// Name of database object.
+ /// Decorated name of database object.
+ public string DecorateName(string name)
+ {
+ return String.Concat(_leftDecorator, name, _rightDecorator);
+ }
+
+ /// Strip string representing name of database object from decorators.
+ /// Decorated name of database object.
+ /// Not decorated name of database object.
+ public string StripName(string name)
+ {
+ string res = name.Trim(StringExtensions.InvalidMemberChars);
+
+ if (!_leftDecoratorIsInInvalidMembersChars && res.StartsWith(_leftDecorator))
+ res = res.Substring(_leftDecorator.Length);
+
+ if (!_rightDecoratorIsInInvalidMembersChars && res.EndsWith(_rightDecorator))
+ res = res.Substring(0, res.Length - _rightDecorator.Length);
+
+ return res;
+ }
+
+ /// Decorate string representing name of database object.
+ /// String builder to which add decorated name.
+ /// Name of database object.
+ public void DecorateName(StringBuilder sb, string name)
+ {
+ sb.Append(_leftDecorator);
+ sb.Append(name);
+ sb.Append(_rightDecorator);
+ }
+
+ /// Get database parameter name.
+ /// Friendly parameter name or number.
+ /// Formatted parameter name.
+ public string GetParameterName(object parameter)
+ {
+ return String.Format(_parameterFormat, parameter).Replace(" ", "_");
+ }
+
+ /// Get database parameter name.
+ /// String builder to which add parameter name.
+ /// Friendly parameter name or number.
+ public void GetParameterName(StringBuilder sb, object parameter)
+ {
+ sb.AppendFormat(_parameterFormat, parameter.ToString().Replace(" ", "_"));
+ }
+
+ #endregion Decorators
+
+ #region Connection
+
+ /// Open managed connection.
+ /// Opened connection.
+ public IDbConnection Open()
+ {
+ IDbConnection conn = null;
+ DynamicConnection ret = null;
+ bool opened = false;
+
+ lock (SyncLock)
+ {
+ if (_tempConn == null)
+ {
+ if (TransactionPool.Count == 0 || !_singleConnection)
+ {
+ conn = _provider.CreateConnection();
+ conn.ConnectionString = _connectionString;
+ conn.Open();
+ opened = true;
+
+ TransactionPool.Add(conn, new Stack());
+ CommandsPool.Add(conn, new List());
+ }
+ else
+ {
+ conn = TransactionPool.Keys.First();
+
+ if (conn.State != ConnectionState.Open)
+ {
+ conn.Open();
+ opened = true;
+ }
+ }
+
+ ret = new DynamicConnection(this, conn, _singleTransaction);
+ }
+ else
+ ret = _tempConn;
+ }
+
+ if (opened)
+ ExecuteInitCommands(ret);
+
+ return ret;
+ }
+
+ /// Close connection if we are allowed to.
+ /// Connection to manage.
+ internal void Close(IDbConnection connection)
+ {
+ if (connection == null)
+ return;
+
+ if (!_singleConnection && connection != null && TransactionPool.ContainsKey(connection))
+ {
+ // Close all commands
+ if (CommandsPool.ContainsKey(connection))
+ {
+ var tmp = CommandsPool[connection].ToList();
+ tmp.ForEach(cmd => cmd.Dispose());
+
+ CommandsPool[connection].Clear();
+ }
+
+ // Rollback remaining transactions
+ while (TransactionPool[connection].Count > 0)
+ {
+ IDbTransaction trans = TransactionPool[connection].Pop();
+ trans.Rollback();
+ trans.Dispose();
+ }
+
+ // Close connection
+ if (connection.State == ConnectionState.Open)
+ connection.Close();
+
+ // remove from pools
+ lock (SyncLock)
+ {
+ TransactionPool.Remove(connection);
+ CommandsPool.Remove(connection);
+ }
+
+ // Set stamp
+ _poolStamp = DateTime.Now.Ticks;
+
+ // Dispose the corpse
+ connection.Dispose();
+ }
+ }
+
+ /// Gets or sets contains commands executed when connection is opened.
+ public List InitCommands { get; set; }
+
+ private void ExecuteInitCommands(IDbConnection conn)
+ {
+ if (InitCommands != null)
+ using (IDbCommand command = conn.CreateCommand())
+ foreach (string commandText in InitCommands)
+ command
+ .SetCommand(commandText)
+ .ExecuteNonQuery();
+ }
+
+ #endregion Connection
+
+ #region Transaction
+
+ /// Begins a global database transaction.
+ /// Using this method connection is set to single open
+ /// connection until all transactions are finished.
+ /// Returns representation.
+ public IDbTransaction BeginTransaction()
+ {
+ _tempConn = Open() as DynamicConnection;
+
+ return _tempConn.BeginTransaction(null, () =>
+ {
+ var t = TransactionPool.TryGetValue(_tempConn.Connection);
+
+ if (t == null | t.Count == 0)
+ {
+ _tempConn.Dispose();
+ _tempConn = null;
+ }
+ });
+ }
+
+ #endregion Transaction
+
+ #region IExtendedDisposable Members
+
+ /// Performs application-defined tasks associated with freeing,
+ /// releasing, or resetting unmanaged resources.
+ public void Dispose()
+ {
+#if !DYNAMORM_OMMIT_OLDSYNTAX
+ var tables = TablesCache.Values.ToList();
+ TablesCache.Clear();
+
+ tables.ForEach(t => t.Dispose());
+#endif
+
+ foreach (var con in TransactionPool)
+ {
+ // Close all commands
+ if (CommandsPool.ContainsKey(con.Key))
+ {
+ var tmp = CommandsPool[con.Key].ToList();
+ tmp.ForEach(cmd => cmd.Dispose());
+
+ CommandsPool[con.Key].Clear();
+ }
+
+ // Rollback remaining transactions
+ while (con.Value.Count > 0)
+ {
+ IDbTransaction trans = con.Value.Pop();
+ trans.Rollback();
+ trans.Dispose();
+ }
+
+ // Close connection
+ if (con.Key.State == ConnectionState.Open)
+ con.Key.Close();
+
+ // Dispose it
+ con.Key.Dispose();
+ }
+
+ // Clear pools
+ lock (SyncLock)
+ {
+ TransactionPool.Clear();
+ CommandsPool.Clear();
+ }
+
+ IsDisposed = true;
+ }
+
+ /// Gets a value indicating whether this instance is disposed.
+ public bool IsDisposed { get; private set; }
+
+ #endregion IExtendedDisposable Members
+ }
+
+ /// Represents database connection options.
+ [Flags]
+ [System.Reflection.ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)]
+ public enum DynamicDatabaseOptions
+ {
+ /// No specific options.
+ None = 0x00000000,
+
+ /// Only single persistent database connection.
+ SingleConnection = 0x00000001,
+
+ /// Only one transaction.
+ SingleTransaction = 0x00000002,
+
+ /// Database supports top syntax (SELECT TOP x ... FROM ...).
+ SupportTop = 0x00000080,
+
+ /// Database supports limit offset syntax (SELECT ... FROM ... LIMIT x OFFSET y).
+ SupportLimitOffset = 0x00000040,
+
+ /// Database support standard schema.
+ SupportSchema = 0x00000010,
+
+ /// Database support stored procedures (EXEC procedure ...).
+ SupportStoredProcedures = 0x00000020,
+
+ /// Debug option allowing to enable command dumps by default.
+ DumpCommands = 0x01000000,
+ }
+
+ /// Extension to ORM objects.
+ public static class DynamicExtensions
+ {
+ #region Type column map
+
+ /// MapEnumerable of .NET types to .
+ public static readonly Dictionary TypeMap = new Dictionary()
+ {
+ { typeof(byte), DbType.Byte },
+ { typeof(sbyte), DbType.SByte },
+ { typeof(short), DbType.Int16 },
+ { typeof(ushort), DbType.UInt16 },
+ { typeof(int), DbType.Int32 },
+ { typeof(uint), DbType.UInt32 },
+ { typeof(long), DbType.Int64 },
+ { typeof(ulong), DbType.UInt64 },
+ { typeof(float), DbType.Single },
+ { typeof(double), DbType.Double },
+ { typeof(decimal), DbType.Decimal },
+ { typeof(bool), DbType.Boolean },
+ { typeof(string), DbType.String },
+ { typeof(char), DbType.StringFixedLength },
+ { typeof(Guid), DbType.Guid },
+ { typeof(DateTime), DbType.DateTime },
+ { typeof(DateTimeOffset), DbType.DateTimeOffset },
+ { typeof(byte[]), DbType.Binary },
+ { typeof(byte?), DbType.Byte },
+ { typeof(sbyte?), DbType.SByte },
+ { typeof(short?), DbType.Int16 },
+ { typeof(ushort?), DbType.UInt16 },
+ { typeof(int?), DbType.Int32 },
+ { typeof(uint?), DbType.UInt32 },
+ { typeof(long?), DbType.Int64 },
+ { typeof(ulong?), DbType.UInt64 },
+ { typeof(float?), DbType.Single },
+ { typeof(double?), DbType.Double },
+ { typeof(decimal?), DbType.Decimal },
+ { typeof(bool?), DbType.Boolean },
+ { typeof(char?), DbType.StringFixedLength },
+ { typeof(Guid?), DbType.Guid },
+ { typeof(DateTime?), DbType.DateTime },
+ { typeof(DateTimeOffset?), DbType.DateTimeOffset }
+ };
+
+ #endregion Type column map
+
+ #region Command extensions
+
+ /// Set connection on the fly.
+ /// in which changes will be made.
+ /// which will be set to instance.
+ /// Returns edited instance.
+ public static IDbCommand SetConnection(this IDbCommand command, IDbConnection connection)
+ {
+ command.Connection = connection;
+
+ return command;
+ }
+
+ /// Set connection on the fly.
+ /// in which changes will be made.
+ /// which will be set to instance.
+ /// Returns edited instance.
+ public static IDbCommand SetTransaction(this IDbCommand command, IDbTransaction transaction)
+ {
+ command.Transaction = transaction;
+
+ return command;
+ }
+
+ #region SetCommand
+
+ /// Set properties on the fly.
+ /// in which changes will be made.
+ /// Indicates or specifies how the property is interpreted.
+ /// The wait time before terminating the attempt to execute a command and generating an error.
+ /// The text command to run against the data source.
+ /// Arguments used to format command.
+ /// Returns edited instance.
+ public static IDbCommand SetCommand(this IDbCommand command, CommandType commandType, int commandTimeout, string commandText, params object[] args)
+ {
+ command.CommandType = commandType;
+ command.CommandTimeout = commandTimeout;
+
+ if (args != null && args.Length > 0)
+ command.CommandText = string.Format(commandText, args);
+ else
+ command.CommandText = commandText;
+
+ return command;
+ }
+
+ /// Set properties on the fly.
+ /// in which changes will be made.
+ /// The wait time before terminating the attempt to execute a command and generating an error.
+ /// The text command to run against the data source.
+ /// Arguments used to format command.
+ /// Returns edited instance.
+ public static IDbCommand SetCommand(this IDbCommand command, int commandTimeout, string commandText, params object[] args)
+ {
+ command.CommandTimeout = commandTimeout;
+
+ if (args != null && args.Length > 0)
+ command.CommandText = string.Format(commandText, args);
+ else
+ command.CommandText = commandText;
+
+ return command;
+ }
+
+ /// Set properties on the fly.
+ /// in which changes will be made.
+ /// Indicates or specifies how the property is interpreted.
+ /// The text command to run against the data source.
+ /// Arguments used to format command.
+ /// Returns edited instance.
+ public static IDbCommand SetCommand(this IDbCommand command, CommandType commandType, string commandText, params object[] args)
+ {
+ command.CommandType = commandType;
+
+ if (args != null && args.Length > 0)
+ command.CommandText = string.Format(commandText, args);
+ else
+ command.CommandText = commandText;
+
+ return command;
+ }
+
+ /// Set properties on the fly.
+ /// in which changes will be made.
+ /// The text command to run against the data source.
+ /// Arguments used to format command.
+ /// Returns edited instance.
+ public static IDbCommand SetCommand(this IDbCommand command, string commandText, params object[] args)
+ {
+ if (args != null && args.Length > 0)
+ command.CommandText = string.Format(commandText, args);
+ else
+ command.CommandText = commandText;
+
+ return command;
+ }
+
+ /// Set properties on the fly.
+ /// in which changes will be made.
+ /// Command builder.
+ /// Returns edited instance.
+ public static IDbCommand SetCommand(this IDbCommand command, IDynamicQueryBuilder builder)
+ {
+ builder.FillCommand(command);
+
+ return command;
+ }
+
+ #endregion SetCommand
+
+ #region AddParameter
+
+ /// Extension method for adding in a bunch of parameters.
+ /// Command to handle.
+ /// Database object required to get proper formatting.
+ /// Items to add.
+ /// Returns edited instance.
+ public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, params object[] args)
+ {
+ if (args != null && args.Count() > 0)
+ foreach (var item in args)
+ cmd.AddParameter(database, item);
+
+ return cmd;
+ }
+
+ /// Extension method for adding in a bunch of parameters.
+ /// Command to handle.
+ /// Database object required to get proper formatting.
+ /// Items to add in an expando object.
+ /// Returns edited instance.
+ public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, ExpandoObject args)
+ {
+ if (args != null && args.Count() > 0)
+ foreach (var item in args.ToDictionary())
+ cmd.AddParameter(database, item.Key, item.Value);
+
+ return cmd;
+ }
+
+ /// Extension for adding single parameter determining only type of object.
+ /// Command to handle.
+ /// Database object required to get proper formatting.
+ /// Items to add.
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand cmd, DynamicDatabase database, object item)
+ {
+ return cmd.AddParameter(database, database.GetParameterName(cmd.Parameters.Count), item);
+ }
+
+ /// Extension for adding single parameter determining only type of object.
+ /// Command to handle.
+ /// Database object required to get proper formatting.
+ /// Name of parameter.
+ /// Items to add.
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand cmd, DynamicDatabase database, string name, object item)
+ {
+ var p = cmd.CreateParameter();
+ p.ParameterName = name;
+
+ if (item == null || item == DBNull.Value)
+ p.Value = DBNull.Value;
+ else
+ {
+ Type type = item.GetType();
+
+ p.DbType = TypeMap.TryGetNullable(type) ?? DbType.String;
+
+ if (type == typeof(ExpandoObject))
+ p.Value = ((IDictionary)item).Values.FirstOrDefault();
+ else
+ p.Value = item;
+
+ if (p.DbType == DbType.String)
+ p.Size = item.ToString().Length > 4000 ? -1 : 4000;
+ }
+
+ cmd.Parameters.Add(p);
+
+ return cmd;
+ }
+
+ /// Extension for adding single parameter determining only type of object.
+ /// Command to handle.
+ /// Query builder containing schema.
+ /// Column schema to use.
+ /// Parameter value.
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand cmd, IDynamicQueryBuilder builder, DynamicSchemaColumn? col, object value)
+ {
+ var p = cmd.CreateParameter();
+ p.ParameterName = builder.Database.GetParameterName(cmd.Parameters.Count);
+
+ if (col.HasValue)
+ {
+ p.DbType = col.Value.Type;
+
+ if ((builder.Database.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema)
+ {
+ p.Size = col.Value.Size;
+ p.Precision = col.Value.Precision;
+ p.Scale = col.Value.Scale;
+
+ // Quick fix - review that
+ // Quick fix 2 - use item.Schema in that case
+ if (p.Scale > p.Precision)
+ p.Scale = 4;
+ }
+
+ p.Value = value == null ? DBNull.Value : value;
+ }
+ else if (value == null || value == DBNull.Value)
+ p.Value = DBNull.Value;
+ else
+ {
+ p.DbType = TypeMap.TryGetNullable(value.GetType()) ?? DbType.String;
+
+ if (p.DbType == DbType.String)
+ p.Size = value.ToString().Length > 4000 ? -1 : 4000;
+
+ p.Value = value;
+ }
+
+ cmd.Parameters.Add(p);
+
+ return cmd;
+ }
+
+ /// Extension for adding single parameter determining only type of object.
+ /// Command to handle.
+ /// Query builder containing schema.
+ /// Column item to add.
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand cmd, IDynamicQueryBuilder builder, DynamicColumn item)
+ {
+ var p = cmd.CreateParameter();
+ p.ParameterName = builder.Database.GetParameterName(cmd.Parameters.Count);
+
+ var col = item.Schema ?? (builder as DynamicQueryBuilder)
+ .NullOr(b => b.GetColumnFromSchema(item.ColumnName),
+ builder.Tables.FirstOrDefault()
+ .NullOr(t => t.Schema
+ .NullOr(s => s.TryGetNullable(item.ColumnName.ToLower()), null), null));
+
+ if (col.HasValue)
+ {
+ p.DbType = col.Value.Type;
+
+ if (builder.SupportSchema)
+ {
+ p.Size = col.Value.Size;
+ p.Precision = col.Value.Precision;
+ p.Scale = col.Value.Scale;
+
+ // Quick fix - review that
+ // Quick fix 2 - use item.Schema in that case
+ if (p.Scale > p.Precision)
+ p.Scale = 4;
+ }
+
+ p.Value = item.Value == null ? DBNull.Value : item.Value;
+ }
+ else if (item.Value == null || item.Value == DBNull.Value)
+ p.Value = DBNull.Value;
+ else
+ {
+ p.DbType = TypeMap.TryGetNullable(item.Value.GetType()) ?? DbType.String;
+
+ if (p.DbType == DbType.String)
+ p.Size = item.Value.ToString().Length > 4000 ? -1 : 4000;
+
+ p.Value = item.Value;
+ }
+
+ cmd.Parameters.Add(p);
+
+ return cmd;
+ }
+
+ /// Add to on the fly.
+ /// to which parameter will be added.
+ /// The name of the .
+ /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value .
+ /// The of the .
+ /// The size of the parameter.
+ /// Indicates the precision of numeric parameters.
+ /// Indicates the scale of numeric parameters.
+ /// The value of the .
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, byte precision, byte scale, object value)
+ {
+ IDbDataParameter param = command.CreateParameter();
+ param.ParameterName = parameterName;
+ param.Direction = parameterDirection;
+ param.DbType = databaseType;
+ param.Size = size;
+ param.Precision = precision;
+ param.Scale = scale;
+ param.Value = value;
+ command.Parameters.Add(param);
+
+ return command;
+ }
+
+ /// Add to on the fly.
+ /// to which parameter will be added.
+ /// The name of the .
+ /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value .
+ /// The of the .
+ /// The size of the parameter.
+ /// Indicates the precision of numeric parameters.
+ /// Indicates the scale of numeric parameters.
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, byte precision, byte scale)
+ {
+ IDbDataParameter param = command.CreateParameter();
+ param.ParameterName = parameterName;
+ param.Direction = parameterDirection;
+ param.DbType = databaseType;
+ param.Size = size;
+ param.Precision = precision;
+ param.Scale = scale;
+ command.Parameters.Add(param);
+
+ return command;
+ }
+
+ /// Add to on the fly.
+ /// to which parameter will be added.
+ /// The name of the .
+ /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value .
+ /// The of the .
+ /// Indicates the precision of numeric parameters.
+ /// Indicates the scale of numeric parameters.
+ /// The value of the .
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, byte precision, byte scale, object value)
+ {
+ IDbDataParameter param = command.CreateParameter();
+ param.ParameterName = parameterName;
+ param.Direction = parameterDirection;
+ param.DbType = databaseType;
+ param.Precision = precision;
+ param.Scale = scale;
+ param.Value = value;
+ command.Parameters.Add(param);
+
+ return command;
+ }
+
+ /// Add to on the fly.
+ /// to which parameter will be added.
+ /// The name of the .
+ /// The of the .
+ /// Indicates the precision of numeric parameters.
+ /// Indicates the scale of numeric parameters.
+ /// The value of the .
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, byte precision, byte scale, object value)
+ {
+ IDbDataParameter param = command.CreateParameter();
+ param.ParameterName = parameterName;
+ param.DbType = databaseType;
+ param.Precision = precision;
+ param.Scale = scale;
+ param.Value = value;
+ command.Parameters.Add(param);
+
+ return command;
+ }
+
+ /// Add to on the fly.
+ /// to which parameter will be added.
+ /// The name of the .
+ /// The of the .
+ /// Indicates the precision of numeric parameters.
+ /// Indicates the scale of numeric parameters.
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, byte precision, byte scale)
+ {
+ IDbDataParameter param = command.CreateParameter();
+ param.ParameterName = parameterName;
+ param.DbType = databaseType;
+ param.Precision = precision;
+ param.Scale = scale;
+ command.Parameters.Add(param);
+
+ return command;
+ }
+
+ /// Add to on the fly.
+ /// to which parameter will be added.
+ /// The name of the .
+ /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value .
+ /// The of the .
+ /// The size of the parameter.
+ /// The value of the .
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, object value)
+ {
+ IDbDataParameter param = command.CreateParameter();
+ param.ParameterName = parameterName;
+ param.Direction = parameterDirection;
+ param.DbType = databaseType;
+ param.Size = size;
+ param.Value = value;
+ command.Parameters.Add(param);
+
+ return command;
+ }
+
+ /// Add to on the fly.
+ /// to which parameter will be added.
+ /// The name of the .
+ /// The of the .
+ /// The size of the parameter.
+ /// The value of the .
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, int size, object value)
+ {
+ IDbDataParameter param = command.CreateParameter();
+ param.ParameterName = parameterName;
+ param.DbType = databaseType;
+ param.Size = size;
+ param.Value = value;
+ command.Parameters.Add(param);
+
+ return command;
+ }
+
+ /// Add to on the fly.
+ /// to which parameter will be added.
+ /// The name of the .
+ /// The of the .
+ /// The value of the .
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, object value)
+ {
+ IDbDataParameter param = command.CreateParameter();
+ param.ParameterName = parameterName;
+ param.DbType = databaseType;
+ param.Value = value;
+ command.Parameters.Add(param);
+
+ return command;
+ }
+
+ /// Add to on the fly.
+ /// to which parameter will be added.
+ /// The name of the .
+ /// The of the .
+ /// The size of the parameter.
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, int size)
+ {
+ IDbDataParameter param = command.CreateParameter();
+ param.ParameterName = parameterName;
+ param.DbType = databaseType;
+ param.Size = size;
+ command.Parameters.Add(param);
+
+ return command;
+ }
+
+ /// Add to on the fly.
+ /// to which parameter will be added.
+ /// The name of the .
+ /// The of the .
+ /// Returns edited instance.
+ public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType)
+ {
+ IDbDataParameter param = command.CreateParameter();
+ param.ParameterName = parameterName;
+ param.DbType = databaseType;
+ command.Parameters.Add(param);
+
+ return command;
+ }
+
+ #endregion AddParameter
+
+ #region SetParameter
+
+ /// Set value for on the fly.
+ /// to which parameter will be added.
+ /// The name of the .
+ /// Value to set on this parameter.
+ /// Returns edited instance.
+ public static IDbCommand SetParameter(this IDbCommand command, string parameterName, object value)
+ {
+ ((IDbDataParameter)command.Parameters[parameterName]).Value = value;
+
+ return command;
+ }
+
+ /// Set value for on the fly.
+ /// to which parameter will be added.
+ /// Index of the .
+ /// Value to set on this parameter.
+ /// Returns edited instance.
+ public static IDbCommand SetParameter(this IDbCommand command, int index, object value)
+ {
+ ((IDbDataParameter)command.Parameters[index]).Value = value;
+
+ return command;
+ }
+
+ #endregion SetParameter
+
+ #region Generic Execution
+
+#if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE
+
+ /// Execute scalar and return string if possible.
+ /// Type to parse to.
+ /// which will be executed.
+ /// Returns resulting instance of T from query.
+ public static T ExecuteScalarAs(this IDbCommand command)
+ {
+ return ExecuteScalarAs(command, default(T), null);
+ }
+
+ /// Execute scalar and return string if possible.
+ /// Type to parse to.
+ /// which will be executed.
+ /// Handler of a try parse method.
+ /// Returns resulting instance of T from query.
+ public static T ExecuteScalarAs(this IDbCommand command, DynamicExtensions.TryParseHandler handler)
+ {
+ return ExecuteScalarAs(command, default(T), handler);
+ }
+
+ /// Execute scalar and return string if possible.
+ /// Type to parse to.
+ /// which will be executed.
+ /// Default result value.
+ /// Returns resulting instance of T from query.
+ public static T ExecuteScalarAs(this IDbCommand command, T defaultValue)
+ {
+ return ExecuteScalarAs(command, defaultValue, null);
+ }
+
+ /// Execute scalar and return string if possible.
+ /// Type to parse to.
+ /// which will be executed.
+ /// Default result value.
+ /// Handler of a try parse method.
+ /// Returns resulting instance of T from query.
+ public static T ExecuteScalarAs(this IDbCommand command, T defaultValue, DynamicExtensions.TryParseHandler handler)
+ {
+ T ret = defaultValue;
+
+ object o = command.ExecuteScalar();
+
+ if (o is T)
+ return (T)o;
+ else if (o != DBNull.Value && o != null)
+ {
+ var method = typeof(T).GetMethod(
+ "TryParse",
+ new[]
+ {
+ typeof(string),
+ Type.GetType(string.Format("{0}&", typeof(T).FullName))
+ });
+
+ if (handler != null)
+ ret = o.ToString().TryParseDefault(defaultValue, handler);
+ else if (method != null)
+ ret = o.ToString().TryParseDefault(defaultValue, delegate(string v, out T r)
+ {
+ r = defaultValue;
+ return (bool)method.Invoke(null, new object[] { v, r });
+ });
+ else if (typeof(T) == typeof(string))
+ ret = (T)(o.ToString() as object);
+ else if (typeof(T) == typeof(object))
+ ret = (T)o;
+ else
+ throw new InvalidOperationException("Provided type can't be parsed using generic approach.");
+ }
+
+ return ret;
+ }
+
+ /// Execute enumerator of specified type.
+ /// Type to parse to.
+ /// which will be executed.
+ /// Default result value.
+ /// Handler of a try parse method.
+ /// Returns enumerator of specified type from query.
+ public static IEnumerable ExecuteEnumeratorOf(this IDbCommand command, T defaultValue, DynamicExtensions.TryParseHandler handler) where T : struct
+ {
+ using (IDataReader reader = command.ExecuteReader())
+ {
+ var method = typeof(T).GetMethod(
+ "TryParse",
+ new[]
+ {
+ typeof(string),
+ Type.GetType(string.Format("{0}&", typeof(T).FullName))
+ });
+
+ while (reader.Read())
+ {
+ T ret = defaultValue;
+
+ if (!reader.IsDBNull(0))
+ {
+ object o = reader.GetValue(0);
+
+ if (o is T)
+ ret = (T)o;
+ else if (o != DBNull.Value)
+ {
+ if (handler != null)
+ ret = o.ToString().TryParseDefault(defaultValue, handler);
+ else if (method != null)
+ ret = o.ToString().TryParseDefault(defaultValue, delegate(string v, out T r)
+ {
+ r = defaultValue;
+ return (bool)method.Invoke(null, new object[] { v, r });
+ });
+ else if (typeof(T) == typeof(string))
+ ret = (T)(o.ToString() as object);
+ else if (typeof(T) == typeof(object))
+ ret = (T)o;
+ else
+ throw new InvalidOperationException("Provided type can't be parsed using generic approach.");
+ }
+ }
+
+ yield return ret;
+ }
+ }
+ }
+
+#endif
+
+ #endregion Generic Execution
+
+ /// Dump command into text writer.
+ /// Command to dump.
+ /// Builder to which write output.
+ /// Returns dumped instance.
+ public static IDbCommand Dump(this IDbCommand command, StringBuilder buider)
+ {
+ using (StringWriter sw = new StringWriter(buider))
+ return command.Dump(sw);
+ }
+
+ /// Dump command into text writer.
+ /// Command to dump.
+ /// Writer to which write output.
+ /// Returns dumped instance.
+ public static IDbCommand Dump(this IDbCommand command, TextWriter writer)
+ {
+ writer.WriteLine("Type: {0}; Timeout: {1}; Query: {2}", command.CommandType, command.CommandTimeout, command.CommandText);
+
+ if (command.Parameters.Count > 0)
+ {
+ writer.WriteLine("Parameters:");
+
+ foreach (IDbDataParameter param in command.Parameters)
+ {
+ writer.WriteLine(" '{0}' ({1} (s:{2} p:{3} s:{4})) = '{5}' ({6});",
+ param.ParameterName,
+ param.DbType,
+ param.Scale,
+ param.Precision,
+ param.Scale,
+ param.Value is byte[] ? ConvertByteArrayToHexString((byte[])param.Value) : param.Value ?? "NULL",
+ param.Value != null ? param.Value.GetType().Name : "DBNull");
+ }
+
+ writer.WriteLine();
+ }
+
+ return command;
+ }
+
+ /// Convert byte array to hex formatted string without separators.
+ /// Byte Array Data.
+ /// Hex string representation of byte array.
+ private static string ConvertByteArrayToHexString(byte[] data)
+ {
+ return ConvertByteArrayToHexString(data, 0);
+ }
+
+ /// Convert byte array to hex formatted string.
+ /// Byte Array Data.
+ /// Put '-' each separatorEach characters.
+ /// Hex string representation of byte array.
+ private static string ConvertByteArrayToHexString(byte[] data, int separatorEach)
+ {
+ int len = data.Length * 2;
+ System.Text.StringBuilder sb = new System.Text.StringBuilder();
+ for (int i = 0; i < len; i++)
+ {
+ sb.AppendFormat("{0:X}", data[(i / 2)] >> (((i % 2) == 0) ? 4 : 0) & 0x0F);
+ if ((separatorEach > 0) && ((i + 1) % separatorEach == 0))
+ sb.AppendFormat("-");
+ }
+
+ return sb.ToString();
+ }
+
+ #endregion Command extensions
+
+ #region Dynamic builders extensions
+
+ /// Turns an to a Dynamic list of things.
+ /// Ready to execute builder.
+ /// List of things.
+ public static List ToList(this IDynamicSelectQueryBuilder b)
+ {
+ return b.Execute().ToList();
+ }
+
+ /// Sets the on create temporary parameter action.
+ /// Class implementing interface.
+ /// The builder on which set delegate.
+ /// Action to invoke.
+ /// Returns instance of builder on which action is set.
+ public static T CreateTemporaryParameterAction(this T b, Action a) where T : IDynamicQueryBuilder
+ {
+ b.OnCreateTemporaryParameter = a;
+ return b;
+ }
+
+ /// Sets the on create real parameter action.
+ /// Class implementing interface.
+ /// The builder on which set delegate.
+ /// Action to invoke.
+ /// Returns instance of builder on which action is set.
+ public static T CreateParameterAction(this T b, Action a) where T : IDynamicQueryBuilder
+ {
+ b.OnCreateParameter = a;
+ return b;
+ }
+
+ /// Sets the virtual mode on builder.
+ /// Class implementing interface.
+ /// The builder on which set virtual mode.
+ /// Virtual mode.
+ /// Returns instance of builder on which virtual mode is set.
+ public static T SetVirtualMode(this T b, bool virtualMode) where T : IDynamicQueryBuilder
+ {
+ b.VirtualMode = virtualMode;
+ return b;
+ }
+
+ /// Creates sub query that can be used inside of from/join/expressions.
+ /// Class implementing interface.
+ /// The builder that will be parent of new sub query.
+ /// Instance of sub query.
+ public static IDynamicSelectQueryBuilder SubQuery(this T b) where T : IDynamicQueryBuilder
+ {
+ return new DynamicSelectQueryBuilder(b.Database, b as DynamicQueryBuilder);
+ }
+
+ /// Creates sub query that can be used inside of from/join/expressions.
+ /// Class implementing interface.
+ /// The builder that will be parent of new sub query.
+ /// The specification for sub query.
+ /// The specification for sub query.
+ /// Instance of sub query.
+ public static IDynamicSelectQueryBuilder SubQuery(this T b, Func fn, params Func[] func) where T : IDynamicQueryBuilder
+ {
+ return new DynamicSelectQueryBuilder(b.Database, b as DynamicQueryBuilder).From(fn, func);
+ }
+
+ /// Creates sub query that can be used inside of from/join/expressions.
+ /// Class implementing interface.
+ /// The builder that will be parent of new sub query.
+ /// First argument is parent query, second one is a sub query.
+ /// This instance to permit chaining.
+ public static T SubQuery(this T b, Action subquery) where T : IDynamicQueryBuilder
+ {
+ var sub = b.SubQuery();
+
+ subquery(b, sub);
+
+ (b as DynamicQueryBuilder).ParseCommand(sub as DynamicQueryBuilder, b.Parameters);
+
+ return b;
+ }
+
+ /// Creates sub query that can be used inside of from/join/expressions.
+ /// Class implementing interface.
+ /// The builder that will be parent of new sub query.
+ /// First argument is parent query, second one is a sub query.
+ /// The specification for sub query.
+ /// The specification for sub query.
+ /// This instance to permit chaining.
+ public static T SubQuery(this T b, Action subquery, Func fn, params Func[] func) where T : IDynamicQueryBuilder
+ {
+ var sub = b.SubQuery(fn, func);
+
+ subquery(b, sub);
+
+ (b as DynamicQueryBuilder).ParseCommand(sub as DynamicQueryBuilder, b.Parameters);
+
+ return b;
+ }
+
+ #endregion Dynamic builders extensions
+
+ #region Dynamic extensions
+
+ /// Turns an to a Dynamic list of things.
+ /// Reader from which read data.
+ /// List of things.
+ public static List ToList(this IDataReader r)
+ {
+ var result = new List();
+
+ while (r.Read())
+ result.Add(r.RowToDynamic());
+
+ return result;
+ }
+
+ /// Turns an to a Dynamic list of things with specified type.
+ /// Type of object to map on.
+ /// Ready to execute builder.
+ /// List of things.
+ public static List ToList(this IDynamicSelectQueryBuilder b) where T : class
+ {
+ return b.Execute().ToList();
+ }
+
+ /// Turns the dictionary into an ExpandoObject.
+ /// Dictionary to convert.
+ /// Converted dictionary.
+ public static dynamic ToDynamic(this IDictionary d)
+ {
+ var result = new ExpandoObject();
+ var dict = result as IDictionary;
+
+ foreach (var prop in d)
+ dict.Add(prop.Key, prop.Value);
+
+ return result;
+ }
+
+ /// Turns the object into an ExpandoObject.
+ /// Object to convert.
+ /// Converted object.
+ public static dynamic ToDynamic(this object o)
+ {
+ var result = new ExpandoObject();
+ var dict = result as IDictionary;
+ var ot = o.GetType();
+
+ if (ot == typeof(ExpandoObject))
+ return o;
+
+ if (o is IDictionary)
+ ((IDictionary)o)
+ .ToList()
+ .ForEach(kvp => dict.Add(kvp.Key, kvp.Value));
+ else if (ot == typeof(NameValueCollection) || ot.IsSubclassOf(typeof(NameValueCollection)))
+ {
+ var nameValue = (NameValueCollection)o;
+ nameValue.Cast()
+ .Select(key => new KeyValuePair(key, nameValue[key]))
+ .ToList()
+ .ForEach(i => dict.Add(i));
+ }
+ else
+ {
+ var mapper = DynamicMapperCache.GetMapper(ot);
+
+ if (mapper != null)
+ {
+ foreach (var item in mapper.ColumnsMap.Values)
+ if (item.Get != null)
+ dict.Add(item.Name, item.Get(o));
+ }
+ else
+ {
+ var props = ot.GetProperties();
+
+ foreach (var item in props)
+ if (item.CanRead)
+ dict.Add(item.Name, item.GetValue(o, null));
+ }
+ }
+
+ return result;
+ }
+
+ /// Convert data row row into dynamic object.
+ /// DataRow from which read.
+ /// Generated dynamic object.
+ public static dynamic RowToDynamic(this DataRow r)
+ {
+ dynamic e = new ExpandoObject();
+ var d = e as IDictionary;
+
+ int len = r.Table.Columns.Count;
+
+ for (int i = 0; i < len; i++)
+ d.Add(r.Table.Columns[i].ColumnName, r.IsNull(i) ? null : r[i]);
+
+ return e;
+ }
+
+ /// Convert data row row into dynamic object (upper case key).
+ /// DataRow from which read.
+ /// Generated dynamic object.
+ public static dynamic RowToDynamicUpper(this DataRow r)
+ {
+ dynamic e = new ExpandoObject();
+ var d = e as IDictionary;
+
+ int len = r.Table.Columns.Count;
+
+ for (int i = 0; i < len; i++)
+ d.Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]);
+
+ return e;
+ }
+
+ /// Convert reader row into dynamic object.
+ /// Reader from which read.
+ /// Generated dynamic object.
+ public static dynamic RowToDynamic(this IDataReader r)
+ {
+ dynamic e = new ExpandoObject();
+ var d = e as IDictionary;
+
+ int c = r.FieldCount;
+ for (int i = 0; i < c; i++)
+ try
+ {
+ d.Add(r.GetName(i), r.IsDBNull(i) ? null : r[i]);
+ }
+ catch (ArgumentException argex)
+ {
+ throw new ArgumentException(
+ string.Format("Field '{0}' is defined more than once in a query.", r.GetName(i)), "Column name or alias", argex);
+ }
+
+ return e;
+ }
+
+ /// Turns the object into a Dictionary.
+ /// Object to convert.
+ /// Resulting dictionary.
+ public static IDictionary ToDictionary(this ExpandoObject o)
+ {
+ return (IDictionary)o;
+ }
+
+ /// Turns the object into a Dictionary.
+ /// Object to convert.
+ /// Resulting dictionary.
+ public static IDictionary ToDictionary(this object o)
+ {
+ return o is IDictionary ?
+ (IDictionary)o :
+ (IDictionary)o.ToDynamic();
+ }
+
+ #endregion Dynamic extensions
+
+ #region Type extensions
+
+ /// Check if type is anonymous.
+ /// Type to test.
+ /// Returns true if type is anonymous.
+ public static bool IsAnonymous(this Type type)
+ {
+ // HACK: The only way to detect anonymous types right now.
+ return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)
+ && type.IsGenericType
+ && (type.Name.Contains("AnonymousType") || type.Name.Contains("AnonType"))
+ && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$"))
+ && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic;
+ }
+
+ /// Check if type implements IEnumerable<> interface.
+ /// Type to check.
+ /// Returns true if it does.
+ public static bool IsGenericEnumerable(this Type type)
+ {
+ return type.IsGenericType &&
+ typeof(IEnumerable<>).IsAssignableFrom(type.GetGenericTypeDefinition());
+ }
+
+ /// Check if type implements IEnumerable<> interface.
+ /// Type to check.
+ /// Returns true if it does.
+ public static bool IsNullableType(this Type type)
+ {
+ Type generic = type.IsGenericType ? type.GetGenericTypeDefinition() : null;
+
+ if (generic != null && generic.Equals(typeof(Nullable<>)) && type.IsClass)
+ return true;
+
+ return false;
+ }
+
+ /// Check if type is collection of any kind.
+ /// Type to check.
+ /// Returns true if it is.
+ public static bool IsCollection(this Type type)
+ {
+ if (!type.IsArray)
+ return type.IsGenericEnumerable();
+
+ return true;
+ }
+
+ /// Check if type is collection of value types like int.
+ /// Type to check.
+ /// Returns true if it is.
+ public static bool IsCollectionOfValueTypes(this Type type)
+ {
+ if (type.IsArray)
+ return type.GetElementType().IsValueType;
+ else
+ {
+ if (type.IsGenericType)
+ {
+ Type[] gt = type.GetGenericArguments();
+
+ return (gt.Length == 1) && gt[0].IsValueType;
+ }
+ }
+
+ return false;
+ }
+
+ #endregion Type extensions
+
+ #region IDictionary extensions
+
+ /// Gets the value associated with the specified key.
+ /// The type of keys in the dictionary.
+ /// The type of values in the dictionary.
+ /// Dictionary to probe.
+ /// The key whose value to get.
+ /// Nullable type containing value or null if key was not found.
+ public static TValue? TryGetNullable(this IDictionary dict, TKey key) where TValue : struct
+ {
+ TValue val;
+
+ if (key != null && dict.TryGetValue(key, out val))
+ return val;
+
+ return null;
+ }
+
+ /// Gets the value associated with the specified key.
+ /// The type of keys in the dictionary.
+ /// The type of values in the dictionary.
+ /// Dictionary to probe.
+ /// The key whose value to get.
+ /// Instance of object or null if not found.
+ public static TValue TryGetValue(this IDictionary dict, TKey key) where TValue : class
+ {
+ TValue val;
+
+ if (key != null && dict.TryGetValue(key, out val))
+ return val;
+
+ return default(TValue);
+ }
+
+ /// Adds element to dictionary and returns added value.
+ /// The type of keys in the dictionary.
+ /// The type of values in the dictionary.
+ /// Dictionary to which add value.
+ /// The key under which value value will be added.
+ /// Value to add.
+ /// Instance of object or null if not found.
+ public static TValue AddAndPassValue(this IDictionary dict, TKey key, TValue value)
+ {
+ dict.Add(key, value);
+ return value;
+ }
+
+ #endregion IDictionary extensions
+
+ #region Mapper extensions
+
+ /// MapEnumerable object enumerator into specified type.
+ /// Type to which columnMap results.
+ /// Source enumerator.
+ /// Enumerator of specified type.
+ public static IEnumerable MapEnumerable(this IEnumerable enumerable)
+ {
+ var mapper = DynamicMapperCache.GetMapper();
+
+ if (mapper == null)
+ throw new InvalidOperationException("Type can't be mapped for unknown reason.");
+
+ foreach (var item in enumerable)
+ yield return (T)mapper.Create(item);
+ }
+
+ /// MapEnumerable object item into specified type.
+ /// Type to which columnMap results.
+ /// Source object.
+ /// Item of specified type.
+ public static T Map(this object item)
+ {
+ var mapper = DynamicMapperCache.GetMapper();
+
+ if (mapper == null)
+ throw new InvalidOperationException("Type can't be mapped for unknown reason.");
+
+ return (T)mapper.Create(item);
+ }
+
+ /// Fill object of specified type with data from source object.
+ /// Type to which columnMap results.
+ /// Item to which columnMap data.
+ /// Item from which extract data.
+ /// Filled item.
+ public static T Fill(this T item, object source)
+ {
+ var mapper = DynamicMapperCache.GetMapper();
+
+ if (mapper == null)
+ throw new InvalidOperationException("Type can't be mapped for unknown reason.");
+
+ mapper.Map(item, source);
+
+ return item;
+ }
+
+ /// MapEnumerable object enumerator into specified type.
+ /// Source enumerator.
+ /// Type to which columnMap results.
+ /// Enumerator of specified type.
+ public static IEnumerable MapEnumerable(this IEnumerable enumerable, Type type)
+ {
+ var mapper = DynamicMapperCache.GetMapper(type);
+
+ if (mapper == null)
+ throw new InvalidOperationException("Type can't be mapped for unknown reason.");
+
+ foreach (var item in enumerable)
+ yield return mapper.Create(item);
+ }
+
+ /// MapEnumerable object item into specified type.
+ /// Source object.
+ /// Type to which columnMap results.
+ /// Item of specified type.
+ public static object Map(this object item, Type type)
+ {
+ var mapper = DynamicMapperCache.GetMapper(type);
+
+ if (mapper == null)
+ throw new InvalidOperationException("Type can't be mapped for unknown reason.");
+
+ return mapper.Create(item);
+ }
+
+ /// Converts the elements of an
+ /// to the specified type.
+ /// The type to convert the elements of source to.
+ /// The that
+ /// contains the elements to be converted.
+ /// An enumerator that contains each element of
+ /// the source sequence converted to the specified type.
+ /// Thrown when
+ /// source is null.
+ /// An element in the
+ /// sequence cannot be cast to type T or enumerator
+ /// is not .
+ public static IEnumerable CastEnumerable(this object enumerator)
+ {
+ return (enumerator as System.Collections.IEnumerable).Cast();
+ }
+
+ #endregion Mapper extensions
+
+ #region TryParse extensions
+
+#if !DYNAMORM_OMMIT_TRYPARSE
+
+ /// Generic try parse.
+ /// Type to parse to.
+ /// Value to parse.
+ /// Handler of a try parse method.
+ /// Returns true if conversion was successful.
+ public static T? TryParse(this string value, TryParseHandler handler) where T : struct
+ {
+ if (String.IsNullOrEmpty(value))
+ return null;
+
+ T result;
+
+ if (handler(value, out result))
+ return result;
+
+ return null;
+ }
+
+ /// Generic try parse with default value.
+ /// Type to parse to.
+ /// Value to parse.
+ /// Default value of a result.
+ /// Handler of a try parse method.
+ /// Returns true if conversion was successful.
+ public static T TryParseDefault(this string value, T defaultValue, TryParseHandler handler)
+ {
+ if (String.IsNullOrEmpty(value))
+ return defaultValue;
+
+ T result;
+
+ if (handler(value, out result))
+ return result;
+
+ return defaultValue;
+ }
+
+ /// Delegate fro try parse function of a type.
+ /// Type which implements this function.
+ /// Value to parse.
+ /// Resulting value.
+ /// Returns true if conversion was successful.
+ public delegate bool TryParseHandler(string value, out T result);
+
+#endif
+
+ #endregion TryParse extensions
+
+ #region Coalesce - besicaly not an extensions
+
+ /// Select first not null value.
+ /// Type to return.
+ /// Values to check.
+ /// First not null or default value.
+ public static T Coalesce(params T[] vals) where T : class
+ {
+ return vals.FirstOrDefault(v => v != null);
+ }
+
+ /// Select first not null value.
+ /// Type to return.
+ /// Values to check.
+ /// First not null or default value.
+ public static T? CoalesceNullable(params T?[] vals) where T : struct
+ {
+ return vals.FirstOrDefault(v => v != null);
+ }
+
+ #endregion Coalesce - besicaly not an extensions
+ }
+
+ /// Dynamic query exception.
+ public class DynamicQueryException : Exception, ISerializable
+ {
+ /// Gets the dumped command which failed.
+ public string Command { get; private set; }
+
+ /// Initializes a new instance of the
+ /// class.
+ /// The command which failed.
+ public DynamicQueryException(IDbCommand command = null)
+ : base("Error executing command.")
+ {
+ if (command != null)
+ {
+ var sb = new StringBuilder();
+ command.Dump(sb);
+ Command = sb.ToString();
+ }
+ }
+
+ /// Initializes a new instance of the
+ /// class.
+ /// The message.
+ /// The command which failed.
+ public DynamicQueryException(string message, IDbCommand command = null)
+ : base(message)
+ {
+ SetCommand(command);
+ }
+
+ /// Initializes a new instance of the
+ /// class.
+ /// The inner exception.
+ /// The command which failed.
+ public DynamicQueryException(Exception innerException, IDbCommand command = null)
+ : base("Error executing command.", innerException)
+ {
+ SetCommand(command);
+ }
+
+ /// Initializes a new instance of the
+ /// class.
+ /// The message.
+ /// The inner exception.
+ /// The command which failed.
+ public DynamicQueryException(string message, Exception innerException, IDbCommand command = null)
+ : base(message, innerException)
+ {
+ SetCommand(command);
+ }
+
+ /// Initializes a new instance of the
+ /// class.
+ /// The
+ /// that holds the serialized object data about the exception being thrown.
+ /// The
+ /// that contains contextual information about the source or destination.
+ public DynamicQueryException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ Command = info.GetString("Command");
+ }
+
+ /// When overridden in a derived class, sets the
+ ///
+ /// with information about the exception.
+ /// The
+ /// that holds the serialized object data about the exception being thrown.
+ /// The
+ /// that contains contextual information about the source or destination.
+ ///
+ ///
+ ///
+ ///
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+
+ if (!string.IsNullOrEmpty(Command))
+ info.AddValue("Command", Command);
+ }
+
+ private void SetCommand(IDbCommand command)
+ {
+ if (command != null && (!(command is DynamicCommand) || ((command is DynamicCommand) && !(command as DynamicCommand).IsDisposed)))
+ {
+ var sb = new StringBuilder();
+ command.Dump(sb);
+ Command = sb.ToString();
+ }
+ }
+ }
+
+ /// Stores information about column from database schema.
+ public struct DynamicSchemaColumn
+ {
+ /// Gets or sets column name.
+ public string Name { get; set; }
+
+ /// Gets or sets column type.
+ public DbType Type { get; set; }
+
+ /// Gets or sets a value indicating whether column is a key.
+ public bool IsKey { get; set; }
+
+ /// Gets or sets a value indicating whether column should have unique value.
+ public bool IsUnique { get; set; }
+
+ /// Gets or sets column size.
+ public int Size { get; set; }
+
+ /// Gets or sets column precision.
+ public byte Precision { get; set; }
+
+ /// Gets or sets column scale.
+ public byte Scale { get; set; }
+ }
+
+#if !DYNAMORM_OMMIT_OLDSYNTAX
+
+ /// Dynamic table is a simple ORM using dynamic objects.
+ ///
+ /// Assume that we have a table representing Users class.
+ ///
+ /// Let's take a look at Query possibilities. Assume we want
+ /// to get enumerator for all records in database, mapped to our class
+ /// instead of dynamic type we can use following syntax.
+ /// Approach first. Use dynamic Query method and just set type
+ /// then just cast it to user class. Remember that you must cast result
+ /// of Query to IEnumerable<object> . because from
+ /// point of view of runtime you are operating on object type.
+ /// (db.Table<User>().Query(type: typeof(User)) as IEnumerable<object>).Cast<User>();
+ /// Second approach is similar. We ask database using dynamic
+ /// Query method. The difference is that we use extension method of
+ /// IEnumerable<object> (to which we must cast to) to map
+ /// object.
+ /// (db.Table<User>().Query(columns: "*") as IEnumerable<object>).MapEnumerable<User>();
+ /// You can also use generic approach. But be careful this method is currently available thanks to framework hack.
+ /// (db.Table<User>().Query<User>() as IEnumerable<object>).Cast<User>()
+ /// Another approach uses existing methods, but still requires a
+ /// cast, because Query also returns dynamic object enumerator.
+ /// (db.Table<User>().Query().Execute() as IEnumerable<object>).MapEnumerable<User>();
+ ///
+ /// Below you can find various invocations of dynamic and non dynamic
+ /// methods of this class. x variable is a class instance.
+ /// First various selects:
+ /// x.Count(columns: "id");
+ /// x.Count(last: new DynamicColumn
+ /// {
+ /// Operator = DynamicColumn.CompareOperator.In,
+ /// Value = new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3)
+ /// });
+ /// x.Count(last: new DynamicColumn
+ /// {
+ /// Operator = DynamicColumn.CompareOperator.In,
+ /// Value = new object[] { "Hendricks", "Goodwin", "Freeman" }
+ /// });
+ /// x.First(columns: "id").id;
+ /// x.Last(columns: "id").id;
+ /// x.Count(first: "Ori");
+ /// x.Min(columns: "id");
+ /// x.Max(columns: "id");
+ /// x.Avg(columns: "id");
+ /// x.Sum(columns: "id");
+ /// x.Scalar(columns: "first", id: 19);
+ /// x.Scalar(columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 });
+ /// x.Scalar(columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 });
+ /// var v = (x.Query(columns: "first,first:occurs:count", group: "first", order: ":desc:2") as IEnumerable<dynamic>).ToList();
+ /// x.Scalar(columns: @"length(""login""):len:avg");
+ /// x.Avg(columns: @"length(""email""):len");
+ /// x.Count(condition1:
+ /// new DynamicColumn()
+ /// {
+ /// ColumnName = "email",
+ /// Aggregate = "length",
+ /// Operator = DynamicColumn.CompareOperator.Gt,
+ /// Value = 27
+ /// });
+ /// var o = x.Single(columns: "id,first,last", id: 19);
+ /// x.Single(where: new DynamicColumn("id").Eq(100)).login;
+ /// x.Count(where: new DynamicColumn("id").Not(100));
+ /// x.Single(where: new DynamicColumn("login").Like("Hoyt.%")).id;
+ /// x.Count(where: new DynamicColumn("login").NotLike("Hoyt.%"));
+ /// x.Count(where: new DynamicColumn("id").Greater(100));
+ /// x.Count(where: new DynamicColumn("id").GreaterOrEqual(100));
+ /// x.Count(where: new DynamicColumn("id").Less(100));
+ /// x.Count(where: new DynamicColumn("id").LessOrEqual(100));
+ /// x.Count(where: new DynamicColumn("id").Between(75, 100));
+ /// x.Count(where: new DynamicColumn("id").In(75, 99, 100));
+ /// x.Count(where: new DynamicColumn("id").In(new[] { 75, 99, 100 }));
+ /// Inserts:
+ /// x.Insert(code: 201, first: "Juri", last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla");
+ /// x.Insert(values: new { code = 202, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" });
+ /// x.Insert(values: new Users
+ /// {
+ /// Id = u.Max(columns: "id") + 1,
+ /// Code = "203",
+ /// First = "Juri",
+ /// Last = "Gagarin",
+ /// Email = "juri.gagarin@megacorp.com",
+ /// Quote = "bla, bla, bla"
+ /// });
+ /// x.Insert(values: new users
+ /// {
+ /// id = u.Max(columns: "id") + 1,
+ /// code = "204",
+ /// first = "Juri",
+ /// last = "Gagarin",
+ /// email = "juri.gagarin@megacorp.com",
+ /// quote = "bla, bla, bla"
+ /// });
+ /// x.Update(id: 1, code: 201, first: "Juri", last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla");
+ /// x.Update(update: new { id = 2, code = 202, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" });
+ /// Updates:
+ /// x.Update(update: new Users
+ /// {
+ /// Id = 3,
+ /// Code = "203",
+ /// First = "Juri",
+ /// Last = "Gagarin",
+ /// Email = "juri.gagarin@megacorp.com",
+ /// Quote = "bla, bla, bla"
+ /// });
+ /// x.Update(update: new users
+ /// {
+ /// id = 4,
+ /// code = "204",
+ /// first = "Juri",
+ /// last = "Gagarin",
+ /// email = "juri.gagarin@megacorp.com",
+ /// quote = "bla, bla, bla"
+ /// });
+ /// x.Update(values: new { code = 205, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" }, where: new { id = 5 });
+ /// x.Update(values: new Users
+ /// {
+ /// Id = 6,
+ /// Code = "206",
+ /// First = "Juri",
+ /// Last = "Gagarin",
+ /// Email = "juri.gagarin@megacorp.com",
+ /// Quote = "bla, bla, bla"
+ /// }, id: 6);
+ /// x.Update(values: new users
+ /// {
+ /// id = 7,
+ /// code = "207",
+ /// first = "Juri",
+ /// last = "Gagarin",
+ /// email = "juri.gagarin@megacorp.com",
+ /// quote = "bla, bla, bla"
+ /// }, id: 7);
+ /// Delete:
+ /// x.Delete(code: 10);
+ /// x.Delete(delete: new { id = 11, code = 11, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" });
+ /// x.Delete(delete: new Users
+ /// {
+ /// Id = 12,
+ /// Code = "12",
+ /// First = "Juri",
+ /// Last = "Gagarin",
+ /// Email = "juri.gagarin@megacorp.com",
+ /// Quote = "bla, bla, bla"
+ /// });
+ /// x.Delete(delete: new users
+ /// {
+ /// id = 13,
+ /// code = "13",
+ /// first = "Juri",
+ /// last = "Gagarin",
+ /// email = "juri.gagarin@megacorp.com",
+ /// quote = "bla, bla, bla"
+ /// });
+ /// x.Delete(where: new { id = 14, code = 14 });
+ ///
+ public class DynamicTable : DynamicObject, IExtendedDisposable, ICloneable
+ {
+ private static HashSet _allowedCommands = new HashSet
+ {
+ "Insert", "Update", "Delete",
+ "Query", "Single", "Where",
+ "First", "Last", "Get",
+ "Count", "Sum", "Avg",
+ "Min", "Max", "Scalar"
+ };
+
+ /// Gets dynamic database.
+ internal DynamicDatabase Database { get; private set; }
+
+ /// Gets type of table (for coning and schema building).
+ internal Type TableType { get; private set; }
+
+ /// Gets name of table.
+ public virtual string TableName { get; private set; }
+
+ /// Gets name of owner.
+ public virtual string OwnerName { get; private set; }
+
+ /// Gets full name of table containing owner and table name.
+ public virtual string FullName
+ {
+ get
+ {
+ return string.IsNullOrEmpty(TableName) ? null : string.IsNullOrEmpty(OwnerName) ?
+ Database.DecorateName(TableName) :
+ string.Format("{0}.{1}", Database.DecorateName(OwnerName), Database.DecorateName(TableName));
+ }
+ }
+
+ /// Gets table schema.
+ /// If database doesn't support schema, only key columns are listed here.
+ public virtual Dictionary Schema { get; private set; }
+
+ private DynamicTable()
+ {
+ }
+
+ /// Initializes a new instance of the class.
+ /// Database and connection management.
+ /// Table name.
+ /// Owner of the table.
+ /// Override keys in schema.
+ public DynamicTable(DynamicDatabase database, string table = "", string owner = "", string[] keys = null)
+ {
+ IsDisposed = false;
+ Database = database;
+ TableName = Database.StripName(table);
+ OwnerName = Database.StripName(owner);
+ TableType = null;
+
+ BuildAndCacheSchema(keys);
+ }
+
+ /// Initializes a new instance of the class.
+ /// Database and connection management.
+ /// Type describing table.
+ /// Override keys in schema.
+ public DynamicTable(DynamicDatabase database, Type type, string[] keys = null)
+ {
+ if (type == null)
+ throw new ArgumentNullException("type", "Type can't be null.");
+
+ IsDisposed = false;
+
+ Database = database;
+ TableType = type;
+
+ var mapper = DynamicMapperCache.GetMapper(type);
+
+ if (mapper != null)
+ {
+ TableName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ?
+ type.Name : mapper.Table.Name;
+ OwnerName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ?
+ type.Name : mapper.Table.Name;
+ }
+
+ BuildAndCacheSchema(keys);
+ }
+
+ #region Schema
+
+ private void BuildAndCacheSchema(string[] keys)
+ {
+ Dictionary schema = null;
+
+ schema = Database.GetSchema(TableType) ??
+ Database.GetSchema(TableName);
+
+ #region Fill currrent table schema
+
+ if (keys == null && TableType != null)
+ {
+ var mapper = DynamicMapperCache.GetMapper(TableType);
+
+ if (mapper != null)
+ {
+ var k = mapper.ColumnsMap.Where(p => p.Value.Column != null && p.Value.Column.IsKey).Select(p => p.Key);
+ if (k.Count() > 0)
+ keys = k.ToArray();
+ }
+ }
+
+ if (schema != null)
+ {
+ if (keys == null)
+ Schema = new Dictionary(schema);
+ else
+ {
+ // TODO: Make this.... nicer
+ List ks = keys.Select(k => k.ToLower()).ToList();
+
+ Schema = schema.ToDictionary(k => k.Key, (v) =>
+ {
+ DynamicSchemaColumn dsc = v.Value;
+ dsc.IsKey = ks.Contains(v.Key);
+ return dsc;
+ });
+ }
+ }
+
+ #endregion Fill currrent table schema
+
+ #region Build ad-hock schema
+
+ if (keys != null && Schema == null)
+ {
+ Schema = keys.Select(k => k.ToLower()).ToList()
+ .ToDictionary(k => k, k => new DynamicSchemaColumn { Name = k, IsKey = true });
+ }
+
+ #endregion Build ad-hock schema
+ }
+
+ #endregion Schema
+
+ #region Basic Queries
+
+ /// Enumerate the reader and yield the result.
+ /// SQL query containing numbered parameters in format provided by
+ /// methods. Also names should be formatted with
+ /// method.
+ /// Arguments (parameters).
+ /// Enumerator of objects expanded from query.
+ public virtual IEnumerable Query(string sql, params object[] args)
+ {
+ using (var con = Database.Open())
+ using (var cmd = con.CreateCommand())
+ using (var rdr = cmd
+ .SetCommand(sql, args)
+ .ExecuteReader())
+ while (rdr.Read())
+ {
+ dynamic val = null;
+
+ // Work around to avoid yield being in try...catch block:
+ // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch
+ try
+ {
+ val = rdr.RowToDynamic();
+ }
+ catch (ArgumentException argex)
+ {
+ var sb = new StringBuilder();
+ cmd.Dump(sb);
+
+ throw new ArgumentException(string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb),
+ argex.InnerException.NullOr(a => a, argex));
+ }
+
+ yield return val;
+ }
+ }
+
+ /// Enumerate the reader and yield the result.
+ /// Command builder.
+ /// Enumerator of objects expanded from query.
+ public virtual IEnumerable Query(IDynamicQueryBuilder builder)
+ {
+ using (var con = Database.Open())
+ using (var cmd = con.CreateCommand())
+ using (var rdr = cmd
+ .SetCommand(builder)
+ .ExecuteReader())
+ while (rdr.Read())
+ {
+ dynamic val = null;
+
+ // Work around to avoid yield being in try...catch block:
+ // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch
+ try
+ {
+ val = rdr.RowToDynamic();
+ }
+ catch (ArgumentException argex)
+ {
+ var sb = new StringBuilder();
+ cmd.Dump(sb);
+
+ throw new ArgumentException(string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb),
+ argex.InnerException.NullOr(a => a, argex));
+ }
+
+ yield return val;
+ }
+ }
+
+ /// Create new .
+ /// New instance.
+ public virtual IDynamicSelectQueryBuilder Query()
+ {
+ var builder = new DynamicSelectQueryBuilder(this.Database);
+
+ var name = this.FullName;
+ if (!string.IsNullOrEmpty(name))
+ builder.From(x => name);
+
+ return builder;
+ }
+
+ /// Returns a single result.
+ /// SQL query containing numbered parameters in format provided by
+ /// methods. Also names should be formatted with
+ /// method.
+ /// Arguments (parameters).
+ /// Result of a query.
+ public virtual object Scalar(string sql, params object[] args)
+ {
+ using (var con = Database.Open())
+ using (var cmd = con.CreateCommand())
+ {
+ return cmd
+ .SetCommand(sql).AddParameters(Database, args)
+ .ExecuteScalar();
+ }
+ }
+
+ /// Returns a single result.
+ /// Command builder.
+ /// Result of a query.
+ public virtual object Scalar(IDynamicQueryBuilder builder)
+ {
+ using (var con = Database.Open())
+ using (var cmd = con.CreateCommand())
+ {
+ return cmd
+ .SetCommand(builder)
+ .ExecuteScalar();
+ }
+ }
+
+ /// Execute stored procedure.
+ /// Name of stored procedure to execute.
+ /// Arguments (parameters) in form of expando object.
+ /// Number of affected rows.
+ public virtual int Procedure(string procName, ExpandoObject args = null)
+ {
+ if ((Database.Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures)
+ throw new InvalidOperationException("Database connection desn't support stored procedures.");
+
+ using (var con = Database.Open())
+ using (var cmd = con.CreateCommand())
+ {
+ return cmd
+ .SetCommand(CommandType.StoredProcedure, procName).AddParameters(Database, args)
+ .ExecuteNonQuery();
+ }
+ }
+
+ /// Execute non query.
+ /// SQL query containing numbered parameters in format provided by
+ /// methods. Also names should be formatted with
+ /// method.
+ /// Arguments (parameters).
+ /// Number of affected rows.
+ public virtual int Execute(string sql, params object[] args)
+ {
+ using (var con = Database.Open())
+ using (var cmd = con.CreateCommand())
+ {
+ return cmd
+ .SetCommand(sql).AddParameters(Database, args)
+ .ExecuteNonQuery();
+ }
+ }
+
+ /// Execute non query.
+ /// Command builder.
+ /// Number of affected rows.
+ public virtual int Execute(IDynamicQueryBuilder builder)
+ {
+ using (var con = Database.Open())
+ using (var cmd = con.CreateCommand())
+ {
+ return cmd
+ .SetCommand(builder)
+ .ExecuteNonQuery();
+ }
+ }
+
+ /// Execute non query.
+ /// Command builders.
+ /// Number of affected rows.
+ public virtual int Execute(IDynamicQueryBuilder[] builers)
+ {
+ int ret = 0;
+
+ using (var con = Database.Open())
+ {
+ using (var trans = con.BeginTransaction())
+ {
+ foreach (var builder in builers)
+ {
+ using (var cmd = con.CreateCommand())
+ {
+ ret += cmd
+ .SetCommand(builder)
+ .ExecuteNonQuery();
+ }
+ }
+
+ trans.Commit();
+ }
+ }
+
+ return ret;
+ }
+
+ #endregion Basic Queries
+
+ #region Insert
+
+ /// Create new .
+ /// New instance.
+ public dynamic Insert()
+ {
+ return new DynamicProxy(new DynamicInsertQueryBuilder(this.Database, this.FullName));
+ }
+
+ /// Adds a record to the database. You can pass in an Anonymous object, an ,
+ /// A regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString.
+ /// Anonymous object, an , a regular old POCO, or a NameValueCollection
+ /// from a Request.Form or Request.QueryString, containing fields to update.
+ /// Number of updated rows.
+ public virtual int Insert(object o)
+ {
+ return Insert()
+ .Insert(o)
+ .Execute();
+ }
+
+ #endregion Insert
+
+ #region Update
+
+ /// Create new .
+ /// New instance.
+ public dynamic Update()
+ {
+ return new DynamicProxy(new DynamicUpdateQueryBuilder(this.Database, this.FullName));
+ }
+
+ /// Updates a record in the database. You can pass in an Anonymous object, an ExpandoObject,
+ /// a regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString.
+ /// Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection
+ /// from a Request.Form or Request.QueryString, containing fields to update.
+ /// Anonymous object, an , a regular old POCO, or a NameValueCollection
+ /// from a Request.Form or Request.QueryString, containing fields with conditions.
+ /// Number of updated rows.
+ public virtual int Update(object o, object key)
+ {
+ return Update()
+ .Values(o)
+ .Where(key)
+ .Execute();
+ }
+
+ /// Updates a record in the database using schema. You can pass in an Anonymous object, an ExpandoObject,
+ /// a regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString.
+ /// Anonymous object, an , a regular old POCO, or a NameValueCollection
+ /// from a Request.Form or Request.QueryString, containing fields to update and conditions.
+ /// Number of updated rows.
+ public virtual int Update(object o)
+ {
+ return Update()
+ .Update(o)
+ .Execute();
+ }
+
+ #endregion Update
+
+ #region Delete
+
+ /// Create new .
+ /// New instance.
+ public dynamic Delete()
+ {
+ return new DynamicProxy(new DynamicDeleteQueryBuilder(this.Database, this.FullName));
+ }
+
+ /// Removes a record from the database. You can pass in an Anonymous object, an ,
+ /// A regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString.
+ /// Anonymous object, an , a regular old POCO, or a NameValueCollection
+ /// from a Request.Form or Request.QueryString, containing fields with where conditions.
+ /// If true use schema to determine key columns and ignore those which
+ /// aren't keys.
+ /// Number of updated rows.
+ public virtual int Delete(object o, bool schema = true)
+ {
+ return Delete()
+ .Where(o, schema)
+ .Execute();
+ }
+
+ #endregion Delete
+
+ #region Universal Dynamic Invoker
+
+ /// This is where the magic begins.
+ /// Binder to invoke.
+ /// Binder arguments.
+ /// Binder invoke result.
+ /// Returns true if invoke was performed.
+ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
+ {
+ // parse the method
+ var info = binder.CallInfo;
+
+ // Get generic types
+ var types = binder.GetGenericTypeArguments();
+
+ // accepting named args only... SKEET!
+ if (info.ArgumentNames.Count != args.Length)
+ throw new InvalidOperationException("Please use named arguments for this type of query - the column name, orderby, columns, etc");
+
+ var op = binder.Name;
+
+ // Avoid strange things
+ if (!_allowedCommands.Contains(op))
+ throw new InvalidOperationException(string.Format("Dynamic method '{0}' is not supported.", op));
+
+ switch (op)
+ {
+ case "Insert":
+ result = DynamicInsert(args, info, types);
+ break;
+
+ case "Update":
+ result = DynamicUpdate(args, info, types);
+ break;
+
+ case "Delete":
+ result = DynamicDelete(args, info, types);
+ break;
+
+ default:
+ result = DynamicQuery(args, info, op, types);
+ break;
+ }
+
+ return true;
+ }
+
+ private object DynamicInsert(object[] args, CallInfo info, IList types)
+ {
+ var builder = new DynamicInsertQueryBuilder(this.Database);
+
+ if (types != null && types.Count == 1)
+ HandleTypeArgument(null, info, ref types, builder, 0);
+
+ if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0)
+ builder.Table(this.TableName, this.Schema);
+
+ // loop the named args - see if we have order, columns and constraints
+ if (info.ArgumentNames.Count > 0)
+ {
+ for (int i = 0; i < args.Length; i++)
+ {
+ var name = info.ArgumentNames[i].ToLower();
+
+ switch (name)
+ {
+ case "table":
+ if (args[i] is string)
+ builder.Table(args[i].ToString());
+ else goto default;
+ break;
+
+ case "values":
+ builder.Insert(args[i]);
+ break;
+
+ case "type":
+ if (types == null || types.Count == 0)
+ HandleTypeArgument(args, info, ref types, builder, i);
+ else goto default;
+ break;
+
+ default:
+ builder.Insert(name, args[i]);
+ break;
+ }
+ }
+ }
+
+ // Execute
+ return Execute(builder);
+ }
+
+ private object DynamicUpdate(object[] args, CallInfo info, IList types)
+ {
+ var builder = new DynamicUpdateQueryBuilder(this.Database);
+
+ if (types != null && types.Count == 1)
+ HandleTypeArgument(null, info, ref types, builder, 0);
+
+ if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0)
+ builder.Table(this.TableName, this.Schema);
+
+ // loop the named args - see if we have order, columns and constraints
+ if (info.ArgumentNames.Count > 0)
+ {
+ for (int i = 0; i < args.Length; i++)
+ {
+ var name = info.ArgumentNames[i].ToLower();
+
+ switch (name)
+ {
+ case "table":
+ if (args[i] is string)
+ builder.Table(args[i].ToString());
+ else goto default;
+ break;
+
+ case "update":
+ builder.Update(args[i]);
+ break;
+
+ case "values":
+ builder.Values(args[i]);
+ break;
+
+ case "where":
+ builder.Where(args[i]);
+ break;
+
+ case "type":
+ if (types == null || types.Count == 0)
+ HandleTypeArgument(args, info, ref types, builder, i);
+ else goto default;
+ break;
+
+ default:
+ builder.Update(name, args[i]);
+ break;
+ }
+ }
+ }
+
+ // Execute
+ return Execute(builder);
+ }
+
+ private object DynamicDelete(object[] args, CallInfo info, IList types)
+ {
+ var builder = new DynamicDeleteQueryBuilder(this.Database);
+
+ if (types != null && types.Count == 1)
+ HandleTypeArgument(null, info, ref types, builder, 0);
+
+ if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0)
+ builder.Table(this.TableName, this.Schema);
+
+ // loop the named args - see if we have order, columns and constraints
+ if (info.ArgumentNames.Count > 0)
+ {
+ for (int i = 0; i < args.Length; i++)
+ {
+ var name = info.ArgumentNames[i].ToLower();
+
+ switch (name)
+ {
+ case "table":
+ if (args[i] is string)
+ builder.Table(args[i].ToString());
+ else goto default;
+ break;
+
+ case "where":
+ builder.Where(args[i], false);
+ break;
+
+ case "delete":
+ builder.Where(args[i], true);
+ break;
+
+ case "type":
+ if (types == null || types.Count == 0)
+ HandleTypeArgument(args, info, ref types, builder, i);
+ else goto default;
+ break;
+
+ default:
+ builder.Where(name, args[i]);
+ break;
+ }
+ }
+ }
+
+ // Execute
+ return Execute(builder);
+ }
+
+ private object DynamicQuery(object[] args, CallInfo info, string op, IList types)
+ {
+ object result;
+ var builder = new DynamicSelectQueryBuilder(this.Database);
+
+ if (types != null && types.Count == 1)
+ HandleTypeArgument(null, info, ref types, builder, 0);
+
+ if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0)
+ builder.From(x => this.TableName);
+
+ // loop the named args - see if we have order, columns and constraints
+ if (info.ArgumentNames.Count > 0)
+ {
+ for (int i = 0; i < args.Length; i++)
+ {
+ var name = info.ArgumentNames[i].ToLower();
+
+ // TODO: Make this nicer
+ switch (name)
+ {
+ case "order":
+ if (args[i] is string)
+ builder.OrderByColumn(((string)args[i]).Split(','));
+ else if (args[i] is string[])
+ builder.OrderByColumn(args[i] as string);
+ else if (args[i] is DynamicColumn[])
+ builder.OrderByColumn((DynamicColumn[])args[i]);
+ else if (args[i] is DynamicColumn)
+ builder.OrderByColumn((DynamicColumn)args[i]);
+ else goto default;
+ break;
+
+ case "group":
+ if (args[i] is string)
+ builder.GroupByColumn(((string)args[i]).Split(','));
+ else if (args[i] is string[])
+ builder.GroupByColumn(args[i] as string);
+ else if (args[i] is DynamicColumn[])
+ builder.GroupByColumn((DynamicColumn[])args[i]);
+ else if (args[i] is DynamicColumn)
+ builder.GroupByColumn((DynamicColumn)args[i]);
+ else goto default;
+ break;
+
+ case "columns":
+ {
+ var agregate = (op == "Sum" || op == "Max" || op == "Min" || op == "Avg" || op == "Count") ?
+ op.ToUpper() : null;
+
+ if (args[i] is string || args[i] is string[])
+ builder.SelectColumn((args[i] as String).NullOr(s => s.Split(','), args[i] as String[])
+ .Select(c =>
+ {
+ var col = DynamicColumn.ParseSelectColumn(c);
+ if (string.IsNullOrEmpty(col.Aggregate))
+ col.Aggregate = agregate;
+
+ return col;
+ }).ToArray());
+ else if (args[i] is DynamicColumn || args[i] is DynamicColumn[])
+ builder.SelectColumn((args[i] as DynamicColumn).NullOr(c => new DynamicColumn[] { c }, args[i] as DynamicColumn[])
+ .Select(c =>
+ {
+ if (string.IsNullOrEmpty(c.Aggregate))
+ c.Aggregate = agregate;
+
+ return c;
+ }).ToArray());
+ else goto default;
+ }
+
+ break;
+
+ case "where":
+ builder.Where(args[i]);
+ break;
+
+ case "table":
+ if (args[i] is string)
+ builder.From(x => args[i].ToString());
+ else goto default;
+ break;
+
+ case "type":
+ if (types == null || types.Count == 0)
+ HandleTypeArgument(args, info, ref types, builder, i);
+ else goto default;
+ break;
+
+ default:
+ builder.Where(name, args[i]);
+ break;
+ }
+ }
+ }
+
+ if (op == "Count" && !builder.HasSelectColumns)
+ {
+ result = Scalar(builder.Select(x => x.Count()));
+
+ if (result is long)
+ result = (int)(long)result;
+ }
+ else if (op == "Sum" || op == "Max" ||
+ op == "Min" || op == "Avg" || op == "Count")
+ {
+ if (!builder.HasSelectColumns)
+ throw new InvalidOperationException("You must select one column to agregate.");
+
+ result = Scalar(builder);
+
+ if (op == "Count" && result is long)
+ result = (int)(long)result;
+ else if (result == DBNull.Value)
+ result = null;
+ }
+ else
+ {
+ // build the SQL
+ var justOne = op == "First" || op == "Last" || op == "Get" || op == "Single";
+
+ // Be sure to sort by DESC on selected columns
+ /*if (op == "Last")
+ {
+ if (builder.Order.Count > 0)
+ foreach (var o in builder.Order)
+ o.Order = o.Order == DynamicColumn.SortOrder.Desc ?
+ DynamicColumn.SortOrder.Asc : DynamicColumn.SortOrder.Desc;
+ }*/
+
+ if (justOne && !(op == "Last"))
+ {
+ if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset)
+ builder.Limit(1);
+ else if ((Database.Options & DynamicDatabaseOptions.SupportTop) == DynamicDatabaseOptions.SupportTop)
+ builder.Top(1);
+ }
+
+ if (op == "Scalar")
+ {
+ if (!builder.HasSelectColumns)
+ throw new InvalidOperationException("You must select one column in scalar statement.");
+
+ result = Scalar(builder);
+ }
+ else
+ {
+ if (justOne)
+ {
+ if (op == "Last")
+ result = Query(builder).LastOrDefault(); // Last record fallback
+ else
+ result = Query(builder).FirstOrDefault(); // return a single record
+ }
+ else
+ result = Query(builder); // return lots
+
+ // MapEnumerable to specified result (still needs to be casted after that)
+ if (types != null)
+ {
+ if (types.Count == 1)
+ result = justOne ?
+ result.Map(types[0]) :
+ ((IEnumerable)result).MapEnumerable(types[0]);
+
+ // TODO: Dictionaries
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private void HandleTypeArgument(object[] args, CallInfo info, ref IList types, T builder, int i) where T : DynamicQueryBuilder
+ {
+ if (args != null)
+ {
+ if (args[i] is Type[])
+ types = new List((Type[])args[i]);
+ else if (args[i] is Type)
+ types = new List(new Type[] { (Type)args[i] });
+ }
+
+ if (types != null && types.Count == 1 && !info.ArgumentNames.Any(a => a.ToLower() == "table"))
+ builder.Table(types[0]);
+ }
+
+ #endregion Universal Dynamic Invoker
+
+ #region IExtendedDisposable Members
+
+ /// Performs application-defined tasks associated with
+ /// freeing, releasing, or resetting unmanaged resources.
+ public void Dispose()
+ {
+ // Lose reference but don't kill it.
+ if (Database != null)
+ {
+ Database.RemoveFromCache(this);
+ Database = null;
+ }
+
+ IsDisposed = true;
+ }
+
+ /// Gets a value indicating whether this instance is disposed.
+ public bool IsDisposed { get; private set; }
+
+ #endregion IExtendedDisposable Members
+
+ #region ICloneable Members
+
+ /// Creates a new object that is a copy of the current
+ /// instance.
+ /// A new object that is a copy of this instance.
+ public object Clone()
+ {
+ return new DynamicTable()
+ {
+ Database = this.Database,
+ Schema = this.Schema,
+ TableName = this.TableName,
+ TableType = this.TableType
+ };
+ }
+
+ #endregion ICloneable Members
+ }
+
+#endif
+
+ /// Helper class to easy manage transaction.
+ public class DynamicTransaction : IDbTransaction, IExtendedDisposable
+ {
+ private DynamicDatabase _db;
+ private DynamicConnection _con;
+ private bool _singleTransaction;
+ private Action _disposed;
+ private bool _operational = false;
+
+ /// Initializes a new instance of the class.
+ /// Database connection manager.
+ /// Active connection.
+ /// Are we using single transaction mode? I so... act correctly.
+ /// One of the values.
+ /// This action is invoked when transaction is disposed.
+ internal DynamicTransaction(DynamicDatabase db, DynamicConnection con, bool singleTransaction, IsolationLevel? il, Action disposed)
+ {
+ _db = db;
+ _con = con;
+ _singleTransaction = singleTransaction;
+ _disposed = disposed;
+
+ lock (_db.SyncLock)
+ {
+ if (!_db.TransactionPool.ContainsKey(_con.Connection))
+ throw new InvalidOperationException("Can't create transaction using disposed connection.");
+ else if (_singleTransaction && _db.TransactionPool[_con.Connection].Count > 0)
+ _operational = false;
+ else
+ {
+ _db.TransactionPool[_con.Connection]
+ .Push(il.HasValue ? _con.Connection.BeginTransaction(il.Value) : _con.Connection.BeginTransaction());
+ _db.PoolStamp = DateTime.Now.Ticks;
+ _operational = true;
+ }
+ }
+ }
+
+ /// Commits the database transaction.
+ public void Commit()
+ {
+ lock (_db.SyncLock)
+ {
+ if (_operational)
+ {
+ var t = _db.TransactionPool.TryGetValue(_con.Connection);
+
+ if (t != null && t.Count > 0)
+ {
+ IDbTransaction trans = t.Pop();
+
+ _db.PoolStamp = DateTime.Now.Ticks;
+
+ trans.Commit();
+ trans.Dispose();
+ }
+
+ _operational = false;
+ }
+ }
+ }
+
+ /// Rolls back a transaction from a pending state.
+ public void Rollback()
+ {
+ lock (_db.SyncLock)
+ {
+ if (_operational)
+ {
+ var t = _db.TransactionPool.TryGetValue(_con.Connection);
+
+ if (t != null && t.Count > 0)
+ {
+ IDbTransaction trans = t.Pop();
+
+ _db.PoolStamp = DateTime.Now.Ticks;
+
+ trans.Rollback();
+ trans.Dispose();
+ }
+
+ _operational = false;
+ }
+ }
+ }
+
+ /// Gets connection object to associate with the transaction.
+ public IDbConnection Connection
+ {
+ get { return _con; }
+ }
+
+ /// Gets for this transaction.
+ public IsolationLevel IsolationLevel { get; private set; }
+
+ #region IExtendedDisposable Members
+
+ /// Performs application-defined tasks associated with
+ /// freeing, releasing, or resetting unmanaged resources.
+ public void Dispose()
+ {
+ Rollback();
+
+ if (_disposed != null)
+ _disposed();
+ }
+
+ /// Gets a value indicating whether this instance is disposed.
+ public bool IsDisposed { get { return !_operational; } }
+
+ #endregion IExtendedDisposable Members
+ }
+
+ namespace Builders
+ {
+ /// Dynamic delete query builder interface.
+ /// This interface it publically available. Implementation should be hidden.
+ public interface IDynamicDeleteQueryBuilder : IDynamicQueryBuilder
+ {
+ /// Execute this builder.
+ /// Result of an execution..
+ int Execute();
+
+ ///
+ /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition
+ /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used
+ /// as needed.
+ /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator.
+ /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in:
+ /// 'Where( x => x.Or( condition ) )'.
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ IDynamicDeleteQueryBuilder Where(Func func);
+
+ /// Add where condition.
+ /// Condition column with operator and value.
+ /// Builder instance.
+ IDynamicDeleteQueryBuilder Where(DynamicColumn column);
+
+ /// Add where condition.
+ /// Condition column.
+ /// Condition operator.
+ /// Condition value.
+ /// Builder instance.
+ IDynamicDeleteQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value);
+
+ /// Add where condition.
+ /// Condition column.
+ /// Condition value.
+ /// Builder instance.
+ IDynamicDeleteQueryBuilder Where(string column, object value);
+
+ /// Add where condition.
+ /// Set conditions as properties and values of an object.
+ /// If true use schema to determine key columns and ignore those which
+ /// aren't keys.
+ /// Builder instance.
+ IDynamicDeleteQueryBuilder Where(object conditions, bool schema = false);
+ }
+
+ /// Dynamic insert query builder interface.
+ /// This interface it publically available. Implementation should be hidden.
+ public interface IDynamicInsertQueryBuilder : IDynamicQueryBuilder
+ {
+ /// Execute this builder.
+ /// Result of an execution..
+ int Execute();
+
+ ///
+ /// Specifies the columns to insert using the dynamic lambda expressions given. Each expression correspond to one
+ /// column, and can:
+ /// - Resolve to a string, in this case a '=' must appear in the string.
+ /// - Resolve to a expression with the form: 'x => x.Column = Value'.
+ ///
+ /// The specifications.
+ /// The specifications.
+ /// This instance to permit chaining.
+ IDynamicInsertQueryBuilder Values(Func fn, params Func[] func);
+
+ /// Add insert fields.
+ /// Insert column.
+ /// Insert value.
+ /// Builder instance.
+ IDynamicInsertQueryBuilder Insert(string column, object value);
+
+ /// Add insert fields.
+ /// Set insert value as properties and values of an object.
+ /// Builder instance.
+ IDynamicInsertQueryBuilder Insert(object o);
+ }
+
+ /// Dynamic query builder base interface.
+ /// This interface it publically available. Implementation should be hidden.
+ public interface IDynamicQueryBuilder
+ {
+ /// Gets instance.
+ DynamicDatabase Database { get; }
+
+ /// Gets tables information.
+ IList Tables { get; }
+
+ /// Gets the tables used in this builder.
+ IDictionary Parameters { get; }
+
+ /// Gets or sets a value indicating whether add virtual parameters.
+ bool VirtualMode { get; set; }
+
+ /// Gets a value indicating whether database supports standard schema.
+ bool SupportSchema { get; }
+
+ /// Fill command with query.
+ /// Command to fill.
+ /// Filled instance of .
+ IDbCommand FillCommand(IDbCommand command);
+
+ ///
+ /// Generates the text this command will execute against the underlying database.
+ ///
+ /// The text to execute against the underlying database.
+ /// This method must be override by derived classes.
+ string CommandText();
+
+ /// Gets or sets the on create temporary parameter action.
+ /// This is exposed to allow setting schema of column.
+ Action OnCreateTemporaryParameter { get; set; }
+
+ /// Gets or sets the on create real parameter action.
+ /// This is exposed to allow modification of parameter.
+ Action OnCreateParameter { get; set; }
+ }
+
+ /// Dynamic select query builder interface.
+ /// This interface it publically available. Implementation should be hidden.
+ public interface IDynamicSelectQueryBuilder : IDynamicQueryBuilder ////, IEnumerable
+ {
+ /// Execute this builder.
+ /// Enumerator of objects expanded from query.
+ IEnumerable Execute();
+
+ /// Execute this builder and map to given type.
+ /// Type of object to map on.
+ /// Enumerator of objects expanded from query.
+ IEnumerable Execute() where T : class;
+
+ /// Execute this builder as a data reader.
+ /// Action containing reader.
+ void ExecuteDataReader(Action reader);
+
+ /// Returns a single result.
+ /// Result of a query.
+ object Scalar();
+
+ #region From/Join
+
+ ///
+ /// Adds to the 'From' clause the contents obtained by parsing the dynamic lambda expressions given. The supported
+ /// formats are:
+ /// - Resolve to a string: 'x => "Table AS Alias', where the alias part is optional.
+ /// - Resolve to an expression: 'x => x.Table.As( x.Alias )', where the alias part is optional.
+ /// - Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this
+ /// case the alias is not annotated.
+ ///
+ /// The specification.
+ /// The specification.
+ /// This instance to permit chaining.
+ IDynamicSelectQueryBuilder From(Func fn, params Func[] func);
+
+ ///
+ /// Adds to the 'Join' clause the contents obtained by parsing the dynamic lambda expressions given. The supported
+ /// formats are:
+ /// - Resolve to a string: 'x => "Table AS Alias ON Condition', where the alias part is optional.
+ /// - Resolve to an expression: 'x => x.Table.As( x.Alias ).On( condition )', where the alias part is optional.
+ /// - Generic expression: 'x => x( expression ).As( x.Alias ).On( condition )', where the alias part is mandatory.
+ /// In this case the alias is not annotated.
+ /// The expression might be prepended by a method that, in this case, is used to specify the specific join type you
+ /// want to perform, as in: 'x => x.Left()...". Two considerations apply:
+ /// - If a 'false' argument is used when no 'Join' part appears in its name, then no 'Join' suffix is added
+ /// with a space in between.
+ /// - If a 'false' argument is used when a 'Join' part does appear, then no split is performed to separate the
+ /// 'Join' part.
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ IDynamicSelectQueryBuilder Join(params Func[] func);
+
+ #endregion From/Join
+
+ #region Where
+
+ ///
+ /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition
+ /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used
+ /// as needed.
+ /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator.
+ /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in:
+ /// 'Where( x => x.Or( condition ) )'.
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ IDynamicSelectQueryBuilder Where(Func func);
+
+ /// Add where condition.
+ /// Condition column with operator and value.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Where(DynamicColumn column);
+
+ /// Add where condition.
+ /// Condition column.
+ /// Condition operator.
+ /// Condition value.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value);
+
+ /// Add where condition.
+ /// Condition column.
+ /// Condition value.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Where(string column, object value);
+
+ /// Add where condition.
+ /// Set conditions as properties and values of an object.
+ /// If true use schema to determine key columns and ignore those which
+ /// aren't keys.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Where(object conditions, bool schema = false);
+
+ #endregion Where
+
+ #region Select
+
+ ///
+ /// Adds to the 'Select' clause the contents obtained by parsing the dynamic lambda expressions given. The supported
+ /// formats are:
+ /// - Resolve to a string: 'x => "Table.Column AS Alias', where the alias part is optional.
+ /// - Resolve to an expression: 'x => x.Table.Column.As( x.Alias )', where the alias part is optional.
+ /// - Select all columns from a table: 'x => x.Table.All()'.
+ /// - Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this case
+ /// the alias is not annotated.
+ ///
+ /// The specification.
+ /// The specification.
+ /// This instance to permit chaining.
+ IDynamicSelectQueryBuilder Select(Func fn, params Func[] func);
+
+ /// Add select columns.
+ /// Columns to add to object.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder SelectColumn(params DynamicColumn[] columns);
+
+ /// Add select columns.
+ /// Columns to add to object.
+ /// Column format consist of Column Name , Alias and
+ /// Aggregate function in this order separated by ': '.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder SelectColumn(params string[] columns);
+
+ #endregion Select
+
+ #region GroupBy
+
+ ///
+ /// Adds to the 'Group By' clause the contents obtained from from parsing the dynamic lambda expression given.
+ ///
+ /// The specification.
+ /// The specification.
+ /// This instance to permit chaining.
+ IDynamicSelectQueryBuilder GroupBy(Func fn, params Func[] func);
+
+ /// Add select columns.
+ /// Columns to group by.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder GroupByColumn(params DynamicColumn[] columns);
+
+ /// Add select columns.
+ /// Columns to group by.
+ /// Column format consist of Column Name and
+ /// Alias in this order separated by ': '.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder GroupByColumn(params string[] columns);
+
+ #endregion GroupBy
+
+ #region OrderBy
+
+ ///
+ /// Adds to the 'Order By' clause the contents obtained from from parsing the dynamic lambda expression given. It
+ /// accepts a multipart column specification followed by an optional Ascending() or Descending() virtual methods
+ /// to specify the direction. If no virtual method is used, the default is ascending order. You can also use the
+ /// shorter versions Asc() and Desc().
+ ///
+ /// The specification.
+ /// The specification.
+ /// This instance to permit chaining.
+ IDynamicSelectQueryBuilder OrderBy(Func fn, params Func[] func);
+
+ /// Add select columns.
+ /// Columns to order by.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder OrderByColumn(params DynamicColumn[] columns);
+
+ /// Add select columns.
+ /// Columns to order by.
+ /// Column format consist of Column Name and
+ /// Alias in this order separated by ': '.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder OrderByColumn(params string[] columns);
+
+ #endregion OrderBy
+
+ #region Top/Limit/Offset/Distinct
+
+ /// Set top if database support it.
+ /// How many objects select.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Top(int? top);
+
+ /// Set top if database support it.
+ /// How many objects select.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Limit(int? limit);
+
+ /// Set top if database support it.
+ /// How many objects skip selecting.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Offset(int? offset);
+
+ /// Set distinct mode.
+ /// Distinct mode.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Distinct(bool distinct = true);
+
+ #endregion Top/Limit/Offset/Distinct
+ }
+
+ /// Dynamic update query builder interface.
+ /// This interface it publically available. Implementation should be hidden.
+ public interface IDynamicUpdateQueryBuilder : IDynamicQueryBuilder
+ {
+ /// Execute this builder.
+ /// Result of an execution..
+ int Execute();
+
+ #region Update
+
+ /// Add update value or where condition using schema.
+ /// Update or where column name.
+ /// Column value.
+ /// Builder instance.
+ IDynamicUpdateQueryBuilder Update(string column, object value);
+
+ /// Add update values and where condition columns using schema.
+ /// Set values or conditions as properties and values of an object.
+ /// Builder instance.
+ IDynamicUpdateQueryBuilder Update(object conditions);
+
+ #endregion Update
+
+ #region Values
+
+ ///
+ /// Specifies the columns to update using the dynamic lambda expressions given. Each expression correspond to one
+ /// column, and can:
+ /// - Resolve to a string, in this case a '=' must appear in the string.
+ /// - Resolve to a expression with the form: 'x => x.Column = Value'.
+ ///
+ /// The specifications.
+ /// This instance to permit chaining.
+ IDynamicUpdateQueryBuilder Set(params Func[] func);
+
+ /// Add insert fields.
+ /// Insert column.
+ /// Insert value.
+ /// Builder instance.
+ IDynamicUpdateQueryBuilder Values(string column, object value);
+
+ /// Add insert fields.
+ /// Set insert value as properties and values of an object.
+ /// Builder instance.
+ IDynamicUpdateQueryBuilder Values(object o);
+
+ #endregion Values
+
+ #region Where
+
+ ///
+ /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition
+ /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used
+ /// as needed.
+ /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator.
+ /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in:
+ /// 'Where( x => x.Or( condition ) )'.
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ IDynamicUpdateQueryBuilder Where(Func func);
+
+ /// Add where condition.
+ /// Condition column with operator and value.
+ /// Builder instance.
+ IDynamicUpdateQueryBuilder Where(DynamicColumn column);
+
+ /// Add where condition.
+ /// Condition column.
+ /// Condition operator.
+ /// Condition value.
+ /// Builder instance.
+ IDynamicUpdateQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value);
+
+ /// Add where condition.
+ /// Condition column.
+ /// Condition value.
+ /// Builder instance.
+ IDynamicUpdateQueryBuilder Where(string column, object value);
+
+ /// Add where condition.
+ /// Set conditions as properties and values of an object.
+ /// If true use schema to determine key columns and ignore those which
+ /// aren't keys.
+ /// Builder instance.
+ IDynamicUpdateQueryBuilder Where(object conditions, bool schema = false);
+
+ #endregion Where
+ }
+
+ /// Interface describing parameter info.
+ public interface IParameter
+ {
+ /// Gets the parameter position in command.
+ /// Available after filling the command.
+ int Ordinal { get; }
+
+ /// Gets the parameter temporary name.
+ string Name { get; }
+
+ /// Gets or sets the parameter value.
+ object Value { get; set; }
+
+ /// Gets or sets a value indicating whether name of temporary parameter is well known.
+ bool WellKnown { get; set; }
+
+ /// Gets or sets a value indicating whether this is virtual.
+ bool Virtual { get; set; }
+
+ /// Gets or sets the parameter schema information.
+ DynamicSchemaColumn? Schema { get; set; }
+ }
+
+ /// Interface describing table information.
+ public interface ITableInfo
+ {
+ /// Gets table owner name.
+ string Owner { get; }
+
+ /// Gets table name.
+ string Name { get; }
+
+ /// Gets table alias.
+ string Alias { get; }
+
+ /// Gets table schema.
+ Dictionary Schema { get; }
+ }
+
+ namespace Extensions
+ {
+ internal static class DynamicModifyBuilderExtensions
+ {
+ internal static T Table(this T builder, Func func) where T : DynamicModifyBuilder
+ {
+ if (func == null)
+ throw new ArgumentNullException("Function cannot be null.");
+
+ using (var parser = DynamicParser.Parse(func))
+ {
+ var result = parser.Result;
+
+ // If the expression result is string.
+ if (result is string)
+ return builder.Table((string)result);
+ else if (result is Type)
+ return builder.Table((Type)result);
+ else if (result is DynamicParser.Node)
+ {
+ // Or if it resolves to a dynamic node
+ var node = (DynamicParser.Node)result;
+
+ string owner = null;
+ string main = null;
+
+ while (true)
+ {
+ // Deny support for the AS() virtual method...
+ if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS")
+ throw new ArgumentException(string.Format("Alias is not supported on modification builders. (Parsing: {0})", result));
+
+ // Support for table specifications...
+ if (node is DynamicParser.Node.GetMember)
+ {
+ if (owner != null)
+ throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result));
+
+ if (main != null)
+ owner = ((DynamicParser.Node.GetMember)node).Name;
+ else
+ main = ((DynamicParser.Node.GetMember)node).Name;
+
+ node = node.Host;
+ continue;
+ }
+
+ // Support for generic sources...
+ if (node is DynamicParser.Node.Invoke)
+ {
+ if (owner == null && main == null)
+ {
+ var invoke = (DynamicParser.Node.Invoke)node;
+
+ if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type)
+ return builder.Table((Type)invoke.Arguments[0]);
+ else if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is String)
+ return builder.Table((string)invoke.Arguments[0]);
+ else
+ throw new ArgumentException(string.Format("Invalid argument count or type when parsing '{2}'. Invoke supports only one argument of type Type or String", owner, main, result));
+ }
+ else if (owner != null)
+ throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result));
+ else if (main != null)
+ throw new ArgumentException(string.Format("Main '{0}' is already set when parsing '{1}'.", main, result));
+ }
+
+ if (!string.IsNullOrEmpty(main))
+ return builder.Table(string.Format("{0}{1}",
+ string.IsNullOrEmpty(owner) ? string.Empty : string.Format("{0}.", owner),
+ main));
+ }
+ }
+
+ throw new ArgumentException(string.Format("Unable to set table parsing '{0}'", result));
+ }
+ }
+
+ internal static T Table(this T builder, string tableName, Dictionary schema = null) where T : DynamicModifyBuilder
+ {
+ var tuple = tableName.Validated("Table Name").SplitSomethingAndAlias();
+
+ if (!string.IsNullOrEmpty(tuple.Item2))
+ throw new ArgumentException(string.Format("Can not use aliases in INSERT steatement. ({0})", tableName), "tableName");
+
+ var parts = tuple.Item1.Split('.');
+
+ if (parts.Length > 2)
+ throw new ArgumentException(string.Format("Table name can consist only from name or owner and name. ({0})", tableName), "tableName");
+
+ builder.Tables.Clear();
+ builder.Tables.Add(new DynamicQueryBuilder.TableInfo(builder.Database,
+ builder.Database.StripName(parts.Last()).Validated("Table"), null,
+ parts.Length == 2 ? builder.Database.StripName(parts.First()).Validated("Owner", canbeNull: true) : null));
+
+ if (schema != null)
+ (builder.Tables[0] as DynamicQueryBuilder.TableInfo).Schema = schema;
+
+ return builder;
+ }
+
+ internal static T Table(this T builder, Type type) where T : DynamicQueryBuilder
+ {
+ if (type.IsAnonymous())
+ throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}).", type.FullName));
+
+ var mapper = DynamicMapperCache.GetMapper(type);
+
+ if (mapper == null)
+ throw new InvalidOperationException("Cant assign unmapable type as a table.");
+
+ if (builder is DynamicModifyBuilder)
+ {
+ builder.Tables.Clear();
+ builder.Tables.Add(new DynamicQueryBuilder.TableInfo(builder.Database, type));
+ }
+ else if (builder is DynamicSelectQueryBuilder)
+ (builder as DynamicSelectQueryBuilder).From(x => x(type));
+
+ return builder;
+ }
+ }
+
+ internal static class DynamicWhereQueryExtensions
+ {
+ #region Where
+
+ internal static T InternalWhere(this T builder, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
+ {
+ return builder.InternalWhere(false, false, func);
+ }
+
+ internal static T InternalWhere(this T builder, bool addBeginBrace, bool addEndBrace, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
+ {
+ if (func == null) throw new ArgumentNullException("Array of functions cannot be null.");
+
+ using (var parser = DynamicParser.Parse(func))
+ {
+ string condition = null;
+ bool and = true;
+
+ var result = parser.Result;
+ if (result is string)
+ {
+ condition = (string)result;
+
+ if (condition.ToUpper().IndexOf("OR") == 0)
+ {
+ and = false;
+ condition = condition.Substring(3);
+ }
+ else if (condition.ToUpper().IndexOf("AND") == 0)
+ condition = condition.Substring(4);
+ }
+ else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType)
+ return builder.InternalWhere(result);
+ else
+ {
+ // Intercepting the 'x => x.And()' and 'x => x.Or()' virtual methods...
+ if (result is DynamicParser.Node.Method && ((DynamicParser.Node.Method)result).Host is DynamicParser.Node.Argument)
+ {
+ var node = (DynamicParser.Node.Method)result;
+ var name = node.Name.ToUpper();
+ if (name == "AND" || name == "OR")
+ {
+ object[] args = ((DynamicParser.Node.Method)node).Arguments;
+ if (args == null) throw new ArgumentNullException("arg", string.Format("{0} is not a parameterless method.", name));
+ if (args.Length != 1) throw new ArgumentException(string.Format("{0} requires one and only one parameter: {1}.", name, args.Sketch()));
+
+ and = name == "AND" ? true : false;
+ result = args[0];
+ }
+ }
+
+ // Just parsing the contents now...
+ condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition");
+ }
+
+ if (addBeginBrace) builder.OpenBracketsCount++;
+ if (addEndBrace) builder.OpenBracketsCount--;
+
+ if (builder.WhereCondition == null)
+ builder.WhereCondition = string.Format("{0}{1}{2}",
+ addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty);
+ else
+ builder.WhereCondition = string.Format("{0} {1} {2}{3}{4}", builder.WhereCondition, and ? "AND" : "OR",
+ addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty);
+ }
+
+ return builder;
+ }
+
+ internal static T InternalWhere(this T builder, DynamicColumn column) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
+ {
+ bool virt = builder.VirtualMode;
+ if (column.VirtualColumn.HasValue)
+ builder.VirtualMode = column.VirtualColumn.Value;
+
+ // It's kind of uglu, but... well it works.
+ if (column.Or)
+ switch (column.Operator)
+ {
+ default:
+ case DynamicColumn.CompareOperator.Eq: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break;
+ case DynamicColumn.CompareOperator.Not: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break;
+ case DynamicColumn.CompareOperator.Like: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break;
+ case DynamicColumn.CompareOperator.NotLike: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); break;
+ case DynamicColumn.CompareOperator.In: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break;
+ case DynamicColumn.CompareOperator.Lt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break;
+ case DynamicColumn.CompareOperator.Lte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break;
+ case DynamicColumn.CompareOperator.Gt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break;
+ case DynamicColumn.CompareOperator.Gte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break;
+ case DynamicColumn.CompareOperator.Between: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Between(column.Value))); break;
+ }
+ else
+ switch (column.Operator)
+ {
+ default:
+ case DynamicColumn.CompareOperator.Eq: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break;
+ case DynamicColumn.CompareOperator.Not: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break;
+ case DynamicColumn.CompareOperator.Like: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break;
+ case DynamicColumn.CompareOperator.NotLike: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break;
+ case DynamicColumn.CompareOperator.In: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break;
+ case DynamicColumn.CompareOperator.Lt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break;
+ case DynamicColumn.CompareOperator.Lte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break;
+ case DynamicColumn.CompareOperator.Gt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break;
+ case DynamicColumn.CompareOperator.Gte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break;
+ case DynamicColumn.CompareOperator.Between: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break;
+ }
+
+ builder.VirtualMode = virt;
+
+ return builder;
+ }
+
+ internal static T InternalWhere(this T builder, string column, DynamicColumn.CompareOperator op, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
+ {
+ if (value is DynamicColumn)
+ {
+ var v = (DynamicColumn)value;
+
+ if (string.IsNullOrEmpty(v.ColumnName))
+ v.ColumnName = column;
+
+ return builder.InternalWhere(v);
+ }
+ else if (value is IEnumerable)
+ {
+ foreach (DynamicColumn v in (IEnumerable)value)
+ builder.InternalWhere(v);
+
+ return builder;
+ }
+
+ return builder.InternalWhere(new DynamicColumn
+ {
+ ColumnName = column,
+ Operator = op,
+ Value = value
+ });
+ }
+
+ internal static T InternalWhere(this T builder, string column, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
+ {
+ return builder.InternalWhere(column, DynamicColumn.CompareOperator.Eq, value);
+ }
+
+ internal static T InternalWhere(this T builder, object conditions, bool schema = false) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
+ {
+ if (conditions is DynamicColumn)
+ return builder.InternalWhere((DynamicColumn)conditions);
+ else if (conditions is IEnumerable)
+ {
+ foreach (DynamicColumn v in (IEnumerable)conditions)
+ builder.InternalWhere(v);
+
+ return builder;
+ }
+
+ var dict = conditions.ToDictionary();
+ var mapper = DynamicMapperCache.GetMapper(conditions.GetType());
+ var table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty);
+
+ foreach (var condition in dict)
+ {
+ if (mapper.Ignored.Contains(condition.Key) || condition.Key == "_table")
+ continue;
+
+ string colName = mapper != null ? mapper.PropertyMap.TryGetValue(condition.Key) ?? condition.Key : condition.Key;
+
+ DynamicSchemaColumn? col = null;
+
+ // This should be used on typed queries or update/delete steatements, which usualy operate on a single table.
+ if (schema)
+ {
+ col = builder.GetColumnFromSchema(colName, mapper, table);
+
+ if ((!col.HasValue || !col.Value.IsKey) &&
+ (mapper == null || mapper.ColumnsMap.TryGetValue(colName).NullOr(m => m.Ignore || m.Column.NullOr(c => !c.IsKey, true), true)))
+ continue;
+
+ colName = col.HasValue ? col.Value.Name : colName;
+ }
+
+ if (!string.IsNullOrEmpty(table))
+ builder.InternalWhere(x => x(builder.FixObjectName(string.Format("{0}.{1}", table, colName))) == condition.Value);
+ else
+ builder.InternalWhere(x => x(builder.FixObjectName(colName)) == condition.Value);
+ }
+
+ return builder;
+ }
+
+ #endregion Where
+ }
+ }
+
+ namespace Implementation
+ {
+ /// Implementation of dynamic delete query builder.
+ internal class DynamicDeleteQueryBuilder : DynamicModifyBuilder, IDynamicDeleteQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The database.
+ internal DynamicDeleteQueryBuilder(DynamicDatabase db)
+ : base(db)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The database.
+ /// Name of the table.
+ public DynamicDeleteQueryBuilder(DynamicDatabase db, string tableName)
+ : base(db, tableName)
+ {
+ }
+
+ /// Generates the text this command will execute against the underlying database.
+ /// The text to execute against the underlying database.
+ /// This method must be override by derived classes.
+ public override string CommandText()
+ {
+ var info = Tables.Single();
+ return string.Format("DELETE FROM {0}{1}{2}{3}",
+ string.IsNullOrEmpty(info.Owner) ? string.Empty : string.Format("{0}.", Database.DecorateName(info.Owner)),
+ Database.DecorateName(info.Name),
+ string.IsNullOrEmpty(WhereCondition) ? string.Empty : " WHERE ",
+ WhereCondition);
+ }
+
+ #region Where
+
+ ///
+ /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition
+ /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used
+ /// as needed.
+ /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator.
+ /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in:
+ /// 'Where( x => x.Or( condition ) )'.
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ public virtual IDynamicDeleteQueryBuilder Where(Func func)
+ {
+ return this.InternalWhere(func);
+ }
+
+ /// Add where condition.
+ /// Condition column with operator and value.
+ /// Builder instance.
+ public virtual IDynamicDeleteQueryBuilder Where(DynamicColumn column)
+ {
+ return this.InternalWhere(column);
+ }
+
+ /// Add where condition.
+ /// Condition column.
+ /// Condition operator.
+ /// Condition value.
+ /// Builder instance.
+ public virtual IDynamicDeleteQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value)
+ {
+ return this.InternalWhere(column, op, value);
+ }
+
+ /// Add where condition.
+ /// Condition column.
+ /// Condition value.
+ /// Builder instance.
+ public virtual IDynamicDeleteQueryBuilder Where(string column, object value)
+ {
+ return this.InternalWhere(column, value);
+ }
+
+ /// Add where condition.
+ /// Set conditions as properties and values of an object.
+ /// If true use schema to determine key columns and ignore those which
+ /// aren't keys.
+ /// Builder instance.
+ public virtual IDynamicDeleteQueryBuilder Where(object conditions, bool schema = false)
+ {
+ return this.InternalWhere(conditions, schema);
+ }
+
+ #endregion Where
+ }
+
+ /// Implementation of dynamic insert query builder.
+ internal class DynamicInsertQueryBuilder : DynamicModifyBuilder, IDynamicInsertQueryBuilder
+ {
+ private string _columns;
+ private string _values;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The database.
+ internal DynamicInsertQueryBuilder(DynamicDatabase db)
+ : base(db)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The database.
+ /// Name of the table.
+ public DynamicInsertQueryBuilder(DynamicDatabase db, string tableName)
+ : base(db, tableName)
+ {
+ }
+
+ /// Generates the text this command will execute against the underlying database.
+ /// The text to execute against the underlying database.
+ /// This method must be override by derived classes.
+ public override string CommandText()
+ {
+ var info = Tables.Single();
+ return string.Format("INSERT INTO {0}{1} ({2}) VALUES ({3})",
+ string.IsNullOrEmpty(info.Owner) ? string.Empty : string.Format("{0}.", Database.DecorateName(info.Owner)),
+ Database.DecorateName(info.Name), _columns, _values);
+ }
+
+ #region Insert
+
+ ///
+ /// Specifies the columns to insert using the dynamic lambda expressions given. Each expression correspond to one
+ /// column, and can:
+ /// - Resolve to a string, in this case a '=' must appear in the string.
+ /// - Resolve to a expression with the form: 'x => x.Column = Value'.
+ ///
+ /// The specifications.
+ /// The specifications.
+ /// This instance to permit chaining.
+ public virtual IDynamicInsertQueryBuilder Values(Func fn, params Func[] func)
+ {
+ if (fn == null)
+ throw new ArgumentNullException("Array of specifications cannot be null.");
+
+ int index = InsertFunc(-1, fn);
+
+ if (func != null)
+ foreach (var f in func)
+ index = InsertFunc(index, f);
+
+ return this;
+ }
+
+ private int InsertFunc(int index, Func f)
+ {
+ index++;
+
+ if (f == null)
+ throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index));
+
+ using (var parser = DynamicParser.Parse(f))
+ {
+ var result = parser.Result;
+ if (result == null)
+ throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index));
+
+ string main = null;
+ string value = null;
+ string str = null;
+
+ // When 'x => x.Table.Column = value' or 'x => x.Column = value'...
+ if (result is DynamicParser.Node.SetMember)
+ {
+ var node = (DynamicParser.Node.SetMember)result;
+
+ DynamicSchemaColumn? col = GetColumnFromSchema(node.Name);
+ main = Database.DecorateName(node.Name);
+ value = Parse(node.Value, pars: Parameters, nulls: true, columnSchema: col);
+
+ _columns = _columns == null ? main : string.Format("{0}, {1}", _columns, main);
+ _values = _values == null ? value : string.Format("{0}, {1}", _values, value);
+ return index;
+ }
+ else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType)
+ {
+ Insert(result);
+ return index;
+ }
+
+ // Other specifications are considered invalid...
+ var err = string.Format("Specification '{0}' is invalid.", result);
+ str = Parse(result);
+ if (str.Contains("=")) err += " May have you used a '==' instead of a '=' operator?";
+ throw new ArgumentException(err);
+ }
+ }
+
+ /// Add insert fields.
+ /// Insert column.
+ /// Insert value.
+ /// Builder instance.
+ public virtual IDynamicInsertQueryBuilder Insert(string column, object value)
+ {
+ if (value is DynamicColumn)
+ {
+ var v = (DynamicColumn)value;
+
+ if (string.IsNullOrEmpty(v.ColumnName))
+ v.ColumnName = column;
+
+ return Insert(v);
+ }
+
+ return Insert(new DynamicColumn
+ {
+ ColumnName = column,
+ Value = value,
+ });
+ }
+
+ /// Add insert fields.
+ /// Set insert value as properties and values of an object.
+ /// Builder instance.
+ public virtual IDynamicInsertQueryBuilder Insert(object o)
+ {
+ if (o is DynamicColumn)
+ {
+ var column = (DynamicColumn)o;
+ DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName);
+
+ string main = FixObjectName(column.ColumnName, onlyColumn: true);
+ string value = Parse(column.Value, pars: Parameters, nulls: true, columnSchema: col);
+
+ _columns = _columns == null ? main : string.Format("{0}, {1}", _columns, main);
+ _values = _values == null ? value : string.Format("{0}, {1}", _values, value);
+
+ return this;
+ }
+
+ var dict = o.ToDictionary();
+ var mapper = DynamicMapperCache.GetMapper(o.GetType());
+
+ if (mapper != null)
+ {
+ foreach (var con in dict)
+ if (!mapper.Ignored.Contains(con.Key))
+ Insert(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value);
+ }
+ else
+ foreach (var con in dict)
+ Insert(con.Key, con.Value);
+
+ return this;
+ }
+
+ #endregion Insert
+ }
+
+ /// Base query builder for insert/update/delete statements.
+ internal abstract class DynamicModifyBuilder : DynamicQueryBuilder
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The database.
+ public DynamicModifyBuilder(DynamicDatabase db)
+ : base(db)
+ {
+ VirtualMode = false;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The database.
+ /// Name of the table.
+ public DynamicModifyBuilder(DynamicDatabase db, string tableName)
+ : this(db)
+ {
+ VirtualMode = false;
+ this.Table(tableName);
+ }
+
+ /// Execute this builder.
+ /// Result of an execution..
+ public virtual int Execute()
+ {
+ using (var con = Database.Open())
+ using (var cmd = con.CreateCommand())
+ {
+ return cmd
+ .SetCommand(this)
+ .ExecuteNonQuery();
+ }
+ }
+ }
+
+ /// Implementation of dynamic query builder base interface.
+ internal abstract class DynamicQueryBuilder : IDynamicQueryBuilder
+ {
+ /// Empty interface to allow where query builder implementation use universal approach.
+ internal interface IQueryWithWhere
+ {
+ /// Gets or sets the where condition.
+ string WhereCondition { get; set; }
+
+ /// Gets or sets the amount of not closed brackets in where statement.
+ int OpenBracketsCount { get; set; }
+ }
+
+ private DynamicQueryBuilder _parent = null;
+
+ #region TableInfo
+
+ /// Table information.
+ internal class TableInfo : ITableInfo
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal TableInfo()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The database.
+ /// The name of table.
+ /// The table alias.
+ /// The table owner.
+ public TableInfo(DynamicDatabase db, string name, string alias = null, string owner = null)
+ {
+ Name = name;
+ Alias = alias;
+ Owner = owner;
+
+ if (!name.ContainsAny(StringExtensions.InvalidMemberChars))
+ Schema = db.GetSchema(name, owner: owner);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The database.
+ /// The type which can be mapped to database.
+ /// The table alias.
+ /// The table owner.
+ public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null)
+ {
+ var mapper = DynamicMapperCache.GetMapper(type);
+
+ Name = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ?
+ mapper.Type.Name : mapper.Table.Name;
+
+ Owner = (mapper.Table != null) ? mapper.Table.Owner : owner;
+ Alias = alias;
+
+ Schema = db.GetSchema(type);
+ }
+
+ /// Gets or sets table owner name.
+ public string Owner { get; internal set; }
+
+ /// Gets or sets table name.
+ public string Name { get; internal set; }
+
+ /// Gets or sets table alias.
+ public string Alias { get; internal set; }
+
+ /// Gets or sets table schema.
+ public Dictionary Schema { get; internal set; }
+ }
+
+ /// Generic based table information.
+ /// Type of class that is represented in database.
+ internal class TableInfo : TableInfo
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The database.
+ /// The table alias.
+ /// The table owner.
+ public TableInfo(DynamicDatabase db, string alias = null, string owner = null)
+ : base(db, typeof(T), alias, owner)
+ {
+ }
+ }
+
+ #endregion TableInfo
+
+ #region Parameter
+
+ /// Interface describing parameter info.
+ internal class Parameter : IParameter
+ {
+ /// Gets or sets the parameter position in command.
+ /// Available after filling the command.
+ public int Ordinal { get; internal set; }
+
+ /// Gets or sets the parameter temporary name.
+ public string Name { get; internal set; }
+
+ /// Gets or sets the parameter value.
+ public object Value { get; set; }
+
+ /// Gets or sets a value indicating whether name of temporary parameter is well known.
+ public bool WellKnown { get; set; }
+
+ /// Gets or sets a value indicating whether this is virtual.
+ public bool Virtual { get; set; }
+
+ /// Gets or sets the parameter schema information.
+ public DynamicSchemaColumn? Schema { get; set; }
+ }
+
+ #endregion Parameter
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The database.
+ public DynamicQueryBuilder(DynamicDatabase db)
+ {
+ VirtualMode = false;
+ Tables = new List();
+ Parameters = new Dictionary();
+
+ WhereCondition = null;
+ OpenBracketsCount = 0;
+
+ Database = db;
+ SupportSchema = (db.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema;
+ }
+
+ /// Initializes a new instance of the class.
+ /// The database.
+ /// The parent query.
+ internal DynamicQueryBuilder(DynamicDatabase db, DynamicQueryBuilder parent)
+ : this(db)
+ {
+ _parent = parent;
+ }
+
+ #endregion Constructor
+
+ #region IQueryWithWhere
+
+ /// Gets or sets the where condition.
+ public string WhereCondition { get; set; }
+
+ /// Gets or sets the amount of not closed brackets in where statement.
+ public int OpenBracketsCount { get; set; }
+
+ #endregion IQueryWithWhere
+
+ #region IDynamicQueryBuilder
+
+ /// Gets instance.
+ public DynamicDatabase Database { get; private set; }
+
+ /// Gets the tables used in this builder.
+ public IList Tables { get; private set; }
+
+ /// Gets the tables used in this builder.
+ public IDictionary Parameters { get; private set; }
+
+ /// Gets or sets a value indicating whether add virtual parameters.
+ public bool VirtualMode { get; set; }
+
+ /// Gets or sets the on create temporary parameter action.
+ /// This is exposed to allow setting schema of column.
+ public Action OnCreateTemporaryParameter { get; set; }
+
+ /// Gets or sets the on create real parameter action.
+ /// This is exposed to allow modification of parameter.
+ public Action OnCreateParameter { get; set; }
+
+ /// Gets a value indicating whether database supports standard schema.
+ public bool SupportSchema { get; private set; }
+
+ ///
+ /// Generates the text this command will execute against the underlying database.
+ ///
+ /// The text to execute against the underlying database.
+ /// This method must be override by derived classes.
+ public abstract string CommandText();
+
+ /// Fill command with query.
+ /// Command to fill.
+ /// Filled instance of .
+ public virtual IDbCommand FillCommand(IDbCommand command)
+ {
+ // End not ended where statement
+ if (this is IQueryWithWhere)
+ {
+ while (OpenBracketsCount > 0)
+ {
+ WhereCondition += ")";
+ OpenBracketsCount--;
+ }
+ }
+
+ return command.SetCommand(CommandText()
+ .FillStringWithVariables(s =>
+ {
+ return Parameters.TryGetValue(s).NullOr(p =>
+ {
+ IDbDataParameter param = (IDbDataParameter)command
+ .AddParameter(this, p.Schema, p.Value)
+ .Parameters[command.Parameters.Count - 1];
+
+ (p as Parameter).Ordinal = command.Parameters.Count - 1;
+
+ if (OnCreateParameter != null)
+ OnCreateParameter(p, param);
+
+ return param.ParameterName;
+ }, s);
+ }));
+ }
+
+ #endregion IDynamicQueryBuilder
+
+ #region Parser
+
+ /// Parses the arbitrary object given and translates it into a string with the appropriate
+ /// syntax for the database this parser is specific to.
+ /// The object to parse and translate. It can be any arbitrary object, including null values (if
+ /// permitted) and dynamic lambda expressions.
+ /// If not null, the parameters' list where to store the parameters extracted by the parsing.
+ /// If true, literal (raw) string are allowed. If false and the node is a literal then, as a
+ /// security measure, an exception is thrown.
+ /// True to accept null values and translate them into the appropriate syntax accepted by the
+ /// database. If false and the value is null, then an exception is thrown.
+ /// If set to true decorate element.
+ /// If set parse argument as alias. This is workaround for AS method.
+ /// This parameter is used to determine type of parameter used in query.
+ /// A string containing the result of the parsing, along with the parameters extracted in the
+ /// instance if such is given.
+ /// Null nodes are not accepted.
+ internal virtual string Parse(object node, IDictionary pars = null, bool rawstr = false, bool nulls = false, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null)
+ {
+ // Null nodes are accepted or not depending upon the "nulls" flag...
+ if (node == null)
+ {
+ if (!nulls)
+ throw new ArgumentNullException("node", "Null nodes are not accepted.");
+
+ return Dispatch(node, pars, decorate, columnSchema: columnSchema);
+ }
+
+ // Nodes that are strings are parametrized or not depending the "rawstr" flag...
+ if (node is string)
+ {
+ if (rawstr) return (string)node;
+ else return Dispatch(node, pars, decorate, columnSchema: columnSchema);
+ }
+
+ // If node is a delegate, parse it to create the logical tree...
+ if (node is Delegate)
+ {
+ node = DynamicParser.Parse((Delegate)node).Result;
+ return Parse(node, pars, rawstr, decorate: decorate, columnSchema: columnSchema); // Intercept containers as in (x => "string")
+ }
+
+ return Dispatch(node, pars, decorate, isMultiPart, columnSchema);
+ }
+
+ private string Dispatch(object node, IDictionary pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null)
+ {
+ if (node != null)
+ {
+ if (node is DynamicQueryBuilder) return ParseCommand((DynamicQueryBuilder)node, pars);
+ else if (node is DynamicParser.Node.Argument) return ParseArgument((DynamicParser.Node.Argument)node, isMultiPart);
+ else if (node is DynamicParser.Node.GetMember) return ParseGetMember((DynamicParser.Node.GetMember)node, pars, decorate, isMultiPart, columnSchema);
+ else if (node is DynamicParser.Node.SetMember) return ParseSetMember((DynamicParser.Node.SetMember)node, pars, decorate, isMultiPart, columnSchema);
+ else if (node is DynamicParser.Node.Unary) return ParseUnary((DynamicParser.Node.Unary)node, pars);
+ else if (node is DynamicParser.Node.Binary) return ParseBinary((DynamicParser.Node.Binary)node, pars);
+ else if (node is DynamicParser.Node.Method) return ParseMethod((DynamicParser.Node.Method)node, pars);
+ else if (node is DynamicParser.Node.Invoke) return ParseInvoke((DynamicParser.Node.Invoke)node, pars);
+ else if (node is DynamicParser.Node.Convert) return ParseConvert((DynamicParser.Node.Convert)node, pars);
+ }
+
+ // All other cases are considered constant parameters...
+ return ParseConstant(node, pars, columnSchema);
+ }
+
+ internal virtual string ParseCommand(DynamicQueryBuilder node, IDictionary pars = null)
+ {
+ // Getting the command's text...
+ string str = node.CommandText(); // Avoiding spurious "OUTPUT XXX" statements
+
+ // If there are parameters to transform, but cannot store them, it is an error
+ if (node.Parameters.Count != 0 && pars == null)
+ throw new InvalidOperationException(string.Format("The parameters in this command '{0}' cannot be added to a null collection.", node.Parameters));
+
+ // Copy parameters to new comand
+ foreach (var parameter in node.Parameters)
+ pars.Add(parameter.Key, parameter.Value);
+
+ return string.Format("({0})", str);
+ }
+
+ protected virtual string ParseArgument(DynamicParser.Node.Argument node, bool isMultiPart = true, bool isOwner = false)
+ {
+ if (!string.IsNullOrEmpty(node.Name) && (isOwner || (isMultiPart && IsTableAlias(node.Name))))
+ return node.Name;
+
+ return null;
+ }
+
+ protected virtual string ParseGetMember(DynamicParser.Node.GetMember node, IDictionary pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null)
+ {
+ if (node.Host is DynamicParser.Node.Argument && IsTableAlias(node.Name))
+ {
+ decorate = false;
+ isMultiPart = false;
+ }
+
+ // This hack allows to use argument as alias, but when it is not nesesary use other column.
+ // Let say we hace a table Users with alias usr, and we join to table with alias ua which also has a column Users
+ // This allow use of usr => usr.ua.Users to result in ua."Users" instead of "Users" or usr."ua"."Users", se tests for examples.
+ string parent = null;
+ if (node.Host != null)
+ {
+ if (isMultiPart && node.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, null))
+ {
+ if (node.Host.Host != null && node.Host.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, node.Host.Host.Name))
+ parent = string.Format("{0}.{1}", Parse(node.Host.Host, pars, isMultiPart: false), Parse(node.Host, pars, isMultiPart: false));
+ else
+ parent = Parse(node.Host, pars, isMultiPart: false);
+ }
+ else if (isMultiPart)
+ parent = Parse(node.Host, pars, isMultiPart: isMultiPart);
+ }
+
+ ////string parent = node.Host == null || !isMultiPart ? null : Parse(node.Host, pars, isMultiPart: !IsTable(node.Name, node.Host.Name));
+ string name = parent == null ?
+ decorate ? Database.DecorateName(node.Name) : node.Name :
+ string.Format("{0}.{1}", parent, decorate ? Database.DecorateName(node.Name) : node.Name);
+
+ columnSchema = GetColumnFromSchema(name);
+
+ return name;
+ }
+
+ protected virtual string ParseSetMember(DynamicParser.Node.SetMember node, IDictionary pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null)
+ {
+ if (node.Host is DynamicParser.Node.Argument && IsTableAlias(node.Name))
+ {
+ decorate = false;
+ isMultiPart = false;
+ }
+
+ string parent = null;
+ if (node.Host != null)
+ {
+ if (isMultiPart && node.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, null))
+ {
+ if (node.Host.Host != null && node.Host.Host is DynamicParser.Node.GetMember && IsTable(node.Name, node.Host.Name))
+ parent = string.Format("{0}.{1}", Parse(node.Host.Host, pars, isMultiPart: false), Parse(node.Host, pars, isMultiPart: false));
+ else
+ parent = Parse(node.Host, pars, isMultiPart: false);
+ }
+ else if (isMultiPart)
+ parent = Parse(node.Host, pars, isMultiPart: isMultiPart);
+ }
+
+ ////string parent = node.Host == null || !isMultiPart ? null : Parse(node.Host, pars, isMultiPart: !IsTable(node.Name, node.Host.Name));
+ string name = parent == null ?
+ decorate ? Database.DecorateName(node.Name) : node.Name :
+ string.Format("{0}.{1}", parent, decorate ? Database.DecorateName(node.Name) : node.Name);
+
+ columnSchema = GetColumnFromSchema(name);
+
+ string value = Parse(node.Value, pars, nulls: true, columnSchema: columnSchema);
+ return string.Format("{0} = ({1})", name, value);
+ }
+
+ protected virtual string ParseUnary(DynamicParser.Node.Unary node, IDictionary pars = null)
+ {
+ switch (node.Operation)
+ {
+ // Artifacts from the DynamicParser class that are not usefull here...
+ case ExpressionType.IsFalse:
+ case ExpressionType.IsTrue: return Parse(node.Target, pars);
+
+ // Unary supported operations...
+ case ExpressionType.Not: return string.Format("(NOT {0})", Parse(node.Target, pars));
+ case ExpressionType.Negate: return string.Format("!({0})", Parse(node.Target, pars));
+ }
+
+ throw new ArgumentException("Not supported unary operation: " + node);
+ }
+
+ protected virtual string ParseBinary(DynamicParser.Node.Binary node, IDictionary pars = null)
+ {
+ string op = string.Empty;
+
+ switch (node.Operation)
+ {
+ // Arithmetic binary operations...
+ case ExpressionType.Add: op = "+"; break;
+ case ExpressionType.Subtract: op = "-"; break;
+ case ExpressionType.Multiply: op = "*"; break;
+ case ExpressionType.Divide: op = "/"; break;
+ case ExpressionType.Modulo: op = "%"; break;
+ case ExpressionType.Power: op = "^"; break;
+
+ case ExpressionType.And: op = "AND"; break;
+ case ExpressionType.Or: op = "OR"; break;
+
+ // Logical comparisons...
+ case ExpressionType.GreaterThan: op = ">"; break;
+ case ExpressionType.GreaterThanOrEqual: op = ">="; break;
+ case ExpressionType.LessThan: op = "<"; break;
+ case ExpressionType.LessThanOrEqual: op = "<="; break;
+
+ // Comparisons against 'NULL' require the 'IS' or 'IS NOT' operator instead the numeric ones...
+ case ExpressionType.Equal: op = node.Right == null && !VirtualMode ? "IS" : "="; break;
+ case ExpressionType.NotEqual: op = node.Right == null && !VirtualMode ? "IS NOT" : "<>"; break;
+
+ default: throw new ArgumentException("Not supported operator: '" + node.Operation);
+ }
+
+ DynamicSchemaColumn? columnSchema = null;
+ string left = Parse(node.Left, pars, columnSchema: columnSchema); // Not nulls: left is assumed to be an object
+ string right = Parse(node.Right, pars, nulls: true, columnSchema: columnSchema);
+ return string.Format("({0} {1} {2})", left, op, right);
+ }
+
+ protected virtual string ParseMethod(DynamicParser.Node.Method node, IDictionary pars = null)
+ {
+ string method = node.Name.ToUpper();
+ string parent = node.Host == null ? null : Parse(node.Host, pars: pars);
+ string item = null;
+
+ // Root-level methods...
+ if (node.Host == null)
+ {
+ switch (method)
+ {
+ case "NOT":
+ if (node.Arguments == null || node.Arguments.Length != 1) throw new ArgumentNullException("NOT method expects one argument: " + node.Arguments.Sketch());
+ item = Parse(node.Arguments[0], pars: pars);
+ return string.Format("(NOT {0})", item);
+ }
+ }
+
+ // Column-level methods...
+ if (node.Host != null)
+ {
+ switch (method)
+ {
+ case "BETWEEN":
+ {
+ if (node.Arguments == null || node.Arguments.Length == 0)
+ throw new ArgumentException("BETWEEN method expects at least one argument: " + node.Arguments.Sketch());
+
+ if (node.Arguments.Length > 2)
+ throw new ArgumentException("BETWEEN method expects at most two arguments: " + node.Arguments.Sketch());
+
+ var arguments = node.Arguments;
+
+ if (arguments.Length == 1 && (arguments[0] is IEnumerable || arguments[0] is Array) && !(arguments[0] is byte[]))
+ {
+ var vals = arguments[0] as IEnumerable;
+
+ if (vals == null && arguments[0] is Array)
+ vals = ((Array)arguments[0]).Cast() as IEnumerable;
+
+ if (vals != null)
+ arguments = vals.ToArray();
+ else
+ throw new ArgumentException("BETWEEN method expects single argument to be enumerable of exactly two elements: " + node.Arguments.Sketch());
+ }
+
+ return string.Format("{0} BETWEEN {1} AND {2}", parent, Parse(arguments[0], pars: pars), Parse(arguments[1], pars: pars));
+ }
+
+ case "IN":
+ {
+ if (node.Arguments == null || node.Arguments.Length == 0)
+ throw new ArgumentException("IN method expects at least one argument: " + node.Arguments.Sketch());
+
+ bool firstParam = true;
+ StringBuilder sbin = new StringBuilder();
+ foreach (var arg in node.Arguments)
+ {
+ if (!firstParam)
+ sbin.Append(", ");
+
+ if ((arg is IEnumerable || arg is Array) && !(arg is byte[]))
+ {
+ var vals = arg as IEnumerable;
+
+ if (vals == null && arg is Array)
+ vals = ((Array)arg).Cast() as IEnumerable;
+
+ if (vals != null)
+ foreach (var val in vals)
+ {
+ if (!firstParam)
+ sbin.Append(", ");
+ else
+ firstParam = false;
+
+ sbin.Append(Parse(val, pars: pars));
+ }
+ else
+ sbin.Append(Parse(arg, pars: pars));
+ }
+ else
+ sbin.Append(Parse(arg, pars: pars));
+
+ firstParam = false;
+ }
+
+ return string.Format("{0} IN({1})", parent, sbin.ToString());
+ }
+
+ case "LIKE":
+ if (node.Arguments == null || node.Arguments.Length != 1)
+ throw new ArgumentException("LIKE method expects one argument: " + node.Arguments.Sketch());
+
+ return string.Format("{0} LIKE {1}", parent, Parse(node.Arguments[0], pars: pars));
+
+ case "NOTLIKE":
+ if (node.Arguments == null || node.Arguments.Length != 1)
+ throw new ArgumentException("NOT LIKE method expects one argument: " + node.Arguments.Sketch());
+
+ return string.Format("{0} NOT LIKE {1}", parent, Parse(node.Arguments[0], pars: pars));
+
+ case "AS":
+ if (node.Arguments == null || node.Arguments.Length != 1)
+ throw new ArgumentException("AS method expects one argument: " + node.Arguments.Sketch());
+
+ item = Parse(node.Arguments[0], pars: null, rawstr: true, isMultiPart: false); // pars=null to avoid to parameterize aliases
+ item = item.Validated("Alias"); // Intercepting null and empty aliases
+ return string.Format("{0} AS {1}", parent, item);
+
+ case "COUNT":
+ if (node.Arguments != null && node.Arguments.Length > 1)
+ throw new ArgumentException("COUNT method expects one or none argument: " + node.Arguments.Sketch());
+
+ if (node.Arguments == null || node.Arguments.Length == 0)
+ return "COUNT(*)";
+
+ return string.Format("COUNT({0})", Parse(node.Arguments[0], pars: Parameters, nulls: true));
+ }
+ }
+
+ // Default case: parsing the method's name along with its arguments...
+ method = parent == null ? node.Name : string.Format("{0}.{1}", parent, node.Name);
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("{0}(", method);
+
+ if (node.Arguments != null && node.Arguments.Length != 0)
+ {
+ bool first = true;
+
+ foreach (object argument in node.Arguments)
+ {
+ if (!first)
+ sb.Append(", ");
+ else
+ first = false;
+
+ sb.Append(Parse(argument, pars, nulls: true)); // We don't accept raw strings here!!!
+ }
+ }
+
+ sb.Append(")");
+ return sb.ToString();
+ }
+
+ protected virtual string ParseInvoke(DynamicParser.Node.Invoke node, IDictionary