From 6279297892b79ac426bbd7128b7db441c423545f Mon Sep 17 00:00:00 2001 From: root Date: Mon, 23 Feb 2026 21:59:09 +0100 Subject: [PATCH] Add net40 amalgamated build and refresh amalgamation --- AmalgamationTool/DynamORM.Amalgamation.cs | 31221 ++++++++++---------- DynamORM.Net40.csproj | 29 + 2 files changed, 15367 insertions(+), 15883 deletions(-) create mode 100644 DynamORM.Net40.csproj diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs index 9be7b50..bf9cdd8 100644 --- a/AmalgamationTool/DynamORM.Amalgamation.cs +++ b/AmalgamationTool/DynamORM.Amalgamation.cs @@ -1,15883 +1,15338 @@ -62/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, 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 DynamORM.Builders.Extensions; -using DynamORM.Builders.Implementation; -using DynamORM.Builders; -using DynamORM.Helpers.Dynamics; -using DynamORM.Helpers; -using DynamORM.Mapper; -using DynamORM.Validation; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Collections; -using System.ComponentModel; -using System.Data.Common; -using System.Data; -using System.Dynamic; -using System.IO; -using System.Linq.Expressions; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.Serialization; -using System.Text.RegularExpressions; -using System.Text; -using System; - -[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 -{ - /// Cache data reader in memory. - public class DynamicCachedReader : DynamicObject, IDataReader - { - #region Constructor and Data - - private class Data - { - internal DataTable _schema; - internal int _fields; - internal int _rows; - internal int _rowsAffected; - internal int _position = -1; - internal int _cachePos = -1; - - internal IList _names; - internal IDictionary _ordinals; - internal IList _types; - internal IList _cache; - } - - private List _data = new List(); - private int _currentDataPosition = 0; - - private DynamicCachedReader() - { - } - - /// Initializes a new instance of the class. - /// Reader to cache. - /// The offset row. - /// The limit to number of tows. -1 is no limit. - /// The progress delegate. - public DynamicCachedReader(IDataReader reader, int offset = 0, int limit = -1, - Func progress = null) - { - InitDataReader(reader, offset, limit, progress); - } - - #endregion Constructor and Data - - #region Helpers - - /// Create data reader from dynamic enumerable. - /// List of objects. - /// Instance of containing objects data. - public static DynamicCachedReader FromDynamicEnumerable(IEnumerable objects) - { - var first = (objects as IEnumerable).FirstOrDefault(); - - if (first == null) - return null; - - var firstDict = first as IDictionary; - var r = new DynamicCachedReader(); - r.Init(firstDict.Keys.Count); - - r._data.Add(new Data()); - - var data = r._data[r._currentDataPosition]; - - for (int i = 0; i < firstDict.Keys.Count; i++) - data._types.Add(null); - - foreach (dynamic elem in (objects as IEnumerable)) - { - int c = 0; - var dict = elem as IDictionary; - - foreach (var k in firstDict.Keys) - { - object val = dict[k]; - - data._cache.Add(val); - - if (data._types[c] == null && val != null) - data._types[c] = val.GetType(); - - c++; - } - - data._rows++; - } - - for (int i = 0; i < firstDict.Keys.Count; i++) - if (data._types[i] == null) - data._types[i] = typeof(string); - - data._schema = new DataTable("DYNAMIC"); - data._schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); - data._schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); - data._schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); - data._schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); - data._schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); - data._schema.Columns.Add(new DataColumn("DataType", typeof(Type))); - data._schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); - data._schema.Columns.Add(new DataColumn("NativeType", typeof(int))); - data._schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); - - int ordinal = 0; - DataRow dr = null; - - foreach (var column in firstDict.Keys) - { - dr = data._schema.NewRow(); - - dr[0] = column; - dr[1] = ordinal; - dr[2] = 0; - dr[3] = 0; - dr[4] = 0; - dr[5] = data._types[ordinal]; - dr[6] = data._types[ordinal].ToDbType(); - dr[7] = data._types[ordinal].ToDbType(); - dr[8] = true; - dr[9] = false; - dr[10] = false; - dr[11] = false; - - data._schema.Rows.Add(dr); - - data._names.Add(dr[0].ToString()); - data._ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); - data._types.Add((Type)dr[5]); - - dr.AcceptChanges(); - } - - dr.AcceptChanges(); - - return r; - } - - /// Create data reader from enumerable. - /// Type of enumerated objects. - /// List of objects. - /// Instance of containing objects data. - public static DynamicCachedReader FromEnumerable(IEnumerable objects) - { - var mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidCastException(string.Format("Object type '{0}' can't be mapped.", typeof(T).FullName)); - - var r = new DynamicCachedReader(); - r.Init(mapper.ColumnsMap.Count + 1); - r.CreateSchemaTable(mapper); - r.FillFromEnumerable(objects, mapper); - - r.IsClosed = false; - - return r; - } - - /// Create data reader from enumerable. - /// Type of enumerated objects. - /// List of objects. - /// Instance of containing objects data. - public static DynamicCachedReader FromEnumerable(Type elementType, IEnumerable objects) - { - var mapper = DynamicMapperCache.GetMapper(elementType); - - if (mapper == null) - throw new InvalidCastException( - string.Format("Object type '{0}' can't be mapped.", elementType.FullName)); - - var r = new DynamicCachedReader(); - r.Init(mapper.ColumnsMap.Count + 1); - r.CreateSchemaTable(mapper); - r.FillFromEnumerable(elementType, objects, mapper); - - r.IsClosed = false; - - return r; - } - - private void InitDataReader(IDataReader reader, int offset = 0, int limit = -1, - Func progress = null) - { - do - { - Init(reader.FieldCount); - - var data = _data[_currentDataPosition]; - - data._schema = reader.GetSchemaTable(); - data._rowsAffected = reader.RecordsAffected; - - int i = 0; - - for (i = 0; i < data._fields; i++) - { - data._names.Add(reader.GetName(i)); - data._types.Add(reader.GetFieldType(i)); - - if (!data._ordinals.ContainsKey(reader.GetName(i).ToUpper())) - data._ordinals.Add(reader.GetName(i).ToUpper(), i); - } - - int current = 0; - while (reader.Read()) - { - if (current < offset) - { - current++; - continue; - } - - for (i = 0; i < data._fields; i++) - data._cache.Add(reader[i]); - - data._rows++; - current++; - - if (limit >= 0 && data._rows >= limit) - break; - - if (progress != null && !progress(this, data._rows)) - break; - } - - IsClosed = false; - data._position = -1; - data._cachePos = -1; - - if (progress != null) - progress(this, data._rows); - } while (reader.NextResult()); - - reader.Close(); - } - - private void FillFromEnumerable(IEnumerable objects, DynamicTypeMap mapper) - { - var data = _data[_currentDataPosition]; - foreach (var elem in objects) - { - foreach (var col in mapper.ColumnsMap) - { - object val = null; - - if (col.Value.Get != null) - val = col.Value.Get(elem); - - data._cache.Add(val); - } - - data._cache.Add(elem); - - data._rows++; - } - } - - private void FillFromEnumerable(Type elementType, IEnumerable objects, DynamicTypeMap mapper) - { - var data = _data[_currentDataPosition]; - - foreach (var elem in objects) - { - foreach (var col in mapper.ColumnsMap) - { - object val = null; - - if (col.Value.Get != null) - val = col.Value.Get(elem); - - data._cache.Add(val); - } - - data._cache.Add(elem); - - data._rows++; - } - } - - private void CreateSchemaTable(DynamicTypeMap mapper) - { - var data = _data[_currentDataPosition]; - data._schema = new DataTable("DYNAMIC"); - data._schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); - data._schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); - data._schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); - data._schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); - data._schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); - data._schema.Columns.Add(new DataColumn("DataType", typeof(Type))); - data._schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); - data._schema.Columns.Add(new DataColumn("NativeType", typeof(int))); - data._schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); - - int ordinal = 0; - DataRow dr = null; - - foreach (var column in mapper.ColumnsMap) - { - dr = data._schema.NewRow(); - - dr[0] = column.Value.Column.NullOr(x => x.Name ?? column.Value.Name, column.Value.Name); - dr[1] = ordinal; - dr[2] = column.Value.Column.NullOr(x => x.Size ?? int.MaxValue, int.MaxValue); - dr[3] = column.Value.Column.NullOr(x => x.Precision ?? 0, 0); - dr[4] = column.Value.Column.NullOr(x => x.Scale ?? 0, 0); - dr[5] = column.Value.Column.NullOr(x => x.Type.HasValue ? x.Type.Value.ToType() : column.Value.Type, - column.Value.Type); - dr[6] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), - column.Value.Type.ToDbType()); - dr[7] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), - column.Value.Type.ToDbType()); - dr[8] = column.Value.Column.NullOr(x => x.IsKey, false) - ? true - : column.Value.Column.NullOr(x => x.AllowNull, true); - dr[9] = column.Value.Column.NullOr(x => x.IsUnique, false); - dr[10] = column.Value.Column.NullOr(x => x.IsKey, false); - dr[11] = false; - - data._schema.Rows.Add(dr); - - data._names.Add(dr[0].ToString()); - data._ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); - data._types.Add((Type)dr[5]); - - dr.AcceptChanges(); - } - - dr = data._schema.NewRow(); - - dr[0] = "#O"; - dr[1] = ordinal; - dr[2] = int.MaxValue; - dr[3] = 0; - dr[4] = 0; - dr[5] = mapper.Type; - dr[6] = DbType.Object; - dr[7] = DbType.Object; - dr[8] = true; - dr[9] = false; - dr[10] = false; - dr[11] = false; - - data._schema.Rows.Add(dr); - - data._names.Add("#O"); - data._ordinals.Add("#O".ToUpper(), ordinal++); - data._types.Add(mapper.Type); - - dr.AcceptChanges(); - } - - private void Init(int fieldCount) - { - _data.Add(new Data() - { - _rows = 0, - _fields = fieldCount, - _names = new List(fieldCount), - _ordinals = new Dictionary(fieldCount), - _types = new List(fieldCount), - _cache = new List(fieldCount * 100), - }); - - _currentDataPosition = _data.Count - 1; - } - - /// Sets the current position in reader. - /// The position. - public void SetPosition(int pos) - { - if (pos >= -1 && pos < _data[_currentDataPosition]._rows) - { - _data[_currentDataPosition]._position = pos; - _data[_currentDataPosition]._cachePos = - _data[_currentDataPosition]._position * _data[_currentDataPosition]._fields; - } - else - throw new IndexOutOfRangeException(); - } - - #endregion Helpers - - #region IDataReader Members - - /// Closes the System.Data.IDataReader Object. - public void Close() - { - IsClosed = true; - _currentDataPosition = -1; - } - - /// Gets a value indicating the depth of nesting for the current row. - /// This implementation use this field to indicate row count. - public int Depth - { - get { return _data[_currentDataPosition]._rows; } - } - - /// Returns a System.Data.DataTable that describes the column metadata of the - /// System.Data.IDataReader.A System.Data.DataTable that describes - /// the column metadata. - /// The System.Data.IDataReader is closed. - public DataTable GetSchemaTable() - { - return _data[_currentDataPosition]._schema; - } - - /// Gets a value indicating whether the data reader is closed. - public bool IsClosed { get; private set; } - - /// Advances the data reader to the next result, when reading the results of batch SQL statements. - /// Returns true if there are more rows; otherwise, false. - public bool NextResult() - { - _currentDataPosition++; - - return _data.Count < _currentDataPosition; - } - - /// Advances the System.Data.IDataReader to the next record. - /// Returns true if there are more rows; otherwise, false. - public bool Read() - { - var data = _data[_currentDataPosition]; - - data._cachePos = (++data._position) * data._fields; - - return data._position < data._rows; - } - - /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. - /// The number of rows changed, inserted, or deleted; 0 if no rows were affected or the statement - /// failed; and -1 for SELECT statements. - public int RecordsAffected - { - get { return _data[_currentDataPosition]._rowsAffected; } - } - - #endregion IDataReader Members - - #region IDisposable Members - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public void Dispose() - { - foreach (var data in _data) - { - data._names.Clear(); - data._types.Clear(); - data._cache.Clear(); - data._schema.Dispose(); - } - - _data.Clear(); - } - - #endregion IDisposable Members - - #region IDataRecord Members - - /// Gets the number of columns in the current row. - /// When not positioned in a valid record set, 0; otherwise, the number of columns in the current record. The default is -1. - public int FieldCount - { - get { return _data[_currentDataPosition]._fields; } - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public bool GetBoolean(int i) - { - var data = _data[_currentDataPosition]; - return (bool)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public byte GetByte(int i) - { - var data = _data[_currentDataPosition]; - return (byte)data._cache[data._cachePos + i]; - } - - /// Reads a stream of bytes from the specified column offset into the buffer - /// as an array, starting at the given buffer offset. - /// The index of the field to find. - /// The index within the field from which to start the read operation. - /// The buffer into which to read the stream of bytes. - /// The index for buffer to start the read operation. - /// The number of bytes to read. - /// The actual number of bytes read. - public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) - { - var data = _data[_currentDataPosition]; - - using (MemoryStream ms = new MemoryStream((byte[])data._cache[data._cachePos + i])) - return ms.Read(buffer, bufferoffset, length); - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public char GetChar(int i) - { - var data = _data[_currentDataPosition]; - return (char)data._cache[data._cachePos + i]; - } - - /// Reads a stream of characters from the specified column offset into the buffer - /// as an array, starting at the given buffer offset. - /// The zero-based column ordinal. - /// The index within the row from which to start the read operation. - /// The buffer into which to read the stream of bytes. - /// The index for buffer to start the read operation. - /// The number of bytes to read. - /// The actual number of characters read. - public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) - { - var data = _data[_currentDataPosition]; - - using (MemoryStream ms = new MemoryStream((byte[])data._cache[data._cachePos + i])) - { - byte[] buff = new byte[buffer.Length]; - long ret = ms.Read(buff, bufferoffset, length); - - for (int n = bufferoffset; n < ret; n++) - buffer[n] = (char)buff[n]; - - return ret; - } - } - - /// Returns an System.Data.IDataReader for the specified column ordinal. - /// The index of the field to find. - /// An System.Data.IDataReader. - public IDataReader GetData(int i) - { - return null; - } - - /// Gets the data type information for the specified field. - /// The index of the field to find. - /// The data type information for the specified field. - public string GetDataTypeName(int i) - { - return _data[_currentDataPosition]._types[i].Name; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public DateTime GetDateTime(int i) - { - var data = _data[_currentDataPosition]; - return (DateTime)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public decimal GetDecimal(int i) - { - var data = _data[_currentDataPosition]; - return (decimal)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public double GetDouble(int i) - { - var data = _data[_currentDataPosition]; - return (double)data._cache[data._cachePos + i]; - } - - /// Gets the System.Type information corresponding to the type of System.Object - /// that would be returned from System.Data.IDataRecord.GetValue(System.Int32). - /// The index of the field to find. - /// The System.Type information corresponding to the type of System.Object that - /// would be returned from System.Data.IDataRecord.GetValue(System.Int32). - public Type GetFieldType(int i) - { - return _data[_currentDataPosition]._types[i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public float GetFloat(int i) - { - var data = _data[_currentDataPosition]; - return (float)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public Guid GetGuid(int i) - { - var data = _data[_currentDataPosition]; - return (Guid)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public short GetInt16(int i) - { - var data = _data[_currentDataPosition]; - return (short)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public int GetInt32(int i) - { - var data = _data[_currentDataPosition]; - return (int)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public long GetInt64(int i) - { - var data = _data[_currentDataPosition]; - return (long)data._cache[data._cachePos + i]; - } - - /// Gets the name for the field to find. - /// The index of the field to find. - /// The name of the field or the empty string (""), if there is no value to return. - public string GetName(int i) - { - return _data[_currentDataPosition]._names[i]; - } - - /// Return the index of the named field. - /// The name of the field to find. - /// The index of the named field. - public int GetOrdinal(string name) - { - var data = _data[_currentDataPosition]; - - if (data._ordinals.ContainsKey(name.ToUpper())) - return data._ordinals[name.ToUpper()]; - - return -1; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public string GetString(int i) - { - var data = _data[_currentDataPosition]; - - return (string)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public object GetValue(int i) - { - var data = _data[_currentDataPosition]; - - return data._cache[data._cachePos + i]; - } - - /// Gets all the attribute fields in the collection for the current record. - /// An array of System.Object to copy the attribute fields into. - /// The number of instances of System.Object in the array. - public int GetValues(object[] values) - { - var data = _data[_currentDataPosition]; - - for (int i = 0; i < data._fields; i++) - values[i] = data._cache[data._cachePos + i]; - - return data._fields; - } - - /// Return whether the specified field is set to null. - /// The index of the field to find. - /// Returns true if the specified field is set to null; otherwise, false. - public bool IsDBNull(int i) - { - var data = _data[_currentDataPosition]; - return data._cache[data._cachePos + i] == null || data._cache[data._cachePos + i] == DBNull.Value; - } - - /// Gets or sets specified value in current record. - /// Name of column. - /// Value of specified column. - public object this[string name] - { - get - { - var data = _data[_currentDataPosition]; - - if (data._ordinals.ContainsKey(name.ToUpper())) - return data._cache[data._cachePos + data._ordinals[name.ToUpper()]]; - - throw new IndexOutOfRangeException(String.Format("Field '{0}' not found.", name)); - } - } - - /// Gets or sets specified value in current record. - /// The index of the field to find. - /// Value of specified column. - public object this[int i] - { - get - { - var data = _data[_currentDataPosition]; - return data._cache[data._cachePos + i]; - } - } - - #endregion IDataRecord Members - } - - /// 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() - { - ParameterDirection = ParameterDirection.Input; - } - - /// 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 parameter direction when used in procedure invocation. - public ParameterDirection ParameterDirection { 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 - string[] 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 - string[] 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; - } - - if (_db.DumpCommands) - _db.DumpCommand(_command); - - return _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) - { - List pool = _db.CommandsPool.TryGetValue(_con.Connection); - - if (pool != null && pool.Contains(this)) - pool.Remove(this); - } - - IsDisposed = true; - - if (_command != null) - { - _command.Parameters.Clear(); - - _command.Dispose(); - _command = null; - } - } - } - - /// 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. - /// Custom parameter describing transaction options. - /// This action is invoked when transaction is disposed. - /// Returns representation. - internal DynamicTransaction BeginTransaction(IsolationLevel? il, object custom, Action disposed) - { - return new DynamicTransaction(_db, this, _singleTransaction, il, disposed, null); - } - - #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, null); - } - - /// Begins a database transaction with the specified - /// value. - /// One of the values. - /// Returns representation. - public IDbTransaction BeginTransaction(IsolationLevel il) - { - return BeginTransaction(il, null, null); - } - - /// Begins a database transaction with the specified - /// value. - /// Custom parameter describing transaction options. - /// Returns representation. - public IDbTransaction BeginTransaction(object custom) - { - return BeginTransaction(null, custom, 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 { return Connection.Database; } - } - - /// 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 DynamicProcedureInvoker _proc; - 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; } - - /// Gets active builders that weren't disposed. - internal List RemainingBuilders { 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 the procedures invoker. - /// - /// - /// using (var db = GetORM()) - /// { - /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_Scalar AS SELECT 42;"); - /// var res0 = db.Procedures.sp_Exp_Scalar(); - /// var res1 = db.Procedures.sp_Exp_Scalar(); - /// - /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_ReturnInt AS RETURN 42;"); - /// var res2 = db.Procedures.sp_Exp_ReturnInt(); - /// - /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeData AS - /// SELECT 1 Id, 'Some Name 1' [Name], 'Some Desc 1' [Desc], GETDATE() [Date] - /// UNION ALL SELECT 2 Id, 'Some Name 2', 'Some Desc 2', GETDATE() [Date];"); - /// var res3 = db.Procedures.sp_Exp_SomeData(); - /// var res4 = db.Procedures.sp_Exp_SomeData>(); - /// - /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeInputAndOutput - /// @Name nvarchar(50), - /// @Result nvarchar(256) OUTPUT - /// AS - /// SELECT @Result = 'Hi, ' + @Name + ' your lucky number is 42!';"); - /// var res5 = db.Procedures.sp_Exp_SomeInputAndOutput(Name: "G4g4r1n", out_Result: new DynamicColumn - /// { - /// Schema = new DynamicSchemaColumn - /// { - /// Size = 256, - /// }, - /// }, ret_Return: 0); - /// var res6 = db.Procedures.sp_Exp_SomeInputAndOutput(Name: "G4g4r1n", out_Result: new DynamicSchemaColumn - /// { - /// Size = 256, - /// }, ret_Return: 0); - /// - /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeInputAndOutputWithDataAndReturn - /// @Name nvarchar(50), - /// @Result nvarchar(256) OUTPUT - /// AS - /// SELECT @Result = 'Hi, ' + @Name + ' your lucky number is 42!' - /// - /// SELECT 1 Id, 'Some Name 1' [Name], 'Some Desc 1' [Desc], GETDATE() [Date] - /// UNION ALL SELECT 2 Id, 'Some Name 2', 'Some Desc 2', GETDATE() [Date] - /// - /// RETURN 42;"); - /// var res7 = db.Procedures.sp_Exp_SomeInputAndOutputWithDataAndReturn, sp_Exp_SomeInputAndOutputWithDataAndReturn_Result>(Name: "G4g4r1n", out_Result: new DynamicColumn - /// { - /// Schema = new DynamicSchemaColumn - /// { - /// Size = 256, - /// }, - /// }, ret_Return: 0); - /// var res8 = db.Procedures.sp_Exp_SomeInputAndOutputWithDataAndReturn, sp_Exp_SomeInputAndOutputWithDataAndReturn_Result>(Name: "G4g4r1n", out_Result: new DynamicSchemaColumn - /// { - /// Size = 256, - /// }, ret_Return: 0); - /// } - /// - ///private class sp_Exp_SomeData_Result - ///{ - /// public virtual int Id { get; set; } - /// public virtual string Name { get; set; } - /// public virtual string Desc { get; set; } - /// public virtual DateTime Date { get; set; } - ///} - ///private class sp_Exp_SomeInputAndOutput_Result - ///{ - /// public virtual string Result { get; set; } - /// public virtual string Return { get; set; } - ///} - ///private class sp_Exp_SomeInputAndOutputWithDataAndReturn_Result - ///{ - /// public class Data - /// { - /// public virtual int Id { get; set; } - /// public virtual string Name { get; set; } - /// public virtual string Desc { get; set; } - /// public virtual DateTime Date { get; set; } - /// } - /// public virtual List sp_Exp_SomeInputAndOutputWithDataAndReturn { get; set; } - /// public virtual string Result { get; set; } - /// public virtual string Return { get; set; } - ///} - /// - /// - public dynamic Procedures - { - get - { - if (_proc == null) - { - if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != - DynamicDatabaseOptions.SupportStoredProcedures) - throw new InvalidOperationException("Database connection doesn't support stored procedures."); - - _proc = new DynamicProcedureInvoker(this); - } - - return _proc; - } - } - - /// Gets or sets a value indicating whether - /// dump commands to console or not. - public bool DumpCommands { get; set; } - - /// Gets or sets the dump command delegate. - /// The dump command delegate. - public Action DumpCommandDelegate { get; set; } - -#if NETFRAMEWORK - // https://github.com/dotnet/runtime/issues/26229 - /// 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) - { - } -#endif - - /// 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); - } - - private DbProviderFactory FindDbProviderFactoryFromConnection(Type t) - { - foreach (var type in t.Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(DbProviderFactory)))) - { - DbProviderFactory provider = null; - bool dispose = false; - - var pi = type.GetProperty("Instance", - BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty); - if (pi != null) - provider = (DbProviderFactory)pi.GetValue(null, null); - else - { - var fi = type.GetField("Instance", - BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField); - if (fi != null) - provider = (DbProviderFactory)fi.GetValue(null); - } - - if (provider == null) - { - var ci = type.GetConstructor(Type.EmptyTypes); - if (ci != null) - { - provider = ci.Invoke(null) as DbProviderFactory; - dispose = true; - } - } - - try - { - if (provider != null) - { - using (var c = provider.CreateConnection()) - { - if (c.GetType() == t) - return provider; - } - } - } - finally - { - if (provider != null && dispose && provider is IDisposable) - ((IDisposable)provider).Dispose(); - } - } - - return null; - } - - /// Initializes a new instance of the class. - /// Active database connection. - /// Connection options. required. - public DynamicDatabase(IDbConnection connection, DynamicDatabaseOptions options) - { - // Try to find correct provider if possible - _provider = FindDbProviderFactoryFromConnection(connection.GetType()); - - 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>(); - RemainingBuilders = new List(); -#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 (KeyValuePair item in TablesCache.Where(kvp => kvp.Value == dynamicTable) - .ToList()) - TablesCache.Remove(item.Key); - } - -#endif - - #endregion Table - - /// Adds cached builder. - /// New dynamic builder. - internal void AddToCache(IDynamicQueryBuilder builder) - { - lock (SyncLock) - RemainingBuilders.Add(builder); - } - - /// Removes cached builder. - /// Disposed dynamic builder. - internal void RemoveFromCache(IDynamicQueryBuilder builder) - { - lock (SyncLock) - RemainingBuilders.Remove(builder); - } - - #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. - /// Table alias. - /// use no lock. - /// This instance to permit chaining. - public virtual IDynamicSelectQueryBuilder From(string alias = null, bool noLock = false) - { - // TODO: Make it more readable and maitainable - if (noLock) - { - if (string.IsNullOrEmpty(alias)) - return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).NoLock()); - else - return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias).NoLock()); - } - else - { - if (string.IsNullOrEmpty(alias)) - return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T))); - else - return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias)); - } - } - - /// 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 - { - return Insert(typeof(T), e); - } - - /// 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(Type t, IEnumerable e) - { - int affected = 0; - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); - - if (mapper != null) - { - using (IDbConnection con = Open()) - using (IDbTransaction tra = con.BeginTransaction()) - using (IDbCommand cmd = con.CreateCommand()) - { - try - { - Dictionary parameters = - new Dictionary(); - - if (!string.IsNullOrEmpty(mapper.InsertCommandText)) - { - cmd.CommandText = mapper.InsertCommandText; - - foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values - .Where(di => !di.Ignore && di.InsertCommandParameter != null) - .OrderBy(di => di.InsertCommandParameter.Ordinal)) - { - IDbDataParameter para = cmd.CreateParameter(); - para.ParameterName = col.InsertCommandParameter.Name; - para.DbType = col.InsertCommandParameter.Type; - cmd.Parameters.Add(para); - - parameters[para] = col; - } - } - else - PrepareBatchInsert(t, mapper, cmd, parameters); - - foreach (var o in e) - { - foreach (KeyValuePair 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; - - StringBuilder 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 - { - return Update(typeof(T), e); - } - - /// 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(Type t, IEnumerable e) - { - int affected = 0; - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); - - if (mapper != null) - { - using (IDbConnection con = Open()) - using (IDbTransaction tra = con.BeginTransaction()) - using (IDbCommand cmd = con.CreateCommand()) - { - try - { - Dictionary parameters = - new Dictionary(); - - if (!string.IsNullOrEmpty(mapper.UpdateCommandText)) - { - cmd.CommandText = mapper.UpdateCommandText; - - foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values - .Where(di => !di.Ignore && di.UpdateCommandParameter != null) - .OrderBy(di => di.UpdateCommandParameter.Ordinal)) - { - IDbDataParameter para = cmd.CreateParameter(); - para.ParameterName = col.UpdateCommandParameter.Name; - para.DbType = col.UpdateCommandParameter.Type; - cmd.Parameters.Add(para); - - parameters[para] = col; - } - } - else - PrepareBatchUpdate(t, mapper, cmd, parameters); - - foreach (var o in e) - { - foreach (KeyValuePair 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; - - StringBuilder 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 - { - return UpdateOrInsert(typeof(T), e); - } - - /// 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(Type t, IEnumerable e) - { - int affected = 0; - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); - - if (mapper != null) - { - using (IDbConnection con = Open()) - using (IDbTransaction tra = con.BeginTransaction()) - using (IDbCommand cmdUp = con.CreateCommand()) - using (IDbCommand cmdIn = con.CreateCommand()) - { - try - { - #region Update - - Dictionary parametersUp = - new Dictionary(); - - if (!string.IsNullOrEmpty(mapper.UpdateCommandText)) - { - cmdUp.CommandText = mapper.UpdateCommandText; - - foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values - .Where(di => !di.Ignore && di.UpdateCommandParameter != null) - .OrderBy(di => di.UpdateCommandParameter.Ordinal)) - { - IDbDataParameter para = cmdUp.CreateParameter(); - para.ParameterName = col.UpdateCommandParameter.Name; - para.DbType = col.UpdateCommandParameter.Type; - cmdUp.Parameters.Add(para); - - parametersUp[para] = col; - } - } - else - PrepareBatchUpdate(t, mapper, cmdUp, parametersUp); - - #endregion Update - - #region Insert - - Dictionary parametersIn = - new Dictionary(); - - if (!string.IsNullOrEmpty(mapper.InsertCommandText)) - { - cmdIn.CommandText = mapper.InsertCommandText; - - foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values - .Where(di => !di.Ignore && di.InsertCommandParameter != null) - .OrderBy(di => di.InsertCommandParameter.Ordinal)) - { - IDbDataParameter para = cmdIn.CreateParameter(); - para.ParameterName = col.InsertCommandParameter.Name; - para.DbType = col.InsertCommandParameter.Type; - cmdIn.Parameters.Add(para); - - parametersIn[para] = col; - } - } - else - PrepareBatchInsert(t, mapper, cmdIn, parametersIn); - - #endregion Insert - - foreach (var o in e) - { - foreach (KeyValuePair m in parametersUp) - m.Key.Value = m.Value.Get(o); - - int a = cmdUp.ExecuteNonQuery(); - if (a == 0) - { - foreach (KeyValuePair 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; - - StringBuilder 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)); - } - - /// Adds to the DELETE FROM clause using . - /// Type which can be represented in database. - /// This instance to permit chaining. - public virtual IDynamicDeleteQueryBuilder Delete(Type t) - { - return new DynamicDeleteQueryBuilder(this).Table(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 - { - return Delete(typeof(T), e); - } - - /// 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(Type t, IEnumerable e) - { - int affected = 0; - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); - - if (mapper != null) - { - using (IDbConnection con = Open()) - using (IDbTransaction tra = con.BeginTransaction()) - using (IDbCommand cmd = con.CreateCommand()) - { - try - { - Dictionary parameters = - new Dictionary(); - - if (!string.IsNullOrEmpty(mapper.DeleteCommandText)) - { - cmd.CommandText = mapper.DeleteCommandText; - - foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values - .Where(di => !di.Ignore && di.DeleteCommandParameter != null) - .OrderBy(di => di.DeleteCommandParameter.Ordinal)) - { - IDbDataParameter para = cmd.CreateParameter(); - para.ParameterName = col.DeleteCommandParameter.Name; - para.DbType = col.DeleteCommandParameter.Type; - cmd.Parameters.Add(para); - - parameters[para] = col; - } - } - else - PrepareBatchDelete(t, mapper, cmd, parameters); - - foreach (var o in e) - { - foreach (KeyValuePair 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; - - StringBuilder problematicCommand = new StringBuilder(); - cmd.Dump(problematicCommand); - - throw new InvalidOperationException(problematicCommand.ToString(), ex); - } - } - } - - return affected; - } - - private void PrepareBatchInsert(DynamicTypeMap mapper, IDbCommand cmd, - Dictionary parameters) - { - PrepareBatchInsert(typeof(T), mapper, cmd, parameters); - } - - private void PrepareBatchInsert(Type t, DynamicTypeMap mapper, IDbCommand cmd, - Dictionary parameters) - { - DynamicPropertyInvoker currentprop = null; - Dictionary temp = new Dictionary(); - Dictionary schema = this.GetSchema(t); - int ord = 0; - - IDynamicInsertQueryBuilder ib = Insert(t) - .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 (KeyValuePair prop in mapper.PropertyMap) - if (!mapper.Ignored.Contains(prop.Key)) - { - string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; - currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); - - if (currentprop.Ignore || (currentprop.Column != null && currentprop.Column.IsNoInsert)) - continue; - - if (currentprop.Get != null) - ib.Insert(new DynamicColumn() - { - ColumnName = col, - Schema = schema == null ? null : schema.TryGetNullable(col.ToLower()), - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - } - - ib.FillCommand(cmd); - - // Cache command - mapper.InsertCommandText = cmd.CommandText; - } - - private void PrepareBatchUpdate(DynamicTypeMap mapper, IDbCommand cmd, - Dictionary parameters) - { - PrepareBatchUpdate(typeof(T), mapper, cmd, parameters); - } - - private void PrepareBatchUpdate(Type t, DynamicTypeMap mapper, IDbCommand cmd, - Dictionary parameters) - { - DynamicPropertyInvoker currentprop = null; - Dictionary temp = new Dictionary(); - Dictionary schema = this.GetSchema(t); - int ord = 0; - - IDynamicUpdateQueryBuilder ib = Update(t) - .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 (KeyValuePair prop in mapper.PropertyMap) - if (!mapper.Ignored.Contains(prop.Key)) - { - string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; - currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); - - if (currentprop.Ignore) - continue; - - if (currentprop.Get != null) - { - DynamicSchemaColumn? colS = schema == null ? null : schema.TryGetNullable(col.ToLower()); - - if (colS.HasValue) - { - if (colS.Value.IsKey) - ib.Where(new DynamicColumn() - { - ColumnName = col, - Schema = colS, - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - else if (currentprop.Column == null || !currentprop.Column.IsNoUpdate) - ib.Values(new DynamicColumn() - { - ColumnName = col, - Schema = colS, - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - } - else if (currentprop.Column != null && currentprop.Column.IsKey) - ib.Where(new DynamicColumn() - { - ColumnName = col, - Schema = colS, - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - } - } - - ib.FillCommand(cmd); - - // Cache command - mapper.UpdateCommandText = cmd.CommandText; - } - - private void PrepareBatchDelete(DynamicTypeMap mapper, IDbCommand cmd, - Dictionary parameters) - { - PrepareBatchDelete(typeof(T), mapper, cmd, parameters); - } - - private void PrepareBatchDelete(Type t, DynamicTypeMap mapper, IDbCommand cmd, - Dictionary parameters) - { - DynamicPropertyInvoker currentprop = null; - Dictionary temp = new Dictionary(); - Dictionary schema = this.GetSchema(t); - int ord = 0; - - IDynamicDeleteQueryBuilder ib = Delete(t) - .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 (KeyValuePair prop in mapper.PropertyMap) - if (!mapper.Ignored.Contains(prop.Key)) - { - string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; - currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); - - if (currentprop.Ignore) - continue; - - if (currentprop.Get != null) - { - DynamicSchemaColumn? colS = schema == null ? null : schema.TryGetNullable(col.ToLower()); - - if (colS != null) - { - if (colS.Value.IsKey) - ib.Where(new DynamicColumn() - { - ColumnName = col, - Schema = colS, - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - } - else if (currentprop.Column != null && currentprop.Column.IsKey) - ib.Where(new DynamicColumn() - { - ColumnName = col, - Schema = colS, - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - } - } - - ib.FillCommand(cmd); - - // Cache command - mapper.DeleteCommandText = cmd.CommandText; - } - - #endregion From/Insert/Update/Delete - - #region Procedure - - /// Execute stored procedure. - /// Name of stored procedure to execute. - /// Number of affected rows. - public virtual int Procedure(string procName) - { - return Procedure(procName, (DynamicExpando)null); - } - - /// 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, params object[] args) - { - if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != - DynamicDatabaseOptions.SupportStoredProcedures) - throw new InvalidOperationException("Database connection desn't support stored procedures."); - - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(CommandType.StoredProcedure, procName) - .AddParameters(this, args) - .ExecuteNonQuery(); - } - } - - /// 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, DynamicExpando args) - { - if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != - DynamicDatabaseOptions.SupportStoredProcedures) - throw new InvalidOperationException("Database connection desn't support stored procedures."); - - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(CommandType.StoredProcedure, procName) - .AddParameters(this, args) - .ExecuteNonQuery(); - } - } - - /// 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) - { - if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != - DynamicDatabaseOptions.SupportStoredProcedures) - throw new InvalidOperationException("Database connection desn't support stored procedures."); - - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(CommandType.StoredProcedure, procName) - .AddParameters(this, args) - .ExecuteNonQuery(); - } - } - - #endregion Procedure - - #region Execute - - /// 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 (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(sql).AddParameters(this, args) - .ExecuteNonQuery(); - } - } - - /// Execute non query. - /// Command builder. - /// Number of affected rows. - public virtual int Execute(IDynamicQueryBuilder builder) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(builder) - .ExecuteNonQuery(); - } - } - - /// Execute non query. - /// Command builders. - /// Number of affected rows. - public virtual int Execute(IDynamicQueryBuilder[] builders) - { - int ret = 0; - - using (IDbConnection con = Open()) - { - using (IDbTransaction trans = con.BeginTransaction()) - { - foreach (IDynamicQueryBuilder builder in builders) - using (IDbCommand cmd = con.CreateCommand()) - ret += cmd - .SetCommand(builder) - .ExecuteNonQuery(); - - trans.Commit(); - } - } - - return ret; - } - - #endregion Execute - - #region Scalar - - /// 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 (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(sql).AddParameters(this, args) - .ExecuteScalar(); - } - } - - /// Returns a single result. - /// Command builder. - /// Result of a query. - public virtual object Scalar(IDynamicQueryBuilder builder) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(builder) - .ExecuteScalar(); - } - } - -#if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE - - /// Returns a single result. - /// What kind of result is expected. - /// 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 T ScalarAs(string sql, params object[] args) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(sql).AddParameters(this, args) - .ExecuteScalarAs(); - } - } - - /// Returns a single result. - /// What kind of result is expected. - /// Command builder. - /// Default value. - /// Result of a query. - public virtual T ScalarAs(IDynamicQueryBuilder builder, T defaultValue = default(T)) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(builder) - .ExecuteScalarAs(defaultValue); - } - } - -#endif - - #endregion Scalar - - #region Query - - /// 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 (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(sql) - .AddParameters(this, args) - .ExecuteReader()) - using (IDataReader cache = new DynamicCachedReader(rdr)) - { - while (cache.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 = cache.RowToDynamic(); - } - catch (ArgumentException argex) - { - StringBuilder 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 (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(builder) - .ExecuteReader()) - using (var cache = new DynamicCachedReader(rdr)) - while (cache.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 = cache.RowToDynamic(); - } - catch (ArgumentException argex) - { - StringBuilder 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; - } - } - } - - #endregion Query - - #region CachedQuery - - /// 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 DynamicCachedReader CachedQuery(string sql, params object[] args) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(sql) - .AddParameters(this, args) - .ExecuteReader()) - return new DynamicCachedReader(rdr); - } - } - - /// Enumerate the reader and yield the result. - /// Command builder. - /// Enumerator of objects expanded from query. - public virtual DynamicCachedReader CachedQuery(IDynamicQueryBuilder builder) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(builder) - .ExecuteReader()) - return new DynamicCachedReader(rdr); - } - } - - #endregion Query - - #region Schema - - /// Builds query cache if necessary and returns it. - /// The builder containing query to read schema from. - /// Query schema. - public Dictionary GetQuerySchema(IDynamicSelectQueryBuilder builder) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand().SetCommand(builder)) - return ReadSchema(cmd) - .Distinct() - .ToDictionary(k => k.Name.ToLower(), k => k); - } - - /// Builds query cache if necessary and returns it. - /// SQL query from which read schema. - /// SQL query arguments. - /// Query schema. - public Dictionary GetQuerySchema(string sql, params object[] args) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand().SetCommand(sql, args)) - return ReadSchema(cmd) - .Distinct() - .ToDictionary(k => k.Name.ToLower(), k => k); - } - - /// 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).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; - } - - /// Clears the schema from cache. - /// Use this method to refresh table information. - /// Name of table for which clear schema. - /// Owner of table for which clear schema. - public void ClearSchema(string table = null, string owner = null) - { - lock (SyncLock) - if (Schema.ContainsKey(table.ToLower())) - Schema.Remove(table.ToLower()); - } - - /// Clears the schema from cache. - /// Use this method to refresh table information. - /// Type of table for which clear schema. - public void ClearSchema() - { - ClearSchema(typeof(T)); - } - - /// Clears the schema from cache. - /// Use this method to refresh table information. - /// Type of table for which clear schema. - public void ClearSchema(Type table) - { - lock (SyncLock) - if (Schema.ContainsKey(table.FullName)) - { - if (Schema[table.FullName] != null) - Schema[table.FullName].Clear(); - Schema.Remove(table.FullName); - } - } - - /// Clears the all schemas from cache. - /// Use this method to refresh all table information. - public void ClearSchema() - { - lock (SyncLock) - { - foreach (KeyValuePair> s in Schema) - if (s.Value != null) - s.Value.Clear(); - - Schema.Clear(); - } - } - - /// 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 IList ReadSchema(string table, string owner) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand() - .SetCommand(string.Format("SELECT * FROM {0}{1} WHERE 1 = 0", - !string.IsNullOrEmpty(owner) ? string.Format("{0}.", DecorateName(owner)) : string.Empty, - DecorateName(table)))) - return ReadSchema(cmd) - .ToList(); - } - - /// Get schema describing objects from reader. - /// Command containing query to execute. - /// List of objects . - /// If your database doesn't get those values in upper case (like most of the databases) you should override this method. - public virtual IEnumerable ReadSchema(IDbCommand cmd) - { - DataTable st = null; - - using (IDataReader rdr = cmd.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo)) - st = rdr.GetSchemaTable(); - - using (st) - foreach (DataRow col in st.Rows) - { - dynamic c = col.RowToDynamicUpper(); - - yield return new DynamicSchemaColumn - { - Name = c.COLUMNNAME, - Type = ReadSchemaType(c), - IsKey = c.ISKEY ?? false, - IsUnique = c.ISUNIQUE ?? false, - AllowNull = c.ALLOWNULL ?? false, - Size = (int)(c.COLUMNSIZE ?? 0), - Precision = (byte)(c.NUMERICPRECISION ?? 0), - Scale = (byte)(c.NUMERICSCALE ?? 0) - }; - } - } - - /// Reads the type of column from the schema. - /// The schema. - /// Generic parameter type. - protected virtual DbType ReadSchemaType(dynamic schema) - { - Type type = (Type)schema.DATATYPE; - - // Small hack for SQL Server Provider - if (type == typeof(string) && Provider != null && Provider.GetType().Name == "SqlClientFactory") - { - var map = schema as IDictionary; - string typeName = (map.TryGetValue("DATATYPENAME") ?? string.Empty).ToString(); - - switch (typeName) - { - case "varchar": - return DbType.AnsiString; - - case "nvarchar": - return DbType.String; - } - } - - return DynamicExtensions.TypeMap.TryGetNullable(type) ?? DbType.String; - } - - 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; - owner = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Owner) ? null : mapper.Table.Owner; - } - - 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) - .Where(x => x.Name != null) - .DistinctBy(x => x.Name.ToLower()) - .ToDictionary(k => k.Name.ToLower(), k => k); - - Schema[tableName.ToLower()] = schema; - } - - #endregion Database schema - - #region Type schema - - if ((mapperSchema && !Schema.ContainsKey(mapper.Type.FullName)) || - (schema == null && mapper != null && !mapper.Type.IsAnonymous())) - { - // 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 - : DynamicExtensions.TypeMap.TryGetNullable(v.Value.Type) ?? DbType.String).Value, - IsUnique = DynamicExtensions.CoalesceNullable( - v.Value.Column != null ? v.Value.Column.IsUnique : null, - col.HasValue ? col.Value.IsUnique : false).Value, - AllowNull = DynamicExtensions.CoalesceNullable( - v.Value.Column != null ? v.Value.Column.AllowNull : true, - col.HasValue ? col.Value.AllowNull : true).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, - DynamicExtensions.TypeMap.TryGetNullable(v.Value.Type) ?? DbType.String).Value, - IsUnique = DynamicExtensions - .CoalesceNullable(v.Value.Column != null ? v.Value.Column.IsUnique : null, false) - .Value, - AllowNull = DynamicExtensions - .CoalesceNullable(v.Value.Column != null ? v.Value.Column.AllowNull : true, true) - .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(" ", "_")); - } - - /// Dumps the command into console output. - /// The command to dump. - public virtual void DumpCommand(IDbCommand cmd) - { - if (DumpCommandDelegate != null) - DumpCommandDelegate(cmd, cmd.DumpToString()); - else - cmd.Dump(Console.Out); - } - - #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)) - { - List 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(); - connection = null; - } - } - - /// 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, null, () => - { - Stack t = TransactionPool.TryGetValue(_tempConn.Connection); - - if (t == null | t.Count == 0) - { - _tempConn.Dispose(); - _tempConn = null; - } - }); - } - - /// Begins a database transaction with the specified - /// value. - /// One of the values. - /// Returns representation. - public IDbTransaction BeginTransaction(IsolationLevel il) - { - _tempConn = Open() as DynamicConnection; - - return _tempConn.BeginTransaction(il, null, () => - { - Stack t = TransactionPool.TryGetValue(_tempConn.Connection); - - if (t == null | t.Count == 0) - { - _tempConn.Dispose(); - _tempConn = null; - } - }); - } - - /// Begins a database transaction with the specified - /// value. - /// Custom parameter describing transaction options. - /// Returns representation. - public IDbTransaction BeginTransaction(object custom) - { - _tempConn = Open() as DynamicConnection; - - return _tempConn.BeginTransaction(null, custom, () => - { - Stack 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 - List tables = TablesCache.Values.ToList(); - TablesCache.Clear(); - - tables.ForEach(t => t.Dispose()); - tables.Clear(); - tables = null; -#endif - - foreach (KeyValuePair> con in TransactionPool) - { - // Close all commands - if (CommandsPool.ContainsKey(con.Key)) - { - List tmp = CommandsPool[con.Key].ToList(); - tmp.ForEach(cmd => cmd.Dispose()); - - CommandsPool[con.Key].Clear(); - tmp.Clear(); - CommandsPool[con.Key] = tmp = null; - } - - // 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(); - } - - while (RemainingBuilders.Count > 0) - RemainingBuilders.First().Dispose(); - - // Clear pools - lock (SyncLock) - { - TransactionPool.Clear(); - CommandsPool.Clear(); - RemainingBuilders.Clear(); - - TransactionPool = null; - CommandsPool = null; - RemainingBuilders = null; - } - - ClearSchema(); - if (_proc != null) - _proc.Dispose(); - - 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 supports limit offset syntax (SELECT FIRST x SKIP y ... FROM ...). - SupportFirstSkip = 0x00000020, - - /// Database support standard schema. - SupportSchema = 0x00000010, - - /// Database support stored procedures (EXEC procedure ...). - SupportStoredProcedures = 0x00000100, - - /// Database support stored procedures results (Return value). - SupportStoredProceduresResult = 0x00000200, - - /// Database support with no lock syntax. - SupportNoLock = 0x00001000, - - /// Debug option allowing to enable command dumps by default. - DumpCommands = 0x01000000, - } - - /// Dynamic expando is a simple and temporary class to resolve memory leaks inside ExpandoObject. - public class DynamicExpando : DynamicObject, IDictionary, ICollection>, - IEnumerable>, IEnumerable - { - /// Class containing information about last accessed property of dynamic object. - public class PropertyAccess - { - /// Enum describing type of access to object. - public enum TypeOfAccess - { - /// Get member. - Get, - - /// Set member. - Set, - } - - /// Gets the type of operation. - public TypeOfAccess Operation { get; internal set; } - - /// Gets the name of property. - public string Name { get; internal set; } - - /// Gets the type from binder. - public Type RequestedType { get; internal set; } - - /// Gets the type of value stored in object. - public Type Type { get; internal set; } - - /// Gets the value stored in object. - public object Value { get; internal set; } - - /// Gets the last access time. - public long Ticks { get; internal set; } - } - - private Dictionary _data = new Dictionary(); - - private PropertyAccess _lastProp = new PropertyAccess(); - - /// Initializes a new instance of the class. - public DynamicExpando() - { - } - - /// Gets the last accesses property. - /// Description of last accessed property. - public PropertyAccess GetLastAccessesProperty() - { - return _lastProp; - } - - /// Tries to get member value. - /// Returns true, if get member was tried, false otherwise. - /// The context binder. - /// The invocation result. - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - result = _data.TryGetValue(binder.Name); - - _lastProp.Operation = PropertyAccess.TypeOfAccess.Get; - _lastProp.RequestedType = binder.ReturnType; - _lastProp.Name = binder.Name; - _lastProp.Value = result; - _lastProp.Type = result == null ? typeof(void) : result.GetType(); - _lastProp.Ticks = DateTime.Now.Ticks; - - return true; - } - - /// Tries to set member. - /// Returns true, if set member was tried, false otherwise. - /// The context binder. - /// Value which will be set. - public override bool TrySetMember(SetMemberBinder binder, object value) - { - _data[binder.Name] = value; - - _lastProp.Operation = PropertyAccess.TypeOfAccess.Set; - _lastProp.RequestedType = binder.ReturnType; - _lastProp.Name = binder.Name; - _lastProp.Value = value; - _lastProp.Type = value == null ? typeof(void) : value.GetType(); - _lastProp.Ticks = DateTime.Now.Ticks; - - return true; - } - - #region IDictionary implementation - - bool IDictionary.ContainsKey(string key) - { - return _data.ContainsKey(key); - } - - void IDictionary.Add(string key, object value) - { - _data.Add(key, value); - } - - bool IDictionary.Remove(string key) - { - return _data.Remove(key); - } - - bool IDictionary.TryGetValue(string key, out object value) - { - return _data.TryGetValue(key, out value); - } - - object IDictionary.this[string index] - { - get { return _data[index]; } - set { _data[index] = value; } - } - - ICollection IDictionary.Keys - { - get { return _data.Keys; } - } - - ICollection IDictionary.Values - { - get { return _data.Values; } - } - - #endregion IDictionary implementation - - #region ICollection implementation - - void ICollection>.Add(KeyValuePair item) - { - ((ICollection>)_data).Add(item); - } - - void ICollection>.Clear() - { - _data.Clear(); - } - - bool ICollection>.Contains(KeyValuePair item) - { - return ((ICollection>)_data).Contains(item); - } - - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - { - ((ICollection>)_data).CopyTo(array, arrayIndex); - } - - bool ICollection>.Remove(KeyValuePair item) - { - return ((ICollection>)_data).Remove(item); - } - - int ICollection>.Count - { - get { return _data.Count; } - } - - bool ICollection>.IsReadOnly - { - get { return ((ICollection>)_data).IsReadOnly; } - } - - #endregion ICollection implementation - - #region IEnumerable implementation - - IEnumerator> IEnumerable>.GetEnumerator() - { - return _data.GetEnumerator(); - } - - #endregion IEnumerable implementation - - #region IEnumerable implementation - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)_data).GetEnumerator(); - } - - #endregion IEnumerable implementation - } - - /// 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(TimeSpan), DbType.Time }, - { 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(TimeSpan?), DbType.Time }, - { 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 (object item in args) - { - if (item is DynamicExpando) - cmd.AddParameters(database, (DynamicExpando)item); - else if (item is ExpandoObject) - cmd.AddParameters(database, (ExpandoObject)item); - else - 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 (KeyValuePair item in args.ToDictionary()) - cmd.AddParameter(database, item.Key, item.Value); - - 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, DynamicExpando args) - { - if (args != null && args.Count() > 0) - foreach (KeyValuePair 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) - { - IDbDataParameter 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(DynamicExpando) || type == typeof(ExpandoObject)) - p.Value = CorrectValue(p.DbType, ((IDictionary)item).Values.FirstOrDefault()); - else - p.Value = CorrectValue(p.DbType, item); - - if (p.DbType == DbType.String) - p.Size = item.ToString().Length > 4000 ? -1 : 4000; - else if (p.DbType == DbType.AnsiString) - p.Size = item.ToString().Length > 8000 ? -1 : 8000; - } - - 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) - { - IDbDataParameter 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 = CorrectValue(p.DbType, 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.AnsiString) - p.Size = value.ToString().Length > 8000 ? -1 : 8000; - else if (p.DbType == DbType.String) - p.Size = value.ToString().Length > 4000 ? -1 : 4000; - - p.Value = CorrectValue(p.DbType, value); - } - - cmd.Parameters.Add(p); - - return cmd; - } - - private static object CorrectValue(DbType type, object value) - { - if (value == null || value == DBNull.Value) - return DBNull.Value; - - if ((type == DbType.String || type == DbType.AnsiString || type == DbType.StringFixedLength || - type == DbType.AnsiStringFixedLength) && - !(value is string)) - return value.ToString(); - else if (type == DbType.Guid && value is string) - return Guid.Parse(value.ToString()); - else if (type == DbType.Guid && value is byte[] && ((byte[])value).Length == 16) - return new Guid((byte[])value); - else if - (type == DbType.DateTime && - value is TimeSpan) // HACK: This is specific for SQL Server, to be verified with other databases - return DateTime.Today.Add((TimeSpan)value); - - return value; - } - - /// 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) - { - IDbDataParameter p = cmd.CreateParameter(); - p.ParameterName = builder.Database.GetParameterName(cmd.Parameters.Count); - - DynamicSchemaColumn? 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 : CorrectValue(p.DbType, 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.AnsiString) - p.Size = item.Value.ToString().Length > 8000 ? -1 : 8000; - else if (p.DbType == DbType.String) - p.Size = item.Value.ToString().Length > 4000 ? -1 : 4000; - - p.Value = CorrectValue(p.DbType, 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 = CorrectValue(param.DbType, 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 = CorrectValue(param.DbType, 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 = CorrectValue(param.DbType, 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 = CorrectValue(param.DbType, value ?? DBNull.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 = CorrectValue(param.DbType, value ?? DBNull.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 = CorrectValue(param.DbType, value ?? DBNull.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) - { - try - { - var p = ((IDbDataParameter)command.Parameters[parameterName]); - p.Value = CorrectValue(p.DbType, value); - } - catch (Exception ex) - { - throw new ArgumentException( - string.Format("Error setting parameter {0} in command {1}", parameterName, - command.CommandText ?? string.Empty), ex); - } - - 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) - { - try - { - var p = ((IDbDataParameter)command.Parameters[index]); - p.Value = CorrectValue(p.DbType, value); - } - catch (Exception ex) - { - throw new ArgumentException( - string.Format("Error setting parameter {0} in command {1}", index, - command.CommandText ?? string.Empty), ex); - } - - 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) - { - if (handler != null) - ret = o.ToString().TryParseDefault(defaultValue, handler); - else if (o is IConvertible && typeof(T).GetInterfaces().Any(i => i == typeof(IConvertible))) - ret = (T)Convert.ChangeType(o, typeof(T)); - else if (typeof(T) == typeof(Guid)) - { - if (o.GetType() == typeof(byte[])) - ret = (T)(object)new Guid((byte[])o); - else - ret = (T)(object)Guid.Parse(o.ToString()); - } - else if (typeof(T) == typeof(string)) - ret = (T)(o.ToString() as object); - else if (typeof(T) == typeof(object)) - ret = (T)o; - else - { - MethodInfo method = typeof(T).GetMethod( - "TryParse", - new Type[] - { - typeof(string), - Type.GetType(string.Format("{0}&", typeof(T).FullName)) - }); - - 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 - 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()) - { - MethodInfo 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 (o is IConvertible && typeof(T).GetInterfaces().Any(i => i == typeof(IConvertible))) - ret = (T)Convert.ChangeType(o, typeof(T)); - else if (typeof(T) == typeof(Guid)) - { - if (o.GetType() == typeof(byte[])) - ret = (T)(object)new Guid((byte[])o); - else - ret = (T)(object)Guid.Parse(o.ToString()); - } - else if (typeof(T) == typeof(string)) - ret = (T)(o.ToString() as object); - else if (typeof(T) == typeof(object)) - ret = (T)o; - 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 - throw new InvalidOperationException( - "Provided type can't be parsed using generic approach."); - } - } - - yield return ret; - } - } - } - -#endif - - #endregion Generic Execution - - /// Dump command into string. - /// Command to dump. - /// Returns dumped instance in string form. - public static string DumpToString(this IDbCommand command) - { - StringBuilder sb = new StringBuilder(); - command.Dump(sb); - return sb.ToString(); - } - - /// 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) - { - try - { - 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(); - } - } - catch (NullReferenceException) - { - writer.WriteLine("Command disposed."); - } - - 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(); - } - - /// 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(); - } - - /// 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 - { - if (a != null) - { - if (b.OnCreateTemporaryParameter == null) - b.OnCreateTemporaryParameter = new List>(); - - b.OnCreateTemporaryParameter.Add(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 - { - if (a != null) - { - if (b.OnCreateParameter == null) - b.OnCreateParameter = new List>(); - - b.OnCreateParameter.Add(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 - { - IDynamicSelectQueryBuilder sub = b.SubQuery(); - - subquery(b, sub); - try - { - (b as DynamicQueryBuilder).ParseCommand(sub as DynamicQueryBuilder, b.Parameters); - } - catch (ArgumentException) - { - // This might occur if join was made to subquery - } - - 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 - { - IDynamicSelectQueryBuilder 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) - { - List result = new List(); - - while (r.Read()) - result.Add(r.RowToDynamic()); - - return result; - } - - /// Turns the dictionary into an ExpandoObject. - /// Dictionary to convert. - /// Converted dictionary. - public static dynamic ToDynamic(this IDictionary d) - { - DynamicExpando result = new DynamicExpando(); - IDictionary dict = (IDictionary)result; - - foreach (KeyValuePair prop in d) - dict.Add(prop.Key, prop.Value); - - return result; - } - - /// Turns the dictionary into an ExpandoObject. - /// Dictionary to convert. - /// Converted dictionary. - public static dynamic ToExpando(this IDictionary d) - { - ExpandoObject result = new ExpandoObject(); - IDictionary dict = (IDictionary)result; - - foreach (KeyValuePair 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) - { - Type ot = o.GetType(); - - if (ot == typeof(DynamicExpando) || ot == typeof(ExpandoObject)) - return o; - - DynamicExpando result = new DynamicExpando(); - IDictionary dict = (IDictionary)result; - - if (o is IDictionary) - ((IDictionary)o) - .ToList() - .ForEach(kvp => dict.Add(kvp.Key, kvp.Value)); - else if (ot == typeof(NameValueCollection) || ot.IsSubclassOf(typeof(NameValueCollection))) - { - NameValueCollection nameValue = (NameValueCollection)o; - nameValue.Cast() - .Select(key => new KeyValuePair(key, nameValue[key])) - .ToList() - .ForEach(i => dict.Add(i)); - } - else - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(ot); - - if (mapper != null) - { - foreach (DynamicPropertyInvoker item in mapper.ColumnsMap.Values) - if (item.Get != null) - dict.Add(item.Name, item.Get(o)); - } - else - { - PropertyInfo[] props = ot.GetProperties(); - - foreach (PropertyInfo item in props) - if (item.CanRead) - dict.Add(item.Name, item.GetValue(o, null)); - } - } - - return result; - } - - /// Turns the object into an ExpandoObject. - /// Object to convert. - /// Converted object. - public static dynamic ToExpando(this object o) - { - Type ot = o.GetType(); - - if (ot == typeof(ExpandoObject) || ot == typeof(DynamicExpando)) - return o; - - ExpandoObject result = new ExpandoObject(); - IDictionary dict = (IDictionary)result; - - if (o is IDictionary) - ((IDictionary)o) - .ToList() - .ForEach(kvp => dict.Add(kvp.Key, kvp.Value)); - else if (ot == typeof(NameValueCollection) || ot.IsSubclassOf(typeof(NameValueCollection))) - { - NameValueCollection nameValue = (NameValueCollection)o; - nameValue.Cast() - .Select(key => new KeyValuePair(key, nameValue[key])) - .ToList() - .ForEach(i => dict.Add(i)); - } - else - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(ot); - - if (mapper != null) - { - foreach (DynamicPropertyInvoker item in mapper.ColumnsMap.Values) - if (item.Get != null) - dict.Add(item.Name, item.Get(o)); - } - else - { - PropertyInfo[] props = ot.GetProperties(); - - foreach (PropertyInfo 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 DynamicExpando(); - - int len = r.Table.Columns.Count; - - for (int i = 0; i < len; i++) - ((IDictionary)e).Add(r.Table.Columns[i].ColumnName, r.IsNull(i) ? null : r[i]); - - return e; - } - - /// Convert data row row into dynamic object. - /// DataRow from which read. - /// Generated dynamic object. - public static dynamic RowToExpando(this DataRow r) - { - dynamic e = new ExpandoObject(); - - int len = r.Table.Columns.Count; - - for (int i = 0; i < len; i++) - ((IDictionary)e).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 DynamicExpando(); - - int len = r.Table.Columns.Count; - - for (int i = 0; i < len; i++) - ((IDictionary)e).Add(r.Table.Columns[i].ColumnName.ToUpper(), - 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 RowToExpandoUpper(this DataRow r) - { - // ERROR: Memory leak - dynamic e = new ExpandoObject(); - - int len = r.Table.Columns.Count; - - for (int i = 0; i < len; i++) - ((IDictionary)e).Add(r.Table.Columns[i].ColumnName.ToUpper(), - r.IsNull(i) ? null : r[i]); - - return e; - } - - internal static Dictionary RowToDynamicUpperDict(this DataRow r) - { - dynamic e = new Dictionary(); - - int len = r.Table.Columns.Count; - - for (int i = 0; i < len; i++) - e.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 DynamicExpando(); - IDictionary 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; - } - - /// Convert reader row into dynamic object. - /// Reader from which read. - /// Generated dynamic object. - public static dynamic RowToExpando(this IDataReader r) - { - dynamic e = new ExpandoObject(); - IDictionary 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 DynamicExpando 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 && type.GetInterfaces() - .Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); - } - - /// 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 || type.IsValueType || type.IsEnum)) - 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.GetInterfaces().Any(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>))) - { - Type[] gt = type.GetGenericArguments(); - - return (gt.Length == 1) && gt[0].IsValueType; - } - } - - return false; - } - - /// Gets corresponding to the - /// provided . - /// The type to be converted. - /// Returns corresponding to the - /// provided . - public static DbType ToDbType(this Type t) - { - return TypeMap.TryGetNullable(t) ?? DbType.Object; - } - - /// Gets corresponding to the - /// provided . - /// The type to be converted. - /// Returns corresponding to the - /// provided . - public static Type ToType(this DbType dbt) - { - if (dbt == DbType.String) - return typeof(string); - - foreach (KeyValuePair tdbt in TypeMap) - if (tdbt.Value == dbt) - return tdbt.Key; - - return typeof(object); - } - - /// Determines whether the specified value is has only ASCII chars. - /// The value to check. - /// Returns true if the specified value has only ASCII cars; otherwise, false. - public static bool IsASCII(this string value) - { - return Encoding.UTF8.GetByteCount(value) == value.Length; - } - - #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 != 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 IDataReader extensions - - /// Gets the information corresponding - /// to the type of that would be returned from - /// . - /// The data reader. - /// The index of the field to find. - /// The information corresponding to the - /// type of that would be returned from - /// . - public static DbType GetFieldDbType(this IDataReader r, int i) - { - return TypeMap.TryGetNullable(r.GetFieldType(i)) ?? DbType.String; - } - - internal static IEnumerable EnumerateReader(this IDataReader r) - { - while (r.Read()) - yield return r.RowToDynamic(); - } - - /// Creates cached reader object from non cached reader. - /// The reader to cache. - /// The offset row. - /// The limit to number of tows. -1 is no limit. - /// The progress delegate. - /// Returns new instance of cached reader or current instance of a reader. - public static IDataReader CachedReader(this IDataReader r, int offset = 0, int limit = -1, - Func progress = null) - { - if (r is DynamicCachedReader) - return r; - - return new DynamicCachedReader(r, offset, limit, progress); - } - - #endregion IDataReader 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) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - foreach (object 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) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - return (T)mapper.Create(item); - } - - /// MapEnumerable object enumerator into specified type using property names. - /// Type to which columnMap results. - /// Source enumerator. - /// Enumerator of specified type. - public static IEnumerable MapEnumerableByProperty(this IEnumerable enumerable) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - foreach (object item in enumerable) - yield return (T)mapper.CreateByProperty(item); - } - - /// MapEnumerable object item into specified type using property names. - /// Type to which columnMap results. - /// Source object. - /// Item of specified type. - public static T MapByProperty(this object item) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - return (T)mapper.CreateByProperty(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) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - mapper.Map(source, item); - - return item; - } - - /// Fill object of specified type with data from source object using property names. - /// Type to which columnMap results. - /// Item to which columnMap data. - /// Item from which extract data. - /// Filled item. - public static T FillByProperty(this T item, object source) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - mapper.MapByProperty(source, item); - - 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) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - foreach (object 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) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - return mapper.Create(item); - } - - /// MapEnumerable object enumerator into specified type using property names. - /// Source enumerator. - /// Type to which columnMap results. - /// Enumerator of specified type. - public static IEnumerable MapEnumerableByProperty(this IEnumerable enumerable, Type type) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - foreach (object item in enumerable) - yield return mapper.CreateByProperty(item); - } - - /// MapEnumerable object item into specified type using property names. - /// Source object. - /// Type to which columnMap results. - /// Item of specified type. - public static object MapByProperty(this object item, Type type) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - return mapper.CreateByProperty(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 from 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 procedure invoker. - /// Unfortunately I can use out and ref to - /// return parameters, . - /// But see example for workaround. If there aren't any return parameters execution will return scalar value. - /// Scalar result is not converted to provided generic type (if any). For output results there is possibility to map to provided class. - /// You still can use out, return and both way parameters by providing variable prefix: - /// dynamic res = db.Procedures.sp_Test_Scalar_In_Out(inp: Guid.NewGuid(), out_outp: Guid.Empty); - /// Console.Out.WriteLine(res.outp); - /// Prefixes: out_, ret_, both_. Result will contain field without prefix. - /// Here is an example with result class: - /// public class ProcResult { [Column("outp")] public Guid Output { get; set; } } - /// ProcResult res4 = db.Procedures.sp_Test_Scalar_In_Out<ProcResult>(inp: Guid.NewGuid(), out_outp: Guid.Empty) as ProcResult; - /// As you can se, you can use mapper to do job for you. - public class DynamicProcedureInvoker : DynamicObject, IDisposable - { - private DynamicDatabase _db; - private List _prefixes; - - internal DynamicProcedureInvoker(DynamicDatabase db, List prefixes = null) - { - _prefixes = prefixes; - _db = db; - } - - /// This is where the magic begins. - /// Binder to create owner. - /// Binder invoke result. - /// Returns true if invoke was performed. - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - List pref = new List(); - - if (_prefixes != null) - pref.AddRange(_prefixes); - - pref.Add(binder.Name); - - result = new DynamicProcedureInvoker(_db, pref); - - return true; - } - - /// 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 - CallInfo info = binder.CallInfo; - - // Get generic types - IList types = binder.GetGenericTypeArguments(); - - Dictionary retParams = null; - - using (IDbConnection con = _db.Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - if (_prefixes == null || _prefixes.Count == 0) - cmd.SetCommand(CommandType.StoredProcedure, binder.Name); - else - cmd.SetCommand(CommandType.StoredProcedure, - string.Format("{0}.{1}", string.Join(".", _prefixes), binder.Name)); - - #region Prepare arguments - - int alen = args.Length; - bool retIsAdded = false; - - if (alen > 0) - { - for (int i = 0; i < alen; i++) - { - object arg = args[i]; - - if (arg is DynamicExpando) - cmd.AddParameters(_db, (DynamicExpando)arg); - else if (arg is ExpandoObject) - cmd.AddParameters(_db, (ExpandoObject)arg); - else if (arg is DynamicColumn) - { - var dcv = (DynamicColumn)arg; - - string paramName = i.ToString(); - bool isOut = false; - bool isRet = false; - bool isBoth = false; - - if (info.ArgumentNames.Count > i) - { - isOut = info.ArgumentNames[i].StartsWith("out_"); - isRet = info.ArgumentNames[i].StartsWith("ret_"); - isBoth = info.ArgumentNames[i].StartsWith("both_"); - - paramName = isOut || isRet ? info.ArgumentNames[i].Substring(4) : - isBoth ? info.ArgumentNames[i].Substring(5) : - info.ArgumentNames[i]; - } - - paramName = dcv.Alias ?? dcv.ColumnName ?? - (dcv.Schema.HasValue ? dcv.Schema.Value.Name : null) ?? - paramName; - - if (!isOut && !isRet && !isBoth) - { - isOut = dcv.ParameterDirection == ParameterDirection.Output; - isRet = dcv.ParameterDirection == ParameterDirection.ReturnValue; - isBoth = dcv.ParameterDirection == ParameterDirection.InputOutput; - } - - if (isRet) - retIsAdded = true; - - if (isOut || isRet || isBoth) - { - if (retParams == null) - retParams = new Dictionary(); - retParams.Add(paramName, cmd.Parameters.Count); - } - - if (dcv.Schema != null) - { - var ds = dcv.Schema.Value; - cmd.AddParameter( - _db.GetParameterName(paramName), - isOut ? ParameterDirection.Output : - isRet ? ParameterDirection.ReturnValue : - isBoth ? ParameterDirection.InputOutput : - ParameterDirection.Input, - ds.Type, ds.Size, ds.Precision, ds.Scale, - (isOut || isRet) ? DBNull.Value : dcv.Value); - } - else - cmd.AddParameter( - _db.GetParameterName(paramName), - isOut ? ParameterDirection.Output : - isRet ? ParameterDirection.ReturnValue : - isBoth ? ParameterDirection.InputOutput : - ParameterDirection.Input, - arg == null ? DbType.String : arg.GetType().ToDbType(), - isRet ? 4 : 0, - (isOut || isRet) ? DBNull.Value : dcv.Value); - } - else if (arg is DynamicSchemaColumn) - { - var dsc = (DynamicSchemaColumn)arg; - - string paramName = i.ToString(); - bool isOut = false; - bool isRet = false; - bool isBoth = false; - - if (info.ArgumentNames.Count > i) - { - isOut = info.ArgumentNames[i].StartsWith("out_"); - isRet = info.ArgumentNames[i].StartsWith("ret_"); - isBoth = info.ArgumentNames[i].StartsWith("both_"); - - paramName = isOut || isRet ? info.ArgumentNames[i].Substring(4) : - isBoth ? info.ArgumentNames[i].Substring(5) : - info.ArgumentNames[i]; - } - - paramName = dsc.Name ?? paramName; - - if (isRet) - retIsAdded = true; - - if (isOut || isRet || isBoth) - { - if (retParams == null) - retParams = new Dictionary(); - retParams.Add(paramName, cmd.Parameters.Count); - } - - cmd.AddParameter( - _db.GetParameterName(paramName), - isOut ? ParameterDirection.Output : - isRet ? ParameterDirection.ReturnValue : - isBoth ? ParameterDirection.InputOutput : - ParameterDirection.Input, - dsc.Type, dsc.Size, dsc.Precision, dsc.Scale, - DBNull.Value); - } - else - { - if (info.ArgumentNames.Count > i && !string.IsNullOrEmpty(info.ArgumentNames[i])) - { - bool isOut = info.ArgumentNames[i].StartsWith("out_"); - bool isRet = info.ArgumentNames[i].StartsWith("ret_"); - bool isBoth = info.ArgumentNames[i].StartsWith("both_"); - - if (isRet) - retIsAdded = true; - - string paramName = isOut || isRet ? info.ArgumentNames[i].Substring(4) : - isBoth ? info.ArgumentNames[i].Substring(5) : - info.ArgumentNames[i]; - - if (isOut || isBoth || isRet) - { - if (retParams == null) - retParams = new Dictionary(); - retParams.Add(paramName, cmd.Parameters.Count); - } - - cmd.AddParameter( - _db.GetParameterName(paramName), - isOut ? ParameterDirection.Output : - isRet ? ParameterDirection.ReturnValue : - isBoth ? ParameterDirection.InputOutput : - ParameterDirection.Input, - arg == null ? isRet ? DbType.Int32 : DbType.String : arg.GetType().ToDbType(), - isRet ? 4 : 0, - (isOut || isRet) ? DBNull.Value : arg); - } - else - cmd.AddParameter(_db, arg); - } - } - } - - #endregion Prepare arguments - - #region Get main result - - object mainResult = null; - - if (types.Count > 0) - { - mainResult = types[0].GetDefaultValue(); - - if (types[0] == typeof(IDataReader)) - { - using (IDataReader rdr = cmd.ExecuteReader()) - mainResult = rdr.CachedReader(); - } - else if (types[0] == typeof(DataTable)) - { - using (IDataReader rdr = cmd.ExecuteReader()) - using (IDataReader cache = rdr.CachedReader()) - mainResult = cache.ToDataTable(binder.Name); - } - else if (types[0].IsGenericEnumerable()) - { - Type argType = types[0].GetGenericArguments().First(); - if (argType == typeof(object)) - { - using (IDataReader rdr = cmd.ExecuteReader()) - using (IDataReader cache = rdr.CachedReader()) - mainResult = cache.EnumerateReader().ToList(); - } - else if (argType.IsValueType || argType == typeof(string)) - { - Type listType = typeof(List<>).MakeGenericType(new Type[] { argType }); - IList listInstance = (IList)Activator.CreateInstance(listType); - - object defVal = listType.GetDefaultValue(); - - using (IDataReader rdr = cmd.ExecuteReader()) - using (IDataReader cache = rdr.CachedReader()) - while (cache.Read()) - listInstance.Add(cache[0] != null && cache[0] != DBNull.Value - ? argType.CastObject(cache[0]) - : defVal); - - mainResult = listInstance; - } - else if (argType == typeof(Guid)) - { - Type listType = typeof(List<>).MakeGenericType(new Type[] { argType }); - IList listInstance = (IList)Activator.CreateInstance(listType); - - object defVal = listType.GetDefaultValue(); - - using (IDataReader rdr = cmd.ExecuteReader()) - using (IDataReader cache = rdr.CachedReader()) - while (cache.Read()) - { - if (cache[0] != null && cache[0] != DBNull.Value && - Guid.TryParse(cache[0].ToString(), out Guid g)) - listInstance.Add(g); - } - - mainResult = listInstance; - } - else - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(argType); - if (mapper == null) - throw new InvalidCastException( - string.Format("Don't know what to do with this type: '{0}'.", argType.ToString())); - - using (IDataReader rdr = cmd.ExecuteReader()) - using (IDataReader cache = rdr.CachedReader()) - { - var lt = typeof(List<>); - var ltc = lt.MakeGenericType(argType); - var instance = Activator.CreateInstance(ltc) as IList; - - foreach (var item in cache.EnumerateReader()) - instance.Add(DynamicExtensions.Map(item, argType)); - - mainResult = instance; - } - - //mainResult = cache.EnumerateReader().MapEnumerable(argType).ToList(); - } - } - else if (types[0].IsValueType || types[0] == typeof(string)) - { - mainResult = cmd.ExecuteScalar(); - if (mainResult != null && mainResult != DBNull.Value) - mainResult = types[0].CastObject(mainResult); - } - else if (types[0] == typeof(Guid)) - { - mainResult = cmd.ExecuteScalar(); - if (mainResult != null && mainResult != DBNull.Value && - Guid.TryParse(mainResult.ToString(), out Guid g)) - mainResult = g; - } - else - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(types[0]); - if (mapper == null) - throw new InvalidCastException(string.Format("Don't know what to do with this type: '{0}'.", - types[0].ToString())); - - using (IDataReader rdr = cmd.ExecuteReader()) - if (rdr.Read()) - mainResult = (rdr.RowToDynamic() as object).Map(types[0]); - else - mainResult = null; - } - } - else - { - var returnName = _db.GetParameterName("___result___"); - if ((_db.Options & DynamicDatabaseOptions.SupportStoredProceduresResult) == - DynamicDatabaseOptions.SupportStoredProceduresResult) - { - if (!retIsAdded) - cmd.AddParameter(returnName, ParameterDirection.ReturnValue, DbType.Int32, 4, 0, 0, - DBNull.Value); - } - - mainResult = cmd.ExecuteNonQuery(); - - IDbDataParameter returnParam = null; - if (!retIsAdded) - { - if ((_db.Options & DynamicDatabaseOptions.SupportStoredProceduresResult) == - DynamicDatabaseOptions.SupportStoredProceduresResult) - returnParam = cmd.Parameters[returnName] as IDbDataParameter; - } - else - { - foreach (var e in cmd.Parameters) - { - var p = e as IDbDataParameter; - if (p != null && p.Direction == ParameterDirection.ReturnValue) - { - returnParam = p; - break; - } - } - } - - if (returnParam != null && returnParam.Value != null && returnParam.Value != DBNull.Value) - mainResult = returnParam.Value; - } - - #endregion Get main result - - #region Handle out params - - if (retParams != null) - { - Dictionary res = new Dictionary(); - - if (mainResult != null) - { - if (mainResult == DBNull.Value) - res.Add(binder.Name, null); - else - res.Add(binder.Name, mainResult); - } - - foreach (KeyValuePair pos in retParams) - res.Add(pos.Key, ((IDbDataParameter)cmd.Parameters[pos.Value]).Value); - - if (types.Count > 1) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(types[1]); - - if (mapper != null) - result = mapper.Create(res.ToDynamic()); - else - result = res.ToDynamic(); - } - else - result = res.ToDynamic(); - } - else - result = mainResult; - - #endregion Handle out params - } - - return true; - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public void Dispose() - { - } - } - - /// Dynamic query exception. - [Serializable] - public class DynamicQueryException : Exception, ISerializable - { - /// Initializes a new instance of the - /// class. - /// The command which failed. - public DynamicQueryException(IDbCommand command = null) - : base(string.Format("Error executing command.{0}{1}", Environment.NewLine, - command != null ? command.DumpToString() : string.Empty)) - { - } - - /// Initializes a new instance of the - /// class. - /// The message. - /// The command which failed. - public DynamicQueryException(string message, IDbCommand command = null) - : base(string.Format("{0}{1}{2}", message, Environment.NewLine, - command != null ? command.DumpToString() : string.Empty)) - { - } - - /// Initializes a new instance of the - /// class. - /// The inner exception. - /// The command which failed. - public DynamicQueryException(Exception innerException, IDbCommand command = null) - : base( - string.Format("Error executing command.{0}{1}", Environment.NewLine, - command != null ? command.DumpToString() : string.Empty), innerException) - { - } - - /// 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( - string.Format("{0}{1}{2}", message, Environment.NewLine, - command != null ? command.DumpToString() : string.Empty), innerException) - { - } - } - - /// 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 a value indicating whether column allows null or not. - public bool AllowNull { 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 Queryto 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 = owner != null ? Database.StripName(owner) : string.Empty; - 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; - - DynamicTypeMap 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.Owner) - ? null - : mapper.Table.Owner; - } - - BuildAndCacheSchema(keys); - } - - #region Schema - - private void BuildAndCacheSchema(string[] keys) - { - Dictionary schema = null; - - schema = Database.GetSchema(TableType) ?? - Database.GetSchema(TableName, OwnerName); - - #region Fill currrent table schema - - if (keys == null && TableType != null) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(TableType); - - if (mapper != null) - { - IEnumerable 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) - { - return Database.Query(sql, args); - } - - /// Enumerate the reader and yield the result. - /// Command builder. - /// Enumerator of objects expanded from query. - public virtual IEnumerable Query(IDynamicQueryBuilder builder) - { - return Database.Query(builder); - } - - /// Create new . - /// New instance. - public virtual IDynamicSelectQueryBuilder Query() - { - IDynamicSelectQueryBuilder builder = new DynamicSelectQueryBuilder(this.Database); - - string 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) - { - return Database.Scalar(sql, args); - } - - /// Returns a single result. - /// Command builder. - /// Result of a query. - public virtual object Scalar(IDynamicQueryBuilder builder) - { - return Database.Scalar(builder); - } - -#if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE - - /// Returns a single result. - /// What kind of result is expected. - /// 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 T ScalarAs(string sql, params object[] args) - { - return Database.ScalarAs(sql, args); - } - - /// Returns a single result. - /// What kind of result is expected. - /// Command builder. - /// Default value. - /// Result of a query. - public virtual T ScalarAs(IDynamicQueryBuilder builder, T defaultValue = default(T)) - { - return Database.ScalarAs(builder, defaultValue); - } - -#endif - - /// Execute stored procedure. - /// Name of stored procedure to execute. - /// Number of affected rows. - public virtual int Procedure(string procName) - { - return Database.Procedure(procName); - } - - /// 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, params object[] args) - { - return Database.Procedure(procName, args); - } - - /// 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, DynamicExpando args) - { - return Database.Procedure(procName, args); - } - - /// 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) - { - return Database.Procedure(procName, args); - } - - /// 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) - { - return Database.Execute(sql, args); - } - - /// Execute non query. - /// Command builder. - /// Number of affected rows. - public virtual int Execute(IDynamicQueryBuilder builder) - { - return Database.Execute(builder); - } - - /// Execute non query. - /// Command builders. - /// Number of affected rows. - public virtual int Execute(IDynamicQueryBuilder[] builders) - { - return Database.Execute(builders); - } - - #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 - CallInfo info = binder.CallInfo; - - // Get generic types - IList 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"); - - string 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) - { - DynamicInsertQueryBuilder 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( - string.IsNullOrEmpty(this.OwnerName) - ? this.TableName - : string.Format("{0}.{1}", this.OwnerName, 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++) - { - string fullName = info.ArgumentNames[i]; - string name = fullName.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(fullName, args[i]); - break; - } - } - } - - // Execute - return Execute(builder); - } - - private object DynamicUpdate(object[] args, CallInfo info, IList types) - { - DynamicUpdateQueryBuilder 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( - string.IsNullOrEmpty(this.OwnerName) - ? this.TableName - : string.Format("{0}.{1}", this.OwnerName, 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++) - { - string fullName = info.ArgumentNames[i]; - string name = fullName.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(fullName, args[i]); - break; - } - } - } - - // Execute - return Execute(builder); - } - - private object DynamicDelete(object[] args, CallInfo info, IList types) - { - DynamicDeleteQueryBuilder 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( - string.IsNullOrEmpty(this.OwnerName) - ? this.TableName - : string.Format("{0}.{1}", this.OwnerName, 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++) - { - string fullName = info.ArgumentNames[i]; - string name = fullName.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(fullName, args[i]); - break; - } - } - } - - // Execute - return Execute(builder); - } - - private object DynamicQuery(object[] args, CallInfo info, string op, IList types) - { - object result; - DynamicSelectQueryBuilder 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 => - string.IsNullOrEmpty(this.OwnerName) - ? this.TableName - : string.Format("{0}.{1}", this.OwnerName, 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++) - { - string fullName = info.ArgumentNames[i]; - string name = fullName.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": - { - string 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 => - { - DynamicColumn 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(fullName, 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 - bool justOne = op == "First" || op == "Last" || op == "Get" || op == "Single"; - - // Be sure to sort by DESC on selected columns - 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 _isDisposed = false; - private bool _isOperational = 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. - /// Pass custom transaction parameters. - internal DynamicTransaction(DynamicDatabase db, DynamicConnection con, bool singleTransaction, - IsolationLevel? il, Action disposed, object customParams) - { - _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) - { - _isOperational = false; - - if (_db.DumpCommands && _db.DumpCommandDelegate != null) - _db.DumpCommandDelegate(null, "BEGIN TRAN [NON OPERATIONAL]"); - } - else - { - if (customParams != null) - { - MethodInfo mi = _con.Connection.GetType().GetMethods().Where(m => - m.GetParameters().Count() == 1 && - m.GetParameters().First().ParameterType == customParams.GetType()).FirstOrDefault(); - if (mi != null) - { - _db.TransactionPool[_con.Connection] - .Push((IDbTransaction)mi.Invoke(_con.Connection, new object[] { customParams, })); - - if (_db.DumpCommands && _db.DumpCommandDelegate != null) - _db.DumpCommandDelegate(null, "BEGIN TRAN [CUSTOM ARGS]"); - } - else - throw new MissingMethodException(string.Format( - "Method 'BeginTransaction' accepting parameter of type '{0}' in '{1}' not found.", - customParams.GetType().FullName, _con.Connection.GetType().FullName)); - } - else - { - _db.TransactionPool[_con.Connection] - .Push(il.HasValue - ? _con.Connection.BeginTransaction(il.Value) - : _con.Connection.BeginTransaction()); - - if (_db.DumpCommands && _db.DumpCommandDelegate != null) - _db.DumpCommandDelegate(null, "BEGIN TRAN"); - } - - _db.PoolStamp = DateTime.Now.Ticks; - _isOperational = true; - } - } - } - - /// Commits the database transaction. - public void Commit() - { - lock (_db.SyncLock) - { - if (_isOperational) - { - Stack 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(); - - if (_db.DumpCommands && _db.DumpCommandDelegate != null) - _db.DumpCommandDelegate(null, "COMMIT"); - } - - _isOperational = false; - } - else if (!_isDisposed && _db.DumpCommands && _db.DumpCommandDelegate != null) - _db.DumpCommandDelegate(null, "COMMIT [NON OPERATIONAL]"); - } - } - - /// Rolls back a transaction from a pending state. - public void Rollback() - { - lock (_db.SyncLock) - { - if (_isOperational) - { - Stack 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(); - - if (_db.DumpCommands && _db.DumpCommandDelegate != null) - _db.DumpCommandDelegate(null, "ROLLBACK"); - } - - _isOperational = false; - } - else if (!_isDisposed && _db.DumpCommands && _db.DumpCommandDelegate != null) - _db.DumpCommandDelegate(null, "ROLLBACK [NON OPERATIONAL]"); - } - } - - /// 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() - { - _isDisposed = true; - Rollback(); - - if (_disposed != null) - _disposed(); - } - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed - { - get { return !_isOperational; } - } - - #endregion IExtendedDisposable Members - } - - namespace Builders - { - /// Dynamic delete query builder interface. - /// This interface it publicly 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 publicly 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 publicly available. Implementation should be hidden. - public interface IDynamicQueryBuilder : IExtendedDisposable - { - /// 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 actions. - /// This is exposed to allow setting schema of column. - List> OnCreateTemporaryParameter { get; set; } - - /// Gets or sets the on create real parameter actions. - /// This is exposed to allow modification of parameter. - List> OnCreateParameter { get; set; } - } - - /// Dynamic select query builder interface. - /// This interface it publicly 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(); - -#if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE - - /// Returns a single result. - /// Type to parse to. - /// Default value. - /// Result of a query. - T ScalarAs(T defaultValue = default(T)); - -#endif - - #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 Having - - /// - /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition - /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used - /// as needed. - /// - If several Having() 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: - /// 'Having( x => x.Or( condition ) )'. - /// - /// The specification. - /// This instance to permit chaining. - IDynamicSelectQueryBuilder Having(Func func); - - /// Add Having condition. - /// Condition column with operator and value. - /// Builder instance. - IDynamicSelectQueryBuilder Having(DynamicColumn column); - - /// Add Having condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value); - - /// Add Having condition. - /// Condition column. - /// Condition value. - /// Builder instance. - IDynamicSelectQueryBuilder Having(string column, object value); - - /// Add Having 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 Having(object conditions, bool schema = false); - - #endregion Having - - #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 publicly 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 : IExtendedDisposable - { - /// 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 : IExtendedDisposable - { - /// Gets table owner name. - string Owner { get; } - - /// Gets table name. - string Name { get; } - - /// Gets table alias. - string Alias { get; } - - /// Gets table no lock status. - bool NoLock { get; } - - /// Gets table schema. - Dictionary Schema { get; } - } - - namespace Extensions - { - internal static class DynamicHavingQueryExtensions - { - #region Where - - internal static T InternalHaving(this T builder, Func func) - where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - return builder.InternalHaving(false, false, func); - } - - internal static T InternalHaving(this T builder, bool addBeginBrace, bool addEndBrace, - Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - if (func == null) throw new ArgumentNullException("Array of functions cannot be null."); - - using (DynamicParser parser = DynamicParser.Parse(func)) - { - string condition = null; - bool and = true; - - object 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.InternalHaving(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) - { - DynamicParser.Node.Method node = (DynamicParser.Node.Method)result; - string 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.HavingOpenBracketsCount++; - if (addEndBrace) builder.HavingOpenBracketsCount--; - - if (builder.HavingCondition == null) - builder.HavingCondition = string.Format("{0}{1}{2}", - addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); - else - builder.HavingCondition = string.Format("{0} {1} {2}{3}{4}", builder.HavingCondition, - and ? "AND" : "OR", - addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); - } - - return builder; - } - - internal static T InternalHaving(this T builder, DynamicColumn column) - where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - bool virt = builder.VirtualMode; - if (column.VirtualColumn.HasValue) - builder.VirtualMode = column.VirtualColumn.Value; - - Action modParam = (p) => - { - if (column.Schema.HasValue) - p.Schema = column.Schema; - - if (!p.Schema.HasValue) - p.Schema = column.Schema ?? builder.GetColumnFromSchema(column.ColumnName); - }; - - builder.CreateTemporaryParameterAction(modParam); - - // It's kind of uglu, but... well it works. - if (column.Or) - switch (column.Operator) - { - default: - case DynamicColumn.CompareOperator.Eq: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break; - case DynamicColumn.CompareOperator.Not: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break; - case DynamicColumn.CompareOperator.Like: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break; - case DynamicColumn.CompareOperator.NotLike: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); - break; - case DynamicColumn.CompareOperator.In: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break; - case DynamicColumn.CompareOperator.Lt: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break; - case DynamicColumn.CompareOperator.Lte: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break; - case DynamicColumn.CompareOperator.Gt: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break; - case DynamicColumn.CompareOperator.Gte: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break; - case DynamicColumn.CompareOperator.Between: - builder.InternalHaving(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.InternalHaving(column.BeginBlock, column.EndBlock, - x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break; - case DynamicColumn.CompareOperator.Not: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break; - case DynamicColumn.CompareOperator.Like: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break; - case DynamicColumn.CompareOperator.NotLike: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break; - case DynamicColumn.CompareOperator.In: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break; - case DynamicColumn.CompareOperator.Lt: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break; - case DynamicColumn.CompareOperator.Lte: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break; - case DynamicColumn.CompareOperator.Gt: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break; - case DynamicColumn.CompareOperator.Gte: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break; - case DynamicColumn.CompareOperator.Between: - builder.InternalHaving(column.BeginBlock, column.EndBlock, - x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break; - } - - builder.OnCreateTemporaryParameter.Remove(modParam); - builder.VirtualMode = virt; - - return builder; - } - - internal static T InternalHaving(this T builder, string column, DynamicColumn.CompareOperator op, - object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - if (value is DynamicColumn) - { - DynamicColumn v = (DynamicColumn)value; - - if (string.IsNullOrEmpty(v.ColumnName)) - v.ColumnName = column; - - return builder.InternalHaving(v); - } - else if (value is IEnumerable) - { - foreach (DynamicColumn v in (IEnumerable)value) - builder.InternalHaving(v); - - return builder; - } - - return builder.InternalHaving(new DynamicColumn - { - ColumnName = column, - Operator = op, - Value = value - }); - } - - internal static T InternalHaving(this T builder, string column, object value) - where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - return builder.InternalHaving(column, DynamicColumn.CompareOperator.Eq, value); - } - - internal static T InternalHaving(this T builder, object conditions, bool schema = false) - where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - if (conditions is DynamicColumn) - return builder.InternalHaving((DynamicColumn)conditions); - else if (conditions is IEnumerable) - { - foreach (DynamicColumn v in (IEnumerable)conditions) - builder.InternalHaving(v); - - return builder; - } - - IDictionary dict = conditions.ToDictionary(); - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType()); - string table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty); - - foreach (KeyValuePair 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.InternalHaving(x => - x(builder.FixObjectName(string.Format("{0}.{1}", table, colName))) == condition.Value); - else - builder.InternalHaving(x => x(builder.FixObjectName(colName)) == condition.Value); - } - - return builder; - } - - #endregion Where - } - - 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 (DynamicParser parser = DynamicParser.Parse(func)) - { - object 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 - DynamicParser.Node 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) - { - DynamicParser.Node.Invoke 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 - { - Tuple 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"); - - string[] 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)); - - DynamicTypeMap 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 (DynamicParser parser = DynamicParser.Parse(func)) - { - string condition = null; - bool and = true; - - object 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) - { - DynamicParser.Node.Method node = (DynamicParser.Node.Method)result; - string 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.WhereOpenBracketsCount++; - if (addEndBrace) builder.WhereOpenBracketsCount--; - - 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; - - Action modParam = (p) => - { - if (column.Schema.HasValue) - p.Schema = column.Schema; - - if (!p.Schema.HasValue) - p.Schema = column.Schema ?? builder.GetColumnFromSchema(column.ColumnName); - }; - - builder.CreateTemporaryParameterAction(modParam); - - // 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.OnCreateTemporaryParameter.Remove(modParam); - 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) - { - DynamicColumn 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; - } - - IDictionary dict = conditions.ToDictionary(); - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType()); - string table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty); - - foreach (KeyValuePair 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() - { - ITableInfo 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() - { - ITableInfo 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 (Func 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 (DynamicParser parser = DynamicParser.Parse(f)) - { - object 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) - { - DynamicParser.Node.SetMember node = (DynamicParser.Node.SetMember)result; - - DynamicSchemaColumn? col = GetColumnFromSchema(node.Name); - main = Database.DecorateName(node.Name); - value = Parse(node.Value, ref col, pars: Parameters, nulls: true); - - _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... - string 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) - { - DynamicColumn 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) - { - DynamicColumn column = (DynamicColumn)o; - DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName); - - string main = FixObjectName(column.ColumnName, onlyColumn: true); - string value = Parse(column.Value, ref col, pars: Parameters, nulls: true); - - _columns = _columns == null ? main : string.Format("{0}, {1}", _columns, main); - _values = _values == null ? value : string.Format("{0}, {1}", _values, value); - - return this; - } - - IDictionary dict = o.ToDictionary(); - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(o.GetType()); - - if (mapper != null) - { - foreach (KeyValuePair con in dict) - if (!mapper.Ignored.Contains(con.Key)) - { - string colName = mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key; - DynamicPropertyInvoker propMap = mapper.ColumnsMap.TryGetValue(colName.ToLower()); - - if (propMap == null || propMap.Column == null || !propMap.Column.IsNoInsert) - Insert(colName, con.Value); // TODO: This probably should get value from mapper - } - } - else - foreach (KeyValuePair con in dict) - Insert(con.Key, con.Value); - - return this; - } - - #endregion Insert - - #region IExtendedDisposable - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public override void Dispose() - { - base.Dispose(); - - _columns = _values = null; - } - - #endregion IExtendedDisposable - } - - /// 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 (IDbConnection con = Database.Open()) - using (IDbCommand 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 WhereOpenBracketsCount { get; set; } - } - - /// Empty interface to allow having query builder implementation use universal approach. - internal interface IQueryWithHaving - { - /// Gets or sets the having condition. - string HavingCondition { get; set; } - - /// Gets or sets the amount of not closed brackets in having statement. - int HavingOpenBracketsCount { get; set; } - } - - private DynamicQueryBuilder _parent = null; - - #region TableInfo - - /// Table information. - internal class TableInfo : ITableInfo - { - /// - /// Initializes a new instance of the class. - /// - internal TableInfo() - { - IsDisposed = false; - } - - /// - /// Initializes a new instance of the class. - /// - /// The database. - /// The name of table. - /// The table alias. - /// The table owner. - /// The table should be used with nolock. - public TableInfo(DynamicDatabase db, string name, string alias = null, string owner = null, - bool nolock = false) - : this() - { - Name = name; - Alias = alias; - Owner = owner; - NoLock = nolock; - - 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. - /// The table should be used with nolock. - public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null, - bool nolock = false) - : this() - { - DynamicTypeMap 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; - NoLock = nolock; - - 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 alias. - public bool NoLock { get; internal set; } - - /// Gets or sets table schema. - public Dictionary Schema { get; internal set; } - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public virtual void Dispose() - { - IsDisposed = true; - - ////if (Schema != null) - //// Schema.Clear(); - - Owner = Name = Alias = null; - Schema = null; - } - } - - /// 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 - { - /// Initializes a new instance of the - /// class. - public Parameter() - { - IsDisposed = false; - } - - /// 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; } - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public virtual void Dispose() - { - IsDisposed = true; - - Name = null; - Schema = null; - } - } - - #endregion Parameter - - #region Constructor - - /// - /// Initializes a new instance of the class. - /// - /// The database. - public DynamicQueryBuilder(DynamicDatabase db) - { - IsDisposed = false; - VirtualMode = false; - Tables = new List(); - Parameters = new Dictionary(); - OnCreateTemporaryParameter = new List>(); - OnCreateParameter = new List>(); - - WhereCondition = null; - WhereOpenBracketsCount = 0; - - Database = db; - if (Database != null) - Database.AddToCache(this); - - SupportSchema = (db.Options & DynamicDatabaseOptions.SupportSchema) == - DynamicDatabaseOptions.SupportSchema; - SupportNoLock = (db.Options & DynamicDatabaseOptions.SupportNoLock) == - DynamicDatabaseOptions.SupportNoLock; - } - - /// 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 WhereOpenBracketsCount { 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 actions. - /// This is exposed to allow setting schema of column. - public List> OnCreateTemporaryParameter { get; set; } - - /// Gets or sets the on create real parameter actions. - /// This is exposed to allow modification of parameter. - public List> OnCreateParameter { get; set; } - - /// Gets a value indicating whether database supports standard schema. - public bool SupportSchema { get; private set; } - - /// Gets a value indicating whether database supports with no lock syntax. - public bool SupportNoLock { 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 (WhereOpenBracketsCount > 0) - { - WhereCondition += ")"; - WhereOpenBracketsCount--; - } - } - - // End not ended having statement - if (this is IQueryWithHaving) - { - IQueryWithHaving h = this as IQueryWithHaving; - - while (h.HavingOpenBracketsCount > 0) - { - h.HavingCondition += ")"; - h.HavingOpenBracketsCount--; - } - } - - 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.ForEach(x => x(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. - /// 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? c = null; - - return Parse(node, ref c, pars, rawstr, nulls, decorate, isMultiPart); - } - - /// 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. - /// This parameter is used to determine type of parameter used in query. - /// 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. - /// 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, ref DynamicSchemaColumn? columnSchema, - IDictionary pars = null, bool rawstr = false, bool nulls = false, - bool decorate = true, bool isMultiPart = true) - { - // 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, ref columnSchema, pars, decorate); - } - - // 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, ref columnSchema, pars, decorate); - } - - // If node is a delegate, parse it to create the logical tree... - if (node is Delegate) - { - using (DynamicParser p = DynamicParser.Parse((Delegate)node)) - { - node = p.Result; - - return Parse(node, ref columnSchema, pars, rawstr, - decorate: decorate); // Intercept containers as in (x => "string") - } - } - - return Dispatch(node, ref columnSchema, pars, decorate, isMultiPart); - } - - private string Dispatch(object node, ref DynamicSchemaColumn? columnSchema, - IDictionary pars = null, bool decorate = true, bool isMultiPart = true) - { - 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, ref columnSchema, pars, decorate, - isMultiPart); - else if (node is DynamicParser.Node.SetMember) - return ParseSetMember((DynamicParser.Node.SetMember)node, ref columnSchema, pars, decorate, - isMultiPart); - 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, ref columnSchema, pars); - else if (node is DynamicParser.Node.Invoke) - return ParseInvoke((DynamicParser.Node.Invoke)node, ref columnSchema, 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) - return string.Format("({0})", str); - - // TODO: Make special condiion - ////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 (KeyValuePair 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, - ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, - bool decorate = true, bool isMultiPart = true) - { - 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, - ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, - bool decorate = true, bool isMultiPart = true) - { - 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, ref columnSchema, pars, nulls: true); - 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, ref columnSchema, - pars); // Not nulls: left is assumed to be an object - string right = Parse(node.Right, ref columnSchema, pars, nulls: true); - return string.Format("({0} {1} {2})", left, op, right); - } - - protected virtual string ParseMethod(DynamicParser.Node.Method node, - ref DynamicSchemaColumn? columnSchema, IDictionary pars = null) - { - string method = node.Name.ToUpper(); - string parent = node.Host == null ? null : Parse(node.Host, ref columnSchema, 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], ref columnSchema, 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()); - - object[] arguments = node.Arguments; - - if (arguments.Length == 1 && - (arguments[0] is IEnumerable || arguments[0] is Array) && - !(arguments[0] is byte[])) - { - IEnumerable 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], ref columnSchema, pars: pars), - Parse(arguments[1], ref columnSchema, 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 (object arg in node.Arguments) - { - if (!firstParam) - sbin.Append(", "); - - if ((arg is IEnumerable || arg is Array) && !(arg is byte[])) - { - IEnumerable vals = arg as IEnumerable; - - if (vals == null && arg is Array) - vals = ((Array)arg).Cast() as IEnumerable; - - if (vals != null) - foreach (object val in vals) - { - if (!firstParam) - sbin.Append(", "); - else - firstParam = false; - - sbin.Append(Parse(val, ref columnSchema, pars: pars)); - } - else - sbin.Append(Parse(arg, ref columnSchema, pars: pars)); - } - else - sbin.Append(Parse(arg, ref columnSchema, pars: pars)); - - firstParam = false; - } - - return string.Format("{0} IN({1})", parent, sbin.ToString()); - } - - case "NOTIN": - { - 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 (object arg in node.Arguments) - { - if (!firstParam) - sbin.Append(", "); - - if ((arg is IEnumerable || arg is Array) && !(arg is byte[])) - { - IEnumerable vals = arg as IEnumerable; - - if (vals == null && arg is Array) - vals = ((Array)arg).Cast() as IEnumerable; - - if (vals != null) - foreach (object val in vals) - { - if (!firstParam) - sbin.Append(", "); - else - firstParam = false; - - sbin.Append(Parse(val, ref columnSchema, pars: pars)); - } - else - sbin.Append(Parse(arg, ref columnSchema, pars: pars)); - } - else - sbin.Append(Parse(arg, ref columnSchema, pars: pars)); - - firstParam = false; - } - - return string.Format("{0} NOT 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], ref columnSchema, 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], ref columnSchema, 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 "NOLOCK": - if (!SupportNoLock) - return parent; - - if (node.Arguments != null && node.Arguments.Length > 1) - throw new ArgumentException("NOLOCK method expects no arguments."); - - return string.Format("{0} {1}", parent, "WITH(NOLOCK)"); - - 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], ref columnSchema, pars: Parameters, nulls: true)); - - case "COUNT0": - if (node.Arguments != null && node.Arguments.Length > 0) - throw new ArgumentException("COUNT0 method doesn't expect arguments"); - - return "COUNT(0)"; - } - } - - // 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, ref columnSchema, pars, - nulls: true)); // We don't accept raw strings here!!! - } - } - - sb.Append(")"); - return sb.ToString(); - } - - protected virtual string ParseInvoke(DynamicParser.Node.Invoke node, - ref DynamicSchemaColumn? columnSchema, IDictionary pars = null) - { - // This is used as an especial syntax to merely concatenate its arguments. It is used as a way to extend the supported syntax without the need of treating all the possible cases... - if (node.Arguments == null || node.Arguments.Length == 0) - return string.Empty; - - StringBuilder sb = new StringBuilder(); - foreach (object arg in node.Arguments) - { - if (arg is string) - { - sb.Append((string)arg); - - if (node.Arguments.Length == 1 && !columnSchema.HasValue) - columnSchema = GetColumnFromSchema((string)arg); - } - else - sb.Append(Parse(arg, ref columnSchema, pars, rawstr: true, nulls: true)); - } - - return sb.ToString(); - } - - protected virtual string ParseConvert(DynamicParser.Node.Convert node, - IDictionary pars = null) - { - // The cast mechanism is left for the specific database implementation, that should override this method - // as needed... - string r = Parse(node.Target, pars); - return r; - } - - protected virtual string ParseConstant(object node, IDictionary pars = null, - DynamicSchemaColumn? columnSchema = null) - { - if (node == null && !VirtualMode) - return ParseNull(); - - if (pars != null) - { - bool wellKnownName = VirtualMode && node is String && ((String)node).StartsWith("[$") && - ((String)node).EndsWith("]") && ((String)node).Length > 4; - - // If we have a list of parameters to store it, let's parametrize it - Parameter par = new Parameter() - { - Name = wellKnownName - ? ((String)node).Substring(2, ((String)node).Length - 3) - : Guid.NewGuid().ToString(), - Value = wellKnownName ? null : node, - WellKnown = wellKnownName, - Virtual = VirtualMode, - Schema = columnSchema, - }; - - // If we are adding parameter we inform external sources about this. - if (OnCreateTemporaryParameter != null) - OnCreateTemporaryParameter.ForEach(x => x(par)); - - pars.Add(par.Name, par); - - return string.Format("[${0}]", par.Name); - } - - return node.ToString(); // Last resort case - } - - protected virtual string ParseNull() - { - return "NULL"; // Override if needed - } - - #endregion Parser - - #region Helpers - - internal bool IsTableAlias(string name) - { - DynamicQueryBuilder builder = this; - - while (builder != null) - { - if (builder.Tables.Any(t => t.Alias == name)) - return true; - - builder = builder._parent; - } - - return false; - } - - internal bool IsTable(string name, string owner) - { - DynamicQueryBuilder builder = this; - - while (builder != null) - { - if ((string.IsNullOrEmpty(owner) && - builder.Tables.Any(t => t.Name.ToLower() == name.ToLower())) || - (!string.IsNullOrEmpty(owner) && builder.Tables.Any(t => - t.Name.ToLower() == name.ToLower() && - !string.IsNullOrEmpty(t.Owner) && t.Owner.ToLower() == owner.ToLower()))) - return true; - - builder = builder._parent; - } - - return false; - } - - internal string FixObjectName(string main, bool onlyColumn = false) - { - if (main.IndexOf("(") > 0 && main.IndexOf(")") > 0) - return main.FillStringWithVariables( - f => string.Format("({0})", FixObjectNamePrivate(f, onlyColumn)), "(", ")"); - else - return FixObjectNamePrivate(main, onlyColumn); - } - - private string FixObjectNamePrivate(string f, bool onlyColumn = false) - { - IEnumerable objects = f.Split('.') - .Select(x => Database.StripName(x)); - - if (onlyColumn || objects.Count() == 1) - f = Database.DecorateName(objects.Last()); - else if (!IsTableAlias(objects.First())) - f = string.Join(".", objects.Select(o => Database.DecorateName(o))); - else - f = string.Format("{0}.{1}", objects.First(), - string.Join(".", objects.Skip(1).Select(o => Database.DecorateName(o)))); - - return f; - } - - internal DynamicSchemaColumn? GetColumnFromSchema(string colName, DynamicTypeMap mapper = null, - string table = null) - { - // This is tricky and will not always work unfortunetly. - ////if (colName.ContainsAny(StringExtensions.InvalidMultipartMemberChars)) - //// return null; - - // First we need to get real column name and it's owner if exist. - string[] parts = colName.Split('.'); - for (int i = 0; i < parts.Length; i++) - parts[i] = Database.StripName(parts[i]); - - string columnName = parts.Last(); - - // Get table name from mapper - string tableName = table; - - if (string.IsNullOrEmpty(tableName)) - { - tableName = (mapper != null && mapper.Table != null) ? mapper.Table.Name : string.Empty; - - if (parts.Length > 1 && string.IsNullOrEmpty(tableName)) - { - // OK, we have a multi part identifier, that's good, we can get table name - tableName = string.Join(".", parts.Take(parts.Length - 1)); - } - } - - // Try to get table info from cache - ITableInfo tableInfo = !string.IsNullOrEmpty(tableName) - ? - Tables.FirstOrDefault(x => !string.IsNullOrEmpty(x.Alias) && x.Alias.ToLower() == tableName) ?? - Tables.FirstOrDefault(x => x.Name.ToLower() == tableName.ToLower()) ?? Tables.FirstOrDefault() - : - this is DynamicModifyBuilder || Tables.Count == 1 - ? Tables.FirstOrDefault() - : null; - - // Try to get column from schema - if (tableInfo != null && tableInfo.Schema != null) - return tableInfo.Schema.TryGetNullable(columnName.ToLower()); - - // Well, we failed to find a column - return null; - } - - #endregion Helpers - - #region IExtendedDisposable - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public virtual void Dispose() - { - IsDisposed = true; - - if (Database != null) - Database.RemoveFromCache(this); - - if (Parameters != null) - { - foreach (KeyValuePair p in Parameters) - p.Value.Dispose(); - - Parameters.Clear(); - Parameters = null; - } - - if (Tables != null) - { - foreach (ITableInfo t in Tables) - if (t != null) - t.Dispose(); - - Tables.Clear(); - Tables = null; - } - - WhereCondition = null; - Database = null; - } - - #endregion IExtendedDisposable - } - - /// Implementation of dynamic select query builder. - internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, - DynamicQueryBuilder.IQueryWithWhere, DynamicQueryBuilder.IQueryWithHaving - { - private int? _limit = null; - private int? _offset = null; - private bool _distinct = false; - - private string _select; - private string _from; - private string _join; - private string _groupby; - private string _orderby; - - #region IQueryWithHaving - - /// Gets or sets the having condition. - public string HavingCondition { get; set; } - - /// Gets or sets the amount of not closed brackets in having statement. - public int HavingOpenBracketsCount { get; set; } - - #endregion IQueryWithHaving - - /// - /// Gets a value indicating whether this instance has select columns. - /// - public bool HasSelectColumns - { - get { return !string.IsNullOrEmpty(_select); } - } - - /// - /// Initializes a new instance of the class. - /// - /// The database. - public DynamicSelectQueryBuilder(DynamicDatabase db) - : base(db) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The database. - /// The parent query. - internal DynamicSelectQueryBuilder(DynamicDatabase db, DynamicQueryBuilder parent) - : base(db, parent) - { - } - - /// Generates the text this command will execute against the underlying database. - /// The text to execute against the underlying database. - public override string CommandText() - { - bool lused = false; - bool oused = false; - - StringBuilder sb = new StringBuilder("SELECT"); - if (_distinct) sb.AppendFormat(" DISTINCT"); - - if (_limit.HasValue) - { - if ((Database.Options & DynamicDatabaseOptions.SupportTop) == DynamicDatabaseOptions.SupportTop) - { - sb.AppendFormat(" TOP {0}", _limit); - lused = true; - } - else if ((Database.Options & DynamicDatabaseOptions.SupportFirstSkip) == - DynamicDatabaseOptions.SupportFirstSkip) - { - sb.AppendFormat(" FIRST {0}", _limit); - lused = true; - } - } - - if (_offset.HasValue && (Database.Options & DynamicDatabaseOptions.SupportFirstSkip) == - DynamicDatabaseOptions.SupportFirstSkip) - { - sb.AppendFormat(" SKIP {0}", _offset); - oused = true; - } - - if (_select != null) sb.AppendFormat(" {0}", _select); - else sb.Append(" *"); - if (_from != null) sb.AppendFormat(" FROM {0}", _from); - if (_join != null) sb.AppendFormat(" {0}", _join); - if (WhereCondition != null) sb.AppendFormat(" WHERE {0}", WhereCondition); - if (_groupby != null) sb.AppendFormat(" GROUP BY {0}", _groupby); - if (HavingCondition != null) sb.AppendFormat(" HAVING {0}", HavingCondition); - if (_orderby != null) sb.AppendFormat(" ORDER BY {0}", _orderby); - if (_limit.HasValue && !lused && (Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == - DynamicDatabaseOptions.SupportLimitOffset) - sb.AppendFormat(" LIMIT {0}", _limit); - if (_offset.HasValue && !oused && (Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == - DynamicDatabaseOptions.SupportLimitOffset) - sb.AppendFormat(" OFFSET {0}", _offset); - - return sb.ToString(); - } - - #region Execution - - /// Execute this builder. - /// Enumerator of objects expanded from query. - public virtual IEnumerable Execute() - { - using (IDbConnection con = Database.Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(this) - .ExecuteReader()) - using (IDataReader cache = new DynamicCachedReader(rdr)) - while (cache.Read()) - { - dynamic val = null; - - // Work around to avoid yield being in try...catchblock: - // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch - try - { - val = cache.RowToDynamic(); - } - catch (ArgumentException argex) - { - StringBuilder 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; - } - } - } - - /// Execute this builder and map to given type. - /// Type of object to map on. - /// Enumerator of objects expanded from query. - public virtual IEnumerable Execute() where T : class - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - using (IDbConnection con = Database.Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(this) - .ExecuteReader()) - using (IDataReader cache = new DynamicCachedReader(rdr)) - while (cache.Read()) - { - dynamic val = null; - - // Work around to avoid yield being in try...catchblock: - // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch - try - { - val = cache.RowToDynamic(); - } - catch (ArgumentException argex) - { - StringBuilder 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 mapper.Create(val) as T; - } - } - } - - /// Execute this builder as a data reader. - /// Action containing reader. - public virtual void ExecuteDataReader(Action reader) - { - using (IDbConnection con = Database.Open()) - using (IDbCommand cmd = con.CreateCommand()) - using (IDataReader rdr = cmd - .SetCommand(this) - .ExecuteReader()) - reader(rdr); - } - - /// Execute this builder as a data reader, but - /// first makes a full reader copy in memory. - /// Action containing reader. - public virtual void ExecuteCachedDataReader(Action reader) - { - using (IDbConnection con = Database.Open()) - using (IDbCommand cmd = con.CreateCommand()) - using (IDataReader rdr = cmd - .SetCommand(this) - .ExecuteReader()) - using (IDataReader cache = new DynamicCachedReader(rdr)) - reader(cache); - } - - /// Returns a single result. - /// Result of a query. - public virtual object Scalar() - { - using (IDbConnection con = Database.Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(this) - .ExecuteScalar(); - } - } - -#if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE - - /// Returns a single result. - /// Type to parse to. - /// Default value. - /// Result of a query. - public virtual T ScalarAs(T defaultValue = default(T)) - { - using (IDbConnection con = Database.Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(this) - .ExecuteScalarAs(defaultValue); - } - } - -#endif - - #endregion Execution - - #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. - public virtual IDynamicSelectQueryBuilder From(Func fn, - params Func[] func) - { - if (fn == null) - throw new ArgumentNullException("Array of functions cannot be or contain null."); - - int index = FromFunc(-1, fn); - foreach (Func f in func) - index = FromFunc(index, f); - - return this; - } - - private int FromFunc(int index, Func f) - { - if (f == null) - throw new ArgumentNullException("Array of functions cannot be or contain null."); - - index++; - ITableInfo tableInfo = null; - using (DynamicParser parser = DynamicParser.Parse(f)) - { - object result = parser.Result; - - // If the expression result is string. - if (result is string) - { - string node = (string)result; - Tuple tuple = node.SplitSomethingAndAlias(); - string[] parts = tuple.Item1.Split('.'); - tableInfo = new TableInfo(Database, - Database.StripName(parts.Last()).Validated("Table"), - tuple.Item2.Validated("Alias", canbeNull: true), - parts.Length == 2 - ? Database.StripName(parts.First()).Validated("Owner", canbeNull: true) - : null); - } - else if (result is Type) - { - Type type = (Type)result; - if (type.IsAnonymous()) - throw new InvalidOperationException(string.Format( - "Cant assign anonymous type as a table ({0}). Parsing {1}", type.FullName, result)); - - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException(string.Format( - "Cant assign unmapable type as a table ({0}). Parsing {1}", type.FullName, result)); - - tableInfo = new TableInfo(Database, type); - } - else if (result is DynamicParser.Node) - { - // Or if it resolves to a dynamic node - DynamicParser.Node node = (DynamicParser.Node)result; - - string owner = null; - string main = null; - string alias = null; - bool nolock = false; - Type type = null; - - while (true) - { - // Support for the AS() virtual method... - if (node is DynamicParser.Node.Method && - ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") - { - if (alias != null) - throw new ArgumentException(string.Format( - "Alias '{0}' is already set when parsing '{1}'.", alias, result)); - - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args == null) - throw new ArgumentNullException("arg", "AS() is not a parameterless method."); - - if (args.Length != 1) - throw new ArgumentException("AS() requires one and only one parameter: " + - args.Sketch()); - - alias = Parse(args[0], rawstr: true, decorate: false).Validated("Alias"); - - node = node.Host; - continue; - } - - // Support for the NoLock() virtual method... - if (node is DynamicParser.Node.Method && - ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") - { - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args != null && args.Length > 0) - throw new ArgumentNullException("arg", "NoLock() doesn't support arguments."); - - nolock = true; - - node = node.Host; - continue; - } - - /*if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "subquery") - { - main = Parse(this.SubQuery(((DynamicParser.Node.Method)node).Arguments.Where(p => p is Func).Cast>().ToArray()), Parameters); - continue; - }*/ - - // 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) - throw new ArgumentException(string.Format( - "Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); - - if (main != null) - owner = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); - else - { - DynamicParser.Node.Invoke invoke = (DynamicParser.Node.Invoke)node; - if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type) - { - type = (Type)invoke.Arguments[0]; - if (type.IsAnonymous()) - throw new InvalidOperationException( - string.Format( - "Cant assign anonymous type as a table ({0}). Parsing {1}", - type.FullName, result)); - - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException( - string.Format( - "Cant assign unmapable type as a table ({0}). Parsing {1}", - type.FullName, result)); - - main = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) - ? mapper.Type.Name - : mapper.Table.Name; - - owner = (mapper.Table != null) ? mapper.Table.Owner : owner; - } - else - main = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); - } - - node = node.Host; - continue; - } - - // Just finished the parsing... - if (node is DynamicParser.Node.Argument) break; - - // All others are assumed to be part of the main element... - if (main != null) - main = Parse(node, pars: Parameters); - else - main = Parse(node, pars: Parameters); - - break; - } - - if (!string.IsNullOrEmpty(main)) - tableInfo = type == null - ? new TableInfo(Database, main, alias, owner, nolock) - : new TableInfo(Database, type, alias, owner, nolock); - else - throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, - result)); - } - - // Or it is a not supported expression... - if (tableInfo == null) - throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, - result)); - - Tables.Add(tableInfo); - - // We finally add the contents... - StringBuilder sb = new StringBuilder(); - - if (!string.IsNullOrEmpty(tableInfo.Owner)) - sb.AppendFormat("{0}.", Database.DecorateName(tableInfo.Owner)); - - sb.Append(tableInfo.Name.ContainsAny(StringExtensions.InvalidMemberChars) - ? tableInfo.Name - : Database.DecorateName(tableInfo.Name)); - - if (!string.IsNullOrEmpty(tableInfo.Alias)) - sb.AppendFormat(" AS {0}", tableInfo.Alias); - - if (SupportNoLock && tableInfo.NoLock) - sb.AppendFormat(" WITH(NOLOCK)"); - - _from = string.IsNullOrEmpty(_from) - ? sb.ToString() - : string.Format("{0}, {1}", _from, sb.ToString()); - } - - return index; - } - - /// - /// 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. - public virtual IDynamicSelectQueryBuilder Join(params Func[] func) - { - // We need to do two passes to add aliases first. - return JoinInternal(true, func).JoinInternal(false, 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. - /// - /// If true just pass by to locate tables and aliases, otherwise create rules. - /// The specification. - /// This instance to permit chaining. - protected virtual DynamicSelectQueryBuilder JoinInternal(bool justAddTables, - params Func[] func) - { - if (func == null) throw new ArgumentNullException("Array of functions cannot be null."); - - int index = -1; - - foreach (Func f in func) - { - index++; - ITableInfo tableInfo = null; - - if (f == null) - throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); - - using (DynamicParser parser = DynamicParser.Parse(f)) - { - object result = parser.Result; - if (result == null) - throw new ArgumentException( - string.Format("Specification #{0} resolves to null.", index)); - - string type = null; - string main = null; - string owner = null; - string alias = null; - string condition = null; - bool nolock = false; - Type tableType = null; - - // If the expression resolves to a string... - if (result is string) - { - string node = (string)result; - - int n = node.ToUpper().IndexOf("JOIN "); - - if (n < 0) - main = node; - else - { - // For strings we only accept 'JOIN' as a suffix - type = node.Substring(0, n + 4); - main = node.Substring(n + 4); - } - - n = main.ToUpper().IndexOf("ON"); - - if (n >= 0) - { - condition = main.Substring(n + 3); - main = main.Substring(0, n).Trim(); - } - - Tuple - tuple = main - .SplitSomethingAndAlias(); // In this case we split on the remaining 'main' - string[] parts = tuple.Item1.Split('.'); - main = Database.StripName(parts.Last()).Validated("Table"); - owner = parts.Length == 2 - ? Database.StripName(parts.First()).Validated("Owner", canbeNull: true) - : null; - alias = tuple.Item2.Validated("Alias", canbeNull: true); - } - else if (result is DynamicParser.Node) - { - // Or if it resolves to a dynamic node... - DynamicParser.Node node = (DynamicParser.Node)result; - while (true) - { - // Support for the ON() virtual method... - if (node is DynamicParser.Node.Method && - ((DynamicParser.Node.Method)node).Name.ToUpper() == "ON") - { - if (condition != null) - throw new ArgumentException(string.Format( - "Condition '{0}' is already set when parsing '{1}'.", alias, result)); - - object[] args = ((DynamicParser.Node.Method)node).Arguments; - if (args == null) - throw new ArgumentNullException("arg", - "ON() is not a parameterless method."); - - if (args.Length != 1) - throw new ArgumentException("ON() requires one and only one parameter: " + - args.Sketch()); - - condition = Parse(args[0], rawstr: true, - pars: justAddTables ? null : Parameters); - - node = node.Host; - continue; - } - - // Support for the AS() virtual method... - if (node is DynamicParser.Node.Method && - ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") - { - if (alias != null) - throw new ArgumentException(string.Format( - "Alias '{0}' is already set when parsing '{1}'.", alias, result)); - - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args == null) - throw new ArgumentNullException("arg", - "AS() is not a parameterless method."); - - if (args.Length != 1) - throw new ArgumentException("AS() requires one and only one parameter: " + - args.Sketch()); - - alias = Parse(args[0], rawstr: true, decorate: false, isMultiPart: false) - .Validated("Alias"); - - node = node.Host; - continue; - } - - // Support for the NoLock() virtual method... - if (node is DynamicParser.Node.Method && - ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") - { - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args != null && args.Length > 0) - throw new ArgumentNullException("arg", - "NoLock() doesn't support arguments."); - - nolock = true; - - node = node.Host; - continue; - } - - // 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 Join Type specifications... - if (node is DynamicParser.Node.Method && - (node.Host is DynamicParser.Node.Argument || - node.Host is DynamicParser.Node.Invoke)) - { - if (type != null) - throw new ArgumentException(string.Format( - "Join type '{0}' is already set when parsing '{1}'.", main, result)); - type = ((DynamicParser.Node.Method)node).Name; - - bool avoid = false; - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args != null && args.Length > 0) - { - avoid = args[0] is bool && !((bool)args[0]); - string proposedType = args.FirstOrDefault(a => a is string) as string; - if (!string.IsNullOrEmpty(proposedType)) - type = proposedType; - } - - type = type.ToUpper(); // Normalizing, and stepping out the trivial case... - if (type != "JOIN") - { - // Special cases - // x => x.LeftOuter() / x => x.RightOuter()... - type = type.Replace("OUTER", " OUTER ") - .Replace(" ", " ") - .Trim(' '); - - // x => x.Left()... - int n = type.IndexOf("JOIN"); - - if (n < 0 && !avoid) - type += " JOIN"; - - // x => x.InnerJoin() / x => x.JoinLeft() ... - else - { - if (!avoid) - { - if (n == 0) type = type.Replace("JOIN", "JOIN "); - else type = type.Replace("JOIN", " JOIN"); - } - } - } - - node = node.Host; - continue; - } - - // Support for generic sources... - if (node is DynamicParser.Node.Invoke) - { - if (owner != null) - throw new ArgumentException(string.Format( - "Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, - result)); - - if (main != null) - owner = string.Format("{0}", - Parse(node, rawstr: true, pars: justAddTables ? null : Parameters)); - else - { - DynamicParser.Node.Invoke invoke = (DynamicParser.Node.Invoke)node; - if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type) - { - tableType = (Type)invoke.Arguments[0]; - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(tableType); - - if (mapper == null) - throw new InvalidOperationException( - string.Format("Cant assign unmapable type as a table ({0}).", - tableType.FullName)); - - main = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) - ? mapper.Type.Name - : mapper.Table.Name; - - owner = (mapper.Table != null) ? mapper.Table.Owner : owner; - } - else - main = string.Format("{0}", - Parse(node, rawstr: true, pars: justAddTables ? null : Parameters)); - } - - node = node.Host; - continue; - } - - // Just finished the parsing... - if (node is DynamicParser.Node.Argument) break; - throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", - index, result)); - } - } - else - { - // Or it is a not supported expression... - throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, - result)); - } - - // We annotate the aliases being conservative... - main = main.Validated("Main"); - - if (justAddTables) - { - if (!string.IsNullOrEmpty(main)) - tableInfo = tableType == null - ? new TableInfo(Database, main, alias, owner, nolock) - : new TableInfo(Database, tableType, alias, owner, nolock); - else - throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", - index, result)); - - Tables.Add(tableInfo); - } - else - { - // Get cached table info - tableInfo = string.IsNullOrEmpty(alias) - ? Tables.SingleOrDefault(t => t.Name == main && string.IsNullOrEmpty(t.Alias)) - : Tables.SingleOrDefault(t => t.Alias == alias); - - // We finally add the contents if we can... - StringBuilder sb = new StringBuilder(); - if (string.IsNullOrEmpty(type)) - type = "JOIN"; - - sb.AppendFormat("{0} ", type); - - if (!string.IsNullOrEmpty(tableInfo.Owner)) - sb.AppendFormat("{0}.", Database.DecorateName(tableInfo.Owner)); - - sb.Append(tableInfo.Name.ContainsAny(StringExtensions.InvalidMemberChars) - ? tableInfo.Name - : Database.DecorateName(tableInfo.Name)); - - if (!string.IsNullOrEmpty(tableInfo.Alias)) - sb.AppendFormat(" AS {0}", tableInfo.Alias); - - if (SupportNoLock && tableInfo.NoLock) - sb.AppendFormat(" WITH(NOLOCK)"); - - if (!string.IsNullOrEmpty(condition)) - sb.AppendFormat(" ON {0}", condition); - - _join = string.IsNullOrEmpty(_join) - ? sb.ToString() - : string.Format("{0} {1}", _join, sb.ToString()); // No comma in this case - } - } - } - - return this; - } - - #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. - public virtual IDynamicSelectQueryBuilder Where(Func func) - { - return this.InternalWhere(func); - } - - /// Add where condition. - /// Condition column with operator and value. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder Where(DynamicColumn column) - { - return this.InternalWhere(column); - } - - /// Add where condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder 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 IDynamicSelectQueryBuilder 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 IDynamicSelectQueryBuilder Where(object conditions, bool schema = false) - { - return this.InternalWhere(conditions, schema); - } - - #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. - public virtual IDynamicSelectQueryBuilder Select(Func fn, - params Func[] func) - { - if (fn == null) - throw new ArgumentNullException("Array of specifications cannot be null."); - - int index = SelectFunc(-1, fn); - if (func != null) - foreach (Func f in func) - index = SelectFunc(index, f); - - return this; - } - - private int SelectFunc(int index, Func f) - { - index++; - if (f == null) - throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); - - using (DynamicParser parser = DynamicParser.Parse(f)) - { - object result = parser.Result; - if (result == null) - throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); - - string main = null; - string alias = null; - bool all = false; - bool anon = false; - - // If the expression resolves to a string... - if (result is string) - { - string node = (string)result; - Tuple tuple = node.SplitSomethingAndAlias(); - main = tuple.Item1.Validated("Table and/or Column"); - - main = FixObjectName(main); - - alias = tuple.Item2.Validated("Alias", canbeNull: true); - } - else if (result is DynamicParser.Node) - { - // Or if it resolves to a dynamic node... - ParseSelectNode(result, ref main, ref alias, ref all); - } - else if (result.GetType().IsAnonymous()) - { - anon = true; - - foreach (KeyValuePair prop in result.ToDictionary()) - { - if (prop.Value is string) - { - string node = (string)prop.Value; - Tuple tuple = node.SplitSomethingAndAlias(); - main = FixObjectName(tuple.Item1.Validated("Table and/or Column")); - - ////alias = tuple.Item2.Validated("Alias", canbeNull: true); - } - else if (prop.Value is DynamicParser.Node) - { - // Or if it resolves to a dynamic node... - ParseSelectNode(prop.Value, ref main, ref alias, ref all); - } - else - { - // Or it is a not supported expression... - throw new ArgumentException(string.Format( - "Specification #{0} in anonymous type is invalid: {1}", index, prop.Value)); - } - - alias = Database.DecorateName(prop.Key); - ParseSelectAddColumn(main, alias, all); - } - } - else - { - // Or it is a not supported expression... - throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, - result)); - } - - if (!anon) - ParseSelectAddColumn(main, alias, all); - } - - return index; - } - - /// Add select columns. - /// Columns to add to object. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder SelectColumn(params DynamicColumn[] columns) - { - foreach (DynamicColumn col in columns) - Select(x => col.ToSQLSelectColumn(Database)); - - return this; - } - - /// 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. - public virtual IDynamicSelectQueryBuilder SelectColumn(params string[] columns) - { - DynamicColumn[] cols = new DynamicColumn[columns.Length]; - for (int i = 0; i < columns.Length; i++) - cols[i] = DynamicColumn.ParseSelectColumn(columns[i]); - - return SelectColumn(cols); - } - - #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. - public virtual IDynamicSelectQueryBuilder GroupBy(Func fn, - params Func[] func) - { - if (fn == null) - throw new ArgumentNullException("Array of specifications cannot be null."); - - int index = GroupByFunc(-1, fn); - - if (func != null) - for (int i = 0; i < func.Length; i++) - { - Func f = func[i]; - index = GroupByFunc(index, f); - } - - return this; - } - - private int GroupByFunc(int index, Func f) - { - index++; - if (f == null) - throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); - - using (DynamicParser parser = DynamicParser.Parse(f)) - { - object result = parser.Result; - if (result == null) - throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); - - string main = null; - - if (result is string) - main = FixObjectName(result as string); - else - main = Parse(result, pars: Parameters); - - main = main.Validated("Group By"); - if (_groupby == null) - _groupby = main; - else - _groupby = string.Format("{0}, {1}", _groupby, main); - } - - return index; - } - - /// Add select columns. - /// Columns to group by. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder GroupByColumn(params DynamicColumn[] columns) - { - for (int i = 0; i < columns.Length; i++) - { - DynamicColumn col = columns[i]; - GroupBy(x => col.ToSQLGroupByColumn(Database)); - } - - return this; - } - - /// Add select columns. - /// Columns to group by. - /// Column format consist of Column Name and - /// Alias in this order separated by ':'. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder GroupByColumn(params string[] columns) - { - return GroupByColumn(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray()); - } - - #endregion GroupBy - - #region Having - - /// - /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition - /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used - /// as needed. - /// - If several Having() 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: - /// 'Having( x => x.Or( condition ) )'. - /// - /// The specification. - /// This instance to permit chaining. - public virtual IDynamicSelectQueryBuilder Having(Func func) - { - return this.InternalHaving(func); - } - - /// Add Having condition. - /// Condition column with operator and value. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder Having(DynamicColumn column) - { - return this.InternalHaving(column); - } - - /// Add Having condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, - object value) - { - return this.InternalHaving(column, op, value); - } - - /// Add Having condition. - /// Condition column. - /// Condition value. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder Having(string column, object value) - { - return this.InternalHaving(column, value); - } - - /// Add Having 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 IDynamicSelectQueryBuilder Having(object conditions, bool schema = false) - { - return this.InternalHaving(conditions, schema); - } - - #endregion Having - - #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. - public virtual IDynamicSelectQueryBuilder OrderBy(Func fn, - params Func[] func) - { - if (fn == null) - throw new ArgumentNullException("Array of specifications cannot be null."); - - int index = OrderByFunc(-1, fn); - - if (func != null) - for (int i = 0; i < func.Length; i++) - { - Func f = func[i]; - index = OrderByFunc(index, f); - } - - return this; - } - - private int OrderByFunc(int index, Func f) - { - index++; - if (f == null) - throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); - - using (DynamicParser parser = DynamicParser.Parse(f)) - { - object result = parser.Result; - if (result == null) - throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); - - string main = null; - bool ascending = true; - - if (result is int) - main = result.ToString(); - else if (result is string) - { - string[] parts = ((string)result).Split(' '); - main = Database.StripName(parts.First()); - - int colNo; - if (!Int32.TryParse(main, out colNo)) - main = FixObjectName(main); - - ascending = parts.Length != 2 || parts.Last().ToUpper() == "ASCENDING" || - parts.Last().ToUpper() == "ASC"; - } - else - { - // Intercepting trailing 'Ascending' or 'Descending' virtual methods... - if (result is DynamicParser.Node.Method) - { - DynamicParser.Node.Method node = (DynamicParser.Node.Method)result; - string name = node.Name.ToUpper(); - if (name == "ASCENDING" || name == "ASC" || name == "DESCENDING" || name == "DESC") - { - object[] args = node.Arguments; - if (args != null && !(node.Host is DynamicParser.Node.Argument)) - throw new ArgumentException(string.Format( - "{0} must be a parameterless method, but found: {1}.", name, - args.Sketch())); - else if ((args == null || args.Length != 1) && - node.Host is DynamicParser.Node.Argument) - throw new ArgumentException(string.Format( - "{0} requires one numeric parameter, but found: {1}.", name, - args.Sketch())); - - ascending = (name == "ASCENDING" || name == "ASC") ? true : false; - - if (args != null && args.Length == 1) - { - int col = -1; - if (args[0] is int) - main = args[0].ToString(); - else if (args[0] is string) - { - if (Int32.TryParse(args[0].ToString(), out col)) - main = col.ToString(); - else - main = FixObjectName(args[0].ToString()); - } - else - main = Parse(args[0], pars: Parameters); - } - - result = node.Host; - } - } - - // Just parsing the contents... - if (!(result is DynamicParser.Node.Argument)) - main = Parse(result, pars: Parameters); - } - - main = main.Validated("Order By"); - main = string.Format("{0} {1}", main, ascending ? "ASC" : "DESC"); - - if (_orderby == null) - _orderby = main; - else - _orderby = string.Format("{0}, {1}", _orderby, main); - } - - return index; - } - - /// Add select columns. - /// Columns to order by. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder OrderByColumn(params DynamicColumn[] columns) - { - for (int i = 0; i < columns.Length; i++) - { - DynamicColumn col = columns[i]; - OrderBy(x => col.ToSQLOrderByColumn(Database)); - } - - return this; - } - - /// Add select columns. - /// Columns to order by. - /// Column format consist of Column Name and - /// Alias in this order separated by ':'. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder OrderByColumn(params string[] columns) - { - return OrderByColumn(columns.Select(c => DynamicColumn.ParseOrderByColumn(c)).ToArray()); - } - - #endregion OrderBy - - #region Top/Limit/Offset/Distinct - - /// Set top if database support it. - /// How many objects select. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder Top(int? top) - { - return Limit(top); - } - - /// Set top if database support it. - /// How many objects select. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder Limit(int? limit) - { - if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != - DynamicDatabaseOptions.SupportLimitOffset && - (Database.Options & DynamicDatabaseOptions.SupportFirstSkip) != - DynamicDatabaseOptions.SupportFirstSkip && - (Database.Options & DynamicDatabaseOptions.SupportTop) != DynamicDatabaseOptions.SupportTop) - throw new NotSupportedException("Database doesn't support LIMIT clause."); - - _limit = limit; - return this; - } - - /// Set top if database support it. - /// How many objects skip selecting. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder Offset(int? offset) - { - if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != - DynamicDatabaseOptions.SupportLimitOffset && - (Database.Options & DynamicDatabaseOptions.SupportFirstSkip) != - DynamicDatabaseOptions.SupportFirstSkip) - throw new NotSupportedException("Database doesn't support OFFSET clause."); - - _offset = offset; - return this; - } - - /// Set distinct mode. - /// Distinct mode. - /// Builder instance. - public virtual IDynamicSelectQueryBuilder Distinct(bool distinct = true) - { - _distinct = distinct; - return this; - } - - #endregion Top/Limit/Offset/Distinct - - #region Helpers - - private void ParseSelectAddColumn(string main, string alias, bool all) - { - // We annotate the aliases being conservative... - main = main.Validated("Main"); - - ////if (alias != null && !main.ContainsAny(StringExtensions.InvalidMemberChars)) TableAliasList.Add(new KTableAlias(main, alias)); - - // If all columns are requested... - if (all) - main += ".*"; - - // We finally add the contents... - string str = (alias == null || all) ? main : string.Format("{0} AS {1}", main, alias); - _select = _select == null ? str : string.Format("{0}, {1}", _select, str); - } - - private void ParseSelectNode(object result, ref string column, ref string alias, ref bool all) - { - string main = null; - - DynamicParser.Node node = (DynamicParser.Node)result; - while (true) - { - // Support for the AS() virtual method... - if (node is DynamicParser.Node.Method && - ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") - { - if (alias != null) - throw new ArgumentException( - string.Format("Alias '{0}' is already set when parsing '{1}'.", alias, result)); - - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args == null) - throw new ArgumentNullException("arg", "AS() is not a parameterless method."); - - if (args.Length != 1) - throw new ArgumentException( - "AS() requires one and only one parameter: " + args.Sketch()); - - // Yes, we decorate columns - alias = Parse(args[0], rawstr: true, decorate: true, isMultiPart: false).Validated("Alias"); - - node = node.Host; - continue; - } - - // Support for the ALL() virtual method... - if (node is DynamicParser.Node.Method && - ((DynamicParser.Node.Method)node).Name.ToUpper() == "ALL") - { - if (all) - throw new ArgumentException(string.Format( - "Flag to select all columns is already set when parsing '{0}'.", result)); - - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args != null) - throw new ArgumentException( - "ALL() must be a parameterless virtual method, but found: " + args.Sketch()); - - all = true; - - node = node.Host; - continue; - } - - // Support for table and/or column specifications... - if (node is DynamicParser.Node.GetMember) - { - if (main != null) - throw new ArgumentException( - string.Format("Main '{0}' is already set when parsing '{1}'.", main, result)); - - main = ((DynamicParser.Node.GetMember)node).Name; - - if (node.Host is DynamicParser.Node.GetMember) - { - // If leaf then decorate - main = Database.DecorateName(main); - - // Supporting multipart specifications... - node = node.Host; - - // Get table/alias name - string table = ((DynamicParser.Node.GetMember)node).Name; - bool isAlias = node.Host is DynamicParser.Node.Argument && IsTableAlias(table); - - if (isAlias) - main = string.Format("{0}.{1}", table, main); - else if (node.Host is DynamicParser.Node.GetMember) - { - node = node.Host; - main = string.Format("{0}.{1}.{2}", - Database.DecorateName(((DynamicParser.Node.GetMember)node).Name), - Database.DecorateName(table), main); - } - else - main = string.Format("{0}.{1}", Database.DecorateName(table), main); - } - else if (node.Host is DynamicParser.Node.Argument) - { - string table = ((DynamicParser.Node.Argument)node.Host).Name; - - if (IsTableAlias(table)) - main = string.Format("{0}.{1}", table, Database.DecorateName(main)); - else if (!IsTableAlias(main)) - main = Database.DecorateName(main); - } - else if (!(node.Host is DynamicParser.Node.Argument && IsTableAlias(main))) - main = Database.DecorateName(main); - - node = node.Host; - - continue; - } - - // Support for generic sources... - if (node is DynamicParser.Node.Invoke) - { - if (main != null) - throw new ArgumentException( - string.Format("Main '{0}' is already set when parsing '{1}'.", main, result)); - - main = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); - - node = node.Host; - continue; - } - - // Just finished the parsing... - if (node is DynamicParser.Node.Argument) - { - if (string.IsNullOrEmpty(main) && IsTableAlias(node.Name)) - main = node.Name; - - break; - } - - // All others are assumed to be part of the main element... - if (main != null) - throw new ArgumentException(string.Format("Main '{0}' is already set when parsing '{1}'.", - main, result)); - main = Parse(node, pars: Parameters); - - break; - } - - column = main; - } - - #endregion Helpers - - #region IExtendedDisposable - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public override void Dispose() - { - base.Dispose(); - - _select = _from = _join = _groupby = _orderby = null; - } - - #endregion IExtendedDisposable - } - - /// Update query builder. - internal class DynamicUpdateQueryBuilder : DynamicModifyBuilder, IDynamicUpdateQueryBuilder, - DynamicQueryBuilder.IQueryWithWhere - { - private string _columns; - - internal DynamicUpdateQueryBuilder(DynamicDatabase db) - : base(db) - { - } - - public DynamicUpdateQueryBuilder(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() - { - ITableInfo info = Tables.Single(); - return string.Format("UPDATE {0}{1} SET {2}{3}{4}", - string.IsNullOrEmpty(info.Owner) - ? string.Empty - : string.Format("{0}.", Database.DecorateName(info.Owner)), - Database.DecorateName(info.Name), _columns, - string.IsNullOrEmpty(WhereCondition) ? string.Empty : " WHERE ", - WhereCondition); - } - - #region Update - - /// Add update value or where condition using schema. - /// Update or where column name. - /// Column value. - /// Builder instance. - public virtual IDynamicUpdateQueryBuilder Update(string column, object value) - { - DynamicSchemaColumn? col = GetColumnFromSchema(column); - - if (!col.HasValue && SupportSchema) - throw new InvalidOperationException( - string.Format("Column '{0}' not found in schema, can't use universal approach.", column)); - - if (col.HasValue && col.Value.IsKey) - Where(column, value); - else - Values(column, value); - - return this; - } - - /// Add update values and where condition columns using schema. - /// Set values or conditions as properties and values of an object. - /// Builder instance. - public virtual IDynamicUpdateQueryBuilder Update(object conditions) - { - if (conditions is DynamicColumn) - { - DynamicColumn column = (DynamicColumn)conditions; - - DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName); - - if (!col.HasValue && SupportSchema) - throw new InvalidOperationException( - string.Format("Column '{0}' not found in schema, can't use universal approach.", - column)); - - if (col.HasValue && col.Value.IsKey) - Where(column); - else - Values(column.ColumnName, column.Value); - - return this; - } - - IDictionary dict = conditions.ToDictionary(); - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType()); - - foreach (KeyValuePair con in dict) - { - if (mapper.Ignored.Contains(con.Key)) - continue; - - string colName = mapper != null ? mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key : con.Key; - DynamicSchemaColumn? col = GetColumnFromSchema(colName); - - if (!col.HasValue && SupportSchema) - throw new InvalidOperationException(string.Format( - "Column '{0}' not found in schema, can't use universal approach.", colName)); - - if (col.HasValue) - { - colName = col.Value.Name; - - if (col.Value.IsKey) - { - Where(colName, con.Value); - - continue; - } - } - - DynamicPropertyInvoker propMap = mapper.ColumnsMap.TryGetValue(colName.ToLower()); - if (propMap == null || propMap.Column == null || !propMap.Column.IsNoUpdate) - Values(colName, con.Value); - } - - return this; - } - - #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. - public virtual IDynamicUpdateQueryBuilder Set(params Func[] func) - { - if (func == null) - throw new ArgumentNullException("Array of specifications cannot be null."); - - int index = -1; - foreach (Func f in func) - { - index++; - if (f == null) - throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); - - object result = null; - - using (DynamicParser p = DynamicParser.Parse(f)) - { - result = p.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) - { - DynamicParser.Node.SetMember node = (DynamicParser.Node.SetMember)result; - - DynamicSchemaColumn? col = GetColumnFromSchema(node.Name); - main = Database.DecorateName(node.Name); - value = Parse(node.Value, ref col, pars: Parameters, nulls: true); - - str = string.Format("{0} = {1}", main, value); - _columns = _columns == null ? str : string.Format("{0}, {1}", _columns, str); - continue; - } - else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType) - { - Values(result); - continue; - } - - // Other specifications are considered invalid... - string 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); - } - } - - return this; - } - - /// Add insert fields. - /// Insert column. - /// Insert value. - /// Builder instance. - public virtual IDynamicUpdateQueryBuilder Values(string column, object value) - { - if (value is DynamicColumn) - { - DynamicColumn v = (DynamicColumn)value; - - if (string.IsNullOrEmpty(v.ColumnName)) - v.ColumnName = column; - - return Values(v); - } - - return Values(new DynamicColumn - { - ColumnName = column, - Value = value, - }); - } - - /// Add insert fields. - /// Set insert value as properties and values of an object. - /// Builder instance. - public virtual IDynamicUpdateQueryBuilder Values(object o) - { - if (o is DynamicColumn) - { - DynamicColumn column = (DynamicColumn)o; - DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName); - - string main = FixObjectName(column.ColumnName, onlyColumn: true); - string value = Parse(column.Value, ref col, pars: Parameters, nulls: true); - - string str = string.Format("{0} = {1}", main, value); - _columns = _columns == null ? str : string.Format("{0}, {1}", _columns, str); - - return this; - } - - IDictionary dict = o.ToDictionary(); - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(o.GetType()); - - if (mapper != null) - { - foreach (KeyValuePair con in dict) - if (!mapper.Ignored.Contains(con.Key)) - Values(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value); - } - else - foreach (KeyValuePair con in dict) - Values(con.Key, con.Value); - - return this; - } - - #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. - public virtual IDynamicUpdateQueryBuilder Where(Func func) - { - return this.InternalWhere(func); - } - - /// Add where condition. - /// Condition column with operator and value. - /// Builder instance. - public virtual IDynamicUpdateQueryBuilder Where(DynamicColumn column) - { - return this.InternalWhere(column); - } - - /// Add where condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - public virtual IDynamicUpdateQueryBuilder 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 IDynamicUpdateQueryBuilder 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 IDynamicUpdateQueryBuilder Where(object conditions, bool schema = false) - { - return this.InternalWhere(conditions, schema); - } - - #endregion Where - - #region IExtendedDisposable - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public override void Dispose() - { - base.Dispose(); - - _columns = null; - } - - #endregion IExtendedDisposable - } - } - } - - namespace Helpers - { - /// Defines methods to support the comparison of collections for equality. - /// The type of collection to compare. - public class CollectionComparer : IEqualityComparer> - { - /// Determines whether the specified objects are equal. - /// The first object of type T to compare. - /// The second object of type T to compare. - /// Returns true if the specified objects are equal; otherwise, false. - bool IEqualityComparer>.Equals(IEnumerable first, IEnumerable second) - { - return Equals(first, second); - } - - /// Returns a hash code for the specified object. - /// The enumerable for which a hash code is to be returned. - /// A hash code for the specified object. - int IEqualityComparer>.GetHashCode(IEnumerable enumerable) - { - return GetHashCode(enumerable); - } - - /// Returns a hash code for the specified object. - /// The enumerable for which a hash code is to be returned. - /// A hash code for the specified object. - public static int GetHashCode(IEnumerable enumerable) - { - int hash = 17; - - foreach (T val in enumerable.OrderBy(x => x)) - hash = (hash * 23) + val.GetHashCode(); - - return hash; - } - - /// Determines whether the specified objects are equal. - /// The first object of type T to compare. - /// The second object of type T to compare. - /// Returns true if the specified objects are equal; otherwise, false. - public static bool Equals(IEnumerable first, IEnumerable second) - { - if ((first == null) != (second == null)) - return false; - - if (!object.ReferenceEquals(first, second) && (first != null)) - { - if (first.Count() != second.Count()) - return false; - - if ((first.Count() != 0) && HaveMismatchedElement(first, second)) - return false; - } - - return true; - } - - private static bool HaveMismatchedElement(IEnumerable first, IEnumerable second) - { - int firstCount; - int secondCount; - - Dictionary firstElementCounts = GetElementCounts(first, out firstCount); - Dictionary secondElementCounts = GetElementCounts(second, out secondCount); - - if (firstCount != secondCount) - return true; - - foreach (KeyValuePair kvp in firstElementCounts) - if (kvp.Value != (secondElementCounts.TryGetNullable(kvp.Key) ?? 0)) - return true; - - return false; - } - - private static Dictionary GetElementCounts(IEnumerable enumerable, out int nullCount) - { - Dictionary dictionary = new Dictionary(); - nullCount = 0; - - foreach (T element in enumerable) - { - if (element == null) - nullCount++; - else - { - int count = dictionary.TryGetNullable(element) ?? 0; - dictionary[element] = ++count; - } - } - - return dictionary; - } - } - - /// Extensions for data reader handling. - public static class DataReaderExtensions - { - /// Gets the data table from data reader. - /// The data reader. - /// The name to give the table. If tableName is null or an empty string, a default name is given when added to the System.Data.DataTableCollection. - /// The namespace for the XML representation of the data stored in the DataTable. - /// - public static DataTable ToDataTable(this IDataReader r, string name = null, string nameSpace = null) - { - DataTable schemaTable = r.GetSchemaTable(); - DataTable resultTable = new DataTable(name, nameSpace); - - foreach (DataRow col in schemaTable.Rows) - { - dynamic c = col.RowToDynamicUpper(); - - DataColumn dataColumn = new DataColumn(); - dataColumn.ColumnName = c.COLUMNNAME; - dataColumn.DataType = (Type)c.DATATYPE; - dataColumn.ReadOnly = true; - dataColumn.Unique = c.ISUNIQUE; - - resultTable.Columns.Add(dataColumn); - } - - while (r.Read()) - { - DataRow row = resultTable.NewRow(); - for (int i = 0; i < resultTable.Columns.Count; i++) - row[i] = r[i]; - - resultTable.Rows.Add(row); - } - - return resultTable; - } - } - - /// Framework detection and specific implementations. - public static class FrameworkTools - { - #region Mono or .NET Framework detection - - /// This is pretty simple trick. - private static bool _isMono = Type.GetType("Mono.Runtime") != null; - - /// Gets a value indicating whether application is running under mono runtime. - public static bool IsMono - { - get { return _isMono; } - } - - #endregion Mono or .NET Framework detection - - static FrameworkTools() - { - _frameworkTypeArgumentsGetter = CreateTypeArgumentsGetter(); - } - - #region GetGenericTypeArguments - - private static Func> _frameworkTypeArgumentsGetter = null; - - private static Func> CreateTypeArgumentsGetter() - { - // HACK: Creating binders assuming types are correct... this may fail. - if (IsMono) - { - Type binderType = - typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType( - "Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder"); - - if (binderType != null) - { - ParameterExpression param = Expression.Parameter(typeof(InvokeMemberBinder), "o"); - - try - { - return Expression.Lambda>>( - Expression.TypeAs( - Expression.Field( - Expression.TypeAs(param, binderType), "typeArguments"), - typeof(IList)), param).Compile(); - } - catch - { - } - - PropertyInfo prop = binderType.GetProperty("TypeArguments"); - - if (!prop.CanRead) - return null; - - return Expression.Lambda>>( - Expression.TypeAs( - Expression.Property( - Expression.TypeAs(param, binderType), prop.Name), - typeof(IList)), param).Compile(); - } - } - else - { - Type inter = - typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType( - "Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); - - if (inter != null) - { - PropertyInfo prop = inter.GetProperty("TypeArguments"); - - if (!prop.CanRead) - return null; - - ParameterExpression objParm = Expression.Parameter(typeof(InvokeMemberBinder), "o"); - - return Expression.Lambda>>( - Expression.TypeAs( - Expression.Property( - Expression.TypeAs(objParm, inter), prop.Name), - typeof(IList)), objParm).Compile(); - } - } - - return null; - } - - /// Extension method allowing to easily extract generic type - /// arguments from assuming that it - /// inherits from - /// Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder - /// in .NET Framework or - /// Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder - /// under Mono. - /// Binder from which get type arguments. - /// This is generally a bad solution, but there is no other - /// currently so we have to go with it. - /// List of types passed as generic parameters. - public static IList GetGenericTypeArguments(this InvokeMemberBinder binder) - { - // First try to use delegate if exist - if (_frameworkTypeArgumentsGetter != null) - return _frameworkTypeArgumentsGetter(binder); - - if (_isMono) - { - // HACK: Using Reflection - // In mono this is trivial. - - // First we get field info. - FieldInfo field = binder.GetType().GetField("typeArguments", BindingFlags.Instance | - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); - - // If this was a success get and return it's value - if (field != null) - return field.GetValue(binder) as IList; - else - { - PropertyInfo prop = binder.GetType().GetProperty("TypeArguments"); - - // If we have a property, return it's value - if (prop != null) - return prop.GetValue(binder, null) as IList; - } - } - else - { - // HACK: Using Reflection - // In this case, we need more aerobic :D - - // First, get the interface - Type inter = binder.GetType() - .GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); - - if (inter != null) - { - // Now get property. - PropertyInfo prop = inter.GetProperty("TypeArguments"); - - // If we have a property, return it's value - if (prop != null) - return prop.GetValue(binder, null) as IList; - } - } - - // Sadly return null if failed. - return null; - } - - #endregion GetGenericTypeArguments - } - - /// Extends interface. - public interface IExtendedDisposable : IDisposable - { - /// - /// Gets a value indicating whether this instance is disposed. - /// - /// - /// true if this instance is disposed; otherwise, false. - /// - bool IsDisposed { get; } - } - - /// Extends interface. - public interface IFinalizerDisposable : IExtendedDisposable - { - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - void Dispose(bool disposing); - } - - public static class ReaderExtensions - { - public static bool? GetBooleanIfNotNull(this IDataReader r, string name, bool? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetBoolean(ord); - } - - public static byte? GetByteIfNotNull(this IDataReader r, string name, byte? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetByte(ord); - } - - public static char? GetCharIfNotNull(this IDataReader r, string name, char? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetChar(ord); - } - - public static DateTime? GetDateTimeIfNotNull(this IDataReader r, string name, DateTime? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetDateTime(ord); - } - - public static decimal? GetDecimalIfNotNull(this IDataReader r, string name, decimal? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetDecimal(ord); - } - - public static double? GetDoubleIfNotNull(this IDataReader r, string name, double? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetDouble(ord); - } - - public static float? GetFloatIfNotNull(this IDataReader r, string name, float? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetFloat(ord); - } - - public static Guid? GetGuidIfNotNull(this IDataReader r, string name, Guid? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetGuid(ord); - } - - public static short? GetInt16IfNotNull(this IDataReader r, string name, short? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetInt16(ord); - } - - public static int? GetInt32IfNotNull(this IDataReader r, string name, int? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetInt32(ord); - } - - public static long? GetInt64IfNotNull(this IDataReader r, string name, long? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetInt64(ord); - } - - public static string GetStringIfNotNull(this IDataReader r, string name, string def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetString(ord); - } - - public static object GetValueIfNotNull(this IDataReader r, string name, object def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetValue(ord); - } - } - - /// Class containing useful string extensions. - internal static class StringExtensions - { - static StringExtensions() - { - InvalidMultipartMemberChars = _InvalidMultipartMemberChars.ToCharArray(); - InvalidMemberChars = _InvalidMemberChars.ToCharArray(); - } - - private static readonly string _InvalidMultipartMemberChars = " +-*/^%[]{}()!\"\\&=?¿"; - private static readonly string _InvalidMemberChars = "." + _InvalidMultipartMemberChars; - - /// - /// Gets an array with some invalid characters that cannot be used with multipart names for class members. - /// - public static char[] InvalidMultipartMemberChars { get; private set; } - - /// - /// Gets an array with some invalid characters that cannot be used with names for class members. - /// - public static char[] InvalidMemberChars { get; private set; } - - /// - /// Provides with an alternate and generic way to obtain an alternate string representation for this instance, - /// applying the following rules: - /// - Null values are returned as with the value, or a null object. - /// - Enum values are translated into their string representation. - /// - If the type has override the 'ToString' method then it is used. - /// - If it is a dictionary, then a collection of key/value pairs where the value part is also translated. - /// - If it is a collection, then a collection of value items also translated. - /// - If it has public public properties (or if not, if it has public fields), the collection of name/value - /// pairs, with the values translated. - /// - Finally it falls back to the standard 'type.FullName' mechanism. - /// - /// The object to obtain its alternate string representation from. - /// The brackets to use if needed. If not null it must be at least a 2-chars' array containing - /// the opening and closing brackets. - /// Representation of null string.. - /// The alternate string representation of this object. - public static string Sketch(this object obj, char[] brackets = null, string nullString = "(null)") - { - if (obj == null) return nullString; - if (obj is string) return (string)obj; - - Type type = obj.GetType(); - if (type.IsEnum) return obj.ToString(); - - // If the ToString() method has been overriden (by the type itself, or by its parents), let's use it... - MethodInfo method = type.GetMethod("ToString", Type.EmptyTypes); - if (method.DeclaringType != typeof(object)) return obj.ToString(); - - // For alll other cases... - StringBuilder sb = new StringBuilder(); - bool first = true; - - // Dictionaries... - if (obj is IDictionary) - { - if (brackets == null || brackets.Length < 2) - brackets = "[]".ToCharArray(); - - sb.AppendFormat("{0}", brackets[0]); - first = true; - foreach (DictionaryEntry kvp in (IDictionary)obj) - { - if (!first) sb.Append(", "); - else first = false; - sb.AppendFormat("'{0}'='{1}'", kvp.Key.Sketch(), kvp.Value.Sketch()); - } - - sb.AppendFormat("{0}", brackets[1]); - return sb.ToString(); - } - - // IEnumerables... - IEnumerator ator = null; - if (obj is IEnumerable) - ator = ((IEnumerable)obj).GetEnumerator(); - else - { - method = type.GetMethod("GetEnumerator", Type.EmptyTypes); - if (method != null) - ator = (IEnumerator)method.Invoke(obj, null); - } - - if (ator != null) - { - if (brackets == null || brackets.Length < 2) brackets = "[]".ToCharArray(); - sb.AppendFormat("{0}", brackets[0]); - first = true; - while (ator.MoveNext()) - { - if (!first) sb.Append(", "); - else first = false; - sb.AppendFormat("{0}", ator.Current.Sketch()); - } - - sb.AppendFormat("{0}", brackets[1]); - - if (ator is IDisposable) - ((IDisposable)ator).Dispose(); - - return sb.ToString(); - } - - // As a last resort, using the public properties (or fields if needed, or type name)... - BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy; - PropertyInfo[] props = type.GetProperties(flags); - FieldInfo[] infos = type.GetFields(flags); - - if (props.Length == 0 && infos.Length == 0) sb.Append(type.FullName); // Fallback if needed - else - { - if (brackets == null || brackets.Length < 2) brackets = "{}".ToCharArray(); - sb.AppendFormat("{0}", brackets[0]); - first = true; - - if (props.Length != 0) - { - foreach (PropertyInfo prop in props) - { - if (!first) sb.Append(", "); - else first = false; - sb.AppendFormat("{0}='{1}'", prop.Name, prop.GetValue(obj, null).Sketch()); - } - } - else - { - if (infos.Length != 0) - { - foreach (FieldInfo info in infos) - { - if (!first) sb.Append(", "); - else first = false; - sb.AppendFormat("{0}='{1}'", info.Name, info.GetValue(obj).Sketch()); - } - } - } - - sb.AppendFormat("{0}", brackets[1]); - } - - // And returning... - return sb.ToString(); - } - - /// - /// Returns true if the target string contains any of the characters given. - /// - /// The target string. It cannot be null. - /// An array containing the characters to test. It cannot be null. If empty false is returned. - /// True if the target string contains any of the characters given, false otherwise. - public static bool ContainsAny(this string source, char[] items) - { - if (source == null) throw new ArgumentNullException("source", "Source string cannot be null."); - if (items == null) - throw new ArgumentNullException("items", "Array of characters to test cannot be null."); - - if (items.Length == 0) return false; // No characters to validate - int ix = source.IndexOfAny(items); - return ix >= 0 ? true : false; - } - - /// - /// Returns a new validated string using the rules given. - /// - /// The source string. - /// A description of the source string to build errors and exceptions if needed. - /// True if the returned string can be null. - /// True if the returned string can be empty. - /// True to trim the returned string. - /// True to left-trim the returned string. - /// True to right-trim the returned string. - /// If >= 0, the min valid length for the returned string. - /// If >= 0, the max valid length for the returned string. - /// If not '\0', the character to use to left-pad the returned string if needed. - /// If not '\0', the character to use to right-pad the returned string if needed. - /// If not null, an array containing invalid chars that must not appear in the returned - /// string. - /// If not null, an array containing the only characters that are considered valid for the - /// returned string. - /// A new validated string. - public static string Validated(this string source, string desc = null, - bool canbeNull = false, bool canbeEmpty = false, - bool trim = true, bool trimStart = false, bool trimEnd = false, - int minLen = -1, int maxLen = -1, char padLeft = '\0', char padRight = '\0', - char[] invalidChars = null, char[] validChars = null) - { - // Assuring a valid descriptor... - if (string.IsNullOrWhiteSpace(desc)) desc = "Source"; - - // Validating if null sources are accepted... - if (source == null) - { - if (!canbeNull) throw new ArgumentNullException(desc, string.Format("{0} cannot be null.", desc)); - return null; - } - - // Trimming if needed... - if (trim && !(trimStart || trimEnd)) source = source.Trim(); - else - { - if (trimStart) source = source.TrimStart(' '); - if (trimEnd) source = source.TrimEnd(' '); - } - - // Adjusting lenght... - if (minLen > 0) - { - if (padLeft != '\0') source = source.PadLeft(minLen, padLeft); - if (padRight != '\0') source = source.PadRight(minLen, padRight); - } - - if (maxLen > 0) - { - if (padLeft != '\0') source = source.PadLeft(maxLen, padLeft); - if (padRight != '\0') source = source.PadRight(maxLen, padRight); - } - - // Validating emptyness and lenghts... - if (source.Length == 0) - { - if (!canbeEmpty) throw new ArgumentException(string.Format("{0} cannot be empty.", desc)); - return string.Empty; - } - - if (minLen >= 0 && source.Length < minLen) - throw new ArgumentException(string.Format("Lenght of {0} '{1}' is lower than '{2}'.", desc, source, - minLen)); - if (maxLen >= 0 && source.Length > maxLen) - throw new ArgumentException(string.Format("Lenght of {0} '{1}' is bigger than '{2}'.", desc, source, - maxLen)); - - // Checking invalid chars... - if (invalidChars != null) - { - int n = source.IndexOfAny(invalidChars); - if (n >= 0) - throw new ArgumentException(string.Format("Invalid character '{0}' found in {1} '{2}'.", - source[n], desc, source)); - } - - // Checking valid chars... - if (validChars != null) - { - int n = validChars.ToString().IndexOfAny(source.ToCharArray()); - if (n >= 0) - throw new ArgumentException(string.Format("Invalid character '{0}' found in {1} '{2}'.", - validChars.ToString()[n], desc, source)); - } - - return source; - } - - /// - /// Splits the given string with the 'something AS alias' format, returning a tuple containing its 'something' and 'alias' parts. - /// If no alias is detected, then its component in the tuple returned is null and all the contents from the source - /// string are considered as the 'something' part. - /// - /// The source string. - /// A tuple containing the 'something' and 'alias' parts. - public static Tuple SplitSomethingAndAlias(this string source) - { - source = source.Validated("[Something AS Alias]"); - - string something = null; - string alias = null; - int n = source.LastIndexOf(" AS ", StringComparison.OrdinalIgnoreCase); - - if (n < 0) - something = source; - else - { - something = source.Substring(0, n); - alias = source.Substring(n + 4); - } - - return new Tuple(something, alias); - } - - /// Allows to replace parameters inside of string. - /// String containing parameters in format [$ParameterName]. - /// Function that should return value that will be placed in string in place of placed parameter. - /// Prefix of the parameter. This value can't be null or empty, default value [$. - /// Suffix of the parameter. This value can't be null or empty, default value ]. - /// Parsed string. - public static string FillStringWithVariables(this string stringToFill, Func getValue, - string prefix = "[$", string sufix = "]") - { - int startPos = 0, endPos = 0; - prefix.Validated(); - sufix.Validated(); - - startPos = stringToFill.IndexOf(prefix, startPos); - while (startPos >= 0) - { - endPos = stringToFill.IndexOf(sufix, startPos + prefix.Length); - int nextStartPos = stringToFill.IndexOf(prefix, startPos + prefix.Length); - - if (endPos > startPos + prefix.Length + 1 && (nextStartPos > endPos || nextStartPos == -1)) - { - string paramName = stringToFill.Substring(startPos + prefix.Length, - endPos - (startPos + prefix.Length)); - - stringToFill = stringToFill - .Remove(startPos, (endPos - startPos) + sufix.Length) - .Insert(startPos, getValue(paramName)); - } - - startPos = stringToFill.IndexOf(prefix, startPos + prefix.Length); - } - - return stringToFill; - } - } - - /// Class contains unclassified extensions. - internal static class UnclassifiedExtensions - { - /// Easy way to use conditional value. - /// Includes . - /// Input object type to check. - /// Result type. - /// The object to check. - /// The select function. - /// The else value. - /// Selected value or default value. - /// It lets you do this: - /// var lname = thingy.NullOr(t => t.Name).NullOr(n => n.ToLower()); - /// which is more fluent and (IMO) easier to read than this: - /// var lname = (thingy != null ? thingy.Name : null) != null ? thingy.Name.ToLower() : null; - /// - public static R NullOr(this T obj, Func func, R elseValue = default(R)) where T : class - { - return obj != null && obj != DBNull.Value ? func(obj) : elseValue; - } - - /// Easy way to use conditional value. - /// Includes . - /// Input object type to check. - /// Result type. - /// The object to check. - /// The select function. - /// The else value function. - /// Selected value or default value. - /// It lets you do this: - /// var lname = thingy.NullOr(t => t.Name).NullOr(n => n.ToLower()); - /// which is more fluent and (IMO) easier to read than this: - /// var lname = (thingy != null ? thingy.Name : null) != null ? thingy.Name.ToLower() : null; - /// - public static R NullOrFn(this T obj, Func func, Func elseFunc = null) where T : class - { - // Old if to avoid recurency. - return obj != null && obj != DBNull.Value ? func(obj) : elseFunc != null ? elseFunc() : default(R); - } - -#if !NET6_0_OR_GREATER - /// Simple distinct by selector extension. - /// The enumerator of elements distinct by specified selector. - /// Source collection. - /// Distinct key selector. - /// The enumerable element type parameter. - /// The selector type parameter. - public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) - { - HashSet seenKeys = new HashSet(); - foreach (R element in source) - if (seenKeys.Add(keySelector(element))) - yield return element; - } -#endif - } - - namespace Dynamics - { - /// - /// Class able to parse dynamic lambda expressions. Allows to create dynamic logic. - /// - public class DynamicParser : IExtendedDisposable - { - #region Node - - /// - /// Generic bindable operation where some of its operands is a dynamic argument, or a dynamic member or - /// a method of that argument. - /// - [Serializable] - public class Node : IDynamicMetaObjectProvider, IFinalizerDisposable, ISerializable - { - private DynamicParser _parser = null; - - #region MetaNode - - /// - /// Represents the dynamic binding and a binding logic of - /// an object participating in the dynamic binding. - /// - internal class MetaNode : DynamicMetaObject - { - /// - /// Initializes a new instance of the class. - /// - /// The parameter. - /// The restrictions. - /// The value. - public MetaNode(Expression parameter, BindingRestrictions rest, object value) - : base(parameter, rest, value) - { - } - - // Func was cool but caused memory leaks - private DynamicMetaObject GetBinder(Node node) - { - Node o = (Node)this.Value; - node.Parser = o.Parser; - o.Parser.Last = node; - - ParameterExpression p = Expression.Variable(typeof(Node), "ret"); - BlockExpression exp = Expression.Block(new ParameterExpression[] { p }, - Expression.Assign(p, Expression.Constant(node))); - - return new MetaNode(exp, this.Restrictions, node); - } - - /// - /// Performs the binding of the dynamic get member operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindGetMember(GetMemberBinder binder) - { - return GetBinder(new GetMember((Node)this.Value, binder.Name)); - } - - /// - /// Performs the binding of the dynamic set member operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// The representing the value for the set member operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) - { - return GetBinder(new SetMember((Node)this.Value, binder.Name, value.Value)); - } - - /// - /// Performs the binding of the dynamic get index operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// An array of instances - indexes for the get index operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, - DynamicMetaObject[] indexes) - { - return GetBinder(new GetIndex((Node)this.Value, MetaList2List(indexes))); - } - - /// - /// Performs the binding of the dynamic set index operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// An array of instances - indexes for the set index operation. - /// The representing the value for the set index operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, - DynamicMetaObject[] indexes, DynamicMetaObject value) - { - return GetBinder(new SetIndex((Node)this.Value, MetaList2List(indexes), value.Value)); - } - - /// - /// Performs the binding of the dynamic invoke operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// An array of instances - arguments to the invoke operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) - { - return GetBinder(new Invoke((Node)this.Value, MetaList2List(args))); - } - - /// - /// Performs the binding of the dynamic invoke member operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// An array of instances - arguments to the invoke member operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, - DynamicMetaObject[] args) - { - return GetBinder(new Method((Node)this.Value, binder.Name, MetaList2List(args))); - } - - /// - /// Performs the binding of the dynamic binary operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// An instance of the representing the right hand side of the binary operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, - DynamicMetaObject arg) - { - return GetBinder(new Binary((Node)this.Value, binder.Operation, arg.Value)); - } - - /// - /// Performs the binding of the dynamic unary operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) - { - Node o = (Node)this.Value; - Unary node = new Unary(o, binder.Operation) { Parser = o.Parser }; - o.Parser.Last = node; - - // If operation is 'IsTrue' or 'IsFalse', we will return false to keep the engine working... - object ret = node; - if (binder.Operation == ExpressionType.IsTrue) ret = (object)false; - if (binder.Operation == ExpressionType.IsFalse) ret = (object)false; - - ParameterExpression - p = Expression.Variable(ret.GetType(), "ret"); // the type is now obtained from "ret" - BlockExpression exp = Expression.Block( - new ParameterExpression[] { p }, - Expression.Assign(p, - Expression.Constant(ret))); // the expression is now obtained from "ret" - - return new MetaNode(exp, this.Restrictions, node); - } - - /// - /// Performs the binding of the dynamic conversion operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindConvert(ConvertBinder binder) - { - Node o = (Node)this.Value; - Convert node = new Convert(o, binder.ReturnType) { Parser = o.Parser }; - o.Parser.Last = node; - - // Reducing the object to return if this is an assignment node... - object ret = o; - bool done = false; - - while (!done) - { - if (ret is SetMember) - ret = ((SetMember)o).Value; - else if (ret is SetIndex) - ret = ((SetIndex)o).Value; - else - done = true; - } - - // Creating an instance... - if (binder.ReturnType == typeof(string)) ret = ret.ToString(); - else - { - try - { - if (binder.ReturnType.IsNullableType()) - ret = null; // to avoid cast exceptions - else - ret = Activator.CreateInstance(binder.ReturnType, - true); // true to allow non-public ctor as well - } - catch - { - // as the last resort scenario - ret = new object(); - } - } - - ParameterExpression p = Expression.Variable(binder.ReturnType, "ret"); - BlockExpression exp = Expression.Block( - new ParameterExpression[] { p }, - Expression.Assign(p, - Expression.Constant(ret, binder.ReturnType))); // specifying binder.ReturnType - - return new MetaNode(exp, this.Restrictions, node); - } - - private static object[] MetaList2List(DynamicMetaObject[] metaObjects) - { - if (metaObjects == null) return null; - - object[] list = new object[metaObjects.Length]; - for (int i = 0; i < metaObjects.Length; i++) - list[i] = metaObjects[i].Value; - - return list; - } - } - - #endregion MetaNode - - #region Argument - - /// - /// Describe a dynamic argument used in a dynamic lambda expression. - /// - [Serializable] - public class Argument : Node, ISerializable - { - /// - /// Initializes a new instance of the class. - /// - /// The name. - public Argument(string name) - : base(name) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Argument(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Argument::Disposed}"; - return Name; - } - } - - #endregion Argument - - #region GetMember - - /// - /// Describe a 'get member' operation, as in 'x => x.Member'. - /// - [Serializable] - public class GetMember : Node, ISerializable - { - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The name. - public GetMember(Node host, string name) - : base(host, name) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected GetMember(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::GetMember::Disposed}"; - return string.Format("{0}.{1}", Host.Sketch(), Name.Sketch()); - } - } - - #endregion GetMember - - #region SetMember - - /// - /// Describe a 'set member' operation, as in 'x => x.Member = y'. - /// - [Serializable] - public class SetMember : Node, ISerializable - { - /// - /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been - /// assigned to this instance, or if this instance is disposed. - /// - public object Value { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The name. - /// The value. - public SetMember(Node host, string name, object value) - : base(host, name) - { - Value = value; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected SetMember(SerializationInfo info, StreamingContext context) - : base(info, context) - { - string type = info.GetString("MemberType"); - Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); - if (Value != null) - info.AddValue("MemberValue", Value); - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::SetMember::Disposed}"; - return string.Format("({0}.{1} = {2})", Host.Sketch(), Name.Sketch(), Value.Sketch()); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Value != null) - { - var node = Value as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - - Value = null; - } - } - catch - { - } - } - - base.Dispose(disposing); - } - } - - #endregion SetMember - - #region GetIndex - - /// - /// Describe a 'get indexed' operation, as in 'x => x.Member[...]'. - /// - [Serializable] - public class GetIndex : Node, ISerializable - { - /// Gets the indexes. - public object[] Indexes { get; internal set; } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The indexes. - /// Indexes array cannot be null. - /// Indexes array cannot be empty. - public GetIndex(Node host, object[] indexes) - : base(host) - { - if (indexes == null) - throw new ArgumentNullException("indexes", "Indexes array cannot be null."); - if (indexes.Length == 0) - throw new ArgumentException("Indexes array cannot be empty."); - - Indexes = indexes; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected GetIndex(SerializationInfo info, StreamingContext context) - : base(info, context) - { - int count = (int)info.GetValue("IndexCount", typeof(int)); - - if (count != 0) - { - Indexes = new object[count]; - for (int i = 0; i < count; i++) - { - string typeName = info.GetString("IndexType" + i); - object obj = typeName == "NULL" - ? null - : info.GetValue("IndexValue" + i, Type.GetType(typeName)); - Indexes[i] = obj; - } - } - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - int count = Indexes == null ? 0 : Indexes.Length; - info.AddValue("IndexCount", count); - for (int i = 0; i < count; i++) - { - info.AddValue("IndexType" + i, - Indexes[i] == null ? "NULL" : Indexes[i].GetType().AssemblyQualifiedName); - if (Indexes[i] != null) info.AddValue("IndexValue" + i, Indexes[i]); - } - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::GetIndex::Disposed}"; - - return string.Format("{0}{1}", Host.Sketch(), - Indexes == null ? "[empty]" : Indexes.Sketch()); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Indexes != null) - { - for (int i = 0; i < Indexes.Length; i++) - { - var node = Indexes[i] as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - } - - Array.Clear(Indexes, 0, Indexes.Length); - } - } - catch - { - } - - Indexes = null; - } - - base.Dispose(disposing); - } - } - - #endregion GetIndex - - #region SetIndex - - /// - /// Describe a 'set indexed' operation, as in 'x => x.Member[...] = Value'. - /// - [Serializable] - public class SetIndex : GetIndex, ISerializable - { - /// - /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been - /// assigned to this instance, or if this instance is disposed. - /// - public object Value { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The indexes. - /// The value. - public SetIndex(Node host, object[] indexes, object value) - : base(host, indexes) - { - Value = value; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected SetIndex(SerializationInfo info, StreamingContext context) - : base(info, context) - { - string type = info.GetString("MemberType"); - Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); - if (Value != null) info.AddValue("MemberValue", Value); - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::SetIndex::Disposed}"; - - return string.Format("({0}{1} = {2})", Host.Sketch(), - Indexes == null ? "[empty]" : Indexes.Sketch(), Value.Sketch()); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Value != null) - { - var node = Value as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - - Value = null; - } - } - catch - { - } - } - - base.Dispose(disposing); - } - } - - #endregion SetIndex - - #region Invoke - - /// - /// Describe a method invocation operation, as in 'x => x.Method(...)". - /// - [Serializable] - public class Invoke : Node, ISerializable - { - /// Gets the arguments. - public object[] Arguments { get; internal set; } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The arguments. - public Invoke(Node host, object[] arguments) - : base(host) - { - Arguments = arguments == null || arguments.Length == 0 ? null : arguments; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Invoke(SerializationInfo info, StreamingContext context) - : base(info, context) - { - int count = (int)info.GetValue("ArgumentCount", typeof(int)); - - if (count != 0) - { - Arguments = new object[count]; - for (int i = 0; i < count; i++) - { - string typeName = info.GetString("ArgumentType" + i); - object obj = typeName == "NULL" - ? null - : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); - Arguments[i] = obj; - } - } - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - int count = Arguments == null ? 0 : Arguments.Length; - info.AddValue("ArgumentCount", count); - for (int i = 0; i < count; i++) - { - info.AddValue("ArgumentType" + i, - Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); - if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); - } - - base.GetObjectData(info, context); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Arguments != null) - { - for (int i = 0; i < Arguments.Length; i++) - { - var node = Arguments[i] as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - } - - Array.Clear(Arguments, 0, Arguments.Length); - } - } - catch - { - } - - Arguments = null; - } - - base.Dispose(disposing); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Invoke::Disposed}"; - - return string.Format("{0}{1}", Host.Sketch(), - Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); - } - } - - #endregion Invoke - - #region Method - - /// - /// Describe a method invocation operation, as in 'x => x.Method(...)". - /// - [Serializable] - public class Method : Node, ISerializable - { - /// Gets the arguments. - public object[] Arguments { get; internal set; } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The name. - /// The arguments. - public Method(Node host, string name, object[] arguments) - : base(host, name) - { - Arguments = arguments == null || arguments.Length == 0 ? null : arguments; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Method(SerializationInfo info, StreamingContext context) - : base(info, context) - { - int count = (int)info.GetValue("ArgumentCount", typeof(int)); - - if (count != 0) - { - Arguments = new object[count]; - for (int i = 0; i < count; i++) - { - string typeName = info.GetString("ArgumentType" + i); - object obj = typeName == "NULL" - ? null - : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); - Arguments[i] = obj; - } - } - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - int count = Arguments == null ? 0 : Arguments.Length; - info.AddValue("ArgumentCount", count); - for (int i = 0; i < count; i++) - { - info.AddValue("ArgumentType" + i, - Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); - if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); - } - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Method::Disposed}"; - - return string.Format("{0}.{1}{2}", Host.Sketch(), Name.Sketch(), - Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Arguments != null) - { - for (int i = 0; i < Arguments.Length; i++) - { - var node = Arguments[i] as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - } - - Array.Clear(Arguments, 0, Arguments.Length); - } - } - catch - { - } - - Arguments = null; - } - - base.Dispose(disposing); - } - } - - #endregion Method - - #region Binary - - /// - /// Represents a binary operation between a dynamic element and an arbitrary object, including null ones, as in - /// 'x => (x && null)'. The left operand must be an instance of , whereas the right one - /// can be any object, including null values. - /// - [Serializable] - public class Binary : Node, ISerializable - { - /// Gets the operation. - public ExpressionType Operation { get; private set; } - - /// Gets host of the . - public Node Left - { - get { return Host; } - } - - /// Gets the right side value. - public object Right { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The left. - /// The operation. - /// The right. - public Binary(Node left, ExpressionType operation, object right) - : base(left) - { - Operation = operation; - Right = right; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Binary(SerializationInfo info, StreamingContext context) - : base(info, context) - { - Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); - - string type = info.GetString("RightType"); - Right = type == "NULL" ? null : (Node)info.GetValue("RightItem", Type.GetType(type)); - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("Operation", Operation); - - info.AddValue("RightType", Right == null ? "NULL" : Right.GetType().AssemblyQualifiedName); - if (Right != null) - info.AddValue("RightItem", Right); - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Binary::Disposed}"; - - return string.Format("({0} {1} {2})", Host.Sketch(), Operation, Right.Sketch()); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - { - if (Left != null) - { - if (Left is Node) - { - Node n = (Node)Left; - - if (!n.IsDisposed) - n.Dispose(disposing); - } - } - - if (Right != null) - { - if (Right is Node) - { - Node n = (Node)Right; - - if (!n.IsDisposed) - n.Dispose(disposing); - } - - Right = null; - } - } - } - } - - #endregion Binary - - #region Unary - - /// - /// Represents an unary operation, as in 'x => !x'. The target must be a instance. There - /// is no distinction between pre- and post- version of the same operation. - /// - [Serializable] - public class Unary : Node, ISerializable - { - /// Gets the operation. - public ExpressionType Operation { get; private set; } - - /// Gets host of the . - public Node Target { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The target. - /// The operation. - public Unary(Node target, ExpressionType operation) - : base(target) - { - Operation = operation; - Target = target; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Unary(SerializationInfo info, StreamingContext context) - : base(info, context) - { - Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("Operation", Operation); - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Binary::Disposed}"; - - return string.Format("({0} {1})", Operation, Host.Sketch()); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Target != null) - { - var node = Target as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - - Target = null; - } - } - catch - { - } - } - - base.Dispose(disposing); - } - } - - #endregion Unary - - #region Convert - - /// - /// Represents a conversion operation, as in 'x => (string)x'. - /// - [Serializable] - public class Convert : Node, ISerializable - { - /// Gets the new type to which value will be converted. - public Type NewType { get; private set; } - - /// Gets host of the . - public Node Target - { - get { return Host; } - } - - /// - /// Initializes a new instance of the class. - /// - /// The target. - /// The new type. - public Convert(Node target, Type newType) - : base(target) - { - NewType = newType; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Convert(SerializationInfo info, StreamingContext context) - : base(info, context) - { - NewType = (Type)info.GetValue("NewType", typeof(Type)); - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("NewType", NewType); - - base.GetObjectData(info, context); - } - } - - #endregion Convert - - /// - /// Gets the name of the member. It might be null if this instance is disposed. - /// - public string Name { get; internal set; } - - /// Gets host of the . - public Node Host { get; internal set; } - - /// Gets reference to the parser. - public DynamicParser Parser - { - get { return _parser; } - internal set - { - _parser = value; - if (_parser != null) - _parser._allNodes.Add(this); - } - } - - /// - /// Initializes a new instance of the class. - /// - internal Node() - { - IsDisposed = false; - } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - internal Node(Node host) - : this() - { - if (host == null) - throw new ArgumentNullException("host", "Host cannot be null."); - - Host = host; - } - - /// - /// Initializes a new instance of the class. - /// - /// The name. - internal Node(string name) - : this() - { - Name = name.Validated("Name"); - } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The name. - /// Host cannot be null. - internal Node(Node host, string name) - : this(host) - { - Name = name.Validated("Name"); - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Node(SerializationInfo info, StreamingContext context) - { - Name = info.GetString("MemberName"); - - string type = info.GetString("HostType"); - Host = type == "NULL" ? null : (Node)info.GetValue("HostItem", Type.GetType(type)); - } - - /// Returns whether the given node is an ancestor of this instance. - /// The node to test. - /// True if the given node is an ancestor of this instance. - public bool IsNodeAncestor(Node node) - { - if (node != null) - { - Node parent = Host; - - while (parent != null) - { - if (object.ReferenceEquals(parent, node)) - return true; - - parent = parent.Host; - } - } - - return false; - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Disposed}"; - - return "{DynamicParser::Node::Empty}"; - } - - #region Implementation of IDynamicMetaObjectProvider - - /// Returns the responsible - /// for binding operations performed on this object. - /// The expression tree representation of the runtime value. - /// The to bind this object. - /// Thrown if this instance is disposed. - public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter) - { - if (IsDisposed) - throw new ObjectDisposedException("DynamicParser.Node"); - - return new MetaNode( - parameter, - BindingRestrictions.GetInstanceRestriction(parameter, this), - this); - } - - #endregion Implementation of IDynamicMetaObjectProvider - - #region Implementation of IFinalizerDisposable - - /// Finalizes an instance of the class. - ~Node() - { - Dispose(false); - } - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public virtual void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public virtual void Dispose(bool disposing) - { - if (disposing) - { - IsDisposed = true; - - if (Host != null && !Host.IsDisposed) - Host.Dispose(); - - Host = null; - - Parser = null; - } - } - - #endregion Implementation of IFinalizerDisposable - - #region Implementation of ISerializable - - /// - /// Populates a with the data needed to serialize the target object. - /// - /// The to populate with data. - /// The destination (see ) for this serialization. - public virtual void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (!string.IsNullOrEmpty(Name)) - info.AddValue("MemberName", Name); - - info.AddValue("HostType", Host == null ? "NULL" : Host.GetType().AssemblyQualifiedName); - if (Host != null) - info.AddValue("HostItem", Host); - } - - #endregion Implementation of ISerializable - } - - #endregion Node - - #region Data - - private List _arguments = new List(); - private List _allNodes = new List(); - private object _uncertainResult; - - #endregion Data - - #region Properties - - /// Gets the last node (root of the tree). - public Node Last { get; internal set; } - - /// - /// Gets an enumeration containing the dynamic arguments used in the dynamic lambda expression parsed. - /// - public IEnumerable Arguments - { - get - { - List list = new List(); - if (!IsDisposed && _arguments != null) - list.AddRange(_arguments); - - foreach (Node.Argument arg in list) - yield return arg; - - list.Clear(); - list = null; - } - } - - /// - /// Gets the number of dynamic arguments used in the dynamic lambda expression parsed. - /// - public int Count - { - get { return _arguments == null ? 0 : _arguments.Count; } - } - - /// - /// Gets the result of the parsing of the dynamic lambda expression. This result can be either an arbitrary object, - /// including null, if the expression resolves to it, or an instance of the class that - /// contains the last logic expression evaluated when parsing the dynamic lambda expression. - /// - public object Result - { - get { return _uncertainResult ?? Last; } - } - - #endregion Properties - - private DynamicParser(Delegate f) - { - // I know this can be almost a one liner - // but it causes memory leaks when so. - ParameterInfo[] pars = f.Method.GetParameters(); - foreach (ParameterInfo p in pars) - { - int attrs = p.GetCustomAttributes(typeof(DynamicAttribute), true).Length; - if (attrs != 0) - { - Node.Argument par = new Node.Argument(p.Name) { Parser = this }; - this._arguments.Add(par); - } - else - throw new ArgumentException(string.Format("Argument '{0}' must be dynamic.", p.Name)); - } - - try - { - _uncertainResult = f.DynamicInvoke(_arguments.ToArray()); - } - catch (TargetInvocationException e) - { - if (e.InnerException != null) throw e.InnerException; - throw; - } - } - - /// - /// Parses the dynamic lambda expression given in the form of a delegate, and returns a new instance of the - /// class that holds the dynamic arguments used in the dynamic lambda expression, and - /// the result of the parsing. - /// - /// The dynamic lambda expression to parse. - /// A new instance of . - public static DynamicParser Parse(Delegate f) - { - return new DynamicParser(f); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Disposed}"; - - StringBuilder sb = new StringBuilder(); - - sb.Append("("); - bool first = true; - - if (_arguments != null) - { - foreach (Node.Argument arg in _arguments) - { - if (!first) sb.Append(", "); - else first = false; - sb.Append(arg); - } - } - - sb.Append(")"); - - sb.AppendFormat(" => {0}", Result.Sketch()); - - return sb.ToString(); - } - - #region Implementation of IExtendedDisposable - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - IsDisposed = true; - - if (_uncertainResult != null) - { - if (_uncertainResult is Node) - ((Node)_uncertainResult).Dispose(); - - _uncertainResult = null; - } - - if (Last != null) - { - if (!Last.IsDisposed) - Last.Dispose(); - - Last = null; - } - - if (_arguments != null) - { - _arguments.ForEach(x => - { - if (!x.IsDisposed) x.Dispose(); - }); - - _arguments.Clear(); - _arguments = null; - } - - if (_allNodes != null) - { - _allNodes.ForEach(x => - { - if (!x.IsDisposed) x.Dispose(); - }); - - _allNodes.Clear(); - _allNodes = null; - } - } - - #endregion Implementation of IExtendedDisposable - } - - /// Class that allows to use interfaces as dynamic objects. - /// Type of class to proxy. - /// This is temporary solution. Which allows to use builders as a dynamic type. - public class DynamicProxy : DynamicObject, IDisposable - { - private T _proxy; - private Type _type; - private Dictionary _properties; - private Dictionary _methods; - - /// - /// Initializes a new instance of the class. - /// - /// The object to which proxy should be created. - /// The object to which proxy should be created is null. - public DynamicProxy(T proxiedObject) - { - if (proxiedObject == null) - throw new ArgumentNullException("proxiedObject"); - - _proxy = proxiedObject; - _type = typeof(T); - - DynamicTypeMap mapper = Mapper.DynamicMapperCache.GetMapper(); - - _properties = mapper - .ColumnsMap - .ToDictionary( - k => k.Value.Name, - v => v.Value); - - _methods = GetAllMembers(_type) - .Where(x => x is MethodInfo) - .Cast() - .Where(m => !((m.Name.StartsWith("set_") && m.ReturnType == typeof(void)) || - m.Name.StartsWith("get_"))) - .Where(m => !m.IsStatic && !m.IsGenericMethod) - .ToDictionary( - k => k, - v => - { - try - { - Type type = v.ReturnType == typeof(void) - ? Expression.GetActionType(v.GetParameters().Select(t => t.ParameterType) - .ToArray()) - : Expression.GetDelegateType(v.GetParameters().Select(t => t.ParameterType) - .Concat(new[] { v.ReturnType }).ToArray()); - - return Delegate.CreateDelegate(type, _proxy, v.Name); - } - catch (ArgumentException) - { - return null; - } - }); - } - - /// Provides implementation for type conversion operations. - /// Classes derived from the - /// class can override this method to specify dynamic behavior for - /// operations that convert an object from one type to another. - /// Provides information about the conversion operation. - /// The binder.Type property provides the type to which the object must be - /// converted. For example, for the statement (String)sampleObject in C# - /// (CType(sampleObject, Type) in Visual Basic), where sampleObject is an - /// instance of the class derived from the - /// class, binder.Type returns the type. - /// The binder.Explicit property provides information about the kind of - /// conversion that occurs. It returns true for explicit conversion and - /// false for implicit conversion. - /// The result of the type conversion operation. - /// Returns true if the operation is successful; otherwise, false. - /// If this method returns false, the run-time binder of the language determines the - /// behavior. (In most cases, a language-specific run-time exception is thrown). - public override bool TryConvert(ConvertBinder binder, out object result) - { - if (binder.Type == typeof(T)) - { - result = _proxy; - return true; - } - - if (_proxy != null && - binder.Type.IsAssignableFrom(_proxy.GetType())) - { - result = _proxy; - return true; - } - - return base.TryConvert(binder, out result); - } - - /// Provides the implementation for operations that get member - /// values. Classes derived from the - /// class can override this method to specify dynamic behavior for - /// operations such as getting a value for a property. - /// Provides information about the object that - /// called the dynamic operation. The binder.Name property provides - /// the name of the member on which the dynamic operation is performed. - /// For example, for the Console.WriteLine(sampleObject.SampleProperty) - /// statement, where sampleObject is an instance of the class derived - /// from the class, - /// binder.Name returns "SampleProperty". The binder.IgnoreCase property - /// specifies whether the member name is case-sensitive. - /// The result of the get operation. For example, - /// if the method is called for a property, you can assign the property - /// value to . - /// Returns true if the operation is successful; otherwise, - /// false. If this method returns false, the run-time binder of the - /// language determines the behavior. (In most cases, a run-time exception - /// is thrown). - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - try - { - DynamicPropertyInvoker prop = _properties.TryGetValue(binder.Name); - - result = prop.NullOr(p => p.Get.NullOr(g => g(_proxy), null), null); - - return prop != null && prop.Get != null; - } - catch (Exception ex) - { - throw new InvalidOperationException(string.Format("Cannot get member {0}", binder.Name), ex); - } - } - - /// Provides the implementation for operations that set member - /// values. Classes derived from the - /// class can override this method to specify dynamic behavior for operations - /// such as setting a value for a property. - /// Provides information about the object that called - /// the dynamic operation. The binder.Name property provides the name of - /// the member to which the value is being assigned. For example, for the - /// statement sampleObject.SampleProperty = "Test", where sampleObject is - /// an instance of the class derived from the - /// class, binder.Name returns "SampleProperty". The binder.IgnoreCase - /// property specifies whether the member name is case-sensitive. - /// The value to set to the member. For example, for - /// sampleObject.SampleProperty = "Test", where sampleObject is an instance - /// of the class derived from the - /// class, the is "Test". - /// Returns true if the operation is successful; otherwise, - /// false. If this method returns false, the run-time binder of the - /// language determines the behavior. (In most cases, a language-specific - /// run-time exception is thrown). - public override bool TrySetMember(SetMemberBinder binder, object value) - { - try - { - DynamicPropertyInvoker prop = _properties.TryGetValue(binder.Name); - - if (prop != null && prop.Setter != null) - { - prop.Set(_proxy, value); - return true; - } - - return false; - } - catch (Exception ex) - { - throw new InvalidOperationException( - string.Format("Cannot set member {0} to '{1}'", binder.Name, value), ex); - } - } - - /// Provides the implementation for operations that invoke a member. - /// Classes derived from the - /// class can override this method to specify dynamic behavior for - /// operations such as calling a method. - /// Provides information about the dynamic operation. - /// The binder.Name property provides the name of the member on which the - /// dynamic operation is performed. For example, for the statement - /// sampleObject.SampleMethod(100), where sampleObject is an instance of - /// the class derived from the - /// class, binder.Name returns "SampleMethod". The binder.IgnoreCase property - /// specifies whether the member name is case-sensitive. - /// The arguments that are passed to the object member - /// during the invoke operation. For example, for the statement - /// sampleObject.SampleMethod(100), where sampleObject is derived from the - /// class, - /// First element of is equal to 100. - /// The result of the member invocation. - /// Returns true if the operation is successful; otherwise, - /// false. If this method returns false, the run-time binder of the - /// language determines the behavior. (In most cases, a language-specific - /// run-time exception is thrown). - public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) - { - return TryInvokeMethod(binder.Name, out result, args) || - base.TryInvokeMember(binder, args, out result); - } - - private bool TryInvokeMethod(string name, out object result, object[] args) - { - result = null; - - MethodInfo mi = _methods.Keys - .Where(m => m.Name == name) - .FirstOrDefault(m => - CompareTypes(m.GetParameters().ToArray(), - args.Select(a => a.GetType()).ToArray())); - - Delegate d = _methods.TryGetValue(mi); - - if (d != null) - { - result = d.DynamicInvoke(CompleteArguments(mi.GetParameters().ToArray(), args)); - - if (d.Method.ReturnType == _type && result is T) - result = new DynamicProxy((T)result); - - return true; - } - else if (mi != null) - { - result = mi.Invoke(_proxy, CompleteArguments(mi.GetParameters().ToArray(), args)); - - if (mi.ReturnType == _type && result is T) - result = new DynamicProxy((T)result); - - return true; - } - - return false; - } - - private bool CompareTypes(ParameterInfo[] parameters, Type[] types) - { - if (parameters.Length < types.Length || parameters.Count(p => !p.IsOptional) > types.Length) - return false; - - for (int i = 0; i < types.Length; i++) - if (types[i] != parameters[i].ParameterType && - !parameters[i].ParameterType.IsAssignableFrom(types[i])) - return false; - - return true; - } - - private object[] CompleteArguments(ParameterInfo[] parameters, object[] arguments) - { - return arguments.Concat(parameters.Skip(arguments.Length).Select(p => p.DefaultValue)).ToArray(); - } - - private IEnumerable GetAllMembers(Type type) - { - if (type.IsInterface) - { - List members = new List(); - List considered = new List(); - Queue queue = new Queue(); - - considered.Add(type); - queue.Enqueue(type); - - while (queue.Count > 0) - { - Type subType = queue.Dequeue(); - foreach (Type subInterface in subType.GetInterfaces()) - { - if (considered.Contains(subInterface)) continue; - - considered.Add(subInterface); - queue.Enqueue(subInterface); - } - - MemberInfo[] typeProperties = subType.GetMembers( - BindingFlags.FlattenHierarchy - | BindingFlags.Public - | BindingFlags.Instance); - - IEnumerable newPropertyInfos = typeProperties - .Where(x => !members.Contains(x)); - - members.InsertRange(0, newPropertyInfos); - } - - return members; - } - - return type.GetMembers(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public void Dispose() - { - object res; - TryInvokeMethod("Dispose", out res, new object[] { }); - - _properties.Clear(); - - _methods = null; - _properties = null; - _type = null; - _proxy = default(T); - } - } - } - } - - namespace Mapper - { - /// Allows to add table name to class. - [AttributeUsage(AttributeTargets.Property)] - public class ColumnAttribute : Attribute - { - /// Gets or sets name. - public string Name { get; set; } - - /// Gets or sets column type. - /// Used when overriding schema. - 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 allows null or not. - /// Information only. - public bool AllowNull { get; set; } - - /// Gets or sets a value indicating whether column should have unique value. - /// Used when overriding schema. - public bool? IsUnique { get; set; } - - /// Gets or sets column size. - /// Used when overriding schema. - public int? Size { get; set; } - - /// Gets or sets column precision. - /// Used when overriding schema. - public byte? Precision { get; set; } - - /// Gets or sets column scale. - /// Used when overriding schema. - public byte? Scale { get; set; } - - /// Gets or sets a value indicating whether this column is no allowed to be inserted. - /// This is only a suggestion to automated mapping. - public bool IsNoInsert { get; set; } - - /// Gets or sets a value indicating whether this column is no allowed to be updated. - /// This is only a suggestion to automated mapping. - public bool IsNoUpdate { get; set; } - - #region Constructors - - /// Initializes a new instance of the class. - public ColumnAttribute() - { - AllowNull = true; - } - - /// Initializes a new instance of the class. - /// Name of column. - public ColumnAttribute(string name) - : this() - { - Name = name; - } - - /// Initializes a new instance of the class. - /// Set column as a key column. - public ColumnAttribute(bool isKey) - : this() - { - IsKey = isKey; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - public ColumnAttribute(string name, bool isKey) - : this(name) - { - IsKey = isKey; - } - - /// Initializes a new instance of the class. - /// Set column as a key column. - /// Set column type. - public ColumnAttribute(bool isKey, DbType type) - : this(isKey) - { - Type = type; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - /// Set column type. - public ColumnAttribute(string name, bool isKey, DbType type) - : this(name, isKey) - { - Type = type; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - /// Set column type. - /// Set column value size. - public ColumnAttribute(string name, bool isKey, DbType type, int size) - : this(name, isKey, type) - { - Size = size; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - /// Set column type. - /// Set column value precision. - /// Set column value scale. - public ColumnAttribute(string name, bool isKey, DbType type, byte precision, byte scale) - : this(name, isKey, type) - { - Precision = precision; - Scale = scale; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - /// Set column type. - /// Set column value size. - /// Set column value precision. - /// Set column value scale. - public ColumnAttribute(string name, bool isKey, DbType type, int size, byte precision, byte scale) - : this(name, isKey, type, precision, scale) - { - Size = size; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - /// Set column has unique value. - /// Set column type. - /// Set column value size. - /// Set column value precision. - /// Set column value scale. - public ColumnAttribute(string name, bool isKey, bool isUnique, DbType type, int size, byte precision, - byte scale) - : this(name, isKey, type, size, precision, scale) - { - IsUnique = isUnique; - } - - #endregion Constructors - } - - /// Type cast helper. - public static class DynamicCast - { - /// Gets the default value. - /// The type. - /// Default instance. - public static object GetDefaultValue(this Type type) - { - return type.IsValueType ? TypeDefaults.GetOrAdd(type, t => Activator.CreateInstance(t)) : null; - } - - /// Casts the object to this type. - /// The type to which cast value. - /// The value to cast. - /// Value casted to new type. - public static object CastObject(this Type type, object val) - { - return GetConverter(type, val)(val); - } - - private static readonly ConcurrentDictionary TypeDefaults = - new ConcurrentDictionary(); - - private static readonly ConcurrentDictionary> TypeAsCasts = - new ConcurrentDictionary>(); - - private static readonly ConcurrentDictionary> TypeConvert = - new ConcurrentDictionary>(); - - private static readonly ParameterExpression ConvParameter = Expression.Parameter(typeof(object), "val"); - - [MethodImpl(MethodImplOptions.Synchronized)] - private static Func GetConverter(Type targetType, object val) - { - Func fn; - - if (!targetType.IsValueType && !val.GetType().IsValueType) - { - if (!TypeAsCasts.TryGetValue(targetType, out fn)) - { - UnaryExpression instanceCast = Expression.TypeAs(ConvParameter, targetType); - - fn = Expression - .Lambda>(Expression.TypeAs(instanceCast, typeof(object)), - ConvParameter).Compile(); - TypeAsCasts.AddOrUpdate(targetType, fn, (t, f) => fn); - } - } - else - { - var fromType = val != null ? val.GetType() : typeof(object); - var key = new PairOfTypes(fromType, targetType); - if (TypeConvert.TryGetValue(key, out fn)) - return fn; - - fn = (Func)Expression - .Lambda( - Expression.Convert( - Expression.Convert(Expression.Convert(ConvParameter, fromType), targetType), - typeof(object)), ConvParameter).Compile(); - TypeConvert.AddOrUpdate(key, fn, (t, f) => fn); - } - - return fn; - } - - private class PairOfTypes - { - private readonly Type _first; - private readonly Type _second; - - public PairOfTypes(Type first, Type second) - { - this._first = first; - this._second = second; - } - - public override int GetHashCode() - { - return (31 * _first.GetHashCode()) + _second.GetHashCode(); - } - - public override bool Equals(object obj) - { - if (obj == this) - return true; - - var other = obj as PairOfTypes; - if (other == null) - return false; - - return _first.Equals(other._first) - && _second.Equals(other._second); - } - } - } - - /// Class with mapper cache. - public static class DynamicMapperCache - { - private static readonly object SyncLock = new object(); - private static Dictionary _cache = new Dictionary(); - - /// Get type mapper. - /// Type of mapper. - /// Type mapper. - public static DynamicTypeMap GetMapper() - { - return GetMapper(typeof(T)); - } - - /// Get type mapper. - /// Type of mapper. - /// Type mapper. - public static DynamicTypeMap GetMapper(Type type) - { - if (type == null) - return null; - /*if (type.IsAnonymous()) - return null;*/ - - DynamicTypeMap mapper = null; - - lock (SyncLock) - { - if (!_cache.TryGetValue(type, out mapper)) - { - mapper = new DynamicTypeMap(type); - - if (mapper != null) - _cache.Add(type, mapper); - } - } - - return mapper; - } - } - - /// Exception thrown when mapper fails to set or get a property. - /// - public class DynamicMapperException : Exception - { - /// Initializes a new instance of the class. - public DynamicMapperException() - { - } - - /// Initializes a new instance of the class. - /// The message that describes the error. - public DynamicMapperException(string message) : base(message) - { - } - - /// Initializes a new instance of the class. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public DynamicMapperException(string message, Exception innerException) : base(message, innerException) - { - } - } - - /// Dynamic property invoker. - public class DynamicPropertyInvoker - { - internal class ParameterSpec - { - public string Name { get; set; } - - public DbType Type { get; set; } - - public int Ordinal { get; set; } - } - - /// Gets the array type of property if main type is a form of collection. - public Type ArrayType { get; private set; } - - /// Gets a value indicating whether this property is in fact a generic list. - public bool IsGnericEnumerable { get; private set; } - - /// Gets the type of property. - public Type Type { get; private set; } - - /// Gets value getter. - public Func Get { get; private set; } - - /// Gets value setter. - public Action Setter { get; private set; } - - /// Gets the property information. - public PropertyInfo PropertyInfo { get; private set; } - - /// Gets name of property. - public string Name { get; private set; } - - /// Gets type column description. - public ColumnAttribute Column { get; private set; } - - /// Gets type list of property requirements. - public List Requirements { get; private set; } - - /// Gets a value indicating whether this is ignored in some cases. - public bool Ignore { get; private set; } - - /// Gets a value indicating whether this instance hold data contract type. - public bool IsDataContract { get; private set; } - - /// Initializes a new instance of the class. - /// Property info to be invoked in the future. - /// Column attribute if exist. - public DynamicPropertyInvoker(PropertyInfo property, ColumnAttribute attr) - { - PropertyInfo = property; - Name = property.Name; - Type = property.PropertyType; - - object[] ignore = property.GetCustomAttributes(typeof(IgnoreAttribute), false); - Requirements = property.GetCustomAttributes(typeof(RequiredAttribute), false).Cast() - .ToList(); - - Ignore = ignore != null && ignore.Length > 0; - - IsGnericEnumerable = Type.IsGenericEnumerable(); - - ArrayType = Type.IsArray ? Type.GetElementType() : - IsGnericEnumerable ? Type.GetGenericArguments().First() : - Type; - - IsDataContract = ArrayType.GetCustomAttributes(false) - .Any(x => x.GetType().Name == "DataContractAttribute"); - - if (ArrayType.IsArray) - throw new InvalidOperationException("Jagged arrays are not supported"); - - if (ArrayType.IsGenericEnumerable()) - throw new InvalidOperationException("Enumerables of enumerables are not supported"); - - Column = attr; - - if (attr != null && attr.AllowNull && !Type.IsNullableType() && Type != typeof(string)) - attr.AllowNull = false; - - if (property.CanRead) - Get = CreateGetter(property); - - if (property.CanWrite) - Setter = CreateSetter(property); - } - - private Func CreateGetter(PropertyInfo property) - { - if (property == null || !property.CanRead) - return null; - - var objParm = Expression.Parameter(typeof(object), "o"); - - return Expression.Lambda>( - Expression.Convert( - Expression.Property( - Expression.TypeAs(objParm, property.DeclaringType), - property.Name), - typeof(object)), objParm).Compile(); - } - - private Action CreateSetter(PropertyInfo property) - { - if (!property.CanWrite) - return null; - - var objParm = Expression.Parameter(typeof(object), "o"); - var valueParm = Expression.Parameter(typeof(object), "value"); - - return Expression.Lambda>( - Expression.Assign( - Expression.Property( - Expression.Convert(objParm, property.DeclaringType), - property.Name), - Expression.Convert(valueParm, property.PropertyType)), - objParm, valueParm).Compile(); - } - - /// Sets the specified value to destination object. - /// The destination object. - /// The value. - public void Set(object dest, object val, bool byProperty = false) - { - object value = null; - - try - { - if (!Type.IsAssignableFrom(val.GetType())) - { - if (Type.IsArray || IsGnericEnumerable) - { - if (val != null) - { - if (val is IEnumerable) - { - var lst = (val as IEnumerable) - .Select(x => GetElementVal(ArrayType, x, byProperty)).ToList(); - - value = Array.CreateInstance(ArrayType, lst.Count); - - int i = 0; - foreach (var e in lst) - ((Array)value).SetValue(e, i++); - } - else - { - value = Array.CreateInstance(ArrayType, 1); - ((Array)value).SetValue(GetElementVal(ArrayType, val, byProperty), 0); - } - } - else - value = Array.CreateInstance(ArrayType, 0); - } - else - value = GetElementVal(Type, val, byProperty); - } - else - value = val; - - Setter(dest, value); - } - catch (Exception ex) - { - throw new DynamicMapperException( - string.Format( - "Error trying to convert and set value '{0}' of type '{1}' to type '{2}' in object of type '{3}'", - val == null ? string.Empty : val.ToString(), val.GetType(), Type.FullName, - dest.GetType().FullName), - ex); - } - } - - private object GetElementVal(System.Type etype, object val, bool byProperty) - { - bool nullable = etype.IsGenericType && etype.GetGenericTypeDefinition() == typeof(Nullable<>); - Type type = Nullable.GetUnderlyingType(etype) ?? etype; - - if (val == null && type.IsValueType) - { - if (nullable) - return null; - else - return Activator.CreateInstance(Type); - } - else if ((val == null && !type.IsValueType) || (val != null && type == val.GetType())) - return val; - else if (type.IsEnum && val.GetType().IsValueType) - return Enum.ToObject(type, val); - else if (type.IsEnum) - try - { - return Enum.Parse(type, val.ToString()); - } - catch (ArgumentException) - { - if (nullable) - return null; - - throw; - } - else if (Type == typeof(string) && val.GetType() == typeof(Guid)) - return val.ToString(); - else if (Type == typeof(Guid)) - { - if (val.GetType() == typeof(byte[])) - return new Guid((byte[])val); - else if (val.GetType() == typeof(string)) - { - Guid g; - return Guid.TryParse((string)val, out g) ? g : Guid.Empty; - } - else return (nullable) ? null : (object)Guid.Empty; - } - else if (!typeof(IConvertible).IsAssignableFrom(type) && - (IsDataContract || (!type.IsValueType && val is IDictionary))) - { - if (byProperty) - return val.MapByProperty(type); - - return val.Map(type); - } - else - try - { - return Convert.ChangeType(val, type); - } - catch - { - if (nullable) - return null; - - throw; - } - } - - #region Type command cache - - internal ParameterSpec InsertCommandParameter { get; set; } - - internal ParameterSpec UpdateCommandParameter { get; set; } - - internal ParameterSpec DeleteCommandParameter { get; set; } - - #endregion Type command cache - } - - /// Represents type columnMap. - public class DynamicTypeMap - { - /// Gets mapper destination type creator. - public Type Type { get; private set; } - - /// Gets type table description. - public TableAttribute Table { get; private set; } - - /// Gets object creator. - public Func Creator { get; private set; } - - /// Gets map of columns to properties. - /// Key: Column name (lower), Value: . - public Dictionary ColumnsMap { get; private set; } - - /// Gets map of properties to column. - /// Key: Property name, Value: Column name. - public Dictionary PropertyMap { get; private set; } - - /// Gets list of ignored properties. - public List Ignored { get; private set; } - - /// Initializes a new instance of the class. - /// Type to which columnMap objects. - public DynamicTypeMap(Type type) - { - Type = type; - - object[] attr = type.GetCustomAttributes(typeof(TableAttribute), false); - - if (attr != null && attr.Length > 0) - Table = (TableAttribute)attr[0]; - - Creator = CreateCreator(); - CreateColumnAndPropertyMap(); - } - - private void CreateColumnAndPropertyMap() - { - Dictionary columnMap = new Dictionary(); - Dictionary propertyMap = new Dictionary(); - List ignored = new List(); - - foreach (PropertyInfo pi in GetAllMembers(Type).Where(x => x is PropertyInfo).Cast()) - { - // Skip indexers - if (pi.GetIndexParameters().Any()) - continue; - - ColumnAttribute attr = null; - - object[] attrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true); - - if (attrs != null && attrs.Length > 0) - attr = (ColumnAttribute)attrs[0]; - - string col = attr == null || string.IsNullOrEmpty(attr.Name) ? pi.Name : attr.Name; - - DynamicPropertyInvoker val = new DynamicPropertyInvoker(pi, attr); - columnMap.Add(col.ToLower(), val); - - propertyMap.Add(pi.Name, col); - - if (val.Ignore) - ignored.Add(pi.Name); - } - - ColumnsMap = columnMap; - PropertyMap = propertyMap; - - Ignored = ignored; ////columnMap.Where(i => i.Value.Ignore).Select(i => i.Value.Name).ToList(); - } - - private Func CreateCreator() - { - var c = Type.GetConstructor(Type.EmptyTypes); - if (c == null) - c = Type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, - null); - - if (c != null) - return Expression.Lambda>(Expression.New(Type)).Compile(); - - return null; - } - - /// Create object of type and fill values from source. - /// Object containing values that will be mapped to newly created object. - /// New object of type with matching values from source. - public object Create(object source) - { - return Map(source, Creator()); - } - - /// Create object of type and fill values from source using property names. - /// Object containing values that will be mapped to newly created object. - /// New object of type with matching values from source. - public object CreateByProperty(object source) - { - return MapByProperty(source, Creator()); - } - - /// Fill values from source to object in destination. - /// Object containing values that will be mapped to newly created object. - /// Object of type to which copy values from source. - /// Object of type with matching values from source. - public object Map(object source, object destination) - { - DynamicPropertyInvoker dpi = null; - - foreach (KeyValuePair item in source.ToDictionary()) - { - if (ColumnsMap.TryGetValue(item.Key.ToLower(), out dpi) && item.Value != null) - if (dpi.Setter != null) - dpi.Set(destination, item.Value); - } - - return destination; - } - - /// Fill values from source to object in destination using property names. - /// Object containing values that will be mapped to newly created object. - /// Object of type to which copy values from source. - /// Object of type with matching values from source. - public object MapByProperty(object source, object destination) - { - string cn = null; - DynamicPropertyInvoker dpi = null; - - foreach (KeyValuePair item in source.ToDictionary()) - { - if (PropertyMap.TryGetValue(item.Key, out cn) && item.Value != null) - if (ColumnsMap.TryGetValue(cn.ToLower(), out dpi) && item.Value != null) - if (dpi.Setter != null) - dpi.Set(destination, item.Value, true); - } - - return destination; - } - - /// Validates the object. - /// The value. - /// List of not valid results. - public IList ValidateObject(object val) - { - var result = new List(); - - if (val == null || val.GetType() != Type) - return null; - - foreach (var prop in ColumnsMap.Values) - { - if (prop.Requirements == null || !prop.Requirements.Any()) - continue; - - var v = prop.Get(val); - - foreach (var r in prop.Requirements.Where(x => !x.ElementRequirement)) - { - var valid = r.ValidateSimpleValue(prop, v); - - if (valid == ValidateResult.Valid) - { - if (prop.Type.IsArray || prop.IsGnericEnumerable) - { - var map = DynamicMapperCache.GetMapper(prop.ArrayType); - - var list = v as IEnumerable; - - if (list == null) - { - var enumerable = v as IEnumerable; - if (enumerable != null) - list = enumerable.Cast(); - } - - if (list != null) - foreach (var item in list) - { - if (prop.Requirements.Any(x => x.ElementRequirement)) - { - foreach (var re in prop.Requirements.Where(x => x.ElementRequirement)) - { - var validelem = re.ValidateSimpleValue(prop.ArrayType, - prop.ArrayType.IsGenericEnumerable(), item); - - if (validelem == ValidateResult.NotSupported) - { - result.AddRange(map.ValidateObject(item)); - break; - } - else if (validelem != ValidateResult.Valid) - result.Add(new ValidationResult() - { - Property = prop, - Requirement = r, - Value = item, - Result = validelem, - }); - } - } - else - result.AddRange(map.ValidateObject(item)); - } - } - - continue; - } - - if (valid == ValidateResult.NotSupported) - { - result.AddRange(DynamicMapperCache.GetMapper(prop.Type).ValidateObject(v)); - continue; - } - - result.Add(new ValidationResult() - { - Property = prop, - Requirement = r, - Value = v, - Result = valid, - }); - } - } - - return result; - } - - private IEnumerable GetAllMembers(Type type) - { - if (type.IsInterface) - { - List members = new List(); - List considered = new List(); - Queue queue = new Queue(); - - considered.Add(type); - queue.Enqueue(type); - - while (queue.Count > 0) - { - Type subType = queue.Dequeue(); - foreach (Type subInterface in subType.GetInterfaces()) - { - if (considered.Contains(subInterface)) continue; - - considered.Add(subInterface); - queue.Enqueue(subInterface); - } - - MemberInfo[] typeProperties = subType.GetMembers( - BindingFlags.FlattenHierarchy - | BindingFlags.Public - | BindingFlags.Instance); - - IEnumerable newPropertyInfos = typeProperties - .Where(x => !members.Contains(x)); - - members.InsertRange(0, newPropertyInfos); - } - - return members; - } - - return type.GetMembers(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance); - } - - #region Type command cache - - internal string InsertCommandText { get; set; } - - internal string UpdateCommandText { get; set; } - - internal string DeleteCommandText { get; set; } - - #endregion Type command cache - } - - /// Allows to add ignore action to property. - /// Property still get's mapped from output. - [AttributeUsage(AttributeTargets.Property)] - public class IgnoreAttribute : Attribute - { - } - - /// Allows to add table name to class. - [AttributeUsage(AttributeTargets.Class)] - public class TableAttribute : Attribute - { - /// Gets or sets table owner name. - public string Owner { get; set; } - - /// Gets or sets name. - public string Name { get; set; } - - /// Gets or sets a value indicating whether override database - /// schema values. - /// If database doesn't support schema, you still have to - /// set this to true to get schema from type. - public bool Override { get; set; } - } - } - - namespace Objects - { - /// Base class for strong typed objects. - public class DynamicEntityBase - { - private Dictionary _changedFields = new Dictionary(); - private DynamicEntityState _dynamicEntityState = DynamicEntityState.Unknown; - - /// Occurs when object property is changing. - public event EventHandler PropertyChanging; - - /// Gets the state of the dynamic entity. - /// Current state of entity. - public virtual DynamicEntityState GetDynamicEntityState() - { - return _dynamicEntityState; - } - - /// Sets the state of the dynamic entity. - /// Using this method will reset modified fields list. - /// The state. - public virtual void SetDynamicEntityState(DynamicEntityState state) - { - _dynamicEntityState = state; - - if (_changedFields != null) - _changedFields.Clear(); - } - - /// Called when object property is changing. - /// Name of the property. - /// The old property value. - /// The new property value. - protected virtual void OnPropertyChanging(string propertyName, object oldValue, object newValue) - { - OnPropertyChanging(new DynamicPropertyChangingEventArgs(propertyName, oldValue, newValue)); - } - - /// Raises the event. - /// The instance containing the event data. - protected virtual void OnPropertyChanging(DynamicPropertyChangingEventArgs e) - { - _changedFields[e.PropertyName] = e.NewValue; - if (PropertyChanging != null) - PropertyChanging(this, e); - } - - /// Validates this object instance. - /// Returns list of containing results of validation. - public virtual IList Validate() - { - return DynamicMapperCache.GetMapper(this.GetType()).ValidateObject(this); - } - - /// Saves this object to database. - /// The database. - /// Returns true if operation was successful. - /// Can be thrown when object is in invalid state. - public virtual bool Save(DynamicDatabase database) - { - switch (GetDynamicEntityState()) - { - default: - case DynamicEntityState.Unknown: - throw new InvalidOperationException( - "Unknown object state. Unable to decide whish action should be performed."); - - case DynamicEntityState.New: - return Insert(database); - - case DynamicEntityState.Existing: - if (IsModified()) - return Update(database); - - return true; - - case DynamicEntityState.ToBeDeleted: - return Delete(database); - - case DynamicEntityState.Deleted: - throw new InvalidOperationException("Unable to do any database action on deleted object."); - } - } - - /// Determines whether this instance is in existing state and fields was modified since this state was set modified. - /// Returns true if this instance is modified; otherwise, false. - public virtual bool IsModified() - { - if (GetDynamicEntityState() != DynamicEntityState.Existing) - return false; - - return _changedFields != null && _changedFields.Any(); - } - - #region Insert/Update/Delete - - /// Inserts this object to database. - /// The database. - /// Returns true if operation was successful. - public virtual bool Insert(DynamicDatabase db) - { - using (var query = db.Insert(this.GetType())) - { - if (query - .Values(x => this) - .Execute() > 0) - { - _changedFields.Clear(); - SetDynamicEntityState(DynamicEntityState.Existing); - return true; - } - } - - return false; - } - - /// Updates this object in database. - /// The database. - /// Returns true if operation was successful. - public virtual bool Update(DynamicDatabase db) - { - var t = GetType(); - var mapper = DynamicMapperCache.GetMapper(t); - using (var query = db.Update(t)) - { - MakeQueryWhere(mapper, query); - - bool any = false; - - if (_changedFields.Any()) - { - foreach (var cf in _changedFields) - { - var cn = mapper.PropertyMap[cf.Key]; - var pm = mapper.ColumnsMap[cn.ToLower()]; - if (pm.Ignore) - continue; - - if (pm.Column != null) - { - if (pm.Column.IsKey || pm.Column.IsNoUpdate) - continue; - - if (!pm.Column.AllowNull && cf.Value == null) - continue; - } - - query.Values(cn, cf.Value); - any = true; - } - } - - if (!any) - foreach (var pmk in mapper.ColumnsMap) - { - var pm = pmk.Value; - var val = pm.Get(this); - var cn = pm.Name; - - if (pm.Ignore) - continue; - - if (pm.Column != null) - { - if (!string.IsNullOrEmpty(pm.Column.Name)) - cn = pm.Column.Name; - - if (pm.Column.IsKey) - continue; - - if (!pm.Column.AllowNull && val == null) - continue; - } - - query.Values(cn, val); - } - - if (query.Execute() == 0) - return false; - - SetDynamicEntityState(DynamicEntityState.Existing); - _changedFields.Clear(); - - return true; - } - } - - /// Deletes this object from database. - /// The database. - /// Returns true if operation was successful. - public virtual bool Delete(DynamicDatabase db) - { - var t = this.GetType(); - var mapper = DynamicMapperCache.GetMapper(t); - - using (var query = db.Delete(t)) - { - MakeQueryWhere(mapper, query); - - if (query.Execute() == 0) - return false; - - SetDynamicEntityState(DynamicEntityState.Deleted); - } - - return true; - } - - #endregion Insert/Update/Delete - - #region Select - - /// Refresh non key data from database. - /// The database. - /// All properties that are primary key values must be filled. - /// Returns true if operation was successful. - public virtual bool Refresh(DynamicDatabase db) - { - var t = this.GetType(); - var mapper = DynamicMapperCache.GetMapper(t); - using (var query = db.From(t)) - { - MakeQueryWhere(mapper, query); - var o = (query.Execute() as IEnumerable).FirstOrDefault(); - - if (o == null) - return false; - - mapper.Map(o, this); - - SetDynamicEntityState(DynamicEntityState.Existing); - _changedFields.Clear(); - } - - return true; - } - - #endregion Select - - #region Query Helpers - - private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicUpdateQueryBuilder query) - { - bool keyNotDefined = true; - - foreach (var cm in mapper.ColumnsMap) - { - if (cm.Value.Column != null && cm.Value.Column.IsKey) - { - query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); - keyNotDefined = false; - } - } - - if (keyNotDefined) - throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", - this.GetType().FullName)); - } - - private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicDeleteQueryBuilder query) - { - bool keyNotDefined = true; - - foreach (var cm in mapper.ColumnsMap) - { - if (cm.Value.Column != null && cm.Value.Column.IsKey) - { - query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); - keyNotDefined = false; - } - } - - if (keyNotDefined) - throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", - this.GetType().FullName)); - } - - private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicSelectQueryBuilder query) - { - bool keyNotDefined = true; - - foreach (var cm in mapper.ColumnsMap) - { - if (cm.Value.Column != null && cm.Value.Column.IsKey) - { - var v = cm.Value.Get(this); - - if (v == null) - throw new InvalidOperationException(String.Format( - "Class '{0}' have key columns {1} not filled with data.", - this.GetType().FullName, cm.Value.Name)); - - query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); - keyNotDefined = false; - } - } - - if (keyNotDefined) - throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", - this.GetType().FullName)); - } - - #endregion Query Helpers - } - - /// Possible states of dynamic database objects. - public enum DynamicEntityState - { - /// Default state. This state will only permit to refresh data from database. - /// In this state repository will be unable to tell if object with this state should be added - /// or updated in database, but you can still manually perform update or insert on such object. - Unknown, - - /// This state should be set to new objects in database. - New, - - /// This state is ser when data is refreshed from database or object was loaded from repository. - Existing, - - /// You can set this state to an object if you want repository to perform delete from database. - ToBeDeleted, - - /// This state is set for objects that were deleted from database. - Deleted, - } - - /// Class containing changed property data. - /// - public class DynamicPropertyChangingEventArgs : EventArgs - { - /// Gets the name of the property. - /// The name of the property. - public string PropertyName { get; private set; } - - /// Gets the old property value. - /// The old value. - public object OldValue { get; private set; } - - /// Gets the new property value. - /// The new value. - public object NewValue { get; private set; } - - /// Initializes a new instance of the class. - /// Name of the property. - /// The old property value. - /// The new property value. - public DynamicPropertyChangingEventArgs(string propertyName, object oldValue, object newValue) - { - PropertyName = propertyName; - OldValue = oldValue; - NewValue = newValue; - } - } - - /// Base repository class for specified object type. - /// Type of stored object. - public class DynamicRepositoryBase : IDisposable where T : DynamicEntityBase - { - private DynamicDatabase _database; - - /// Initializes a new instance of the class. - /// The database. - public DynamicRepositoryBase(DynamicDatabase database) - { - _database = database; - } - - /// Get all rows from database. - /// Objects enumerator. - public virtual IEnumerable GetAll() - { - using (var q = _database.From()) - return EnumerateQuery(q); - } - - /// Get rows from database by custom query. - /// The query. - /// Query must be based on object type. - /// Objects enumerator. - public virtual IEnumerable GetByQuery(IDynamicSelectQueryBuilder query) - { - return EnumerateQuery(query); - } - - private IEnumerable EnumerateQuery(IDynamicSelectQueryBuilder query, bool forceType = true) - { - if (forceType) - { - var mapper = DynamicMapperCache.GetMapper(typeof(T)); - - var tn = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) - ? mapper.Type.Name - : mapper.Table.Name; - - if (!query.Tables.Any(t => t.Name == tn)) - throw new InvalidOperationException(string.Format("Query is not related to '{0}' class.", - typeof(T).FullName)); - } - - foreach (var o in query.Execute()) - { - o.SetDynamicEntityState(DynamicEntityState.Existing); - yield return o; - } - } - - /// Saves single object to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Save(T element) - { - return element.Save(_database); - } - - /// Saves collection of objects to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Save(IEnumerable element) - { - return element.All(x => x.Save(_database)); - } - - /// Insert single object to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Insert(T element) - { - return element.Insert(_database); - } - - /// Insert collection of objects to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Insert(IEnumerable element) - { - return element.All(x => x.Insert(_database)); - } - - /// Update single object to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Update(T element) - { - return element.Update(_database); - } - - /// Update collection of objects to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Update(IEnumerable element) - { - return element.All(x => x.Update(_database)); - } - - /// Delete single object to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Delete(T element) - { - return element.Delete(_database); - } - - /// Delete collection of objects to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Delete(IEnumerable element) - { - return element.All(x => x.Delete(_database)); - } - - /// Releases unmanaged and - optionally - managed resources. - public virtual void Dispose() - { - _database = null; - } - } - } - - namespace Validation - { - /// Required attribute can be used to validate fields in objects using mapper class. - [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] - public class RequiredAttribute : Attribute - { - /// Gets or sets minimum value or length of field. - public decimal? Min { get; set; } - - /// Gets or sets maximum value or length of field. - public decimal? Max { get; set; } - - /// Gets or sets pattern to verify. - public Regex Pattern { get; set; } - - /// Gets or sets a value indicating whether property value is required or not. - public bool Required { get; set; } - - /// Gets or sets a value indicating whether this is an element requirement. - public bool ElementRequirement { get; set; } - - /// Initializes a new instance of the class. - /// This field will be required. - public RequiredAttribute(bool required = true) - { - Required = required; - } - - /// Initializes a new instance of the class. - /// Limiting value to set. - /// Whether set maximum parameter (true) or minimum parameter (false). - /// This field will be required. - public RequiredAttribute(float val, bool max, bool required = true) - { - if (max) - Max = (decimal)val; - else - Min = (decimal)val; - Required = required; - } - - /// Initializes a new instance of the class. - /// Minimum value to set. - /// Maximum value to set. - /// This field will be required. - public RequiredAttribute(float min, float max, bool required = true) - { - Min = (decimal)min; - Max = (decimal)max; - Required = required; - } - - /// Initializes a new instance of the class. - /// Minimum value to set. - /// Maximum value to set. - /// Pattern to check. - /// This field will be required. - public RequiredAttribute(float min, float max, string pattern, bool required = true) - { - Min = (decimal)min; - Max = (decimal)max; - Pattern = new Regex(pattern, RegexOptions.Compiled); - Required = required; - } - - internal ValidateResult ValidateSimpleValue(DynamicPropertyInvoker dpi, object val) - { - return ValidateSimpleValue(dpi.Type, dpi.IsGnericEnumerable, val); - } - - internal ValidateResult ValidateSimpleValue(Type type, bool isGnericEnumerable, object val) - { - if (val == null) - { - if (Required) - return ValidateResult.ValueIsMissing; - else - return ValidateResult.Valid; - } - - if (type.IsValueType) - { - if (val is decimal || val is long || val is int || val is float || val is double || val is short || - val is byte || - val is decimal? || val is long? || val is int? || val is float? || val is double? || - val is short? || val is byte?) - { - decimal dec = Convert.ToDecimal(val); - - if (Min.HasValue && Min.Value > dec) - return ValidateResult.ValueTooSmall; - - if (Max.HasValue && Max.Value < dec) - return ValidateResult.ValueTooLarge; - - return ValidateResult.Valid; - } - else - { - var str = val.ToString(); - - if (Min.HasValue && Min.Value > str.Length) - return ValidateResult.ValueTooShort; - - if (Max.HasValue && Max.Value < str.Length) - return ValidateResult.ValueTooLong; - - if (Pattern != null && !Pattern.IsMatch(str)) - return ValidateResult.ValueDontMatchPattern; - - return ValidateResult.Valid; - } - } - else if (type.IsArray || isGnericEnumerable) - { - int? cnt = null; - - var list = val as IEnumerable; - if (list != null) - cnt = list.Count(); - else - { - var enumerable = val as IEnumerable; - if (enumerable != null) - cnt = enumerable.Cast().Count(); - } - - if (Min.HasValue && Min.Value > cnt) - return ValidateResult.TooFewElementsInCollection; - - if (Max.HasValue && Max.Value < cnt) - return ValidateResult.TooManyElementsInCollection; - - return ValidateResult.Valid; - } - else if (type == typeof(string)) - { - var str = (string)val; - - if (Min.HasValue && Min.Value > str.Length) - return ValidateResult.ValueTooShort; - - if (Max.HasValue && Max.Value < str.Length) - return ValidateResult.ValueTooLong; - - if (Pattern != null && !Pattern.IsMatch(str)) - return ValidateResult.ValueDontMatchPattern; - - return ValidateResult.Valid; - } - - return ValidateResult.NotSupported; - } - } - - /// Validation result enum. - public enum ValidateResult - { - /// The valid value. - Valid, - - /// The value is missing. - ValueIsMissing, - - /// The value too small. - ValueTooSmall, - - /// The value too large. - ValueTooLarge, - - /// The too few elements in collection. - TooFewElementsInCollection, - - /// The too many elements in collection. - TooManyElementsInCollection, - - /// The value too short. - ValueTooShort, - - /// The value too long. - ValueTooLong, - - /// The value don't match pattern. - ValueDontMatchPattern, - - /// The not supported. - NotSupported, - } - - /// Validation result. - public class ValidationResult - { - /// Gets the property invoker. - public DynamicPropertyInvoker Property { get; internal set; } - - /// Gets the requirement definition. - public RequiredAttribute Requirement { get; internal set; } - - /// Gets the value that is broken. - public object Value { get; internal set; } - - /// Gets the result. - public ValidateResult Result { get; internal set; } - } - } -} \ No newline at end of file +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, 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 DynamORM.Builders.Extensions; +using DynamORM.Builders.Implementation; +using DynamORM.Builders; +using DynamORM.Helpers.Dynamics; +using DynamORM.Helpers; +using DynamORM.Mapper; +using DynamORM.Validation; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Collections; +using System.ComponentModel; +using System.Data.Common; +using System.Data; +using System.Dynamic; +using System.IO; +using System.Linq.Expressions; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Text.RegularExpressions; +using System.Text; +using System; + +[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 +{ + +#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 Queryto 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 = owner != null ? Database.StripName(owner) : string.Empty; + 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; + + DynamicTypeMap 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.Owner) ? + null : mapper.Table.Owner; + } + + BuildAndCacheSchema(keys); + } + + #region Schema + + private void BuildAndCacheSchema(string[] keys) + { + Dictionary schema = null; + + schema = Database.GetSchema(TableType) ?? + Database.GetSchema(TableName, OwnerName); + + #region Fill currrent table schema + + if (keys == null && TableType != null) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(TableType); + + if (mapper != null) + { + IEnumerable 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) + { + return Database.Query(sql, args); + } + + /// Enumerate the reader and yield the result. + /// Command builder. + /// Enumerator of objects expanded from query. + public virtual IEnumerable Query(IDynamicQueryBuilder builder) + { + return Database.Query(builder); + } + + /// Create new . + /// New instance. + public virtual IDynamicSelectQueryBuilder Query() + { + IDynamicSelectQueryBuilder builder = new DynamicSelectQueryBuilder(this.Database); + + string 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) + { + return Database.Scalar(sql, args); + } + + /// Returns a single result. + /// Command builder. + /// Result of a query. + public virtual object Scalar(IDynamicQueryBuilder builder) + { + return Database.Scalar(builder); + } + +#if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE + + /// Returns a single result. + /// What kind of result is expected. + /// 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 T ScalarAs(string sql, params object[] args) + { + return Database.ScalarAs(sql, args); + } + + /// Returns a single result. + /// What kind of result is expected. + /// Command builder. + /// Default value. + /// Result of a query. + public virtual T ScalarAs(IDynamicQueryBuilder builder, T defaultValue = default(T)) + { + return Database.ScalarAs(builder, defaultValue); + } + +#endif + + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Number of affected rows. + public virtual int Procedure(string procName) + { + return Database.Procedure(procName); + } + + /// 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, params object[] args) + { + return Database.Procedure(procName, args); + } + + /// 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, DynamicExpando args) + { + return Database.Procedure(procName, args); + } + + /// 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) + { + return Database.Procedure(procName, args); + } + + /// 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) + { + return Database.Execute(sql, args); + } + + /// Execute non query. + /// Command builder. + /// Number of affected rows. + public virtual int Execute(IDynamicQueryBuilder builder) + { + return Database.Execute(builder); + } + + /// Execute non query. + /// Command builders. + /// Number of affected rows. + public virtual int Execute(IDynamicQueryBuilder[] builders) + { + return Database.Execute(builders); + } + + #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 + CallInfo info = binder.CallInfo; + + // Get generic types + IList 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"); + + string 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) + { + DynamicInsertQueryBuilder 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(string.IsNullOrEmpty(this.OwnerName) ? this.TableName : string.Format("{0}.{1}", this.OwnerName, 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++) + { + string fullName = info.ArgumentNames[i]; + string name = fullName.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(fullName, args[i]); + break; + } + } + } + + // Execute + return Execute(builder); + } + + private object DynamicUpdate(object[] args, CallInfo info, IList types) + { + DynamicUpdateQueryBuilder 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(string.IsNullOrEmpty(this.OwnerName) ? this.TableName : string.Format("{0}.{1}", this.OwnerName, 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++) + { + string fullName = info.ArgumentNames[i]; + string name = fullName.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(fullName, args[i]); + break; + } + } + } + + // Execute + return Execute(builder); + } + + private object DynamicDelete(object[] args, CallInfo info, IList types) + { + DynamicDeleteQueryBuilder 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(string.IsNullOrEmpty(this.OwnerName) ? this.TableName : string.Format("{0}.{1}", this.OwnerName, 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++) + { + string fullName = info.ArgumentNames[i]; + string name = fullName.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(fullName, args[i]); + break; + } + } + } + + // Execute + return Execute(builder); + } + + private object DynamicQuery(object[] args, CallInfo info, string op, IList types) + { + object result; + DynamicSelectQueryBuilder 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 => string.IsNullOrEmpty(this.OwnerName) ? this.TableName : string.Format("{0}.{1}", this.OwnerName, 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++) + { + string fullName = info.ArgumentNames[i]; + string name = fullName.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": + { + string 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 => + { + DynamicColumn 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(fullName, 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 + bool justOne = op == "First" || op == "Last" || op == "Get" || op == "Single"; + + // Be sure to sort by DESC on selected columns + 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() + { + if (IsDisposed) + return; + + // 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 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; + } + + if (_db.DumpCommands) + _db.DumpCommand(_command); + + return _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() + { + if (IsDisposed) + return; + + var db = _db; + if (db == null) + { + IsDisposed = true; + return; + } + + lock (db.SyncLock) + { + if (IsDisposed) + return; + + IsDisposed = true; + + if (_con != null) + { + List pool = db.CommandsPool.TryGetValue(_con.Connection); + + if (pool != null && pool.Contains(this)) + pool.Remove(this); + } + + if (_command != null) + { + _command.Parameters.Clear(); + + _command.Dispose(); + _command = null; + } + + _con = null; + _db = null; + } + } + + /// 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. + /// Custom parameter describing transaction options. + /// This action is invoked when transaction is disposed. + /// Returns representation. + internal DynamicTransaction BeginTransaction(IsolationLevel? il, object custom, Action disposed) + { + return new DynamicTransaction(_db, this, _singleTransaction, il, disposed, null); + } + + #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, null); + } + + /// Begins a database transaction with the specified + /// value. + /// One of the values. + /// Returns representation. + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return BeginTransaction(il, null, null); + } + + /// Begins a database transaction with the specified + /// value. + /// Custom parameter describing transaction options. + /// Returns representation. + public IDbTransaction BeginTransaction(object custom) + { + return BeginTransaction(null, custom, 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 { return Connection.Database; } + } + + /// 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() + { + if (IsDisposed) + return; + + var db = _db; + if (db != null && Connection != null) + db.Close(Connection); + + Connection = null; + _db = null; + IsDisposed = true; + } + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + #endregion IExtendedDisposable Members + } + + /// 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() + { + ParameterDirection = ParameterDirection.Input; + } + + /// 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 parameter direction when used in procedure invocation. + public ParameterDirection ParameterDirection { 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 + string[] 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 + string[] 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 + } + + /// Dynamic database is a class responsible for managing database. + public class DynamicDatabase : IExtendedDisposable + { + #region Internal fields and properties + + private DbProviderFactory _provider; + private DynamicProcedureInvoker _proc; + 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; } + + /// Gets active builders that weren't disposed. + internal List RemainingBuilders { 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 the procedures invoker. + /// + /// + /// using (var db = GetORM()) + /// { + /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_Scalar AS SELECT 42;"); + /// var res0 = db.Procedures.sp_Exp_Scalar(); + /// var res1 = db.Procedures.sp_Exp_Scalar(); + /// + /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_ReturnInt AS RETURN 42;"); + /// var res2 = db.Procedures.sp_Exp_ReturnInt(); + /// + /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeData AS + /// SELECT 1 Id, 'Some Name 1' [Name], 'Some Desc 1' [Desc], GETDATE() [Date] + /// UNION ALL SELECT 2 Id, 'Some Name 2', 'Some Desc 2', GETDATE() [Date];"); + /// var res3 = db.Procedures.sp_Exp_SomeData(); + /// var res4 = db.Procedures.sp_Exp_SomeData>(); + /// + /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeInputAndOutput + /// @Name nvarchar(50), + /// @Result nvarchar(256) OUTPUT + /// AS + /// SELECT @Result = 'Hi, ' + @Name + ' your lucky number is 42!';"); + /// var res5 = db.Procedures.sp_Exp_SomeInputAndOutput(Name: "G4g4r1n", out_Result: new DynamicColumn + /// { + /// Schema = new DynamicSchemaColumn + /// { + /// Size = 256, + /// }, + /// }, ret_Return: 0); + /// var res6 = db.Procedures.sp_Exp_SomeInputAndOutput(Name: "G4g4r1n", out_Result: new DynamicSchemaColumn + /// { + /// Size = 256, + /// }, ret_Return: 0); + /// + /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeInputAndOutputWithDataAndReturn + /// @Name nvarchar(50), + /// @Result nvarchar(256) OUTPUT + /// AS + /// SELECT @Result = 'Hi, ' + @Name + ' your lucky number is 42!' + /// + /// SELECT 1 Id, 'Some Name 1' [Name], 'Some Desc 1' [Desc], GETDATE() [Date] + /// UNION ALL SELECT 2 Id, 'Some Name 2', 'Some Desc 2', GETDATE() [Date] + /// + /// RETURN 42;"); + /// var res7 = db.Procedures.sp_Exp_SomeInputAndOutputWithDataAndReturn, sp_Exp_SomeInputAndOutputWithDataAndReturn_Result>(Name: "G4g4r1n", out_Result: new DynamicColumn + /// { + /// Schema = new DynamicSchemaColumn + /// { + /// Size = 256, + /// }, + /// }, ret_Return: 0); + /// var res8 = db.Procedures.sp_Exp_SomeInputAndOutputWithDataAndReturn, sp_Exp_SomeInputAndOutputWithDataAndReturn_Result>(Name: "G4g4r1n", out_Result: new DynamicSchemaColumn + /// { + /// Size = 256, + /// }, ret_Return: 0); + /// } + /// + ///private class sp_Exp_SomeData_Result + ///{ + /// public virtual int Id { get; set; } + /// public virtual string Name { get; set; } + /// public virtual string Desc { get; set; } + /// public virtual DateTime Date { get; set; } + ///} + ///private class sp_Exp_SomeInputAndOutput_Result + ///{ + /// public virtual string Result { get; set; } + /// public virtual string Return { get; set; } + ///} + ///private class sp_Exp_SomeInputAndOutputWithDataAndReturn_Result + ///{ + /// public class Data + /// { + /// public virtual int Id { get; set; } + /// public virtual string Name { get; set; } + /// public virtual string Desc { get; set; } + /// public virtual DateTime Date { get; set; } + /// } + /// public virtual List sp_Exp_SomeInputAndOutputWithDataAndReturn { get; set; } + /// public virtual string Result { get; set; } + /// public virtual string Return { get; set; } + ///} + /// + /// + public dynamic Procedures + { + get + { + if (_proc == null) + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection doesn't support stored procedures."); + + _proc = new DynamicProcedureInvoker(this); + } + + return _proc; + } + } + + /// Gets or sets a value indicating whether + /// dump commands to console or not. + public bool DumpCommands { get; set; } + + /// Gets or sets the dump command delegate. + /// The dump command delegate. + public Action DumpCommandDelegate { get; set; } + +#if NETFRAMEWORK + // https://github.com/dotnet/runtime/issues/26229 + /// 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) + { + } +#endif + + /// 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); + } + + private DbProviderFactory FindDbProviderFactoryFromConnection(Type t) + { + foreach (var type in t.Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(DbProviderFactory)))) + { + DbProviderFactory provider = null; + bool dispose = false; + + var pi = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty); + if (pi != null) + provider = (DbProviderFactory)pi.GetValue(null, null); + else + { + var fi = type.GetField("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField); + if (fi != null) + provider = (DbProviderFactory)fi.GetValue(null); + } + + if (provider == null) + { + var ci = type.GetConstructor(Type.EmptyTypes); + if (ci != null) + { + provider = ci.Invoke(null) as DbProviderFactory; + dispose = true; + } + } + + try + { + if (provider != null) + { + using (var c = provider.CreateConnection()) + { + if (c.GetType() == t) + return provider; + } + } + } + finally + { + if (provider != null && dispose && provider is IDisposable) + ((IDisposable)provider).Dispose(); + } + } + + return null; + } + + /// Initializes a new instance of the class. + /// Active database connection. + /// Connection options. required. + public DynamicDatabase(IDbConnection connection, DynamicDatabaseOptions options) + { + // Try to find correct provider if possible + _provider = FindDbProviderFactoryFromConnection(connection.GetType()); + + 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>(); + RemainingBuilders = new List(); +#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 (KeyValuePair item in TablesCache.Where(kvp => kvp.Value == dynamicTable).ToList()) + TablesCache.Remove(item.Key); + } + +#endif + + #endregion Table + + /// Adds cached builder. + /// New dynamic builder. + internal void AddToCache(IDynamicQueryBuilder builder) + { + lock (SyncLock) + RemainingBuilders.Add(builder); + } + + /// Removes cached builder. + /// Disposed dynamic builder. + internal void RemoveFromCache(IDynamicQueryBuilder builder) + { + lock (SyncLock) + RemainingBuilders.Remove(builder); + } + + #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. + /// Table alias. + /// use no lock. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder From(string alias = null, bool noLock = false) + { + // TODO: Make it more readable and maitainable + if (noLock) + { + if (string.IsNullOrEmpty(alias)) + return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).NoLock()); + else + return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias).NoLock()); + } + else + { + if (string.IsNullOrEmpty(alias)) + return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T))); + else + return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias)); + } + } + + /// 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 + { + return Insert(typeof(T), e); + } + + /// 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(Type t, IEnumerable e) + { + int affected = 0; + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); + + if (mapper != null) + { + using (IDbConnection con = Open()) + using (IDbTransaction tra = con.BeginTransaction()) + using (IDbCommand cmd = con.CreateCommand()) + { + try + { + Dictionary parameters = new Dictionary(); + + if (!string.IsNullOrEmpty(mapper.InsertCommandText)) + { + cmd.CommandText = mapper.InsertCommandText; + + foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values + .Where(di => !di.Ignore && di.InsertCommandParameter != null) + .OrderBy(di => di.InsertCommandParameter.Ordinal)) + { + IDbDataParameter para = cmd.CreateParameter(); + para.ParameterName = col.InsertCommandParameter.Name; + para.DbType = col.InsertCommandParameter.Type; + cmd.Parameters.Add(para); + + parameters[para] = col; + } + } + else + PrepareBatchInsert(t, mapper, cmd, parameters); + + foreach (var o in e) + { + foreach (KeyValuePair 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; + + StringBuilder 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 + { + return Update(typeof(T), e); + } + + /// 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(Type t, IEnumerable e) + { + int affected = 0; + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); + + if (mapper != null) + { + using (IDbConnection con = Open()) + using (IDbTransaction tra = con.BeginTransaction()) + using (IDbCommand cmd = con.CreateCommand()) + { + try + { + Dictionary parameters = new Dictionary(); + + if (!string.IsNullOrEmpty(mapper.UpdateCommandText)) + { + cmd.CommandText = mapper.UpdateCommandText; + + foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values + .Where(di => !di.Ignore && di.UpdateCommandParameter != null) + .OrderBy(di => di.UpdateCommandParameter.Ordinal)) + { + IDbDataParameter para = cmd.CreateParameter(); + para.ParameterName = col.UpdateCommandParameter.Name; + para.DbType = col.UpdateCommandParameter.Type; + cmd.Parameters.Add(para); + + parameters[para] = col; + } + } + else + PrepareBatchUpdate(t, mapper, cmd, parameters); + + foreach (var o in e) + { + foreach (KeyValuePair 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; + + StringBuilder 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 + { + return UpdateOrInsert(typeof(T), e); + } + + /// 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(Type t, IEnumerable e) + { + int affected = 0; + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); + + if (mapper != null) + { + using (IDbConnection con = Open()) + using (IDbTransaction tra = con.BeginTransaction()) + using (IDbCommand cmdUp = con.CreateCommand()) + using (IDbCommand cmdIn = con.CreateCommand()) + { + try + { + #region Update + + Dictionary parametersUp = new Dictionary(); + + if (!string.IsNullOrEmpty(mapper.UpdateCommandText)) + { + cmdUp.CommandText = mapper.UpdateCommandText; + + foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values + .Where(di => !di.Ignore && di.UpdateCommandParameter != null) + .OrderBy(di => di.UpdateCommandParameter.Ordinal)) + { + IDbDataParameter para = cmdUp.CreateParameter(); + para.ParameterName = col.UpdateCommandParameter.Name; + para.DbType = col.UpdateCommandParameter.Type; + cmdUp.Parameters.Add(para); + + parametersUp[para] = col; + } + } + else + PrepareBatchUpdate(t, mapper, cmdUp, parametersUp); + + #endregion Update + + #region Insert + + Dictionary parametersIn = new Dictionary(); + + if (!string.IsNullOrEmpty(mapper.InsertCommandText)) + { + cmdIn.CommandText = mapper.InsertCommandText; + + foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values + .Where(di => !di.Ignore && di.InsertCommandParameter != null) + .OrderBy(di => di.InsertCommandParameter.Ordinal)) + { + IDbDataParameter para = cmdIn.CreateParameter(); + para.ParameterName = col.InsertCommandParameter.Name; + para.DbType = col.InsertCommandParameter.Type; + cmdIn.Parameters.Add(para); + + parametersIn[para] = col; + } + } + else + PrepareBatchInsert(t, mapper, cmdIn, parametersIn); + + #endregion Insert + + foreach (var o in e) + { + foreach (KeyValuePair m in parametersUp) + m.Key.Value = m.Value.Get(o); + + int a = cmdUp.ExecuteNonQuery(); + if (a == 0) + { + foreach (KeyValuePair 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; + + StringBuilder 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)); + } + + /// Adds to the DELETE FROM clause using . + /// Type which can be represented in database. + /// This instance to permit chaining. + public virtual IDynamicDeleteQueryBuilder Delete(Type t) + { + return new DynamicDeleteQueryBuilder(this).Table(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 + { + return Delete(typeof(T), e); + } + + /// 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(Type t, IEnumerable e) + { + int affected = 0; + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); + + if (mapper != null) + { + using (IDbConnection con = Open()) + using (IDbTransaction tra = con.BeginTransaction()) + using (IDbCommand cmd = con.CreateCommand()) + { + try + { + Dictionary parameters = new Dictionary(); + + if (!string.IsNullOrEmpty(mapper.DeleteCommandText)) + { + cmd.CommandText = mapper.DeleteCommandText; + + foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values + .Where(di => !di.Ignore && di.DeleteCommandParameter != null) + .OrderBy(di => di.DeleteCommandParameter.Ordinal)) + { + IDbDataParameter para = cmd.CreateParameter(); + para.ParameterName = col.DeleteCommandParameter.Name; + para.DbType = col.DeleteCommandParameter.Type; + cmd.Parameters.Add(para); + + parameters[para] = col; + } + } + else + PrepareBatchDelete(t, mapper, cmd, parameters); + + foreach (var o in e) + { + foreach (KeyValuePair 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; + + StringBuilder problematicCommand = new StringBuilder(); + cmd.Dump(problematicCommand); + + throw new InvalidOperationException(problematicCommand.ToString(), ex); + } + } + } + + return affected; + } + + private void PrepareBatchInsert(DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + PrepareBatchInsert(typeof(T), mapper, cmd, parameters); + } + + private void PrepareBatchInsert(Type t, DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + DynamicPropertyInvoker currentprop = null; + Dictionary temp = new Dictionary(); + Dictionary schema = this.GetSchema(t); + int ord = 0; + + IDynamicInsertQueryBuilder ib = Insert(t) + .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 (KeyValuePair prop in mapper.PropertyMap) + if (!mapper.Ignored.Contains(prop.Key)) + { + string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; + currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); + + if (currentprop.Ignore || (currentprop.Column != null && currentprop.Column.IsNoInsert)) + continue; + + if (currentprop.Get != null) + ib.Insert(new DynamicColumn() + { + ColumnName = col, + Schema = schema == null ? null : schema.TryGetNullable(col.ToLower()), + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + } + + ib.FillCommand(cmd); + + // Cache command + mapper.InsertCommandText = cmd.CommandText; + } + + private void PrepareBatchUpdate(DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + PrepareBatchUpdate(typeof(T), mapper, cmd, parameters); + } + + private void PrepareBatchUpdate(Type t, DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + DynamicPropertyInvoker currentprop = null; + Dictionary temp = new Dictionary(); + Dictionary schema = this.GetSchema(t); + int ord = 0; + + IDynamicUpdateQueryBuilder ib = Update(t) + .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 (KeyValuePair prop in mapper.PropertyMap) + if (!mapper.Ignored.Contains(prop.Key)) + { + string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; + currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); + + if (currentprop.Ignore) + continue; + + if (currentprop.Get != null) + { + DynamicSchemaColumn? colS = schema == null ? null : schema.TryGetNullable(col.ToLower()); + + if (colS.HasValue) + { + if (colS.Value.IsKey) + ib.Where(new DynamicColumn() + { + ColumnName = col, + Schema = colS, + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + else if (currentprop.Column == null || !currentprop.Column.IsNoUpdate) + ib.Values(new DynamicColumn() + { + ColumnName = col, + Schema = colS, + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + } + else if (currentprop.Column != null && currentprop.Column.IsKey) + ib.Where(new DynamicColumn() + { + ColumnName = col, + Schema = colS, + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + } + } + + ib.FillCommand(cmd); + + // Cache command + mapper.UpdateCommandText = cmd.CommandText; + } + + private void PrepareBatchDelete(DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + PrepareBatchDelete(typeof(T), mapper, cmd, parameters); + } + + private void PrepareBatchDelete(Type t, DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + DynamicPropertyInvoker currentprop = null; + Dictionary temp = new Dictionary(); + Dictionary schema = this.GetSchema(t); + int ord = 0; + + IDynamicDeleteQueryBuilder ib = Delete(t) + .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 (KeyValuePair prop in mapper.PropertyMap) + if (!mapper.Ignored.Contains(prop.Key)) + { + string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; + currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); + + if (currentprop.Ignore) + continue; + + if (currentprop.Get != null) + { + DynamicSchemaColumn? colS = schema == null ? null : schema.TryGetNullable(col.ToLower()); + + if (colS != null) + { + if (colS.Value.IsKey) + ib.Where(new DynamicColumn() + { + ColumnName = col, + Schema = colS, + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + } + else if (currentprop.Column != null && currentprop.Column.IsKey) + ib.Where(new DynamicColumn() + { + ColumnName = col, + Schema = colS, + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + } + } + + ib.FillCommand(cmd); + + // Cache command + mapper.DeleteCommandText = cmd.CommandText; + } + + #endregion From/Insert/Update/Delete + + #region Procedure + + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Number of affected rows. + public virtual int Procedure(string procName) + { + return Procedure(procName, (DynamicExpando)null); + } + + /// 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, params object[] args) + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(this, args) + .ExecuteNonQuery(); + } + } + + /// 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, DynamicExpando args) + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(this, args) + .ExecuteNonQuery(); + } + } + + /// 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) + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(this, args) + .ExecuteNonQuery(); + } + } + + #endregion Procedure + + #region Execute + + /// 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 (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(sql).AddParameters(this, args) + .ExecuteNonQuery(); + } + } + + /// Execute non query. + /// Command builder. + /// Number of affected rows. + public virtual int Execute(IDynamicQueryBuilder builder) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(builder) + .ExecuteNonQuery(); + } + } + + /// Execute non query. + /// Command builders. + /// Number of affected rows. + public virtual int Execute(IDynamicQueryBuilder[] builders) + { + int ret = 0; + + using (IDbConnection con = Open()) + { + using (IDbTransaction trans = con.BeginTransaction()) + { + foreach (IDynamicQueryBuilder builder in builders) + using (IDbCommand cmd = con.CreateCommand()) + ret += cmd + .SetCommand(builder) + .ExecuteNonQuery(); + + trans.Commit(); + } + } + + return ret; + } + + #endregion Execute + + #region Scalar + + /// 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 (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(sql).AddParameters(this, args) + .ExecuteScalar(); + } + } + + /// Returns a single result. + /// Command builder. + /// Result of a query. + public virtual object Scalar(IDynamicQueryBuilder builder) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(builder) + .ExecuteScalar(); + } + } + +#if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE + + /// Returns a single result. + /// What kind of result is expected. + /// 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 T ScalarAs(string sql, params object[] args) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(sql).AddParameters(this, args) + .ExecuteScalarAs(); + } + } + + /// Returns a single result. + /// What kind of result is expected. + /// Command builder. + /// Default value. + /// Result of a query. + public virtual T ScalarAs(IDynamicQueryBuilder builder, T defaultValue = default(T)) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(builder) + .ExecuteScalarAs(defaultValue); + } + } + +#endif + + #endregion Scalar + + #region Query + + /// 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 (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(sql) + .AddParameters(this, args) + .ExecuteReader()) + using (IDataReader cache = new DynamicCachedReader(rdr)) + { + while (cache.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 = cache.RowToDynamic(); + } + catch (ArgumentException argex) + { + StringBuilder 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 (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(builder) + .ExecuteReader()) + using (var cache = new DynamicCachedReader(rdr)) + while (cache.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 = cache.RowToDynamic(); + } + catch (ArgumentException argex) + { + StringBuilder 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; + } + } + } + + #endregion Query + + #region CachedQuery + + /// 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 DynamicCachedReader CachedQuery(string sql, params object[] args) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(sql) + .AddParameters(this, args) + .ExecuteReader()) + return new DynamicCachedReader(rdr); + } + } + + /// Enumerate the reader and yield the result. + /// Command builder. + /// Enumerator of objects expanded from query. + public virtual DynamicCachedReader CachedQuery(IDynamicQueryBuilder builder) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(builder) + .ExecuteReader()) + return new DynamicCachedReader(rdr); + } + } + + #endregion Query + + #region Schema + + /// Builds query cache if necessary and returns it. + /// The builder containing query to read schema from. + /// Query schema. + public Dictionary GetQuerySchema(IDynamicSelectQueryBuilder builder) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand().SetCommand(builder)) + return ReadSchema(cmd) + .Distinct() + .ToDictionary(k => k.Name.ToLower(), k => k); + } + + /// Builds query cache if necessary and returns it. + /// SQL query from which read schema. + /// SQL query arguments. + /// Query schema. + public Dictionary GetQuerySchema(string sql, params object[] args) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand().SetCommand(sql, args)) + return ReadSchema(cmd) + .Distinct() + .ToDictionary(k => k.Name.ToLower(), k => k); + } + + /// 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).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; + } + + /// Clears the schema from cache. + /// Use this method to refresh table information. + /// Name of table for which clear schema. + /// Owner of table for which clear schema. + public void ClearSchema(string table = null, string owner = null) + { + lock (SyncLock) + if (Schema.ContainsKey(table.ToLower())) + Schema.Remove(table.ToLower()); + } + + /// Clears the schema from cache. + /// Use this method to refresh table information. + /// Type of table for which clear schema. + public void ClearSchema() + { + ClearSchema(typeof(T)); + } + + /// Clears the schema from cache. + /// Use this method to refresh table information. + /// Type of table for which clear schema. + public void ClearSchema(Type table) + { + lock (SyncLock) + if (Schema.ContainsKey(table.FullName)) + { + if (Schema[table.FullName] != null) + Schema[table.FullName].Clear(); + Schema.Remove(table.FullName); + } + } + + /// Clears the all schemas from cache. + /// Use this method to refresh all table information. + public void ClearSchema() + { + lock (SyncLock) + { + foreach (KeyValuePair> s in Schema) + if (s.Value != null) + s.Value.Clear(); + + Schema.Clear(); + } + } + + /// 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 IList ReadSchema(string table, string owner) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand() + .SetCommand(string.Format("SELECT * FROM {0}{1} WHERE 1 = 0", + !string.IsNullOrEmpty(owner) ? string.Format("{0}.", DecorateName(owner)) : string.Empty, + DecorateName(table)))) + return ReadSchema(cmd) + .ToList(); + } + + /// Get schema describing objects from reader. + /// Command containing query to execute. + /// List of objects . + /// If your database doesn't get those values in upper case (like most of the databases) you should override this method. + public virtual IEnumerable ReadSchema(IDbCommand cmd) + { + DataTable st = null; + + using (IDataReader rdr = cmd.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo)) + st = rdr.GetSchemaTable(); + + using (st) + foreach (DataRow col in st.Rows) + { + dynamic c = col.RowToDynamicUpper(); + + yield return new DynamicSchemaColumn + { + Name = c.COLUMNNAME, + Type = ReadSchemaType(c), + IsKey = c.ISKEY ?? false, + IsUnique = c.ISUNIQUE ?? false, + AllowNull = c.ALLOWNULL ?? false, + Size = (int)(c.COLUMNSIZE ?? 0), + Precision = (byte)(c.NUMERICPRECISION ?? 0), + Scale = (byte)(c.NUMERICSCALE ?? 0) + }; + } + } + + /// Reads the type of column from the schema. + /// The schema. + /// Generic parameter type. + protected virtual DbType ReadSchemaType(dynamic schema) + { + Type type = (Type)schema.DATATYPE; + + // Small hack for SQL Server Provider + if (type == typeof(string) && Provider != null && Provider.GetType().Name == "SqlClientFactory") + { + var map = schema as IDictionary; + string typeName = (map.TryGetValue("DATATYPENAME") ?? string.Empty).ToString(); + + switch (typeName) + { + case "varchar": + return DbType.AnsiString; + + case "nvarchar": + return DbType.String; + } + } + + return DynamicExtensions.TypeMap.TryGetNullable(type) ?? DbType.String; + } + + 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; + owner = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Owner) ? + null : mapper.Table.Owner; + } + + 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) + .Where(x => x.Name != null) + .DistinctBy(x => x.Name.ToLower()) + .ToDictionary(k => k.Name.ToLower(), k => k); + + Schema[tableName.ToLower()] = schema; + } + + #endregion Database schema + + #region Type schema + + if ((mapperSchema && !Schema.ContainsKey(mapper.Type.FullName)) || + (schema == null && mapper != null && !mapper.Type.IsAnonymous())) + { + // 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 : DynamicExtensions.TypeMap.TryGetNullable(v.Value.Type) ?? DbType.String).Value, + IsUnique = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.IsUnique : null, + col.HasValue ? col.Value.IsUnique : false).Value, + AllowNull = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.AllowNull : true, + col.HasValue ? col.Value.AllowNull : true).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, DynamicExtensions.TypeMap.TryGetNullable(v.Value.Type) ?? DbType.String).Value, + IsUnique = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.IsUnique : null, false).Value, + AllowNull = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.AllowNull : true, true).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(" ", "_")); + } + + /// Dumps the command into console output. + /// The command to dump. + public virtual void DumpCommand(IDbCommand cmd) + { + if (DumpCommandDelegate != null) + DumpCommandDelegate(cmd, cmd.DumpToString()); + else + cmd.Dump(Console.Out); + } + + #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)) + { + List 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(); + connection = null; + } + } + + /// 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, null, () => + { + Stack t = TransactionPool.TryGetValue(_tempConn.Connection); + + if (t == null | t.Count == 0) + { + _tempConn.Dispose(); + _tempConn = null; + } + }); + } + + /// Begins a database transaction with the specified + /// value. + /// One of the values. + /// Returns representation. + public IDbTransaction BeginTransaction(IsolationLevel il) + { + _tempConn = Open() as DynamicConnection; + + return _tempConn.BeginTransaction(il, null, () => + { + Stack t = TransactionPool.TryGetValue(_tempConn.Connection); + + if (t == null | t.Count == 0) + { + _tempConn.Dispose(); + _tempConn = null; + } + }); + } + + /// Begins a database transaction with the specified + /// value. + /// Custom parameter describing transaction options. + /// Returns representation. + public IDbTransaction BeginTransaction(object custom) + { + _tempConn = Open() as DynamicConnection; + + return _tempConn.BeginTransaction(null, custom, () => + { + Stack 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 (IsDisposed) + return; + +#if !DYNAMORM_OMMIT_OLDSYNTAX + List tables = TablesCache.Values.ToList(); + TablesCache.Clear(); + + tables.ForEach(t => t.Dispose()); + tables.Clear(); + tables = null; +#endif + + foreach (KeyValuePair> con in TransactionPool) + { + // Close all commands + if (CommandsPool.ContainsKey(con.Key)) + { + List tmp = CommandsPool[con.Key].ToList(); + tmp.ForEach(cmd => cmd.Dispose()); + + CommandsPool[con.Key].Clear(); + tmp.Clear(); + CommandsPool[con.Key] = tmp = null; + } + + // 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(); + } + + while (RemainingBuilders.Count > 0) + RemainingBuilders.First().Dispose(); + + // Clear pools + lock (SyncLock) + { + TransactionPool.Clear(); + CommandsPool.Clear(); + RemainingBuilders.Clear(); + + TransactionPool = null; + CommandsPool = null; + RemainingBuilders = null; + } + + ClearSchema(); + if (_proc != null) + _proc.Dispose(); + + _proc = null; + _tempConn = null; + IsDisposed = true; + } + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + #endregion IExtendedDisposable Members + } + + /// Dynamic query exception. + [Serializable] + public class DynamicQueryException : Exception, ISerializable + { + /// Initializes a new instance of the + /// class. + /// The command which failed. + public DynamicQueryException(IDbCommand command = null) + : base(string.Format("Error executing command.{0}{1}", Environment.NewLine, command != null ? command.DumpToString() : string.Empty)) + { + } + + /// Initializes a new instance of the + /// class. + /// The message. + /// The command which failed. + public DynamicQueryException(string message, IDbCommand command = null) + : base(string.Format("{0}{1}{2}", message, Environment.NewLine, command != null ? command.DumpToString() : string.Empty)) + { + } + + /// Initializes a new instance of the + /// class. + /// The inner exception. + /// The command which failed. + public DynamicQueryException(Exception innerException, IDbCommand command = null) + : base(string.Format("Error executing command.{0}{1}", Environment.NewLine, command != null ? command.DumpToString() : string.Empty), innerException) + { + } + + /// 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(string.Format("{0}{1}{2}", message, Environment.NewLine, command != null ? command.DumpToString() : string.Empty), innerException) + { + } + } + + /// Cache data reader in memory. + public class DynamicCachedReader : DynamicObject, IDataReader + { + #region Constructor and Data + + private class Data + { + internal DataTable _schema; + internal int _fields; + internal int _rows; + internal int _rowsAffected; + internal int _position = -1; + internal int _cachePos = -1; + + internal IList _names; + internal IDictionary _ordinals; + internal IList _types; + internal IList _cache; + } + + private List _data = new List(); + private int _currentDataPosition = 0; + private bool _isDisposed; + + private DynamicCachedReader() + { + } + + /// Initializes a new instance of the class. + /// Reader to cache. + /// The offset row. + /// The limit to number of tows. -1 is no limit. + /// The progress delegate. + public DynamicCachedReader(IDataReader reader, int offset = 0, int limit = -1, Func progress = null) + { + InitDataReader(reader, offset, limit, progress); + } + + #endregion Constructor and Data + + #region Helpers + + /// Create data reader from dynamic enumerable. + /// List of objects. + /// Instance of containing objects data. + public static DynamicCachedReader FromDynamicEnumerable(IEnumerable objects) + { + var first = (objects as IEnumerable).FirstOrDefault(); + + if (first == null) + return null; + + var firstDict = first as IDictionary; + var r = new DynamicCachedReader(); + r.Init(firstDict.Keys.Count); + + r._data.Add(new Data()); + + var data = r._data[r._currentDataPosition]; + + for (int i = 0; i < firstDict.Keys.Count; i++) + data._types.Add(null); + + foreach (dynamic elem in (objects as IEnumerable)) + { + int c = 0; + var dict = elem as IDictionary; + + foreach (var k in firstDict.Keys) + { + object val = dict[k]; + + data._cache.Add(val); + + if (data._types[c] == null && val != null) + data._types[c] = val.GetType(); + + c++; + } + + data._rows++; + } + + for (int i = 0; i < firstDict.Keys.Count; i++) + if (data._types[i] == null) + data._types[i] = typeof(string); + + data._schema = new DataTable("DYNAMIC"); + data._schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); + data._schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); + data._schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); + data._schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); + data._schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); + data._schema.Columns.Add(new DataColumn("DataType", typeof(Type))); + data._schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); + data._schema.Columns.Add(new DataColumn("NativeType", typeof(int))); + data._schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); + + int ordinal = 0; + DataRow dr = null; + + foreach (var column in firstDict.Keys) + { + dr = data._schema.NewRow(); + + dr[0] = column; + dr[1] = ordinal; + dr[2] = 0; + dr[3] = 0; + dr[4] = 0; + dr[5] = data._types[ordinal]; + dr[6] = data._types[ordinal].ToDbType(); + dr[7] = data._types[ordinal].ToDbType(); + dr[8] = true; + dr[9] = false; + dr[10] = false; + dr[11] = false; + + data._schema.Rows.Add(dr); + + data._names.Add(dr[0].ToString()); + data._ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); + data._types.Add((Type)dr[5]); + + dr.AcceptChanges(); + } + + dr.AcceptChanges(); + + return r; + } + + /// Create data reader from enumerable. + /// Type of enumerated objects. + /// List of objects. + /// Instance of containing objects data. + public static DynamicCachedReader FromEnumerable(IEnumerable objects) + { + var mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidCastException(string.Format("Object type '{0}' can't be mapped.", typeof(T).FullName)); + + var r = new DynamicCachedReader(); + r.Init(mapper.ColumnsMap.Count + 1); + r.CreateSchemaTable(mapper); + r.FillFromEnumerable(objects, mapper); + + r.IsClosed = false; + + return r; + } + + /// Create data reader from enumerable. + /// Type of enumerated objects. + /// List of objects. + /// Instance of containing objects data. + public static DynamicCachedReader FromEnumerable(Type elementType, IEnumerable objects) + { + var mapper = DynamicMapperCache.GetMapper(elementType); + + if (mapper == null) + throw new InvalidCastException(string.Format("Object type '{0}' can't be mapped.", elementType.FullName)); + + var r = new DynamicCachedReader(); + r.Init(mapper.ColumnsMap.Count + 1); + r.CreateSchemaTable(mapper); + r.FillFromEnumerable(elementType, objects, mapper); + + r.IsClosed = false; + + return r; + } + + private void InitDataReader(IDataReader reader, int offset = 0, int limit = -1, Func progress = null) + { + do + { + Init(reader.FieldCount); + + var data = _data[_currentDataPosition]; + + data._schema = reader.GetSchemaTable(); + data._rowsAffected = reader.RecordsAffected; + + int i = 0; + + for (i = 0; i < data._fields; i++) + { + data._names.Add(reader.GetName(i)); + data._types.Add(reader.GetFieldType(i)); + + if (!data._ordinals.ContainsKey(reader.GetName(i).ToUpper())) + data._ordinals.Add(reader.GetName(i).ToUpper(), i); + } + + int current = 0; + while (reader.Read()) + { + if (current < offset) + { + current++; + continue; + } + + for (i = 0; i < data._fields; i++) + data._cache.Add(reader[i]); + + data._rows++; + current++; + + if (limit >= 0 && data._rows >= limit) + break; + + if (progress != null && !progress(this, data._rows)) + break; + } + + IsClosed = false; + data._position = -1; + data._cachePos = -1; + + if (progress != null) + progress(this, data._rows); + } while (reader.NextResult()); + + reader.Close(); + } + + private void FillFromEnumerable(IEnumerable objects, DynamicTypeMap mapper) + { + var data = _data[_currentDataPosition]; + foreach (var elem in objects) + { + foreach (var col in mapper.ColumnsMap) + { + object val = null; + + if (col.Value.Get != null) + val = col.Value.Get(elem); + + data._cache.Add(val); + } + + data._cache.Add(elem); + + data._rows++; + } + } + + private void FillFromEnumerable(Type elementType, IEnumerable objects, DynamicTypeMap mapper) + { + var data = _data[_currentDataPosition]; + + foreach (var elem in objects) + { + foreach (var col in mapper.ColumnsMap) + { + object val = null; + + if (col.Value.Get != null) + val = col.Value.Get(elem); + + data._cache.Add(val); + } + + data._cache.Add(elem); + + data._rows++; + } + } + + private void CreateSchemaTable(DynamicTypeMap mapper) + { + var data = _data[_currentDataPosition]; + data._schema = new DataTable("DYNAMIC"); + data._schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); + data._schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); + data._schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); + data._schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); + data._schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); + data._schema.Columns.Add(new DataColumn("DataType", typeof(Type))); + data._schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); + data._schema.Columns.Add(new DataColumn("NativeType", typeof(int))); + data._schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); + + int ordinal = 0; + DataRow dr = null; + + foreach (var column in mapper.ColumnsMap) + { + dr = data._schema.NewRow(); + + dr[0] = column.Value.Column.NullOr(x => x.Name ?? column.Value.Name, column.Value.Name); + dr[1] = ordinal; + dr[2] = column.Value.Column.NullOr(x => x.Size ?? int.MaxValue, int.MaxValue); + dr[3] = column.Value.Column.NullOr(x => x.Precision ?? 0, 0); + dr[4] = column.Value.Column.NullOr(x => x.Scale ?? 0, 0); + dr[5] = column.Value.Column.NullOr(x => x.Type.HasValue ? x.Type.Value.ToType() : column.Value.Type, column.Value.Type); + dr[6] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), column.Value.Type.ToDbType()); + dr[7] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), column.Value.Type.ToDbType()); + dr[8] = column.Value.Column.NullOr(x => x.IsKey, false) ? true : column.Value.Column.NullOr(x => x.AllowNull, true); + dr[9] = column.Value.Column.NullOr(x => x.IsUnique, false); + dr[10] = column.Value.Column.NullOr(x => x.IsKey, false); + dr[11] = false; + + data._schema.Rows.Add(dr); + + data._names.Add(dr[0].ToString()); + data._ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); + data._types.Add((Type)dr[5]); + + dr.AcceptChanges(); + } + + dr = data._schema.NewRow(); + + dr[0] = "#O"; + dr[1] = ordinal; + dr[2] = int.MaxValue; + dr[3] = 0; + dr[4] = 0; + dr[5] = mapper.Type; + dr[6] = DbType.Object; + dr[7] = DbType.Object; + dr[8] = true; + dr[9] = false; + dr[10] = false; + dr[11] = false; + + data._schema.Rows.Add(dr); + + data._names.Add("#O"); + data._ordinals.Add("#O".ToUpper(), ordinal++); + data._types.Add(mapper.Type); + + dr.AcceptChanges(); + } + + private void Init(int fieldCount) + { + _data.Add(new Data() + { + _rows = 0, + _fields = fieldCount, + _names = new List(fieldCount), + _ordinals = new Dictionary(fieldCount), + _types = new List(fieldCount), + _cache = new List(fieldCount * 100), + }); + + _currentDataPosition = _data.Count - 1; + } + + /// Sets the current position in reader. + /// The position. + public void SetPosition(int pos) + { + if (pos >= -1 && pos < _data[_currentDataPosition]._rows) + { + _data[_currentDataPosition]._position = pos; + _data[_currentDataPosition]._cachePos = _data[_currentDataPosition]._position * _data[_currentDataPosition]._fields; + } + else + throw new IndexOutOfRangeException(); + } + + #endregion Helpers + + #region IDataReader Members + + /// Closes the System.Data.IDataReader Object. + public void Close() + { + IsClosed = true; + _currentDataPosition = -1; + } + + /// Gets a value indicating the depth of nesting for the current row. + /// This implementation use this field to indicate row count. + public int Depth + { + get { return _data[_currentDataPosition]._rows; } + } + + /// Returns a System.Data.DataTable that describes the column metadata of the + /// System.Data.IDataReader.A System.Data.DataTable that describes + /// the column metadata. + /// The System.Data.IDataReader is closed. + public DataTable GetSchemaTable() + { + return _data[_currentDataPosition]._schema; + } + + /// Gets a value indicating whether the data reader is closed. + public bool IsClosed { get; private set; } + + /// Advances the data reader to the next result, when reading the results of batch SQL statements. + /// Returns true if there are more rows; otherwise, false. + public bool NextResult() + { + _currentDataPosition++; + + return _data.Count < _currentDataPosition; + } + + /// Advances the System.Data.IDataReader to the next record. + /// Returns true if there are more rows; otherwise, false. + public bool Read() + { + var data = _data[_currentDataPosition]; + + data._cachePos = (++data._position) * data._fields; + + return data._position < data._rows; + } + + /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. + /// The number of rows changed, inserted, or deleted; 0 if no rows were affected or the statement + /// failed; and -1 for SELECT statements. + public int RecordsAffected { get { return _data[_currentDataPosition]._rowsAffected; } } + + #endregion IDataReader Members + + #region IDisposable Members + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + IsClosed = true; + + if (_data == null) + return; + + foreach (var data in _data) + { + if (data == null) + continue; + + if (data._names != null) + data._names.Clear(); + if (data._types != null) + data._types.Clear(); + if (data._cache != null) + data._cache.Clear(); + if (data._schema != null) + data._schema.Dispose(); + } + + _data.Clear(); + } + + #endregion IDisposable Members + + #region IDataRecord Members + + /// Gets the number of columns in the current row. + /// When not positioned in a valid record set, 0; otherwise, the number of columns in the current record. The default is -1. + public int FieldCount { get { return _data[_currentDataPosition]._fields; } } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public bool GetBoolean(int i) + { + var data = _data[_currentDataPosition]; + return (bool)data._cache[data._cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public byte GetByte(int i) + { + var data = _data[_currentDataPosition]; + return (byte)data._cache[data._cachePos + i]; + } + + /// Reads a stream of bytes from the specified column offset into the buffer + /// as an array, starting at the given buffer offset. + /// The index of the field to find. + /// The index within the field from which to start the read operation. + /// The buffer into which to read the stream of bytes. + /// The index for buffer to start the read operation. + /// The number of bytes to read. + /// The actual number of bytes read. + public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + { + var data = _data[_currentDataPosition]; + + using (MemoryStream ms = new MemoryStream((byte[])data._cache[data._cachePos + i])) + return ms.Read(buffer, bufferoffset, length); + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public char GetChar(int i) + { + var data = _data[_currentDataPosition]; + return (char)data._cache[data._cachePos + i]; + } + + /// Reads a stream of characters from the specified column offset into the buffer + /// as an array, starting at the given buffer offset. + /// The zero-based column ordinal. + /// The index within the row from which to start the read operation. + /// The buffer into which to read the stream of bytes. + /// The index for buffer to start the read operation. + /// The number of bytes to read. + /// The actual number of characters read. + public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) + { + var data = _data[_currentDataPosition]; + + using (MemoryStream ms = new MemoryStream((byte[])data._cache[data._cachePos + i])) + { + byte[] buff = new byte[buffer.Length]; + long ret = ms.Read(buff, bufferoffset, length); + + for (int n = bufferoffset; n < ret; n++) + buffer[n] = (char)buff[n]; + + return ret; + } + } + + /// Returns an System.Data.IDataReader for the specified column ordinal. + /// The index of the field to find. + /// An System.Data.IDataReader. + public IDataReader GetData(int i) + { + return null; + } + + /// Gets the data type information for the specified field. + /// The index of the field to find. + /// The data type information for the specified field. + public string GetDataTypeName(int i) + { + return _data[_currentDataPosition]._types[i].Name; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public DateTime GetDateTime(int i) + { + var data = _data[_currentDataPosition]; + return (DateTime)data._cache[data._cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public decimal GetDecimal(int i) + { + var data = _data[_currentDataPosition]; + return (decimal)data._cache[data._cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public double GetDouble(int i) + { + var data = _data[_currentDataPosition]; + return (double)data._cache[data._cachePos + i]; + } + + /// Gets the System.Type information corresponding to the type of System.Object + /// that would be returned from System.Data.IDataRecord.GetValue(System.Int32). + /// The index of the field to find. + /// The System.Type information corresponding to the type of System.Object that + /// would be returned from System.Data.IDataRecord.GetValue(System.Int32). + public Type GetFieldType(int i) + { + return _data[_currentDataPosition]._types[i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public float GetFloat(int i) + { + var data = _data[_currentDataPosition]; + return (float)data._cache[data._cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public Guid GetGuid(int i) + { + var data = _data[_currentDataPosition]; + return (Guid)data._cache[data._cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public short GetInt16(int i) + { + var data = _data[_currentDataPosition]; + return (short)data._cache[data._cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public int GetInt32(int i) + { + var data = _data[_currentDataPosition]; + return (int)data._cache[data._cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public long GetInt64(int i) + { + var data = _data[_currentDataPosition]; + return (long)data._cache[data._cachePos + i]; + } + + /// Gets the name for the field to find. + /// The index of the field to find. + /// The name of the field or the empty string (""), if there is no value to return. + public string GetName(int i) + { + return _data[_currentDataPosition]._names[i]; + } + + /// Return the index of the named field. + /// The name of the field to find. + /// The index of the named field. + public int GetOrdinal(string name) + { + var data = _data[_currentDataPosition]; + + if (data._ordinals.ContainsKey(name.ToUpper())) + return data._ordinals[name.ToUpper()]; + + return -1; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public string GetString(int i) + { + var data = _data[_currentDataPosition]; + + return (string)data._cache[data._cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public object GetValue(int i) + { + var data = _data[_currentDataPosition]; + + return data._cache[data._cachePos + i]; + } + + /// Gets all the attribute fields in the collection for the current record. + /// An array of System.Object to copy the attribute fields into. + /// The number of instances of System.Object in the array. + public int GetValues(object[] values) + { + var data = _data[_currentDataPosition]; + + for (int i = 0; i < data._fields; i++) + values[i] = data._cache[data._cachePos + i]; + + return data._fields; + } + + /// Return whether the specified field is set to null. + /// The index of the field to find. + /// Returns true if the specified field is set to null; otherwise, false. + public bool IsDBNull(int i) + { + var data = _data[_currentDataPosition]; + return data._cache[data._cachePos + i] == null || data._cache[data._cachePos + i] == DBNull.Value; + } + + /// Gets or sets specified value in current record. + /// Name of column. + /// Value of specified column. + public object this[string name] + { + get + { + var data = _data[_currentDataPosition]; + + if (data._ordinals.ContainsKey(name.ToUpper())) + return data._cache[data._cachePos + data._ordinals[name.ToUpper()]]; + + throw new IndexOutOfRangeException(String.Format("Field '{0}' not found.", name)); + } + } + + /// Gets or sets specified value in current record. + /// The index of the field to find. + /// Value of specified column. + public object this[int i] + { + get { + var data = _data[_currentDataPosition]; + return data._cache[data._cachePos + i]; + } + } + + #endregion IDataRecord Members + } + + /// Dynamic procedure invoker. + /// Unfortunately I can use out and ref to + /// return parameters, . + /// But see example for workaround. If there aren't any return parameters execution will return scalar value. + /// Scalar result is not converted to provided generic type (if any). For output results there is possibility to map to provided class. + /// You still can use out, return and both way parameters by providing variable prefix: + /// dynamic res = db.Procedures.sp_Test_Scalar_In_Out(inp: Guid.NewGuid(), out_outp: Guid.Empty); + /// Console.Out.WriteLine(res.outp); + /// Prefixes: out_, ret_, both_. Result will contain field without prefix. + /// Here is an example with result class: + /// public class ProcResult { [Column("outp")] public Guid Output { get; set; } } + /// ProcResult res4 = db.Procedures.sp_Test_Scalar_In_Out<ProcResult>(inp: Guid.NewGuid(), out_outp: Guid.Empty) as ProcResult; + /// As you can se, you can use mapper to do job for you. + public class DynamicProcedureInvoker : DynamicObject, IDisposable + { + private DynamicDatabase _db; + private List _prefixes; + private bool _isDisposed; + + internal DynamicProcedureInvoker(DynamicDatabase db, List prefixes = null) + { + _prefixes = prefixes; + _db = db; + } + + /// This is where the magic begins. + /// Binder to create owner. + /// Binder invoke result. + /// Returns true if invoke was performed. + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + List pref = new List(); + + if (_prefixes != null) + pref.AddRange(_prefixes); + + pref.Add(binder.Name); + + result = new DynamicProcedureInvoker(_db, pref); + + return true; + } + + /// 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 + CallInfo info = binder.CallInfo; + + // Get generic types + IList types = binder.GetGenericTypeArguments(); + + Dictionary retParams = null; + + using (IDbConnection con = _db.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + if (_prefixes == null || _prefixes.Count == 0) + cmd.SetCommand(CommandType.StoredProcedure, binder.Name); + else + cmd.SetCommand(CommandType.StoredProcedure, string.Format("{0}.{1}", string.Join(".", _prefixes), binder.Name)); + + #region Prepare arguments + + int alen = args.Length; + bool retIsAdded = false; + + if (alen > 0) + { + for (int i = 0; i < alen; i++) + { + object arg = args[i]; + + if (arg is DynamicExpando) + cmd.AddParameters(_db, (DynamicExpando)arg); + else if (arg is ExpandoObject) + cmd.AddParameters(_db, (ExpandoObject)arg); + else if (arg is DynamicColumn) + { + var dcv = (DynamicColumn)arg; + + string paramName = i.ToString(); + bool isOut = false; + bool isRet = false; + bool isBoth = false; + + if (info.ArgumentNames.Count > i) + { + isOut = info.ArgumentNames[i].StartsWith("out_"); + isRet = info.ArgumentNames[i].StartsWith("ret_"); + isBoth = info.ArgumentNames[i].StartsWith("both_"); + + paramName = isOut || isRet ? + info.ArgumentNames[i].Substring(4) : + isBoth ? info.ArgumentNames[i].Substring(5) : + info.ArgumentNames[i]; + } + + paramName = dcv.Alias ?? dcv.ColumnName ?? + (dcv.Schema.HasValue ? dcv.Schema.Value.Name : null) ?? + paramName; + + if (!isOut && !isRet && !isBoth) + { + isOut = dcv.ParameterDirection == ParameterDirection.Output; + isRet = dcv.ParameterDirection == ParameterDirection.ReturnValue; + isBoth = dcv.ParameterDirection == ParameterDirection.InputOutput; + } + + if (isRet) + retIsAdded = true; + + if (isOut || isRet || isBoth) + { + if (retParams == null) + retParams = new Dictionary(); + retParams.Add(paramName, cmd.Parameters.Count); + } + + if (dcv.Schema != null) + { + var ds = dcv.Schema.Value; + cmd.AddParameter( + _db.GetParameterName(paramName), + isOut ? ParameterDirection.Output : + isRet ? ParameterDirection.ReturnValue : + isBoth ? ParameterDirection.InputOutput : + ParameterDirection.Input, + ds.Type, ds.Size, ds.Precision, ds.Scale, + (isOut || isRet) ? DBNull.Value : dcv.Value); + } + else + cmd.AddParameter( + _db.GetParameterName(paramName), + isOut ? ParameterDirection.Output : + isRet ? ParameterDirection.ReturnValue : + isBoth ? ParameterDirection.InputOutput : + ParameterDirection.Input, + arg == null ? DbType.String : arg.GetType().ToDbType(), + isRet ? 4 : 0, + (isOut || isRet) ? DBNull.Value : dcv.Value); + } + else if (arg is DynamicSchemaColumn) + { + var dsc = (DynamicSchemaColumn)arg; + + string paramName = i.ToString(); + bool isOut = false; + bool isRet = false; + bool isBoth = false; + + if (info.ArgumentNames.Count > i) + { + isOut = info.ArgumentNames[i].StartsWith("out_"); + isRet = info.ArgumentNames[i].StartsWith("ret_"); + isBoth = info.ArgumentNames[i].StartsWith("both_"); + + paramName = isOut || isRet ? + info.ArgumentNames[i].Substring(4) : + isBoth ? info.ArgumentNames[i].Substring(5) : + info.ArgumentNames[i]; + } + + paramName = dsc.Name ?? paramName; + + if (isRet) + retIsAdded = true; + + if (isOut || isRet || isBoth) + { + if (retParams == null) + retParams = new Dictionary(); + retParams.Add(paramName, cmd.Parameters.Count); + } + + cmd.AddParameter( + _db.GetParameterName(paramName), + isOut ? ParameterDirection.Output : + isRet ? ParameterDirection.ReturnValue : + isBoth ? ParameterDirection.InputOutput : + ParameterDirection.Input, + dsc.Type, dsc.Size, dsc.Precision, dsc.Scale, + DBNull.Value); + } + else + { + if (info.ArgumentNames.Count > i && !string.IsNullOrEmpty(info.ArgumentNames[i])) + { + bool isOut = info.ArgumentNames[i].StartsWith("out_"); + bool isRet = info.ArgumentNames[i].StartsWith("ret_"); + bool isBoth = info.ArgumentNames[i].StartsWith("both_"); + + if (isRet) + retIsAdded = true; + + string paramName = isOut || isRet ? + info.ArgumentNames[i].Substring(4) : + isBoth ? info.ArgumentNames[i].Substring(5) : + info.ArgumentNames[i]; + + if (isOut || isBoth || isRet) + { + if (retParams == null) + retParams = new Dictionary(); + retParams.Add(paramName, cmd.Parameters.Count); + } + + cmd.AddParameter( + _db.GetParameterName(paramName), + isOut ? ParameterDirection.Output : + isRet ? ParameterDirection.ReturnValue : + isBoth ? ParameterDirection.InputOutput : + ParameterDirection.Input, + arg == null ? isRet ? DbType.Int32 : DbType.String : arg.GetType().ToDbType(), + isRet ? 4 : 0, + (isOut || isRet) ? DBNull.Value : arg); + } + else + cmd.AddParameter(_db, arg); + } + } + } + + #endregion Prepare arguments + + #region Get main result + + object mainResult = null; + + if (types.Count > 0) + { + mainResult = types[0].GetDefaultValue(); + + if (types[0] == typeof(IDataReader)) + { + using (IDataReader rdr = cmd.ExecuteReader()) + mainResult = rdr.CachedReader(); + } + else if (types[0] == typeof(DataTable)) + { + using (IDataReader rdr = cmd.ExecuteReader()) + using (IDataReader cache = rdr.CachedReader()) + mainResult = cache.ToDataTable(binder.Name); + } + else if (types[0].IsGenericEnumerable()) + { + Type argType = types[0].GetGenericArguments().First(); + if (argType == typeof(object)) + { + using (IDataReader rdr = cmd.ExecuteReader()) + using (IDataReader cache = rdr.CachedReader()) + mainResult = cache.EnumerateReader().ToList(); + } + else if (argType.IsValueType || argType == typeof(string)) + { + Type listType = typeof(List<>).MakeGenericType(new Type[] { argType }); + IList listInstance = (IList)Activator.CreateInstance(listType); + + object defVal = listType.GetDefaultValue(); + + using (IDataReader rdr = cmd.ExecuteReader()) + using (IDataReader cache = rdr.CachedReader()) + while (cache.Read()) + listInstance.Add(cache[0] != null && cache[0] != DBNull.Value ? argType.CastObject(cache[0]) : defVal); + + mainResult = listInstance; + } + else if (argType == typeof(Guid)) + { + Type listType = typeof(List<>).MakeGenericType(new Type[] { argType }); + IList listInstance = (IList)Activator.CreateInstance(listType); + + object defVal = listType.GetDefaultValue(); + + using (IDataReader rdr = cmd.ExecuteReader()) + using (IDataReader cache = rdr.CachedReader()) + while (cache.Read()) + { + if (cache[0] != null && cache[0] != DBNull.Value && Guid.TryParse(cache[0].ToString(), out Guid g)) + listInstance.Add(g); + } + + mainResult = listInstance; + } + else + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(argType); + if (mapper == null) + throw new InvalidCastException(string.Format("Don't know what to do with this type: '{0}'.", argType.ToString())); + + using (IDataReader rdr = cmd.ExecuteReader()) + using (IDataReader cache = rdr.CachedReader()) + { + var lt = typeof(List<>); + var ltc = lt.MakeGenericType(argType); + var instance = Activator.CreateInstance(ltc) as IList; + + foreach (var item in cache.EnumerateReader()) + instance.Add(DynamicExtensions.Map(item, argType)); + + mainResult = instance; + } + + //mainResult = cache.EnumerateReader().MapEnumerable(argType).ToList(); + } + } + else if (types[0].IsValueType || types[0] == typeof(string)) + { + mainResult = cmd.ExecuteScalar(); + if (mainResult != null && mainResult != DBNull.Value) + mainResult = types[0].CastObject(mainResult); + } + else if (types[0] == typeof(Guid)) + { + mainResult = cmd.ExecuteScalar(); + if (mainResult != null && mainResult != DBNull.Value && Guid.TryParse(mainResult.ToString(), out Guid g)) + mainResult = g; + } + else + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(types[0]); + if (mapper == null) + throw new InvalidCastException(string.Format("Don't know what to do with this type: '{0}'.", types[0].ToString())); + + using (IDataReader rdr = cmd.ExecuteReader()) + if (rdr.Read()) + mainResult = (rdr.RowToDynamic() as object).Map(types[0]); + else + mainResult = null; + } + } + else + { + var returnName = _db.GetParameterName("___result___"); + if ((_db.Options & DynamicDatabaseOptions.SupportStoredProceduresResult) == DynamicDatabaseOptions.SupportStoredProceduresResult) + { + if (!retIsAdded) + cmd.AddParameter(returnName, ParameterDirection.ReturnValue, DbType.Int32, 4, 0, 0, DBNull.Value); + } + + mainResult = cmd.ExecuteNonQuery(); + + IDbDataParameter returnParam = null; + if (!retIsAdded) + { + if ((_db.Options & DynamicDatabaseOptions.SupportStoredProceduresResult) == DynamicDatabaseOptions.SupportStoredProceduresResult) + returnParam = cmd.Parameters[returnName] as IDbDataParameter; + } + else + { + foreach (var e in cmd.Parameters) + { + var p = e as IDbDataParameter; + if (p != null && p.Direction == ParameterDirection.ReturnValue) + { + returnParam = p; + break; + } + } + } + + if (returnParam != null && returnParam.Value != null && returnParam.Value != DBNull.Value) + mainResult = returnParam.Value; + } + + #endregion Get main result + + #region Handle out params + + if (retParams != null) + { + Dictionary res = new Dictionary(); + + if (mainResult != null) + { + if (mainResult == DBNull.Value) + res.Add(binder.Name, null); + else + res.Add(binder.Name, mainResult); + } + + foreach (KeyValuePair pos in retParams) + res.Add(pos.Key, ((IDbDataParameter)cmd.Parameters[pos.Value]).Value); + + if (types.Count > 1) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(types[1]); + + if (mapper != null) + result = mapper.Create(res.ToDynamic()); + else + result = res.ToDynamic(); + } + else + result = res.ToDynamic(); + } + else + result = mainResult; + + #endregion Handle out params + } + + return true; + } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + _db = null; + _prefixes = null; + } + } + + /// 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(TimeSpan), DbType.Time }, + { 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(TimeSpan?), DbType.Time }, + { 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 (object item in args) + { + if (item is DynamicExpando) + cmd.AddParameters(database, (DynamicExpando)item); + else if (item is ExpandoObject) + cmd.AddParameters(database, (ExpandoObject)item); + else + 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 (KeyValuePair item in args.ToDictionary()) + cmd.AddParameter(database, item.Key, item.Value); + + 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, DynamicExpando args) + { + if (args != null && args.Count() > 0) + foreach (KeyValuePair 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) + { + IDbDataParameter 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(DynamicExpando) || type == typeof(ExpandoObject)) + p.Value = CorrectValue(p.DbType, ((IDictionary)item).Values.FirstOrDefault()); + else + p.Value = CorrectValue(p.DbType, item); + + if (p.DbType == DbType.String) + p.Size = item.ToString().Length > 4000 ? -1 : 4000; + else if (p.DbType == DbType.AnsiString) + p.Size = item.ToString().Length > 8000 ? -1 : 8000; + } + + 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) + { + IDbDataParameter 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 = CorrectValue(p.DbType, 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.AnsiString) + p.Size = value.ToString().Length > 8000 ? -1 : 8000; + else if (p.DbType == DbType.String) + p.Size = value.ToString().Length > 4000 ? -1 : 4000; + + p.Value = CorrectValue(p.DbType, value); + } + + cmd.Parameters.Add(p); + + return cmd; + } + + private static object CorrectValue(DbType type, object value) + { + if (value == null || value == DBNull.Value) + return DBNull.Value; + + if ((type == DbType.String || type == DbType.AnsiString || type == DbType.StringFixedLength || type == DbType.AnsiStringFixedLength) && + !(value is string)) + return value.ToString(); + else if (type == DbType.Guid && value is string) + return Guid.Parse(value.ToString()); + else if (type == DbType.Guid && value is byte[] && ((byte[])value).Length == 16) + return new Guid((byte[])value); + else if (type == DbType.DateTime && value is TimeSpan) // HACK: This is specific for SQL Server, to be verified with other databases + return DateTime.Today.Add((TimeSpan)value); + + return value; + } + + /// 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) + { + IDbDataParameter p = cmd.CreateParameter(); + p.ParameterName = builder.Database.GetParameterName(cmd.Parameters.Count); + + DynamicSchemaColumn? 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 : CorrectValue(p.DbType, 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.AnsiString) + p.Size = item.Value.ToString().Length > 8000 ? -1 : 8000; + else if (p.DbType == DbType.String) + p.Size = item.Value.ToString().Length > 4000 ? -1 : 4000; + + p.Value = CorrectValue(p.DbType, 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 = CorrectValue(param.DbType, 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 = CorrectValue(param.DbType, 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 = CorrectValue(param.DbType, 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 = CorrectValue(param.DbType, value ?? DBNull.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 = CorrectValue(param.DbType, value ?? DBNull.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 = CorrectValue(param.DbType, value ?? DBNull.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) + { + try + { + var p = ((IDbDataParameter)command.Parameters[parameterName]); + p.Value = CorrectValue(p.DbType, value); + } + catch (Exception ex) + { + throw new ArgumentException(string.Format("Error setting parameter {0} in command {1}", parameterName, command.CommandText ?? string.Empty), ex); + } + + 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) + { + try + { + var p = ((IDbDataParameter)command.Parameters[index]); + p.Value = CorrectValue(p.DbType, value); + } + catch (Exception ex) + { + throw new ArgumentException(string.Format("Error setting parameter {0} in command {1}", index, command.CommandText ?? string.Empty), ex); + } + + 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) + { + if (handler != null) + ret = o.ToString().TryParseDefault(defaultValue, handler); + else if (o is IConvertible && typeof(T).GetInterfaces().Any(i => i == typeof(IConvertible))) + ret = (T)Convert.ChangeType(o, typeof(T)); + else if (typeof(T) == typeof(Guid)) + { + if (o.GetType() == typeof(byte[])) + ret = (T)(object)new Guid((byte[])o); + else + ret = (T)(object)Guid.Parse(o.ToString()); + } + else if (typeof(T) == typeof(string)) + ret = (T)(o.ToString() as object); + else if (typeof(T) == typeof(object)) + ret = (T)o; + else + { + MethodInfo method = typeof(T).GetMethod( + "TryParse", + new Type[] + { + typeof(string), + Type.GetType(string.Format("{0}&", typeof(T).FullName)) + }); + + 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 + 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()) + { + MethodInfo 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 (o is IConvertible && typeof(T).GetInterfaces().Any(i => i == typeof(IConvertible))) + ret = (T)Convert.ChangeType(o, typeof(T)); + else if (typeof(T) == typeof(Guid)) + { + if (o.GetType() == typeof(byte[])) + ret = (T)(object)new Guid((byte[])o); + else + ret = (T)(object)Guid.Parse(o.ToString()); + } + else if (typeof(T) == typeof(string)) + ret = (T)(o.ToString() as object); + else if (typeof(T) == typeof(object)) + ret = (T)o; + 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 + throw new InvalidOperationException("Provided type can't be parsed using generic approach."); + } + } + + yield return ret; + } + } + } + +#endif + + #endregion Generic Execution + + /// Dump command into string. + /// Command to dump. + /// Returns dumped instance in string form. + public static string DumpToString(this IDbCommand command) + { + StringBuilder sb = new StringBuilder(); + command.Dump(sb); + return sb.ToString(); + } + + /// 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) + { + try + { + 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(); + } + } + catch (NullReferenceException) + { + writer.WriteLine("Command disposed."); + } + + 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(); + } + + /// 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(); + } + + /// 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 + { + if (a != null) + { + if (b.OnCreateTemporaryParameter == null) + b.OnCreateTemporaryParameter = new List>(); + + b.OnCreateTemporaryParameter.Add(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 + { + if (a != null) + { + if (b.OnCreateParameter == null) + b.OnCreateParameter = new List>(); + + b.OnCreateParameter.Add(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 + { + IDynamicSelectQueryBuilder sub = b.SubQuery(); + + subquery(b, sub); + try + { + (b as DynamicQueryBuilder).ParseCommand(sub as DynamicQueryBuilder, b.Parameters); + } + catch (ArgumentException) + { + // This might occur if join was made to subquery + } + + 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 + { + IDynamicSelectQueryBuilder 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) + { + List result = new List(); + + while (r.Read()) + result.Add(r.RowToDynamic()); + + return result; + } + + /// Turns the dictionary into an ExpandoObject. + /// Dictionary to convert. + /// Converted dictionary. + public static dynamic ToDynamic(this IDictionary d) + { + DynamicExpando result = new DynamicExpando(); + IDictionary dict = (IDictionary)result; + + foreach (KeyValuePair prop in d) + dict.Add(prop.Key, prop.Value); + + return result; + } + + /// Turns the dictionary into an ExpandoObject. + /// Dictionary to convert. + /// Converted dictionary. + public static dynamic ToExpando(this IDictionary d) + { + ExpandoObject result = new ExpandoObject(); + IDictionary dict = (IDictionary)result; + + foreach (KeyValuePair 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) + { + Type ot = o.GetType(); + + if (ot == typeof(DynamicExpando) || ot == typeof(ExpandoObject)) + return o; + + DynamicExpando result = new DynamicExpando(); + IDictionary dict = (IDictionary)result; + + if (o is IDictionary) + ((IDictionary)o) + .ToList() + .ForEach(kvp => dict.Add(kvp.Key, kvp.Value)); + else if (ot == typeof(NameValueCollection) || ot.IsSubclassOf(typeof(NameValueCollection))) + { + NameValueCollection nameValue = (NameValueCollection)o; + nameValue.Cast() + .Select(key => new KeyValuePair(key, nameValue[key])) + .ToList() + .ForEach(i => dict.Add(i)); + } + else + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(ot); + + if (mapper != null) + { + foreach (DynamicPropertyInvoker item in mapper.ColumnsMap.Values) + if (item.Get != null) + dict.Add(item.Name, item.Get(o)); + } + else + { + PropertyInfo[] props = ot.GetProperties(); + + foreach (PropertyInfo item in props) + if (item.CanRead) + dict.Add(item.Name, item.GetValue(o, null)); + } + } + + return result; + } + + /// Turns the object into an ExpandoObject. + /// Object to convert. + /// Converted object. + public static dynamic ToExpando(this object o) + { + Type ot = o.GetType(); + + if (ot == typeof(ExpandoObject) || ot == typeof(DynamicExpando)) + return o; + + ExpandoObject result = new ExpandoObject(); + IDictionary dict = (IDictionary)result; + + if (o is IDictionary) + ((IDictionary)o) + .ToList() + .ForEach(kvp => dict.Add(kvp.Key, kvp.Value)); + else if (ot == typeof(NameValueCollection) || ot.IsSubclassOf(typeof(NameValueCollection))) + { + NameValueCollection nameValue = (NameValueCollection)o; + nameValue.Cast() + .Select(key => new KeyValuePair(key, nameValue[key])) + .ToList() + .ForEach(i => dict.Add(i)); + } + else + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(ot); + + if (mapper != null) + { + foreach (DynamicPropertyInvoker item in mapper.ColumnsMap.Values) + if (item.Get != null) + dict.Add(item.Name, item.Get(o)); + } + else + { + PropertyInfo[] props = ot.GetProperties(); + + foreach (PropertyInfo 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 DynamicExpando(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + ((IDictionary)e).Add(r.Table.Columns[i].ColumnName, r.IsNull(i) ? null : r[i]); + + return e; + } + + /// Convert data row row into dynamic object. + /// DataRow from which read. + /// Generated dynamic object. + public static dynamic RowToExpando(this DataRow r) + { + dynamic e = new ExpandoObject(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + ((IDictionary)e).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 DynamicExpando(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + ((IDictionary)e).Add(r.Table.Columns[i].ColumnName.ToUpper(), 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 RowToExpandoUpper(this DataRow r) + { + // ERROR: Memory leak + dynamic e = new ExpandoObject(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + ((IDictionary)e).Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); + + return e; + } + + internal static Dictionary RowToDynamicUpperDict(this DataRow r) + { + dynamic e = new Dictionary(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + e.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 DynamicExpando(); + IDictionary 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; + } + + /// Convert reader row into dynamic object. + /// Reader from which read. + /// Generated dynamic object. + public static dynamic RowToExpando(this IDataReader r) + { + dynamic e = new ExpandoObject(); + IDictionary 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 DynamicExpando 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 && type.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + } + + /// 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 || type.IsValueType || type.IsEnum)) + 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.GetInterfaces().Any(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + { + Type[] gt = type.GetGenericArguments(); + + return (gt.Length == 1) && gt[0].IsValueType; + } + } + + return false; + } + + /// Gets corresponding to the + /// provided . + /// The type to be converted. + /// Returns corresponding to the + /// provided . + public static DbType ToDbType(this Type t) + { + return TypeMap.TryGetNullable(t) ?? DbType.Object; + } + + /// Gets corresponding to the + /// provided . + /// The type to be converted. + /// Returns corresponding to the + /// provided . + public static Type ToType(this DbType dbt) + { + if (dbt == DbType.String) + return typeof(string); + + foreach (KeyValuePair tdbt in TypeMap) + if (tdbt.Value == dbt) + return tdbt.Key; + + return typeof(object); + } + + /// Determines whether the specified value is has only ASCII chars. + /// The value to check. + /// Returns true if the specified value has only ASCII cars; otherwise, false. + public static bool IsASCII(this string value) + { + return Encoding.UTF8.GetByteCount(value) == value.Length; + } + + #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 != 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 IDataReader extensions + + /// Gets the information corresponding + /// to the type of that would be returned from + /// . + /// The data reader. + /// The index of the field to find. + /// The information corresponding to the + /// type of that would be returned from + /// . + public static DbType GetFieldDbType(this IDataReader r, int i) + { + return TypeMap.TryGetNullable(r.GetFieldType(i)) ?? DbType.String; + } + + internal static IEnumerable EnumerateReader(this IDataReader r) + { + while (r.Read()) + yield return r.RowToDynamic(); + } + + /// Creates cached reader object from non cached reader. + /// The reader to cache. + /// The offset row. + /// The limit to number of tows. -1 is no limit. + /// The progress delegate. + /// Returns new instance of cached reader or current instance of a reader. + public static IDataReader CachedReader(this IDataReader r, int offset = 0, int limit = -1, Func progress = null) + { + if (r is DynamicCachedReader) + return r; + + return new DynamicCachedReader(r, offset, limit, progress); + } + + #endregion IDataReader 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) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + foreach (object 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) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + return (T)mapper.Create(item); + } + + /// MapEnumerable object enumerator into specified type using property names. + /// Type to which columnMap results. + /// Source enumerator. + /// Enumerator of specified type. + public static IEnumerable MapEnumerableByProperty(this IEnumerable enumerable) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + foreach (object item in enumerable) + yield return (T)mapper.CreateByProperty(item); + } + + /// MapEnumerable object item into specified type using property names. + /// Type to which columnMap results. + /// Source object. + /// Item of specified type. + public static T MapByProperty(this object item) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + return (T)mapper.CreateByProperty(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) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + mapper.Map(source, item); + + return item; + } + + /// Fill object of specified type with data from source object using property names. + /// Type to which columnMap results. + /// Item to which columnMap data. + /// Item from which extract data. + /// Filled item. + public static T FillByProperty(this T item, object source) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + mapper.MapByProperty(source, item); + + 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) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + foreach (object 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) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + return mapper.Create(item); + } + + /// MapEnumerable object enumerator into specified type using property names. + /// Source enumerator. + /// Type to which columnMap results. + /// Enumerator of specified type. + public static IEnumerable MapEnumerableByProperty(this IEnumerable enumerable, Type type) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + foreach (object item in enumerable) + yield return mapper.CreateByProperty(item); + } + + /// MapEnumerable object item into specified type using property names. + /// Source object. + /// Type to which columnMap results. + /// Item of specified type. + public static object MapByProperty(this object item, Type type) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + return mapper.CreateByProperty(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 from 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 + } + + /// 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 supports limit offset syntax (SELECT FIRST x SKIP y ... FROM ...). + SupportFirstSkip = 0x00000020, + + /// Database support standard schema. + SupportSchema = 0x00000010, + + /// Database support stored procedures (EXEC procedure ...). + SupportStoredProcedures = 0x00000100, + + /// Database support stored procedures results (Return value). + SupportStoredProceduresResult = 0x00000200, + + /// Database support with no lock syntax. + SupportNoLock = 0x00001000, + + /// Debug option allowing to enable command dumps by default. + DumpCommands = 0x01000000, + } + + /// 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 _isDisposed = false; + private bool _isOperational = 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. + /// Pass custom transaction parameters. + internal DynamicTransaction(DynamicDatabase db, DynamicConnection con, bool singleTransaction, IsolationLevel? il, Action disposed, object customParams) + { + _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) + { + _isOperational = false; + + if (_db.DumpCommands && _db.DumpCommandDelegate != null) + _db.DumpCommandDelegate(null, "BEGIN TRAN [NON OPERATIONAL]"); + } + else + { + if (customParams != null) + { + MethodInfo mi = _con.Connection.GetType().GetMethods().Where(m => m.GetParameters().Count() == 1 && m.GetParameters().First().ParameterType == customParams.GetType()).FirstOrDefault(); + if (mi != null) + { + _db.TransactionPool[_con.Connection].Push((IDbTransaction)mi.Invoke(_con.Connection, new object[] { customParams, })); + + if (_db.DumpCommands && _db.DumpCommandDelegate != null) + _db.DumpCommandDelegate(null, "BEGIN TRAN [CUSTOM ARGS]"); + } + else + throw new MissingMethodException(string.Format("Method 'BeginTransaction' accepting parameter of type '{0}' in '{1}' not found.", + customParams.GetType().FullName, _con.Connection.GetType().FullName)); + } + else + { + _db.TransactionPool[_con.Connection] + .Push(il.HasValue ? _con.Connection.BeginTransaction(il.Value) : _con.Connection.BeginTransaction()); + + if (_db.DumpCommands && _db.DumpCommandDelegate != null) + _db.DumpCommandDelegate(null, "BEGIN TRAN"); + } + + _db.PoolStamp = DateTime.Now.Ticks; + _isOperational = true; + } + } + } + + /// Commits the database transaction. + public void Commit() + { + lock (_db.SyncLock) + { + if (_isOperational) + { + Stack 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(); + + if (_db.DumpCommands && _db.DumpCommandDelegate != null) + _db.DumpCommandDelegate(null, "COMMIT"); + } + + _isOperational = false; + } + else if (!_isDisposed && _db.DumpCommands && _db.DumpCommandDelegate != null) + _db.DumpCommandDelegate(null, "COMMIT [NON OPERATIONAL]"); + } + } + + /// Rolls back a transaction from a pending state. + public void Rollback() + { + lock (_db.SyncLock) + { + if (_isOperational) + { + Stack 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(); + + if (_db.DumpCommands && _db.DumpCommandDelegate != null) + _db.DumpCommandDelegate(null, "ROLLBACK"); + } + + _isOperational = false; + } + else if (!_isDisposed && _db.DumpCommands && _db.DumpCommandDelegate != null) + _db.DumpCommandDelegate(null, "ROLLBACK [NON OPERATIONAL]"); + } + } + + /// 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() + { + if (_isDisposed) + return; + + _isDisposed = true; + Rollback(); + + if (_disposed != null) + _disposed(); + + _disposed = null; + _con = null; + _db = null; + } + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get { return !_isOperational; } } + + #endregion IExtendedDisposable Members + } + + /// Dynamic expando is a simple and temporary class to resolve memory leaks inside ExpandoObject. + public class DynamicExpando : DynamicObject, IDictionary, ICollection>, IEnumerable>, IEnumerable + { + /// Class containing information about last accessed property of dynamic object. + public class PropertyAccess + { + /// Enum describing type of access to object. + public enum TypeOfAccess + { + /// Get member. + Get, + + /// Set member. + Set, + } + + /// Gets the type of operation. + public TypeOfAccess Operation { get; internal set; } + + /// Gets the name of property. + public string Name { get; internal set; } + + /// Gets the type from binder. + public Type RequestedType { get; internal set; } + + /// Gets the type of value stored in object. + public Type Type { get; internal set; } + + /// Gets the value stored in object. + public object Value { get; internal set; } + + /// Gets the last access time. + public long Ticks { get; internal set; } + } + + private Dictionary _data = new Dictionary(); + + private PropertyAccess _lastProp = new PropertyAccess(); + + /// Initializes a new instance of the class. + public DynamicExpando() + { + } + + /// Gets the last accesses property. + /// Description of last accessed property. + public PropertyAccess GetLastAccessesProperty() + { + return _lastProp; + } + + /// Tries to get member value. + /// Returns true, if get member was tried, false otherwise. + /// The context binder. + /// The invocation result. + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + result = _data.TryGetValue(binder.Name); + + _lastProp.Operation = PropertyAccess.TypeOfAccess.Get; + _lastProp.RequestedType = binder.ReturnType; + _lastProp.Name = binder.Name; + _lastProp.Value = result; + _lastProp.Type = result == null ? typeof(void) : result.GetType(); + _lastProp.Ticks = DateTime.Now.Ticks; + + return true; + } + + /// Tries to set member. + /// Returns true, if set member was tried, false otherwise. + /// The context binder. + /// Value which will be set. + public override bool TrySetMember(SetMemberBinder binder, object value) + { + _data[binder.Name] = value; + + _lastProp.Operation = PropertyAccess.TypeOfAccess.Set; + _lastProp.RequestedType = binder.ReturnType; + _lastProp.Name = binder.Name; + _lastProp.Value = value; + _lastProp.Type = value == null ? typeof(void) : value.GetType(); + _lastProp.Ticks = DateTime.Now.Ticks; + + return true; + } + + #region IDictionary implementation + + bool IDictionary.ContainsKey(string key) + { + return _data.ContainsKey(key); + } + + void IDictionary.Add(string key, object value) + { + _data.Add(key, value); + } + + bool IDictionary.Remove(string key) + { + return _data.Remove(key); + } + + bool IDictionary.TryGetValue(string key, out object value) + { + return _data.TryGetValue(key, out value); + } + + object IDictionary.this[string index] { get { return _data[index]; } set { _data[index] = value; } } + + ICollection IDictionary.Keys { get { return _data.Keys; } } + + ICollection IDictionary.Values { get { return _data.Values; } } + + #endregion IDictionary implementation + + #region ICollection implementation + + void ICollection>.Add(KeyValuePair item) + { + ((ICollection>)_data).Add(item); + } + + void ICollection>.Clear() + { + _data.Clear(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return ((ICollection>)_data).Contains(item); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((ICollection>)_data).CopyTo(array, arrayIndex); + } + + bool ICollection>.Remove(KeyValuePair item) + { + return ((ICollection>)_data).Remove(item); + } + + int ICollection>.Count { get { return _data.Count; } } + + bool ICollection>.IsReadOnly { get { return ((ICollection>)_data).IsReadOnly; } } + + #endregion ICollection implementation + + #region IEnumerable implementation + + IEnumerator> IEnumerable>.GetEnumerator() + { + return _data.GetEnumerator(); + } + + #endregion IEnumerable implementation + + #region IEnumerable implementation + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_data).GetEnumerator(); + } + + #endregion IEnumerable implementation + } + + /// 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 a value indicating whether column allows null or not. + public bool AllowNull { 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; } + } + + namespace Validation + { + + /// Validation result enum. + public enum ValidateResult + { + /// The valid value. + Valid, + + /// The value is missing. + ValueIsMissing, + + /// The value too small. + ValueTooSmall, + + /// The value too large. + ValueTooLarge, + + /// The too few elements in collection. + TooFewElementsInCollection, + + /// The too many elements in collection. + TooManyElementsInCollection, + + /// The value too short. + ValueTooShort, + + /// The value too long. + ValueTooLong, + + /// The value don't match pattern. + ValueDontMatchPattern, + + /// The not supported. + NotSupported, + } + + /// Required attribute can be used to validate fields in objects using mapper class. + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class RequiredAttribute : Attribute + { + /// Gets or sets minimum value or length of field. + public decimal? Min { get; set; } + + /// Gets or sets maximum value or length of field. + public decimal? Max { get; set; } + + /// Gets or sets pattern to verify. + public Regex Pattern { get; set; } + + /// Gets or sets a value indicating whether property value is required or not. + public bool Required { get; set; } + + /// Gets or sets a value indicating whether this is an element requirement. + public bool ElementRequirement { get; set; } + + /// Initializes a new instance of the class. + /// This field will be required. + public RequiredAttribute(bool required = true) + { + Required = required; + } + + /// Initializes a new instance of the class. + /// Limiting value to set. + /// Whether set maximum parameter (true) or minimum parameter (false). + /// This field will be required. + public RequiredAttribute(float val, bool max, bool required = true) + { + if (max) + Max = (decimal)val; + else + Min = (decimal)val; + Required = required; + } + + /// Initializes a new instance of the class. + /// Minimum value to set. + /// Maximum value to set. + /// This field will be required. + public RequiredAttribute(float min, float max, bool required = true) + { + Min = (decimal)min; + Max = (decimal)max; + Required = required; + } + + /// Initializes a new instance of the class. + /// Minimum value to set. + /// Maximum value to set. + /// Pattern to check. + /// This field will be required. + public RequiredAttribute(float min, float max, string pattern, bool required = true) + { + Min = (decimal)min; + Max = (decimal)max; + Pattern = new Regex(pattern, RegexOptions.Compiled); + Required = required; + } + + internal ValidateResult ValidateSimpleValue(DynamicPropertyInvoker dpi, object val) + { + return ValidateSimpleValue(dpi.Type, dpi.IsGnericEnumerable, val); + } + + internal ValidateResult ValidateSimpleValue(Type type, bool isGnericEnumerable, object val) + { + if (val == null) + { + if (Required) + return ValidateResult.ValueIsMissing; + else + return ValidateResult.Valid; + } + + if (type.IsValueType) + { + if (val is decimal || val is long || val is int || val is float || val is double || val is short || val is byte || + val is decimal? || val is long? || val is int? || val is float? || val is double? || val is short? || val is byte?) + { + decimal dec = Convert.ToDecimal(val); + + if (Min.HasValue && Min.Value > dec) + return ValidateResult.ValueTooSmall; + + if (Max.HasValue && Max.Value < dec) + return ValidateResult.ValueTooLarge; + + return ValidateResult.Valid; + } + else + { + var str = val.ToString(); + + if (Min.HasValue && Min.Value > str.Length) + return ValidateResult.ValueTooShort; + + if (Max.HasValue && Max.Value < str.Length) + return ValidateResult.ValueTooLong; + + if (Pattern != null && !Pattern.IsMatch(str)) + return ValidateResult.ValueDontMatchPattern; + + return ValidateResult.Valid; + } + } + else if (type.IsArray || isGnericEnumerable) + { + int? cnt = null; + + var list = val as IEnumerable; + if (list != null) + cnt = list.Count(); + else + { + var enumerable = val as IEnumerable; + if (enumerable != null) + cnt = enumerable.Cast().Count(); + } + + if (Min.HasValue && Min.Value > cnt) + return ValidateResult.TooFewElementsInCollection; + + if (Max.HasValue && Max.Value < cnt) + return ValidateResult.TooManyElementsInCollection; + + return ValidateResult.Valid; + } + else if (type == typeof(string)) + { + var str = (string)val; + + if (Min.HasValue && Min.Value > str.Length) + return ValidateResult.ValueTooShort; + + if (Max.HasValue && Max.Value < str.Length) + return ValidateResult.ValueTooLong; + + if (Pattern != null && !Pattern.IsMatch(str)) + return ValidateResult.ValueDontMatchPattern; + + return ValidateResult.Valid; + } + + return ValidateResult.NotSupported; + } + } + + /// Validation result. + public class ValidationResult + { + /// Gets the property invoker. + public DynamicPropertyInvoker Property { get; internal set; } + + /// Gets the requirement definition. + public RequiredAttribute Requirement { get; internal set; } + + /// Gets the value that is broken. + public object Value { get; internal set; } + + /// Gets the result. + public ValidateResult Result { get;internal set;} + } + } + + namespace Mapper + { + + /// Allows to add table name to class. + [AttributeUsage(AttributeTargets.Class)] + public class TableAttribute : Attribute + { + /// Gets or sets table owner name. + public string Owner { get; set; } + + /// Gets or sets name. + public string Name { get; set; } + + /// Gets or sets a value indicating whether override database + /// schema values. + /// If database doesn't support schema, you still have to + /// set this to true to get schema from type. + public bool Override { get; set; } + } + + /// Class with mapper cache. + public static class DynamicMapperCache + { + private static readonly object SyncLock = new object(); + private static Dictionary _cache = new Dictionary(); + + /// Get type mapper. + /// Type of mapper. + /// Type mapper. + public static DynamicTypeMap GetMapper() + { + return GetMapper(typeof(T)); + } + + /// Get type mapper. + /// Type of mapper. + /// Type mapper. + public static DynamicTypeMap GetMapper(Type type) + { + if (type == null) + return null; + /*if (type.IsAnonymous()) + return null;*/ + + DynamicTypeMap mapper = null; + + lock (SyncLock) + { + if (!_cache.TryGetValue(type, out mapper)) + { + mapper = new DynamicTypeMap(type); + + if (mapper != null) + _cache.Add(type, mapper); + } + } + + return mapper; + } + } + + /// Allows to add table name to class. + [AttributeUsage(AttributeTargets.Property)] + public class ColumnAttribute : Attribute + { + /// Gets or sets name. + public string Name { get; set; } + + /// Gets or sets column type. + /// Used when overriding schema. + 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 allows null or not. + /// Information only. + public bool AllowNull { get; set; } + + /// Gets or sets a value indicating whether column should have unique value. + /// Used when overriding schema. + public bool? IsUnique { get; set; } + + /// Gets or sets column size. + /// Used when overriding schema. + public int? Size { get; set; } + + /// Gets or sets column precision. + /// Used when overriding schema. + public byte? Precision { get; set; } + + /// Gets or sets column scale. + /// Used when overriding schema. + public byte? Scale { get; set; } + + /// Gets or sets a value indicating whether this column is no allowed to be inserted. + /// This is only a suggestion to automated mapping. + public bool IsNoInsert { get; set; } + + /// Gets or sets a value indicating whether this column is no allowed to be updated. + /// This is only a suggestion to automated mapping. + public bool IsNoUpdate { get; set; } + + #region Constructors + + /// Initializes a new instance of the class. + public ColumnAttribute() + { + AllowNull = true; + } + + /// Initializes a new instance of the class. + /// Name of column. + public ColumnAttribute(string name) + : this() + { + Name = name; + } + + /// Initializes a new instance of the class. + /// Set column as a key column. + public ColumnAttribute(bool isKey) + : this() + { + IsKey = isKey; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + public ColumnAttribute(string name, bool isKey) + : this(name) + { + IsKey = isKey; + } + + /// Initializes a new instance of the class. + /// Set column as a key column. + /// Set column type. + public ColumnAttribute(bool isKey, DbType type) + : this(isKey) + { + Type = type; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + public ColumnAttribute(string name, bool isKey, DbType type) + : this(name, isKey) + { + Type = type; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + /// Set column value size. + public ColumnAttribute(string name, bool isKey, DbType type, int size) + : this(name, isKey, type) + { + Size = size; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + /// Set column value precision. + /// Set column value scale. + public ColumnAttribute(string name, bool isKey, DbType type, byte precision, byte scale) + : this(name, isKey, type) + { + Precision = precision; + Scale = scale; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + /// Set column value size. + /// Set column value precision. + /// Set column value scale. + public ColumnAttribute(string name, bool isKey, DbType type, int size, byte precision, byte scale) + : this(name, isKey, type, precision, scale) + { + Size = size; + } + + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column has unique value. + /// Set column type. + /// Set column value size. + /// Set column value precision. + /// Set column value scale. + public ColumnAttribute(string name, bool isKey, bool isUnique, DbType type, int size, byte precision, byte scale) + : this(name, isKey, type, size, precision, scale) + { + IsUnique = isUnique; + } + + #endregion Constructors + } + + /// Type cast helper. + public static class DynamicCast + { + /// Gets the default value. + /// The type. + /// Default instance. + public static object GetDefaultValue(this Type type) + { + return type.IsValueType ? TypeDefaults.GetOrAdd(type, t => Activator.CreateInstance(t)) : null; + } + + /// Casts the object to this type. + /// The type to which cast value. + /// The value to cast. + /// Value casted to new type. + public static object CastObject(this Type type, object val) + { + return GetConverter(type, val)(val); + } + + private static readonly ConcurrentDictionary TypeDefaults = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary> TypeAsCasts = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> TypeConvert = new ConcurrentDictionary>(); + private static readonly ParameterExpression ConvParameter = Expression.Parameter(typeof(object), "val"); + + [MethodImpl(MethodImplOptions.Synchronized)] + private static Func GetConverter(Type targetType, object val) + { + Func fn; + + if (!targetType.IsValueType && !val.GetType().IsValueType) + { + if (!TypeAsCasts.TryGetValue(targetType, out fn)) + { + UnaryExpression instanceCast = Expression.TypeAs(ConvParameter, targetType); + + fn = Expression.Lambda>(Expression.TypeAs(instanceCast, typeof(object)), ConvParameter).Compile(); + TypeAsCasts.AddOrUpdate(targetType, fn, (t, f) => fn); + } + } + else + { + var fromType = val != null ? val.GetType() : typeof(object); + var key = new PairOfTypes(fromType, targetType); + if (TypeConvert.TryGetValue(key, out fn)) + return fn; + + fn = (Func)Expression.Lambda(Expression.Convert(Expression.Convert(Expression.Convert(ConvParameter, fromType), targetType), typeof(object)), ConvParameter).Compile(); + TypeConvert.AddOrUpdate(key, fn, (t, f) => fn); + } + + return fn; + } + + private class PairOfTypes + { + private readonly Type _first; + private readonly Type _second; + + public PairOfTypes(Type first, Type second) + { + this._first = first; + this._second = second; + } + + public override int GetHashCode() + { + return (31 * _first.GetHashCode()) + _second.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (obj == this) + return true; + + var other = obj as PairOfTypes; + if (other == null) + return false; + + return _first.Equals(other._first) + && _second.Equals(other._second); + } + } + } + + /// Allows to add ignore action to property. + /// Property still get's mapped from output. + [AttributeUsage(AttributeTargets.Property)] + public class IgnoreAttribute : Attribute + { + } + + /// Dynamic property invoker. + public class DynamicPropertyInvoker + { + internal class ParameterSpec + { + public string Name { get; set; } + + public DbType Type { get; set; } + + public int Ordinal { get; set; } + } + + /// Gets the array type of property if main type is a form of collection. + public Type ArrayType { get; private set; } + + /// Gets a value indicating whether this property is in fact a generic list. + public bool IsGnericEnumerable { get; private set; } + + /// Gets the type of property. + public Type Type { get; private set; } + + /// Gets value getter. + public Func Get { get; private set; } + + /// Gets value setter. + public Action Setter { get; private set; } + + /// Gets the property information. + public PropertyInfo PropertyInfo { get; private set; } + + /// Gets name of property. + public string Name { get; private set; } + + /// Gets type column description. + public ColumnAttribute Column { get; private set; } + + /// Gets type list of property requirements. + public List Requirements { get; private set; } + + /// Gets a value indicating whether this is ignored in some cases. + public bool Ignore { get; private set; } + + /// Gets a value indicating whether this instance hold data contract type. + public bool IsDataContract { get; private set; } + + /// Initializes a new instance of the class. + /// Property info to be invoked in the future. + /// Column attribute if exist. + public DynamicPropertyInvoker(PropertyInfo property, ColumnAttribute attr) + { + PropertyInfo = property; + Name = property.Name; + Type = property.PropertyType; + + object[] ignore = property.GetCustomAttributes(typeof(IgnoreAttribute), false); + Requirements = property.GetCustomAttributes(typeof(RequiredAttribute), false).Cast().ToList(); + + Ignore = ignore != null && ignore.Length > 0; + + IsGnericEnumerable = Type.IsGenericEnumerable(); + + ArrayType = Type.IsArray ? Type.GetElementType() : + IsGnericEnumerable ? Type.GetGenericArguments().First() : + Type; + + IsDataContract = ArrayType.GetCustomAttributes(false).Any(x => x.GetType().Name == "DataContractAttribute"); + + if (ArrayType.IsArray) + throw new InvalidOperationException("Jagged arrays are not supported"); + + if (ArrayType.IsGenericEnumerable()) + throw new InvalidOperationException("Enumerables of enumerables are not supported"); + + Column = attr; + + if (attr != null && attr.AllowNull && !Type.IsNullableType() && Type != typeof(string)) + attr.AllowNull = false; + + if (property.CanRead) + Get = CreateGetter(property); + + if (property.CanWrite) + Setter = CreateSetter(property); + } + + private Func CreateGetter(PropertyInfo property) + { + if (property == null || !property.CanRead) + return null; + + var objParm = Expression.Parameter(typeof(object), "o"); + var target = property.DeclaringType.IsValueType + ? (Expression)Expression.Convert(objParm, property.DeclaringType) + : Expression.TypeAs(objParm, property.DeclaringType); + + return Expression.Lambda>( + Expression.Convert( + Expression.Property( + target, + property.Name), + typeof(object)), objParm).Compile(); + } + + private Action CreateSetter(PropertyInfo property) + { + if (!property.CanWrite) + return null; + + var objParm = Expression.Parameter(typeof(object), "o"); + var valueParm = Expression.Parameter(typeof(object), "value"); + + return Expression.Lambda>( + Expression.Assign( + Expression.Property( + Expression.Convert(objParm, property.DeclaringType), + property.Name), + Expression.Convert(valueParm, property.PropertyType)), + objParm, valueParm).Compile(); + } + + /// Sets the specified value to destination object. + /// The destination object. + /// The value. + public void Set(object dest, object val, bool byProperty = false) + { + object value = null; + + try + { + if (!Type.IsAssignableFrom(val.GetType())) + { + if (Type.IsArray || IsGnericEnumerable) + { + if (val != null) + { + if (val is IEnumerable) + { + var lst = (val as IEnumerable).Select(x => GetElementVal(ArrayType, x, byProperty)).ToList(); + + value = Array.CreateInstance(ArrayType, lst.Count); + + int i = 0; + foreach (var e in lst) + ((Array)value).SetValue(e, i++); + } + else + { + value = Array.CreateInstance(ArrayType, 1); + ((Array)value).SetValue(GetElementVal(ArrayType, val, byProperty), 0); + } + } + else + value = Array.CreateInstance(ArrayType, 0); + } + else + value = GetElementVal(Type, val, byProperty); + } + else + value = val; + + Setter(dest, value); + } + catch (Exception ex) + { + throw new DynamicMapperException( + string.Format("Error trying to convert and set value '{0}' of type '{1}' to type '{2}' in object of type '{3}'", + val == null ? string.Empty : val.ToString(), val.GetType(), Type.FullName, dest.GetType().FullName), + ex); + } + } + + private object GetElementVal(System.Type etype, object val, bool byProperty) + { + bool nullable = etype.IsGenericType && etype.GetGenericTypeDefinition() == typeof(Nullable<>); + Type type = Nullable.GetUnderlyingType(etype) ?? etype; + + if (val == null && type.IsValueType) + { + if (nullable) + return null; + else + return Activator.CreateInstance(Type); + } + else if ((val == null && !type.IsValueType) || (val != null && type == val.GetType())) + return val; + else if (type.IsEnum && val.GetType().IsValueType) + return Enum.ToObject(type, val); + else if (type.IsEnum) + try + { + return Enum.Parse(type, val.ToString()); + } + catch (ArgumentException) + { + if (nullable) + return null; + + throw; + } + else if (Type == typeof(string) && val.GetType() == typeof(Guid)) + return val.ToString(); + else if (Type == typeof(Guid)) + { + if (val.GetType() == typeof(byte[])) + return new Guid((byte[])val); + else if (val.GetType() == typeof(string)) + { + Guid g; + return Guid.TryParse((string)val, out g) ? g : Guid.Empty; + } + else return (nullable) ? null : (object)Guid.Empty; + } + else if (!typeof(IConvertible).IsAssignableFrom(type) && (IsDataContract || (!type.IsValueType && val is IDictionary))) + { + if (byProperty) + return val.MapByProperty(type); + + return val.Map(type); + } + else + try + { + return Convert.ChangeType(val, type); + } + catch + { + if (nullable) + return null; + + throw; + } + } + + #region Type command cache + + internal ParameterSpec InsertCommandParameter { get; set; } + + internal ParameterSpec UpdateCommandParameter { get; set; } + + internal ParameterSpec DeleteCommandParameter { get; set; } + + #endregion Type command cache + } + + /// Exception thrown when mapper fails to set or get a property. + /// + public class DynamicMapperException : Exception + { + /// Initializes a new instance of the class. + public DynamicMapperException() + { + } + + /// Initializes a new instance of the class. + /// The message that describes the error. + public DynamicMapperException(string message) : base(message) + { + } + + /// Initializes a new instance of the class. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public DynamicMapperException(string message, Exception innerException) : base(message, innerException) + { + } + } + + /// Represents type columnMap. + public class DynamicTypeMap + { + /// Gets mapper destination type creator. + public Type Type { get; private set; } + + /// Gets type table description. + public TableAttribute Table { get; private set; } + + /// Gets object creator. + public Func Creator { get; private set; } + + /// Gets map of columns to properties. + /// Key: Column name (lower), Value: . + public Dictionary ColumnsMap { get; private set; } + + /// Gets map of properties to column. + /// Key: Property name, Value: Column name. + public Dictionary PropertyMap { get; private set; } + + /// Gets list of ignored properties. + public List Ignored { get; private set; } + + /// Initializes a new instance of the class. + /// Type to which columnMap objects. + public DynamicTypeMap(Type type) + { + Type = type; + + object[] attr = type.GetCustomAttributes(typeof(TableAttribute), false); + + if (attr != null && attr.Length > 0) + Table = (TableAttribute)attr[0]; + + Creator = CreateCreator(); + CreateColumnAndPropertyMap(); + } + + private void CreateColumnAndPropertyMap() + { + Dictionary columnMap = new Dictionary(); + Dictionary propertyMap = new Dictionary(); + List ignored = new List(); + + foreach (PropertyInfo pi in GetAllMembers(Type).Where(x => x is PropertyInfo).Cast()) + { + // Skip indexers + if (pi.GetIndexParameters().Any()) + continue; + + ColumnAttribute attr = null; + + object[] attrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true); + + if (attrs != null && attrs.Length > 0) + attr = (ColumnAttribute)attrs[0]; + + string col = attr == null || string.IsNullOrEmpty(attr.Name) ? pi.Name : attr.Name; + + DynamicPropertyInvoker val = new DynamicPropertyInvoker(pi, attr); + columnMap.Add(col.ToLower(), val); + + propertyMap.Add(pi.Name, col); + + if (val.Ignore) + ignored.Add(pi.Name); + } + + ColumnsMap = columnMap; + PropertyMap = propertyMap; + + Ignored = ignored; ////columnMap.Where(i => i.Value.Ignore).Select(i => i.Value.Name).ToList(); + } + + private Func CreateCreator() + { + var c = Type.GetConstructor(Type.EmptyTypes); + if (c == null) + c = Type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null); + + if (c != null) + return Expression.Lambda>(Expression.New(Type)).Compile(); + + return null; + } + + /// Create object of type and fill values from source. + /// Object containing values that will be mapped to newly created object. + /// New object of type with matching values from source. + public object Create(object source) + { + return Map(source, Creator()); + } + + /// Create object of type and fill values from source using property names. + /// Object containing values that will be mapped to newly created object. + /// New object of type with matching values from source. + public object CreateByProperty(object source) + { + return MapByProperty(source, Creator()); + } + + /// Fill values from source to object in destination. + /// Object containing values that will be mapped to newly created object. + /// Object of type to which copy values from source. + /// Object of type with matching values from source. + public object Map(object source, object destination) + { + DynamicPropertyInvoker dpi = null; + + foreach (KeyValuePair item in source.ToDictionary()) + { + if (ColumnsMap.TryGetValue(item.Key.ToLower(), out dpi) && item.Value != null) + if (dpi.Setter != null) + dpi.Set(destination, item.Value); + } + + return destination; + } + + /// Fill values from source to object in destination using property names. + /// Object containing values that will be mapped to newly created object. + /// Object of type to which copy values from source. + /// Object of type with matching values from source. + public object MapByProperty(object source, object destination) + { + string cn = null; + DynamicPropertyInvoker dpi = null; + + foreach (KeyValuePair item in source.ToDictionary()) + { + if (PropertyMap.TryGetValue(item.Key, out cn) && item.Value != null) + if (ColumnsMap.TryGetValue(cn.ToLower(), out dpi) && item.Value != null) + if (dpi.Setter != null) + dpi.Set(destination, item.Value, true); + } + + return destination; + } + + /// Validates the object. + /// The value. + /// List of not valid results. + public IList ValidateObject(object val) + { + var result = new List(); + + if (val == null || val.GetType() != Type) + return null; + + foreach (var prop in ColumnsMap.Values) + { + if (prop.Requirements == null || !prop.Requirements.Any()) + continue; + + var v = prop.Get(val); + + foreach (var r in prop.Requirements.Where(x => !x.ElementRequirement)) + { + var valid = r.ValidateSimpleValue(prop, v); + + if (valid == ValidateResult.Valid) + { + if (prop.Type.IsArray || prop.IsGnericEnumerable) + { + var map = DynamicMapperCache.GetMapper(prop.ArrayType); + + var list = v as IEnumerable; + + if (list == null) + { + var enumerable = v as IEnumerable; + if (enumerable != null) + list = enumerable.Cast(); + } + + if (list != null) + foreach (var item in list) + { + if (prop.Requirements.Any(x => x.ElementRequirement)) + { + foreach (var re in prop.Requirements.Where(x => x.ElementRequirement)) + { + var validelem = re.ValidateSimpleValue(prop.ArrayType, prop.ArrayType.IsGenericEnumerable(), item); + + if (validelem == ValidateResult.NotSupported) + { + result.AddRange(map.ValidateObject(item)); + break; + } + else if (validelem != ValidateResult.Valid) + result.Add(new ValidationResult() + { + Property = prop, + Requirement = r, + Value = item, + Result = validelem, + }); + } + } + else + result.AddRange(map.ValidateObject(item)); + } + } + + continue; + } + + if (valid == ValidateResult.NotSupported) + { + result.AddRange(DynamicMapperCache.GetMapper(prop.Type).ValidateObject(v)); + continue; + } + + result.Add(new ValidationResult() + { + Property = prop, + Requirement = r, + Value = v, + Result = valid, + }); + } + } + + return result; + } + + private IEnumerable GetAllMembers(Type type) + { + if (type.IsInterface) + { + List members = new List(); + List considered = new List(); + Queue queue = new Queue(); + + considered.Add(type); + queue.Enqueue(type); + + while (queue.Count > 0) + { + Type subType = queue.Dequeue(); + foreach (Type subInterface in subType.GetInterfaces()) + { + if (considered.Contains(subInterface)) continue; + + considered.Add(subInterface); + queue.Enqueue(subInterface); + } + + MemberInfo[] typeProperties = subType.GetMembers( + BindingFlags.FlattenHierarchy + | BindingFlags.Public + | BindingFlags.Instance); + + IEnumerable newPropertyInfos = typeProperties + .Where(x => !members.Contains(x)); + + members.InsertRange(0, newPropertyInfos); + } + + return members; + } + + return type.GetMembers(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance); + } + + #region Type command cache + + internal string InsertCommandText { get; set; } + + internal string UpdateCommandText { get; set; } + + internal string DeleteCommandText { get; set; } + + #endregion Type command cache + } + } + + namespace Helpers + { + + /// Framework detection and specific implementations. + public static class FrameworkTools + { + #region Mono or .NET Framework detection + + /// This is pretty simple trick. + private static bool _isMono = Type.GetType("Mono.Runtime") != null; + + /// Gets a value indicating whether application is running under mono runtime. + public static bool IsMono { get { return _isMono; } } + + #endregion Mono or .NET Framework detection + + static FrameworkTools() + { + _frameworkTypeArgumentsGetter = CreateTypeArgumentsGetter(); + } + + #region GetGenericTypeArguments + + private static Func> _frameworkTypeArgumentsGetter = null; + + private static Func> CreateTypeArgumentsGetter() + { + // HACK: Creating binders assuming types are correct... this may fail. + if (IsMono) + { + Type binderType = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder"); + + if (binderType != null) + { + ParameterExpression param = Expression.Parameter(typeof(InvokeMemberBinder), "o"); + + try + { + return Expression.Lambda>>( + Expression.TypeAs( + Expression.Field( + Expression.TypeAs(param, binderType), "typeArguments"), + typeof(IList)), param).Compile(); + } + catch + { + } + + PropertyInfo prop = binderType.GetProperty("TypeArguments"); + + if (!prop.CanRead) + return null; + + return Expression.Lambda>>( + Expression.TypeAs( + Expression.Property( + Expression.TypeAs(param, binderType), prop.Name), + typeof(IList)), param).Compile(); + } + } + else + { + Type inter = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); + + if (inter != null) + { + PropertyInfo prop = inter.GetProperty("TypeArguments"); + + if (!prop.CanRead) + return null; + + ParameterExpression objParm = Expression.Parameter(typeof(InvokeMemberBinder), "o"); + + return Expression.Lambda>>( + Expression.TypeAs( + Expression.Property( + Expression.TypeAs(objParm, inter), prop.Name), + typeof(IList)), objParm).Compile(); + } + } + + return null; + } + + /// Extension method allowing to easily extract generic type + /// arguments from assuming that it + /// inherits from + /// Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder + /// in .NET Framework or + /// Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder + /// under Mono. + /// Binder from which get type arguments. + /// This is generally a bad solution, but there is no other + /// currently so we have to go with it. + /// List of types passed as generic parameters. + public static IList GetGenericTypeArguments(this InvokeMemberBinder binder) + { + // First try to use delegate if exist + if (_frameworkTypeArgumentsGetter != null) + return _frameworkTypeArgumentsGetter(binder); + + if (_isMono) + { + // HACK: Using Reflection + // In mono this is trivial. + + // First we get field info. + FieldInfo field = binder.GetType().GetField("typeArguments", BindingFlags.Instance | + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + + // If this was a success get and return it's value + if (field != null) + return field.GetValue(binder) as IList; + else + { + PropertyInfo prop = binder.GetType().GetProperty("TypeArguments"); + + // If we have a property, return it's value + if (prop != null) + return prop.GetValue(binder, null) as IList; + } + } + else + { + // HACK: Using Reflection + // In this case, we need more aerobic :D + + // First, get the interface + Type inter = binder.GetType().GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); + + if (inter != null) + { + // Now get property. + PropertyInfo prop = inter.GetProperty("TypeArguments"); + + // If we have a property, return it's value + if (prop != null) + return prop.GetValue(binder, null) as IList; + } + } + + // Sadly return null if failed. + return null; + } + + #endregion GetGenericTypeArguments + } + + /// Extends interface. + public interface IFinalizerDisposable : IExtendedDisposable + { + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + void Dispose(bool disposing); + } + + /// Class contains unclassified extensions. + internal static class UnclassifiedExtensions + { + /// Easy way to use conditional value. + /// Includes . + /// Input object type to check. + /// Result type. + /// The object to check. + /// The select function. + /// The else value. + /// Selected value or default value. + /// It lets you do this: + /// var lname = thingy.NullOr(t => t.Name).NullOr(n => n.ToLower()); + /// which is more fluent and (IMO) easier to read than this: + /// var lname = (thingy != null ? thingy.Name : null) != null ? thingy.Name.ToLower() : null; + /// + public static R NullOr(this T obj, Func func, R elseValue = default(R)) where T : class + { + return obj != null && obj != DBNull.Value ? + func(obj) : elseValue; + } + + /// Easy way to use conditional value. + /// Includes . + /// Input object type to check. + /// Result type. + /// The object to check. + /// The select function. + /// The else value function. + /// Selected value or default value. + /// It lets you do this: + /// var lname = thingy.NullOr(t => t.Name).NullOr(n => n.ToLower()); + /// which is more fluent and (IMO) easier to read than this: + /// var lname = (thingy != null ? thingy.Name : null) != null ? thingy.Name.ToLower() : null; + /// + public static R NullOrFn(this T obj, Func func, Func elseFunc = null) where T : class + { + // Old if to avoid recurency. + return obj != null && obj != DBNull.Value ? + func(obj) : elseFunc != null ? elseFunc() : default(R); + } + + #if !NET6_0_OR_GREATER + /// Simple distinct by selector extension. + /// The enumerator of elements distinct by specified selector. + /// Source collection. + /// Distinct key selector. + /// The enumerable element type parameter. + /// The selector type parameter. + public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) + { + HashSet seenKeys = new HashSet(); + foreach (R element in source) + if (seenKeys.Add(keySelector(element))) + yield return element; + } + #endif + } + + /// Extensions for data reader handling. + public static class DataReaderExtensions + { + /// Gets the data table from data reader. + /// The data reader. + /// The name to give the table. If tableName is null or an empty string, a default name is given when added to the System.Data.DataTableCollection. + /// The namespace for the XML representation of the data stored in the DataTable. + /// + public static DataTable ToDataTable(this IDataReader r, string name = null, string nameSpace = null) + { + DataTable schemaTable = r.GetSchemaTable(); + DataTable resultTable = new DataTable(name, nameSpace); + + foreach (DataRow col in schemaTable.Rows) + { + dynamic c = col.RowToDynamicUpper(); + + DataColumn dataColumn = new DataColumn(); + dataColumn.ColumnName = c.COLUMNNAME; + dataColumn.DataType = (Type)c.DATATYPE; + dataColumn.ReadOnly = true; + dataColumn.Unique = c.ISUNIQUE; + + resultTable.Columns.Add(dataColumn); + } + + while (r.Read()) + { + DataRow row = resultTable.NewRow(); + for (int i = 0; i < resultTable.Columns.Count; i++) + row[i] = r[i]; + + resultTable.Rows.Add(row); + } + + return resultTable; + } + } + + public static class ReaderExtensions + { + public static bool? GetBooleanIfNotNull(this IDataReader r, string name, bool? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetBoolean(ord); + } + + public static byte? GetByteIfNotNull(this IDataReader r, string name, byte? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetByte(ord); + } + + public static char? GetCharIfNotNull(this IDataReader r, string name, char? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetChar(ord); + } + + public static DateTime? GetDateTimeIfNotNull(this IDataReader r, string name, DateTime? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetDateTime(ord); + } + + public static decimal? GetDecimalIfNotNull(this IDataReader r, string name, decimal? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetDecimal(ord); + } + + public static double? GetDoubleIfNotNull(this IDataReader r, string name, double? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetDouble(ord); + } + + public static float? GetFloatIfNotNull(this IDataReader r, string name, float? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetFloat(ord); + } + + public static Guid? GetGuidIfNotNull(this IDataReader r, string name, Guid? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetGuid(ord); + } + + public static short? GetInt16IfNotNull(this IDataReader r, string name, short? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetInt16(ord); + } + + public static int? GetInt32IfNotNull(this IDataReader r, string name, int? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetInt32(ord); + } + + public static long? GetInt64IfNotNull(this IDataReader r, string name, long? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetInt64(ord); + } + + public static string GetStringIfNotNull(this IDataReader r, string name, string def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetString(ord); + } + + public static object GetValueIfNotNull(this IDataReader r, string name, object def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetValue(ord); + } + } + + /// Class containing useful string extensions. + internal static class StringExtensions + { + static StringExtensions() + { + InvalidMultipartMemberChars = _InvalidMultipartMemberChars.ToCharArray(); + InvalidMemberChars = _InvalidMemberChars.ToCharArray(); + } + + private static readonly string _InvalidMultipartMemberChars = " +-*/^%[]{}()!\"\\&=?¿"; + private static readonly string _InvalidMemberChars = "." + _InvalidMultipartMemberChars; + + /// + /// Gets an array with some invalid characters that cannot be used with multipart names for class members. + /// + public static char[] InvalidMultipartMemberChars { get; private set; } + + /// + /// Gets an array with some invalid characters that cannot be used with names for class members. + /// + public static char[] InvalidMemberChars { get; private set; } + + /// + /// Provides with an alternate and generic way to obtain an alternate string representation for this instance, + /// applying the following rules: + /// - Null values are returned as with the value, or a null object. + /// - Enum values are translated into their string representation. + /// - If the type has override the 'ToString' method then it is used. + /// - If it is a dictionary, then a collection of key/value pairs where the value part is also translated. + /// - If it is a collection, then a collection of value items also translated. + /// - If it has public public properties (or if not, if it has public fields), the collection of name/value + /// pairs, with the values translated. + /// - Finally it falls back to the standard 'type.FullName' mechanism. + /// + /// The object to obtain its alternate string representation from. + /// The brackets to use if needed. If not null it must be at least a 2-chars' array containing + /// the opening and closing brackets. + /// Representation of null string.. + /// The alternate string representation of this object. + public static string Sketch(this object obj, char[] brackets = null, string nullString = "(null)") + { + if (obj == null) return nullString; + if (obj is string) return (string)obj; + + Type type = obj.GetType(); + if (type.IsEnum) return obj.ToString(); + + // If the ToString() method has been overriden (by the type itself, or by its parents), let's use it... + MethodInfo method = type.GetMethod("ToString", Type.EmptyTypes); + if (method.DeclaringType != typeof(object)) return obj.ToString(); + + // For alll other cases... + StringBuilder sb = new StringBuilder(); + bool first = true; + + // Dictionaries... + if (obj is IDictionary) + { + if (brackets == null || brackets.Length < 2) + brackets = "[]".ToCharArray(); + + sb.AppendFormat("{0}", brackets[0]); first = true; foreach (DictionaryEntry kvp in (IDictionary)obj) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("'{0}'='{1}'", kvp.Key.Sketch(), kvp.Value.Sketch()); + } + + sb.AppendFormat("{0}", brackets[1]); + return sb.ToString(); + } + + // IEnumerables... + IEnumerator ator = null; + if (obj is IEnumerable) + ator = ((IEnumerable)obj).GetEnumerator(); + else + { + method = type.GetMethod("GetEnumerator", Type.EmptyTypes); + if (method != null) + ator = (IEnumerator)method.Invoke(obj, null); + } + + if (ator != null) + { + if (brackets == null || brackets.Length < 2) brackets = "[]".ToCharArray(); + sb.AppendFormat("{0}", brackets[0]); first = true; while (ator.MoveNext()) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("{0}", ator.Current.Sketch()); + } + + sb.AppendFormat("{0}", brackets[1]); + + if (ator is IDisposable) + ((IDisposable)ator).Dispose(); + + return sb.ToString(); + } + + // As a last resort, using the public properties (or fields if needed, or type name)... + BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy; + PropertyInfo[] props = type.GetProperties(flags); + FieldInfo[] infos = type.GetFields(flags); + + if (props.Length == 0 && infos.Length == 0) sb.Append(type.FullName); // Fallback if needed + else + { + if (brackets == null || brackets.Length < 2) brackets = "{}".ToCharArray(); + sb.AppendFormat("{0}", brackets[0]); + first = true; + + if (props.Length != 0) + { + foreach (PropertyInfo prop in props) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("{0}='{1}'", prop.Name, prop.GetValue(obj, null).Sketch()); + } + } + else + { + if (infos.Length != 0) + { + foreach (FieldInfo info in infos) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("{0}='{1}'", info.Name, info.GetValue(obj).Sketch()); + } + } + } + + sb.AppendFormat("{0}", brackets[1]); + } + + // And returning... + return sb.ToString(); + } + + /// + /// Returns true if the target string contains any of the characters given. + /// + /// The target string. It cannot be null. + /// An array containing the characters to test. It cannot be null. If empty false is returned. + /// True if the target string contains any of the characters given, false otherwise. + public static bool ContainsAny(this string source, char[] items) + { + if (source == null) throw new ArgumentNullException("source", "Source string cannot be null."); + if (items == null) throw new ArgumentNullException("items", "Array of characters to test cannot be null."); + + if (items.Length == 0) return false; // No characters to validate + int ix = source.IndexOfAny(items); + return ix >= 0 ? true : false; + } + + /// + /// Returns a new validated string using the rules given. + /// + /// The source string. + /// A description of the source string to build errors and exceptions if needed. + /// True if the returned string can be null. + /// True if the returned string can be empty. + /// True to trim the returned string. + /// True to left-trim the returned string. + /// True to right-trim the returned string. + /// If >= 0, the min valid length for the returned string. + /// If >= 0, the max valid length for the returned string. + /// If not '\0', the character to use to left-pad the returned string if needed. + /// If not '\0', the character to use to right-pad the returned string if needed. + /// If not null, an array containing invalid chars that must not appear in the returned + /// string. + /// If not null, an array containing the only characters that are considered valid for the + /// returned string. + /// A new validated string. + public static string Validated(this string source, string desc = null, + bool canbeNull = false, bool canbeEmpty = false, + bool trim = true, bool trimStart = false, bool trimEnd = false, + int minLen = -1, int maxLen = -1, char padLeft = '\0', char padRight = '\0', + char[] invalidChars = null, char[] validChars = null) + { + // Assuring a valid descriptor... + if (string.IsNullOrWhiteSpace(desc)) desc = "Source"; + + // Validating if null sources are accepted... + if (source == null) + { + if (!canbeNull) throw new ArgumentNullException(desc, string.Format("{0} cannot be null.", desc)); + return null; + } + + // Trimming if needed... + if (trim && !(trimStart || trimEnd)) source = source.Trim(); + else + { + if (trimStart) source = source.TrimStart(' '); + if (trimEnd) source = source.TrimEnd(' '); + } + + // Adjusting lenght... + if (minLen > 0) + { + if (padLeft != '\0') source = source.PadLeft(minLen, padLeft); + if (padRight != '\0') source = source.PadRight(minLen, padRight); + } + + if (maxLen > 0) + { + if (padLeft != '\0') source = source.PadLeft(maxLen, padLeft); + if (padRight != '\0') source = source.PadRight(maxLen, padRight); + } + + // Validating emptyness and lenghts... + if (source.Length == 0) + { + if (!canbeEmpty) throw new ArgumentException(string.Format("{0} cannot be empty.", desc)); + return string.Empty; + } + + if (minLen >= 0 && source.Length < minLen) throw new ArgumentException(string.Format("Lenght of {0} '{1}' is lower than '{2}'.", desc, source, minLen)); + if (maxLen >= 0 && source.Length > maxLen) throw new ArgumentException(string.Format("Lenght of {0} '{1}' is bigger than '{2}'.", desc, source, maxLen)); + + // Checking invalid chars... + if (invalidChars != null) + { + int n = source.IndexOfAny(invalidChars); + if (n >= 0) throw new ArgumentException(string.Format("Invalid character '{0}' found in {1} '{2}'.", source[n], desc, source)); + } + + // Checking valid chars... + if (validChars != null) + { + int n = validChars.ToString().IndexOfAny(source.ToCharArray()); + if (n >= 0) throw new ArgumentException(string.Format("Invalid character '{0}' found in {1} '{2}'.", validChars.ToString()[n], desc, source)); + } + + return source; + } + + /// + /// Splits the given string with the 'something AS alias' format, returning a tuple containing its 'something' and 'alias' parts. + /// If no alias is detected, then its component in the tuple returned is null and all the contents from the source + /// string are considered as the 'something' part. + /// + /// The source string. + /// A tuple containing the 'something' and 'alias' parts. + public static Tuple SplitSomethingAndAlias(this string source) + { + source = source.Validated("[Something AS Alias]"); + + string something = null; + string alias = null; + int n = source.LastIndexOf(" AS ", StringComparison.OrdinalIgnoreCase); + + if (n < 0) + something = source; + else + { + something = source.Substring(0, n); + alias = source.Substring(n + 4); + } + + return new Tuple(something, alias); + } + + /// Allows to replace parameters inside of string. + /// String containing parameters in format [$ParameterName]. + /// Function that should return value that will be placed in string in place of placed parameter. + /// Prefix of the parameter. This value can't be null or empty, default value [$. + /// Suffix of the parameter. This value can't be null or empty, default value ]. + /// Parsed string. + public static string FillStringWithVariables(this string stringToFill, Func getValue, string prefix = "[$", string sufix = "]") + { + int startPos = 0, endPos = 0; + prefix.Validated(); + sufix.Validated(); + + startPos = stringToFill.IndexOf(prefix, startPos); + while (startPos >= 0) + { + endPos = stringToFill.IndexOf(sufix, startPos + prefix.Length); + int nextStartPos = stringToFill.IndexOf(prefix, startPos + prefix.Length); + + if (endPos > startPos + prefix.Length + 1 && (nextStartPos > endPos || nextStartPos == -1)) + { + string paramName = stringToFill.Substring(startPos + prefix.Length, endPos - (startPos + prefix.Length)); + + stringToFill = stringToFill + .Remove(startPos, (endPos - startPos) + sufix.Length) + .Insert(startPos, getValue(paramName)); + } + + startPos = stringToFill.IndexOf(prefix, startPos + prefix.Length); + } + + return stringToFill; + } + } + + /// Defines methods to support the comparison of collections for equality. + /// The type of collection to compare. + public class CollectionComparer : IEqualityComparer> + { + /// Determines whether the specified objects are equal. + /// The first object of type T to compare. + /// The second object of type T to compare. + /// Returns true if the specified objects are equal; otherwise, false. + bool IEqualityComparer>.Equals(IEnumerable first, IEnumerable second) + { + return Equals(first, second); + } + + /// Returns a hash code for the specified object. + /// The enumerable for which a hash code is to be returned. + /// A hash code for the specified object. + int IEqualityComparer>.GetHashCode(IEnumerable enumerable) + { + return GetHashCode(enumerable); + } + + /// Returns a hash code for the specified object. + /// The enumerable for which a hash code is to be returned. + /// A hash code for the specified object. + public static int GetHashCode(IEnumerable enumerable) + { + int hash = 17; + + foreach (T val in enumerable.OrderBy(x => x)) + hash = (hash * 23) + val.GetHashCode(); + + return hash; + } + + /// Determines whether the specified objects are equal. + /// The first object of type T to compare. + /// The second object of type T to compare. + /// Returns true if the specified objects are equal; otherwise, false. + public static bool Equals(IEnumerable first, IEnumerable second) + { + if ((first == null) != (second == null)) + return false; + + if (!object.ReferenceEquals(first, second) && (first != null)) + { + if (first.Count() != second.Count()) + return false; + + if ((first.Count() != 0) && HaveMismatchedElement(first, second)) + return false; + } + + return true; + } + + private static bool HaveMismatchedElement(IEnumerable first, IEnumerable second) + { + int firstCount; + int secondCount; + + Dictionary firstElementCounts = GetElementCounts(first, out firstCount); + Dictionary secondElementCounts = GetElementCounts(second, out secondCount); + + if (firstCount != secondCount) + return true; + + foreach (KeyValuePair kvp in firstElementCounts) + if (kvp.Value != (secondElementCounts.TryGetNullable(kvp.Key) ?? 0)) + return true; + + return false; + } + + private static Dictionary GetElementCounts(IEnumerable enumerable, out int nullCount) + { + Dictionary dictionary = new Dictionary(); + nullCount = 0; + + foreach (T element in enumerable) + { + if (element == null) + nullCount++; + else + { + int count = dictionary.TryGetNullable(element) ?? 0; + dictionary[element] = ++count; + } + } + + return dictionary; + } + } + + /// Extends interface. + public interface IExtendedDisposable : IDisposable + { + /// + /// Gets a value indicating whether this instance is disposed. + /// + /// + /// true if this instance is disposed; otherwise, false. + /// + bool IsDisposed { get; } + } + + namespace Dynamics + { + + /// Class that allows to use interfaces as dynamic objects. + /// Type of class to proxy. + /// This is temporary solution. Which allows to use builders as a dynamic type. + public class DynamicProxy : DynamicObject, IDisposable + { + private T _proxy; + private Type _type; + private Dictionary _properties; + private Dictionary _methods; + + /// + /// Initializes a new instance of the class. + /// + /// The object to which proxy should be created. + /// The object to which proxy should be created is null. + public DynamicProxy(T proxiedObject) + { + if (proxiedObject == null) + throw new ArgumentNullException("proxiedObject"); + + _proxy = proxiedObject; + _type = typeof(T); + + DynamicTypeMap mapper = Mapper.DynamicMapperCache.GetMapper(); + + _properties = mapper + .ColumnsMap + .ToDictionary( + k => k.Value.Name, + v => v.Value); + + _methods = GetAllMembers(_type) + .Where(x => x is MethodInfo) + .Cast() + .Where(m => !((m.Name.StartsWith("set_") && m.ReturnType == typeof(void)) || m.Name.StartsWith("get_"))) + .Where(m => !m.IsStatic && !m.IsGenericMethod) + .ToDictionary( + k => k, + v => + { + try + { + Type type = v.ReturnType == typeof(void) ? + Expression.GetActionType(v.GetParameters().Select(t => t.ParameterType).ToArray()) : + Expression.GetDelegateType(v.GetParameters().Select(t => t.ParameterType).Concat(new[] { v.ReturnType }).ToArray()); + + return Delegate.CreateDelegate(type, _proxy, v.Name); + } + catch (ArgumentException) + { + return null; + } + }); + } + + /// Provides implementation for type conversion operations. + /// Classes derived from the + /// class can override this method to specify dynamic behavior for + /// operations that convert an object from one type to another. + /// Provides information about the conversion operation. + /// The binder.Type property provides the type to which the object must be + /// converted. For example, for the statement (String)sampleObject in C# + /// (CType(sampleObject, Type) in Visual Basic), where sampleObject is an + /// instance of the class derived from the + /// class, binder.Type returns the type. + /// The binder.Explicit property provides information about the kind of + /// conversion that occurs. It returns true for explicit conversion and + /// false for implicit conversion. + /// The result of the type conversion operation. + /// Returns true if the operation is successful; otherwise, false. + /// If this method returns false, the run-time binder of the language determines the + /// behavior. (In most cases, a language-specific run-time exception is thrown). + public override bool TryConvert(ConvertBinder binder, out object result) + { + if (binder.Type == typeof(T)) + { + result = _proxy; + return true; + } + + if (_proxy != null && + binder.Type.IsAssignableFrom(_proxy.GetType())) + { + result = _proxy; + return true; + } + + return base.TryConvert(binder, out result); + } + + /// Provides the implementation for operations that get member + /// values. Classes derived from the + /// class can override this method to specify dynamic behavior for + /// operations such as getting a value for a property. + /// Provides information about the object that + /// called the dynamic operation. The binder.Name property provides + /// the name of the member on which the dynamic operation is performed. + /// For example, for the Console.WriteLine(sampleObject.SampleProperty) + /// statement, where sampleObject is an instance of the class derived + /// from the class, + /// binder.Name returns "SampleProperty". The binder.IgnoreCase property + /// specifies whether the member name is case-sensitive. + /// The result of the get operation. For example, + /// if the method is called for a property, you can assign the property + /// value to . + /// Returns true if the operation is successful; otherwise, + /// false. If this method returns false, the run-time binder of the + /// language determines the behavior. (In most cases, a run-time exception + /// is thrown). + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + try + { + DynamicPropertyInvoker prop = _properties.TryGetValue(binder.Name); + + result = prop.NullOr(p => p.Get.NullOr(g => g(_proxy), null), null); + + return prop != null && prop.Get != null; + } + catch (Exception ex) + { + throw new InvalidOperationException(string.Format("Cannot get member {0}", binder.Name), ex); + } + } + + /// Provides the implementation for operations that set member + /// values. Classes derived from the + /// class can override this method to specify dynamic behavior for operations + /// such as setting a value for a property. + /// Provides information about the object that called + /// the dynamic operation. The binder.Name property provides the name of + /// the member to which the value is being assigned. For example, for the + /// statement sampleObject.SampleProperty = "Test", where sampleObject is + /// an instance of the class derived from the + /// class, binder.Name returns "SampleProperty". The binder.IgnoreCase + /// property specifies whether the member name is case-sensitive. + /// The value to set to the member. For example, for + /// sampleObject.SampleProperty = "Test", where sampleObject is an instance + /// of the class derived from the + /// class, the is "Test". + /// Returns true if the operation is successful; otherwise, + /// false. If this method returns false, the run-time binder of the + /// language determines the behavior. (In most cases, a language-specific + /// run-time exception is thrown). + public override bool TrySetMember(SetMemberBinder binder, object value) + { + try + { + DynamicPropertyInvoker prop = _properties.TryGetValue(binder.Name); + + if (prop != null && prop.Setter != null) + { + prop.Set(_proxy, value); + return true; + } + + return false; + } + catch (Exception ex) + { + throw new InvalidOperationException(string.Format("Cannot set member {0} to '{1}'", binder.Name, value), ex); + } + } + + /// Provides the implementation for operations that invoke a member. + /// Classes derived from the + /// class can override this method to specify dynamic behavior for + /// operations such as calling a method. + /// Provides information about the dynamic operation. + /// The binder.Name property provides the name of the member on which the + /// dynamic operation is performed. For example, for the statement + /// sampleObject.SampleMethod(100), where sampleObject is an instance of + /// the class derived from the + /// class, binder.Name returns "SampleMethod". The binder.IgnoreCase property + /// specifies whether the member name is case-sensitive. + /// The arguments that are passed to the object member + /// during the invoke operation. For example, for the statement + /// sampleObject.SampleMethod(100), where sampleObject is derived from the + /// class, + /// First element of is equal to 100. + /// The result of the member invocation. + /// Returns true if the operation is successful; otherwise, + /// false. If this method returns false, the run-time binder of the + /// language determines the behavior. (In most cases, a language-specific + /// run-time exception is thrown). + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + return TryInvokeMethod(binder.Name, out result, args) || base.TryInvokeMember(binder, args, out result); + } + + private bool TryInvokeMethod(string name, out object result, object[] args) + { + result = null; + + MethodInfo mi = _methods.Keys + .Where(m => m.Name == name) + .FirstOrDefault(m => + CompareTypes(m.GetParameters().ToArray(), + args.Select(a => a.GetType()).ToArray())); + + Delegate d = _methods.TryGetValue(mi); + + if (d != null) + { + result = d.DynamicInvoke(CompleteArguments(mi.GetParameters().ToArray(), args)); + + if (d.Method.ReturnType == _type && result is T) + result = new DynamicProxy((T)result); + + return true; + } + else if (mi != null) + { + result = mi.Invoke(_proxy, CompleteArguments(mi.GetParameters().ToArray(), args)); + + if (mi.ReturnType == _type && result is T) + result = new DynamicProxy((T)result); + + return true; + } + + return false; + } + + private bool CompareTypes(ParameterInfo[] parameters, Type[] types) + { + if (parameters.Length < types.Length || parameters.Count(p => !p.IsOptional) > types.Length) + return false; + + for (int i = 0; i < types.Length; i++) + if (types[i] != parameters[i].ParameterType && !parameters[i].ParameterType.IsAssignableFrom(types[i])) + return false; + + return true; + } + + private object[] CompleteArguments(ParameterInfo[] parameters, object[] arguments) + { + return arguments.Concat(parameters.Skip(arguments.Length).Select(p => p.DefaultValue)).ToArray(); + } + + private IEnumerable GetAllMembers(Type type) + { + if (type.IsInterface) + { + List members = new List(); + List considered = new List(); + Queue queue = new Queue(); + + considered.Add(type); + queue.Enqueue(type); + + while (queue.Count > 0) + { + Type subType = queue.Dequeue(); + foreach (Type subInterface in subType.GetInterfaces()) + { + if (considered.Contains(subInterface)) continue; + + considered.Add(subInterface); + queue.Enqueue(subInterface); + } + + MemberInfo[] typeProperties = subType.GetMembers( + BindingFlags.FlattenHierarchy + | BindingFlags.Public + | BindingFlags.Instance); + + IEnumerable newPropertyInfos = typeProperties + .Where(x => !members.Contains(x)); + + members.InsertRange(0, newPropertyInfos); + } + + return members; + } + + return type.GetMembers(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance); + } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + object res; + TryInvokeMethod("Dispose", out res, new object[] { }); + + _properties.Clear(); + + _methods = null; + _properties = null; + _type = null; + _proxy = default(T); + } + } + + /// + /// Class able to parse dynamic lambda expressions. Allows to create dynamic logic. + /// + public class DynamicParser : IExtendedDisposable + { + #region Node + + /// + /// Generic bindable operation where some of its operands is a dynamic argument, or a dynamic member or + /// a method of that argument. + /// + [Serializable] + public class Node : IDynamicMetaObjectProvider, IFinalizerDisposable, ISerializable + { + private DynamicParser _parser = null; + + #region MetaNode + + /// + /// Represents the dynamic binding and a binding logic of + /// an object participating in the dynamic binding. + /// + internal class MetaNode : DynamicMetaObject + { + /// + /// Initializes a new instance of the class. + /// + /// The parameter. + /// The restrictions. + /// The value. + public MetaNode(Expression parameter, BindingRestrictions rest, object value) + : base(parameter, rest, value) + { + } + + // Func was cool but caused memory leaks + private DynamicMetaObject GetBinder(Node node) + { + Node o = (Node)this.Value; + node.Parser = o.Parser; + o.Parser.Last = node; + + ParameterExpression p = Expression.Variable(typeof(Node), "ret"); + BlockExpression exp = Expression.Block(new ParameterExpression[] { p }, Expression.Assign(p, Expression.Constant(node))); + + return new MetaNode(exp, this.Restrictions, node); + } + + /// + /// Performs the binding of the dynamic get member operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindGetMember(GetMemberBinder binder) + { + return GetBinder(new GetMember((Node)this.Value, binder.Name)); + } + + /// + /// Performs the binding of the dynamic set member operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// The representing the value for the set member operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) + { + return GetBinder(new SetMember((Node)this.Value, binder.Name, value.Value)); + } + + /// + /// Performs the binding of the dynamic get index operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - indexes for the get index operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) + { + return GetBinder(new GetIndex((Node)this.Value, MetaList2List(indexes))); + } + + /// + /// Performs the binding of the dynamic set index operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - indexes for the set index operation. + /// The representing the value for the set index operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) + { + return GetBinder(new SetIndex((Node)this.Value, MetaList2List(indexes), value.Value)); + } + + /// + /// Performs the binding of the dynamic invoke operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - arguments to the invoke operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) + { + return GetBinder(new Invoke((Node)this.Value, MetaList2List(args))); + } + + /// + /// Performs the binding of the dynamic invoke member operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - arguments to the invoke member operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) + { + return GetBinder(new Method((Node)this.Value, binder.Name, MetaList2List(args))); + } + + /// + /// Performs the binding of the dynamic binary operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An instance of the representing the right hand side of the binary operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) + { + return GetBinder(new Binary((Node)this.Value, binder.Operation, arg.Value)); + } + + /// + /// Performs the binding of the dynamic unary operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) + { + Node o = (Node)this.Value; + Unary node = new Unary(o, binder.Operation) { Parser = o.Parser }; + o.Parser.Last = node; + + // If operation is 'IsTrue' or 'IsFalse', we will return false to keep the engine working... + object ret = node; + if (binder.Operation == ExpressionType.IsTrue) ret = (object)false; + if (binder.Operation == ExpressionType.IsFalse) ret = (object)false; + + ParameterExpression p = Expression.Variable(ret.GetType(), "ret"); // the type is now obtained from "ret" + BlockExpression exp = Expression.Block( + new ParameterExpression[] { p }, + Expression.Assign(p, Expression.Constant(ret))); // the expression is now obtained from "ret" + + return new MetaNode(exp, this.Restrictions, node); + } + + /// + /// Performs the binding of the dynamic conversion operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindConvert(ConvertBinder binder) + { + Node o = (Node)this.Value; + Convert node = new Convert(o, binder.ReturnType) { Parser = o.Parser }; + o.Parser.Last = node; + + // Reducing the object to return if this is an assignment node... + object ret = o; + bool done = false; + + while (!done) + { + if (ret is SetMember) + ret = ((SetMember)o).Value; + else if (ret is SetIndex) + ret = ((SetIndex)o).Value; + else + done = true; + } + + // Creating an instance... + if (binder.ReturnType == typeof(string)) ret = ret.ToString(); + else + { + try + { + if (binder.ReturnType.IsNullableType()) + ret = null; // to avoid cast exceptions + else + ret = Activator.CreateInstance(binder.ReturnType, true); // true to allow non-public ctor as well + } + catch + { + // as the last resort scenario + ret = new object(); + } + } + + ParameterExpression p = Expression.Variable(binder.ReturnType, "ret"); + BlockExpression exp = Expression.Block( + new ParameterExpression[] { p }, + Expression.Assign(p, Expression.Constant(ret, binder.ReturnType))); // specifying binder.ReturnType + + return new MetaNode(exp, this.Restrictions, node); + } + + private static object[] MetaList2List(DynamicMetaObject[] metaObjects) + { + if (metaObjects == null) return null; + + object[] list = new object[metaObjects.Length]; + for (int i = 0; i < metaObjects.Length; i++) + list[i] = metaObjects[i].Value; + + return list; + } + } + + #endregion MetaNode + + #region Argument + + /// + /// Describe a dynamic argument used in a dynamic lambda expression. + /// + [Serializable] + public class Argument : Node, ISerializable + { + /// + /// Initializes a new instance of the class. + /// + /// The name. + public Argument(string name) + : base(name) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Argument(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Argument::Disposed}"; + return Name; + } + } + + #endregion Argument + + #region GetMember + + /// + /// Describe a 'get member' operation, as in 'x => x.Member'. + /// + [Serializable] + public class GetMember : Node, ISerializable + { + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + public GetMember(Node host, string name) + : base(host, name) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected GetMember(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::GetMember::Disposed}"; + return string.Format("{0}.{1}", Host.Sketch(), Name.Sketch()); + } + } + + #endregion GetMember + + #region SetMember + + /// + /// Describe a 'set member' operation, as in 'x => x.Member = y'. + /// + [Serializable] + public class SetMember : Node, ISerializable + { + /// + /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been + /// assigned to this instance, or if this instance is disposed. + /// + public object Value { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + /// The value. + public SetMember(Node host, string name, object value) + : base(host, name) + { + Value = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected SetMember(SerializationInfo info, StreamingContext context) + : base(info, context) + { + string type = info.GetString("MemberType"); + Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); + if (Value != null) + info.AddValue("MemberValue", Value); + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::SetMember::Disposed}"; + return string.Format("({0}.{1} = {2})", Host.Sketch(), Name.Sketch(), Value.Sketch()); + } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Value != null) + { + var node = Value as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + + Value = null; + } + } + catch + { + } + } + + base.Dispose(disposing); + } + } + + #endregion SetMember + + #region GetIndex + + /// + /// Describe a 'get indexed' operation, as in 'x => x.Member[...]'. + /// + [Serializable] + public class GetIndex : Node, ISerializable + { + /// Gets the indexes. + public object[] Indexes { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The indexes. + /// Indexes array cannot be null. + /// Indexes array cannot be empty. + public GetIndex(Node host, object[] indexes) + : base(host) + { + if (indexes == null) + throw new ArgumentNullException("indexes", "Indexes array cannot be null."); + if (indexes.Length == 0) + throw new ArgumentException("Indexes array cannot be empty."); + + Indexes = indexes; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected GetIndex(SerializationInfo info, StreamingContext context) + : base(info, context) + { + int count = (int)info.GetValue("IndexCount", typeof(int)); + + if (count != 0) + { + Indexes = new object[count]; for (int i = 0; i < count; i++) + { + string typeName = info.GetString("IndexType" + i); + object obj = typeName == "NULL" ? null : info.GetValue("IndexValue" + i, Type.GetType(typeName)); + Indexes[i] = obj; + } + } + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + int count = Indexes == null ? 0 : Indexes.Length; info.AddValue("IndexCount", count); + for (int i = 0; i < count; i++) + { + info.AddValue("IndexType" + i, Indexes[i] == null ? "NULL" : Indexes[i].GetType().AssemblyQualifiedName); + if (Indexes[i] != null) info.AddValue("IndexValue" + i, Indexes[i]); + } + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::GetIndex::Disposed}"; + + return string.Format("{0}{1}", Host.Sketch(), Indexes == null ? "[empty]" : Indexes.Sketch()); + } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Indexes != null) + { + for (int i = 0; i < Indexes.Length; i++) + { + var node = Indexes[i] as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + } + + Array.Clear(Indexes, 0, Indexes.Length); + } + } + catch + { + } + + Indexes = null; + } + + base.Dispose(disposing); + } + } + + #endregion GetIndex + + #region SetIndex + + /// + /// Describe a 'set indexed' operation, as in 'x => x.Member[...] = Value'. + /// + [Serializable] + public class SetIndex : GetIndex, ISerializable + { + /// + /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been + /// assigned to this instance, or if this instance is disposed. + /// + public object Value { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The indexes. + /// The value. + public SetIndex(Node host, object[] indexes, object value) + : base(host, indexes) + { + Value = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected SetIndex(SerializationInfo info, StreamingContext context) + : base(info, context) + { + string type = info.GetString("MemberType"); + Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); + if (Value != null) info.AddValue("MemberValue", Value); + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::SetIndex::Disposed}"; + + return string.Format("({0}{1} = {2})", Host.Sketch(), Indexes == null ? "[empty]" : Indexes.Sketch(), Value.Sketch()); + } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Value != null) + { + var node = Value as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + + Value = null; + } + } + catch + { + } + } + + base.Dispose(disposing); + } + } + + #endregion SetIndex + + #region Invoke + + /// + /// Describe a method invocation operation, as in 'x => x.Method(...)". + /// + [Serializable] + public class Invoke : Node, ISerializable + { + /// Gets the arguments. + public object[] Arguments { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The arguments. + public Invoke(Node host, object[] arguments) + : base(host) + { + Arguments = arguments == null || arguments.Length == 0 ? null : arguments; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Invoke(SerializationInfo info, StreamingContext context) + : base(info, context) + { + int count = (int)info.GetValue("ArgumentCount", typeof(int)); + + if (count != 0) + { + Arguments = new object[count]; for (int i = 0; i < count; i++) + { + string typeName = info.GetString("ArgumentType" + i); + object obj = typeName == "NULL" ? null : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); + Arguments[i] = obj; + } + } + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + int count = Arguments == null ? 0 : Arguments.Length; info.AddValue("ArgumentCount", count); + for (int i = 0; i < count; i++) + { + info.AddValue("ArgumentType" + i, Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); + if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); + } + + base.GetObjectData(info, context); + } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Arguments != null) + { + for (int i = 0; i < Arguments.Length; i++) + { + var node = Arguments[i] as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + } + + Array.Clear(Arguments, 0, Arguments.Length); + } + } + catch + { + } + + Arguments = null; + } + + base.Dispose(disposing); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Invoke::Disposed}"; + + return string.Format("{0}{1}", Host.Sketch(), Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); + } + } + + #endregion Invoke + + #region Method + + /// + /// Describe a method invocation operation, as in 'x => x.Method(...)". + /// + [Serializable] + public class Method : Node, ISerializable + { + /// Gets the arguments. + public object[] Arguments { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + /// The arguments. + public Method(Node host, string name, object[] arguments) + : base(host, name) + { + Arguments = arguments == null || arguments.Length == 0 ? null : arguments; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Method(SerializationInfo info, StreamingContext context) + : base(info, context) + { + int count = (int)info.GetValue("ArgumentCount", typeof(int)); + + if (count != 0) + { + Arguments = new object[count]; for (int i = 0; i < count; i++) + { + string typeName = info.GetString("ArgumentType" + i); + object obj = typeName == "NULL" ? null : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); + Arguments[i] = obj; + } + } + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + int count = Arguments == null ? 0 : Arguments.Length; info.AddValue("ArgumentCount", count); + for (int i = 0; i < count; i++) + { + info.AddValue("ArgumentType" + i, Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); + if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); + } + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Method::Disposed}"; + + return string.Format("{0}.{1}{2}", Host.Sketch(), Name.Sketch(), Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); + } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Arguments != null) + { + for (int i = 0; i < Arguments.Length; i++) + { + var node = Arguments[i] as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + } + + Array.Clear(Arguments, 0, Arguments.Length); + } + } + catch + { + } + + Arguments = null; + } + + base.Dispose(disposing); + } + } + + #endregion Method + + #region Binary + + /// + /// Represents a binary operation between a dynamic element and an arbitrary object, including null ones, as in + /// 'x => (x && null)'. The left operand must be an instance of , whereas the right one + /// can be any object, including null values. + /// + [Serializable] + public class Binary : Node, ISerializable + { + /// Gets the operation. + public ExpressionType Operation { get; private set; } + + /// Gets host of the . + public Node Left { get { return Host; } } + + /// Gets the right side value. + public object Right { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The left. + /// The operation. + /// The right. + public Binary(Node left, ExpressionType operation, object right) + : base(left) + { + Operation = operation; + Right = right; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Binary(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); + + string type = info.GetString("RightType"); + Right = type == "NULL" ? null : (Node)info.GetValue("RightItem", Type.GetType(type)); + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Operation", Operation); + + info.AddValue("RightType", Right == null ? "NULL" : Right.GetType().AssemblyQualifiedName); + if (Right != null) + info.AddValue("RightItem", Right); + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Binary::Disposed}"; + + return string.Format("({0} {1} {2})", Host.Sketch(), Operation, Right.Sketch()); + } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + if (Left != null) + { + if (Left is Node) + { + Node n = (Node)Left; + + if (!n.IsDisposed) + n.Dispose(disposing); + } + } + + if (Right != null) + { + if (Right is Node) + { + Node n = (Node)Right; + + if (!n.IsDisposed) + n.Dispose(disposing); + } + + Right = null; + } + } + } + } + + #endregion Binary + + #region Unary + + /// + /// Represents an unary operation, as in 'x => !x'. The target must be a instance. There + /// is no distinction between pre- and post- version of the same operation. + /// + [Serializable] + public class Unary : Node, ISerializable + { + /// Gets the operation. + public ExpressionType Operation { get; private set; } + + /// Gets host of the . + public Node Target { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The target. + /// The operation. + public Unary(Node target, ExpressionType operation) + : base(target) + { + Operation = operation; + Target = target; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Unary(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Operation", Operation); + + base.GetObjectData(info, context); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Binary::Disposed}"; + + return string.Format("({0} {1})", Operation, Host.Sketch()); + } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Target != null) + { + var node = Target as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + + Target = null; + } + } + catch + { + } + } + + base.Dispose(disposing); + } + } + + #endregion Unary + + #region Convert + + /// + /// Represents a conversion operation, as in 'x => (string)x'. + /// + [Serializable] + public class Convert : Node, ISerializable + { + /// Gets the new type to which value will be converted. + public Type NewType { get; private set; } + + /// Gets host of the . + public Node Target { get { return Host; } } + + /// + /// Initializes a new instance of the class. + /// + /// The target. + /// The new type. + public Convert(Node target, Type newType) + : base(target) + { + NewType = newType; + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Convert(SerializationInfo info, StreamingContext context) + : base(info, context) + { + NewType = (Type)info.GetValue("NewType", typeof(Type)); + } + + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("NewType", NewType); + + base.GetObjectData(info, context); + } + } + + #endregion Convert + + /// + /// Gets the name of the member. It might be null if this instance is disposed. + /// + public string Name { get; internal set; } + + /// Gets host of the . + public Node Host { get; internal set; } + + /// Gets reference to the parser. + public DynamicParser Parser + { + get { return _parser; } + internal set + { + _parser = value; + if (_parser != null) + _parser._allNodes.Add(this); + } + } + + /// + /// Initializes a new instance of the class. + /// + internal Node() + { + IsDisposed = false; + } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + internal Node(Node host) + : this() + { + if (host == null) + throw new ArgumentNullException("host", "Host cannot be null."); + + Host = host; + } + + /// + /// Initializes a new instance of the class. + /// + /// The name. + internal Node(string name) + : this() + { + Name = name.Validated("Name"); + } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + /// Host cannot be null. + internal Node(Node host, string name) + : this(host) + { + Name = name.Validated("Name"); + } + + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Node(SerializationInfo info, StreamingContext context) + { + Name = info.GetString("MemberName"); + + string type = info.GetString("HostType"); + Host = type == "NULL" ? null : (Node)info.GetValue("HostItem", Type.GetType(type)); + } + + /// Returns whether the given node is an ancestor of this instance. + /// The node to test. + /// True if the given node is an ancestor of this instance. + public bool IsNodeAncestor(Node node) + { + if (node != null) + { + Node parent = Host; + + while (parent != null) + { + if (object.ReferenceEquals(parent, node)) + return true; + + parent = parent.Host; + } + } + + return false; + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Disposed}"; + + return "{DynamicParser::Node::Empty}"; + } + + #region Implementation of IDynamicMetaObjectProvider + + /// Returns the responsible + /// for binding operations performed on this object. + /// The expression tree representation of the runtime value. + /// The to bind this object. + /// Thrown if this instance is disposed. + public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter) + { + if (IsDisposed) + throw new ObjectDisposedException("DynamicParser.Node"); + + return new MetaNode( + parameter, + BindingRestrictions.GetInstanceRestriction(parameter, this), + this); + } + + #endregion Implementation of IDynamicMetaObjectProvider + + #region Implementation of IFinalizerDisposable + + /// Finalizes an instance of the class. + ~Node() + { + Dispose(false); + } + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public virtual void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public virtual void Dispose(bool disposing) + { + if (disposing) + { + IsDisposed = true; + + if (Host != null && !Host.IsDisposed) + Host.Dispose(); + + Host = null; + + Parser = null; + } + } + + #endregion Implementation of IFinalizerDisposable + + #region Implementation of ISerializable + + /// + /// Populates a with the data needed to serialize the target object. + /// + /// The to populate with data. + /// The destination (see ) for this serialization. + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (!string.IsNullOrEmpty(Name)) + info.AddValue("MemberName", Name); + + info.AddValue("HostType", Host == null ? "NULL" : Host.GetType().AssemblyQualifiedName); + if (Host != null) + info.AddValue("HostItem", Host); + } + + #endregion Implementation of ISerializable + } + + #endregion Node + + #region Data + + private List _arguments = new List(); + private List _allNodes = new List(); + private object _uncertainResult; + + #endregion Data + + #region Properties + + /// Gets the last node (root of the tree). + public Node Last { get; internal set; } + + /// + /// Gets an enumeration containing the dynamic arguments used in the dynamic lambda expression parsed. + /// + public IEnumerable Arguments + { + get + { + List list = new List(); + if (!IsDisposed && _arguments != null) + list.AddRange(_arguments); + + foreach (Node.Argument arg in list) + yield return arg; + + list.Clear(); + list = null; + } + } + + /// + /// Gets the number of dynamic arguments used in the dynamic lambda expression parsed. + /// + public int Count + { + get { return _arguments == null ? 0 : _arguments.Count; } + } + + /// + /// Gets the result of the parsing of the dynamic lambda expression. This result can be either an arbitrary object, + /// including null, if the expression resolves to it, or an instance of the class that + /// contains the last logic expression evaluated when parsing the dynamic lambda expression. + /// + public object Result + { + get { return _uncertainResult ?? Last; } + } + + #endregion Properties + + private DynamicParser(Delegate f) + { + // I know this can be almost a one liner + // but it causes memory leaks when so. + ParameterInfo[] pars = f.Method.GetParameters(); + foreach (ParameterInfo p in pars) + { + int attrs = p.GetCustomAttributes(typeof(DynamicAttribute), true).Length; + if (attrs != 0) + { + Node.Argument par = new Node.Argument(p.Name) { Parser = this }; + this._arguments.Add(par); + } + else + throw new ArgumentException(string.Format("Argument '{0}' must be dynamic.", p.Name)); + } + + try + { + _uncertainResult = f.DynamicInvoke(_arguments.ToArray()); + } + catch (TargetInvocationException e) + { + if (e.InnerException != null) throw e.InnerException; + throw; + } + } + + /// + /// Parses the dynamic lambda expression given in the form of a delegate, and returns a new instance of the + /// class that holds the dynamic arguments used in the dynamic lambda expression, and + /// the result of the parsing. + /// + /// The dynamic lambda expression to parse. + /// A new instance of . + public static DynamicParser Parse(Delegate f) + { + return new DynamicParser(f); + } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Disposed}"; + + StringBuilder sb = new StringBuilder(); + + sb.Append("("); + bool first = true; + + if (_arguments != null) + { + foreach (Node.Argument arg in _arguments) + { + if (!first) sb.Append(", "); else first = false; + sb.Append(arg); + } + } + + sb.Append(")"); + + sb.AppendFormat(" => {0}", Result.Sketch()); + + return sb.ToString(); + } + + #region Implementation of IExtendedDisposable + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (IsDisposed) + return; + + IsDisposed = true; + + if (_uncertainResult != null) + { + if (_uncertainResult is Node) + ((Node)_uncertainResult).Dispose(); + + _uncertainResult = null; + } + + if (Last != null) + { + if (!Last.IsDisposed) + Last.Dispose(); + + Last = null; + } + + if (_arguments != null) + { + _arguments.ForEach(x => { if (!x.IsDisposed) x.Dispose(); }); + + _arguments.Clear(); + _arguments = null; + } + + if (_allNodes != null) + { + _allNodes.ForEach(x => { if (!x.IsDisposed) x.Dispose(); }); + + _allNodes.Clear(); + _allNodes = null; + } + } + + #endregion Implementation of IExtendedDisposable + } + } + } + + namespace Builders + { + + /// Dynamic insert query builder interface. + /// This interface it publicly 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 update query builder interface. + /// This interface it publicly 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 : IExtendedDisposable + { + /// 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; } + } + + /// Dynamic query builder base interface. + /// This interface it publicly available. Implementation should be hidden. + public interface IDynamicQueryBuilder : IExtendedDisposable + { + /// 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 actions. + /// This is exposed to allow setting schema of column. + List> OnCreateTemporaryParameter { get; set; } + + /// Gets or sets the on create real parameter actions. + /// This is exposed to allow modification of parameter. + List> OnCreateParameter { get; set; } + } + + /// Dynamic select query builder interface. + /// This interface it publicly 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(); + + #if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE + + /// Returns a single result. + /// Type to parse to. + /// Default value. + /// Result of a query. + T ScalarAs(T defaultValue = default(T)); + + #endif + + #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 Having + + /// + /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Having() 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: + /// 'Having( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicSelectQueryBuilder Having(Func func); + + /// Add Having condition. + /// Condition column with operator and value. + /// Builder instance. + IDynamicSelectQueryBuilder Having(DynamicColumn column); + + /// Add Having condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value); + + /// Add Having condition. + /// Condition column. + /// Condition value. + /// Builder instance. + IDynamicSelectQueryBuilder Having(string column, object value); + + /// Add Having 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 Having(object conditions, bool schema = false); + + #endregion Having + + #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 delete query builder interface. + /// This interface it publicly 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); + } + + /// Interface describing table information. + public interface ITableInfo : IExtendedDisposable + { + /// Gets table owner name. + string Owner { get; } + + /// Gets table name. + string Name { get; } + + /// Gets table alias. + string Alias { get; } + + /// Gets table no lock status. + bool NoLock { get; } + + /// Gets table schema. + Dictionary Schema { get; } + } + + namespace Extensions + { + + internal static class DynamicHavingQueryExtensions + { + #region Where + + internal static T InternalHaving(this T builder, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + return builder.InternalHaving(false, false, func); + } + + internal static T InternalHaving(this T builder, bool addBeginBrace, bool addEndBrace, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + if (func == null) throw new ArgumentNullException("Array of functions cannot be null."); + + using (DynamicParser parser = DynamicParser.Parse(func)) + { + string condition = null; + bool and = true; + + object 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.InternalHaving(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) + { + DynamicParser.Node.Method node = (DynamicParser.Node.Method)result; + string 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.HavingOpenBracketsCount++; + if (addEndBrace) builder.HavingOpenBracketsCount--; + + if (builder.HavingCondition == null) + builder.HavingCondition = string.Format("{0}{1}{2}", + addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); + else + builder.HavingCondition = string.Format("{0} {1} {2}{3}{4}", builder.HavingCondition, and ? "AND" : "OR", + addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); + } + + return builder; + } + + internal static T InternalHaving(this T builder, DynamicColumn column) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + bool virt = builder.VirtualMode; + if (column.VirtualColumn.HasValue) + builder.VirtualMode = column.VirtualColumn.Value; + + Action modParam = (p) => + { + if (column.Schema.HasValue) + p.Schema = column.Schema; + + if (!p.Schema.HasValue) + p.Schema = column.Schema ?? builder.GetColumnFromSchema(column.ColumnName); + }; + + builder.CreateTemporaryParameterAction(modParam); + + // It's kind of uglu, but... well it works. + if (column.Or) + switch (column.Operator) + { + default: + case DynamicColumn.CompareOperator.Eq: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break; + case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break; + case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break; + case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); break; + case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break; + case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break; + case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break; + case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break; + case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break; + case DynamicColumn.CompareOperator.Between: builder.InternalHaving(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.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break; + case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break; + case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break; + case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break; + case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break; + case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break; + case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break; + case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break; + case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break; + case DynamicColumn.CompareOperator.Between: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break; + } + + builder.OnCreateTemporaryParameter.Remove(modParam); + builder.VirtualMode = virt; + + return builder; + } + + internal static T InternalHaving(this T builder, string column, DynamicColumn.CompareOperator op, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + if (value is DynamicColumn) + { + DynamicColumn v = (DynamicColumn)value; + + if (string.IsNullOrEmpty(v.ColumnName)) + v.ColumnName = column; + + return builder.InternalHaving(v); + } + else if (value is IEnumerable) + { + foreach (DynamicColumn v in (IEnumerable)value) + builder.InternalHaving(v); + + return builder; + } + + return builder.InternalHaving(new DynamicColumn + { + ColumnName = column, + Operator = op, + Value = value + }); + } + + internal static T InternalHaving(this T builder, string column, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + return builder.InternalHaving(column, DynamicColumn.CompareOperator.Eq, value); + } + + internal static T InternalHaving(this T builder, object conditions, bool schema = false) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + if (conditions is DynamicColumn) + return builder.InternalHaving((DynamicColumn)conditions); + else if (conditions is IEnumerable) + { + foreach (DynamicColumn v in (IEnumerable)conditions) + builder.InternalHaving(v); + + return builder; + } + + IDictionary dict = conditions.ToDictionary(); + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType()); + string table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty); + + foreach (KeyValuePair 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.InternalHaving(x => x(builder.FixObjectName(string.Format("{0}.{1}", table, colName))) == condition.Value); + else + builder.InternalHaving(x => x(builder.FixObjectName(colName)) == condition.Value); + } + + return builder; + } + + #endregion Where + } + + 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 (DynamicParser parser = DynamicParser.Parse(func)) + { + string condition = null; + bool and = true; + + object 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) + { + DynamicParser.Node.Method node = (DynamicParser.Node.Method)result; + string 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.WhereOpenBracketsCount++; + if (addEndBrace) builder.WhereOpenBracketsCount--; + + 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; + + Action modParam = (p) => + { + if (column.Schema.HasValue) + p.Schema = column.Schema; + + if (!p.Schema.HasValue) + p.Schema = column.Schema ?? builder.GetColumnFromSchema(column.ColumnName); + }; + + builder.CreateTemporaryParameterAction(modParam); + + // 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.OnCreateTemporaryParameter.Remove(modParam); + 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) + { + DynamicColumn 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; + } + + IDictionary dict = conditions.ToDictionary(); + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType()); + string table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty); + + foreach (KeyValuePair 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 + } + + 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 (DynamicParser parser = DynamicParser.Parse(func)) + { + object 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 + DynamicParser.Node 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) + { + DynamicParser.Node.Invoke 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 + { + Tuple 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"); + + string[] 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)); + + DynamicTypeMap 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; + } + } + } + + namespace Implementation + { + + /// Implementation of dynamic select query builder. + internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere, DynamicQueryBuilder.IQueryWithHaving + { + private int? _limit = null; + private int? _offset = null; + private bool _distinct = false; + + private string _select; + private string _from; + private string _join; + private string _groupby; + private string _orderby; + + #region IQueryWithHaving + + /// Gets or sets the having condition. + public string HavingCondition { get; set; } + + /// Gets or sets the amount of not closed brackets in having statement. + public int HavingOpenBracketsCount { get; set; } + + #endregion IQueryWithHaving + + /// + /// Gets a value indicating whether this instance has select columns. + /// + public bool HasSelectColumns { get { return !string.IsNullOrEmpty(_select); } } + + /// + /// Initializes a new instance of the class. + /// + /// The database. + public DynamicSelectQueryBuilder(DynamicDatabase db) + : base(db) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The database. + /// The parent query. + internal DynamicSelectQueryBuilder(DynamicDatabase db, DynamicQueryBuilder parent) + : base(db, parent) + { + } + + /// Generates the text this command will execute against the underlying database. + /// The text to execute against the underlying database. + public override string CommandText() + { + bool lused = false; + bool oused = false; + + StringBuilder sb = new StringBuilder("SELECT"); + if (_distinct) sb.AppendFormat(" DISTINCT"); + + if (_limit.HasValue) + { + if ((Database.Options & DynamicDatabaseOptions.SupportTop) == DynamicDatabaseOptions.SupportTop) + { + sb.AppendFormat(" TOP {0}", _limit); + lused = true; + } + else if ((Database.Options & DynamicDatabaseOptions.SupportFirstSkip) == DynamicDatabaseOptions.SupportFirstSkip) + { + sb.AppendFormat(" FIRST {0}", _limit); + lused = true; + } + } + + if (_offset.HasValue && (Database.Options & DynamicDatabaseOptions.SupportFirstSkip) == DynamicDatabaseOptions.SupportFirstSkip) + { + sb.AppendFormat(" SKIP {0}", _offset); + oused = true; + } + + if (_select != null) sb.AppendFormat(" {0}", _select); else sb.Append(" *"); + if (_from != null) sb.AppendFormat(" FROM {0}", _from); + if (_join != null) sb.AppendFormat(" {0}", _join); + if (WhereCondition != null) sb.AppendFormat(" WHERE {0}", WhereCondition); + if (_groupby != null) sb.AppendFormat(" GROUP BY {0}", _groupby); + if (HavingCondition != null) sb.AppendFormat(" HAVING {0}", HavingCondition); + if (_orderby != null) sb.AppendFormat(" ORDER BY {0}", _orderby); + if (_limit.HasValue && !lused && (Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset) + sb.AppendFormat(" LIMIT {0}", _limit); + if (_offset.HasValue && !oused && (Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset) + sb.AppendFormat(" OFFSET {0}", _offset); + + return sb.ToString(); + } + + #region Execution + + /// Execute this builder. + /// Enumerator of objects expanded from query. + public virtual IEnumerable Execute() + { + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(this) + .ExecuteReader()) + using (IDataReader cache = new DynamicCachedReader(rdr)) + while (cache.Read()) + { + dynamic val = null; + + // Work around to avoid yield being in try...catchblock: + // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch + try + { + val = cache.RowToDynamic(); + } + catch (ArgumentException argex) + { + StringBuilder 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; + } + } + } + + /// Execute this builder and map to given type. + /// Type of object to map on. + /// Enumerator of objects expanded from query. + public virtual IEnumerable Execute() where T : class + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(this) + .ExecuteReader()) + using (IDataReader cache = new DynamicCachedReader(rdr)) + while (cache.Read()) + { + dynamic val = null; + + // Work around to avoid yield being in try...catchblock: + // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch + try + { + val = cache.RowToDynamic(); + } + catch (ArgumentException argex) + { + StringBuilder 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 mapper.Create(val) as T; + } + } + } + + /// Execute this builder as a data reader. + /// Action containing reader. + public virtual void ExecuteDataReader(Action reader) + { + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + using (IDataReader rdr = cmd + .SetCommand(this) + .ExecuteReader()) + reader(rdr); + } + + /// Execute this builder as a data reader, but + /// first makes a full reader copy in memory. + /// Action containing reader. + public virtual void ExecuteCachedDataReader(Action reader) + { + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + using (IDataReader rdr = cmd + .SetCommand(this) + .ExecuteReader()) + using (IDataReader cache = new DynamicCachedReader(rdr)) + reader(cache); + } + + /// Returns a single result. + /// Result of a query. + public virtual object Scalar() + { + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(this) + .ExecuteScalar(); + } + } + + #if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE + + /// Returns a single result. + /// Type to parse to. + /// Default value. + /// Result of a query. + public virtual T ScalarAs(T defaultValue = default(T)) + { + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(this) + .ExecuteScalarAs(defaultValue); + } + } + + #endif + + #endregion Execution + + #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. + public virtual IDynamicSelectQueryBuilder From(Func fn, params Func[] func) + { + if (fn == null) + throw new ArgumentNullException("Array of functions cannot be or contain null."); + + int index = FromFunc(-1, fn); + foreach (Func f in func) + index = FromFunc(index, f); + + return this; + } + + private int FromFunc(int index, Func f) + { + if (f == null) + throw new ArgumentNullException("Array of functions cannot be or contain null."); + + index++; + ITableInfo tableInfo = null; + using (DynamicParser parser = DynamicParser.Parse(f)) + { + object result = parser.Result; + + // If the expression result is string. + if (result is string) + { + string node = (string)result; + Tuple tuple = node.SplitSomethingAndAlias(); + string[] parts = tuple.Item1.Split('.'); + tableInfo = new TableInfo(Database, + Database.StripName(parts.Last()).Validated("Table"), + tuple.Item2.Validated("Alias", canbeNull: true), + parts.Length == 2 ? Database.StripName(parts.First()).Validated("Owner", canbeNull: true) : null); + } + else if (result is Type) + { + Type type = (Type)result; + if (type.IsAnonymous()) + throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}). Parsing {1}", type.FullName, result)); + + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}). Parsing {1}", type.FullName, result)); + + tableInfo = new TableInfo(Database, type); + } + else if (result is DynamicParser.Node) + { + // Or if it resolves to a dynamic node + DynamicParser.Node node = (DynamicParser.Node)result; + + string owner = null; + string main = null; + string alias = null; + bool nolock = false; + Type type = null; + + while (true) + { + // Support for the AS() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") + { + if (alias != null) + throw new ArgumentException(string.Format("Alias '{0}' is already set when parsing '{1}'.", alias, result)); + + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args == null) + throw new ArgumentNullException("arg", "AS() is not a parameterless method."); + + if (args.Length != 1) + throw new ArgumentException("AS() requires one and only one parameter: " + args.Sketch()); + + alias = Parse(args[0], rawstr: true, decorate: false).Validated("Alias"); + + node = node.Host; + continue; + } + + // Support for the NoLock() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") + { + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null && args.Length > 0) + throw new ArgumentNullException("arg", "NoLock() doesn't support arguments."); + + nolock = true; + + node = node.Host; + continue; + } + + /*if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "subquery") + { + main = Parse(this.SubQuery(((DynamicParser.Node.Method)node).Arguments.Where(p => p is Func).Cast>().ToArray()), Parameters); + continue; + }*/ + + // 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) + throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); + + if (main != null) + owner = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); + else + { + DynamicParser.Node.Invoke invoke = (DynamicParser.Node.Invoke)node; + if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type) + { + type = (Type)invoke.Arguments[0]; + if (type.IsAnonymous()) + throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}). Parsing {1}", type.FullName, result)); + + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}). Parsing {1}", type.FullName, result)); + + main = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + + owner = (mapper.Table != null) ? mapper.Table.Owner : owner; + } + else + main = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); + } + + node = node.Host; + continue; + } + + // Just finished the parsing... + if (node is DynamicParser.Node.Argument) break; + + // All others are assumed to be part of the main element... + if (main != null) + main = Parse(node, pars: Parameters); + else + main = Parse(node, pars: Parameters); + + break; + } + + if (!string.IsNullOrEmpty(main)) + tableInfo = type == null ? new TableInfo(Database, main, alias, owner, nolock) : new TableInfo(Database, type, alias, owner, nolock); + else + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + } + + // Or it is a not supported expression... + if (tableInfo == null) + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + + Tables.Add(tableInfo); + + // We finally add the contents... + StringBuilder sb = new StringBuilder(); + + if (!string.IsNullOrEmpty(tableInfo.Owner)) + sb.AppendFormat("{0}.", Database.DecorateName(tableInfo.Owner)); + + sb.Append(tableInfo.Name.ContainsAny(StringExtensions.InvalidMemberChars) ? tableInfo.Name : Database.DecorateName(tableInfo.Name)); + + if (!string.IsNullOrEmpty(tableInfo.Alias)) + sb.AppendFormat(" AS {0}", tableInfo.Alias); + + if (SupportNoLock && tableInfo.NoLock) + sb.AppendFormat(" WITH(NOLOCK)"); + + _from = string.IsNullOrEmpty(_from) ? sb.ToString() : string.Format("{0}, {1}", _from, sb.ToString()); + } + + return index; + } + + /// + /// 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. + public virtual IDynamicSelectQueryBuilder Join(params Func[] func) + { + // We need to do two passes to add aliases first. + return JoinInternal(true, func).JoinInternal(false, 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. + /// + /// If true just pass by to locate tables and aliases, otherwise create rules. + /// The specification. + /// This instance to permit chaining. + protected virtual DynamicSelectQueryBuilder JoinInternal(bool justAddTables, params Func[] func) + { + if (func == null) throw new ArgumentNullException("Array of functions cannot be null."); + + int index = -1; + + foreach (Func f in func) + { + index++; + ITableInfo tableInfo = null; + + if (f == null) + throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); + + using (DynamicParser parser = DynamicParser.Parse(f)) + { + object result = parser.Result; + if (result == null) throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); + + string type = null; + string main = null; + string owner = null; + string alias = null; + string condition = null; + bool nolock = false; + Type tableType = null; + + // If the expression resolves to a string... + if (result is string) + { + string node = (string)result; + + int n = node.ToUpper().IndexOf("JOIN "); + + if (n < 0) + main = node; + else + { + // For strings we only accept 'JOIN' as a suffix + type = node.Substring(0, n + 4); + main = node.Substring(n + 4); + } + + n = main.ToUpper().IndexOf("ON"); + + if (n >= 0) + { + condition = main.Substring(n + 3); + main = main.Substring(0, n).Trim(); + } + + Tuple tuple = main.SplitSomethingAndAlias(); // In this case we split on the remaining 'main' + string[] parts = tuple.Item1.Split('.'); + main = Database.StripName(parts.Last()).Validated("Table"); + owner = parts.Length == 2 ? Database.StripName(parts.First()).Validated("Owner", canbeNull: true) : null; + alias = tuple.Item2.Validated("Alias", canbeNull: true); + } + else if (result is DynamicParser.Node) + { + // Or if it resolves to a dynamic node... + DynamicParser.Node node = (DynamicParser.Node)result; + while (true) + { + // Support for the ON() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "ON") + { + if (condition != null) + throw new ArgumentException(string.Format("Condition '{0}' is already set when parsing '{1}'.", alias, result)); + + object[] args = ((DynamicParser.Node.Method)node).Arguments; + if (args == null) + throw new ArgumentNullException("arg", "ON() is not a parameterless method."); + + if (args.Length != 1) + throw new ArgumentException("ON() requires one and only one parameter: " + args.Sketch()); + + condition = Parse(args[0], rawstr: true, pars: justAddTables ? null : Parameters); + + node = node.Host; + continue; + } + + // Support for the AS() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") + { + if (alias != null) + throw new ArgumentException(string.Format("Alias '{0}' is already set when parsing '{1}'.", alias, result)); + + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args == null) + throw new ArgumentNullException("arg", "AS() is not a parameterless method."); + + if (args.Length != 1) + throw new ArgumentException("AS() requires one and only one parameter: " + args.Sketch()); + + alias = Parse(args[0], rawstr: true, decorate: false, isMultiPart: false).Validated("Alias"); + + node = node.Host; + continue; + } + + // Support for the NoLock() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") + { + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null && args.Length > 0) + throw new ArgumentNullException("arg", "NoLock() doesn't support arguments."); + + nolock = true; + + node = node.Host; + continue; + } + + // 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 Join Type specifications... + if (node is DynamicParser.Node.Method && (node.Host is DynamicParser.Node.Argument || node.Host is DynamicParser.Node.Invoke)) + { + if (type != null) throw new ArgumentException(string.Format("Join type '{0}' is already set when parsing '{1}'.", main, result)); + type = ((DynamicParser.Node.Method)node).Name; + + bool avoid = false; + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null && args.Length > 0) + { + avoid = args[0] is bool && !((bool)args[0]); + string proposedType = args.FirstOrDefault(a => a is string) as string; + if (!string.IsNullOrEmpty(proposedType)) + type = proposedType; + } + + type = type.ToUpper(); // Normalizing, and stepping out the trivial case... + if (type != "JOIN") + { + // Special cases + // x => x.LeftOuter() / x => x.RightOuter()... + type = type.Replace("OUTER", " OUTER ") + .Replace(" ", " ") + .Trim(' '); + + // x => x.Left()... + int n = type.IndexOf("JOIN"); + + if (n < 0 && !avoid) + type += " JOIN"; + + // x => x.InnerJoin() / x => x.JoinLeft() ... + else + { + if (!avoid) + { + if (n == 0) type = type.Replace("JOIN", "JOIN "); + else type = type.Replace("JOIN", " JOIN"); + } + } + } + + node = node.Host; + continue; + } + + // Support for generic sources... + if (node is DynamicParser.Node.Invoke) + { + if (owner != null) + throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); + + if (main != null) + owner = string.Format("{0}", Parse(node, rawstr: true, pars: justAddTables ? null : Parameters)); + else + { + DynamicParser.Node.Invoke invoke = (DynamicParser.Node.Invoke)node; + if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type) + { + tableType = (Type)invoke.Arguments[0]; + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(tableType); + + if (mapper == null) + throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", tableType.FullName)); + + main = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + + owner = (mapper.Table != null) ? mapper.Table.Owner : owner; + } + else + main = string.Format("{0}", Parse(node, rawstr: true, pars: justAddTables ? null : Parameters)); + } + + node = node.Host; + continue; + } + + // Just finished the parsing... + if (node is DynamicParser.Node.Argument) break; + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + } + } + else + { + // Or it is a not supported expression... + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + } + + // We annotate the aliases being conservative... + main = main.Validated("Main"); + + if (justAddTables) + { + if (!string.IsNullOrEmpty(main)) + tableInfo = tableType == null ? new TableInfo(Database, main, alias, owner, nolock) : new TableInfo(Database, tableType, alias, owner, nolock); + else + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + + Tables.Add(tableInfo); + } + else + { + // Get cached table info + tableInfo = string.IsNullOrEmpty(alias) ? + Tables.SingleOrDefault(t => t.Name == main && string.IsNullOrEmpty(t.Alias)) : + Tables.SingleOrDefault(t => t.Alias == alias); + + // We finally add the contents if we can... + StringBuilder sb = new StringBuilder(); + if (string.IsNullOrEmpty(type)) + type = "JOIN"; + + sb.AppendFormat("{0} ", type); + + if (!string.IsNullOrEmpty(tableInfo.Owner)) + sb.AppendFormat("{0}.", Database.DecorateName(tableInfo.Owner)); + + sb.Append(tableInfo.Name.ContainsAny(StringExtensions.InvalidMemberChars) ? tableInfo.Name : Database.DecorateName(tableInfo.Name)); + + if (!string.IsNullOrEmpty(tableInfo.Alias)) + sb.AppendFormat(" AS {0}", tableInfo.Alias); + + if (SupportNoLock && tableInfo.NoLock) + sb.AppendFormat(" WITH(NOLOCK)"); + + if (!string.IsNullOrEmpty(condition)) + sb.AppendFormat(" ON {0}", condition); + + _join = string.IsNullOrEmpty(_join) ? sb.ToString() : string.Format("{0} {1}", _join, sb.ToString()); // No comma in this case + } + } + } + + return this; + } + + #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. + public virtual IDynamicSelectQueryBuilder Where(Func func) + { + return this.InternalWhere(func); + } + + /// Add where condition. + /// Condition column with operator and value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Where(DynamicColumn column) + { + return this.InternalWhere(column); + } + + /// Add where condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder 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 IDynamicSelectQueryBuilder 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 IDynamicSelectQueryBuilder Where(object conditions, bool schema = false) + { + return this.InternalWhere(conditions, schema); + } + + #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. + public virtual IDynamicSelectQueryBuilder Select(Func fn, params Func[] func) + { + if (fn == null) + throw new ArgumentNullException("Array of specifications cannot be null."); + + int index = SelectFunc(-1, fn); + if (func != null) + foreach (Func f in func) + index = SelectFunc(index, f); + + return this; + } + + private int SelectFunc(int index, Func f) + { + index++; + if (f == null) + throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); + + using (DynamicParser parser = DynamicParser.Parse(f)) + { + object result = parser.Result; + if (result == null) + throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); + + string main = null; + string alias = null; + bool all = false; + bool anon = false; + + // If the expression resolves to a string... + if (result is string) + { + string node = (string)result; + Tuple tuple = node.SplitSomethingAndAlias(); + main = tuple.Item1.Validated("Table and/or Column"); + + main = FixObjectName(main); + + alias = tuple.Item2.Validated("Alias", canbeNull: true); + } + else if (result is DynamicParser.Node) + { + // Or if it resolves to a dynamic node... + ParseSelectNode(result, ref main, ref alias, ref all); + } + else if (result.GetType().IsAnonymous()) + { + anon = true; + + foreach (KeyValuePair prop in result.ToDictionary()) + { + if (prop.Value is string) + { + string node = (string)prop.Value; + Tuple tuple = node.SplitSomethingAndAlias(); + main = FixObjectName(tuple.Item1.Validated("Table and/or Column")); + + ////alias = tuple.Item2.Validated("Alias", canbeNull: true); + } + else if (prop.Value is DynamicParser.Node) + { + // Or if it resolves to a dynamic node... + ParseSelectNode(prop.Value, ref main, ref alias, ref all); + } + else + { + // Or it is a not supported expression... + throw new ArgumentException(string.Format("Specification #{0} in anonymous type is invalid: {1}", index, prop.Value)); + } + + alias = Database.DecorateName(prop.Key); + ParseSelectAddColumn(main, alias, all); + } + } + else + { + // Or it is a not supported expression... + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + } + + if (!anon) + ParseSelectAddColumn(main, alias, all); + } + + return index; + } + + /// Add select columns. + /// Columns to add to object. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder SelectColumn(params DynamicColumn[] columns) + { + foreach (DynamicColumn col in columns) + Select(x => col.ToSQLSelectColumn(Database)); + + return this; + } + + /// 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. + public virtual IDynamicSelectQueryBuilder SelectColumn(params string[] columns) + { + DynamicColumn[] cols = new DynamicColumn[columns.Length]; + for (int i = 0; i < columns.Length; i++) + cols[i] = DynamicColumn.ParseSelectColumn(columns[i]); + + return SelectColumn(cols); + } + + #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. + public virtual IDynamicSelectQueryBuilder GroupBy(Func fn, params Func[] func) + { + if (fn == null) + throw new ArgumentNullException("Array of specifications cannot be null."); + + int index = GroupByFunc(-1, fn); + + if (func != null) + for (int i = 0; i < func.Length; i++) + { + Func f = func[i]; + index = GroupByFunc(index, f); + } + + return this; + } + + private int GroupByFunc(int index, Func f) + { + index++; + if (f == null) + throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); + + using (DynamicParser parser = DynamicParser.Parse(f)) + { + object result = parser.Result; + if (result == null) + throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); + + string main = null; + + if (result is string) + main = FixObjectName(result as string); + else + main = Parse(result, pars: Parameters); + + main = main.Validated("Group By"); + if (_groupby == null) + _groupby = main; + else + _groupby = string.Format("{0}, {1}", _groupby, main); + } + + return index; + } + + /// Add select columns. + /// Columns to group by. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder GroupByColumn(params DynamicColumn[] columns) + { + for (int i = 0; i < columns.Length; i++) + { + DynamicColumn col = columns[i]; + GroupBy(x => col.ToSQLGroupByColumn(Database)); + } + + return this; + } + + /// Add select columns. + /// Columns to group by. + /// Column format consist of Column Name and + /// Alias in this order separated by ':'. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder GroupByColumn(params string[] columns) + { + return GroupByColumn(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray()); + } + + #endregion GroupBy + + #region Having + + /// + /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Having() 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: + /// 'Having( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder Having(Func func) + { + return this.InternalHaving(func); + } + + /// Add Having condition. + /// Condition column with operator and value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(DynamicColumn column) + { + return this.InternalHaving(column); + } + + /// Add Having condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value) + { + return this.InternalHaving(column, op, value); + } + + /// Add Having condition. + /// Condition column. + /// Condition value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(string column, object value) + { + return this.InternalHaving(column, value); + } + + /// Add Having 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 IDynamicSelectQueryBuilder Having(object conditions, bool schema = false) + { + return this.InternalHaving(conditions, schema); + } + + #endregion Having + + #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. + public virtual IDynamicSelectQueryBuilder OrderBy(Func fn, params Func[] func) + { + if (fn == null) + throw new ArgumentNullException("Array of specifications cannot be null."); + + int index = OrderByFunc(-1, fn); + + if (func != null) + for (int i = 0; i < func.Length; i++) + { + Func f = func[i]; + index = OrderByFunc(index, f); + } + + return this; + } + + private int OrderByFunc(int index, Func f) + { + index++; + if (f == null) + throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); + + using (DynamicParser parser = DynamicParser.Parse(f)) + { + object result = parser.Result; + if (result == null) throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); + + string main = null; + bool ascending = true; + + if (result is int) + main = result.ToString(); + else if (result is string) + { + string[] parts = ((string)result).Split(' '); + main = Database.StripName(parts.First()); + + int colNo; + if (!Int32.TryParse(main, out colNo)) + main = FixObjectName(main); + + ascending = parts.Length != 2 || parts.Last().ToUpper() == "ASCENDING" || parts.Last().ToUpper() == "ASC"; + } + else + { + // Intercepting trailing 'Ascending' or 'Descending' virtual methods... + if (result is DynamicParser.Node.Method) + { + DynamicParser.Node.Method node = (DynamicParser.Node.Method)result; + string name = node.Name.ToUpper(); + if (name == "ASCENDING" || name == "ASC" || name == "DESCENDING" || name == "DESC") + { + object[] args = node.Arguments; + if (args != null && !(node.Host is DynamicParser.Node.Argument)) + throw new ArgumentException(string.Format("{0} must be a parameterless method, but found: {1}.", name, args.Sketch())); + else if ((args == null || args.Length != 1) && node.Host is DynamicParser.Node.Argument) + throw new ArgumentException(string.Format("{0} requires one numeric parameter, but found: {1}.", name, args.Sketch())); + + ascending = (name == "ASCENDING" || name == "ASC") ? true : false; + + if (args != null && args.Length == 1) + { + int col = -1; + if (args[0] is int) + main = args[0].ToString(); + else if (args[0] is string) + { + if (Int32.TryParse(args[0].ToString(), out col)) + main = col.ToString(); + else + main = FixObjectName(args[0].ToString()); + } + else + main = Parse(args[0], pars: Parameters); + } + + result = node.Host; + } + } + + // Just parsing the contents... + if (!(result is DynamicParser.Node.Argument)) + main = Parse(result, pars: Parameters); + } + + main = main.Validated("Order By"); + main = string.Format("{0} {1}", main, ascending ? "ASC" : "DESC"); + + if (_orderby == null) + _orderby = main; + else + _orderby = string.Format("{0}, {1}", _orderby, main); + } + + return index; + } + + /// Add select columns. + /// Columns to order by. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder OrderByColumn(params DynamicColumn[] columns) + { + for (int i = 0; i < columns.Length; i++) + { + DynamicColumn col = columns[i]; + OrderBy(x => col.ToSQLOrderByColumn(Database)); + } + + return this; + } + + /// Add select columns. + /// Columns to order by. + /// Column format consist of Column Name and + /// Alias in this order separated by ':'. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder OrderByColumn(params string[] columns) + { + return OrderByColumn(columns.Select(c => DynamicColumn.ParseOrderByColumn(c)).ToArray()); + } + + #endregion OrderBy + + #region Top/Limit/Offset/Distinct + + /// Set top if database support it. + /// How many objects select. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Top(int? top) + { + return Limit(top); + } + + /// Set top if database support it. + /// How many objects select. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Limit(int? limit) + { + if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset && + (Database.Options & DynamicDatabaseOptions.SupportFirstSkip) != DynamicDatabaseOptions.SupportFirstSkip && + (Database.Options & DynamicDatabaseOptions.SupportTop) != DynamicDatabaseOptions.SupportTop) + throw new NotSupportedException("Database doesn't support LIMIT clause."); + + _limit = limit; + return this; + } + + /// Set top if database support it. + /// How many objects skip selecting. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Offset(int? offset) + { + if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset && + (Database.Options & DynamicDatabaseOptions.SupportFirstSkip) != DynamicDatabaseOptions.SupportFirstSkip) + throw new NotSupportedException("Database doesn't support OFFSET clause."); + + _offset = offset; + return this; + } + + /// Set distinct mode. + /// Distinct mode. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Distinct(bool distinct = true) + { + _distinct = distinct; + return this; + } + + #endregion Top/Limit/Offset/Distinct + + #region Helpers + + private void ParseSelectAddColumn(string main, string alias, bool all) + { + // We annotate the aliases being conservative... + main = main.Validated("Main"); + + ////if (alias != null && !main.ContainsAny(StringExtensions.InvalidMemberChars)) TableAliasList.Add(new KTableAlias(main, alias)); + + // If all columns are requested... + if (all) + main += ".*"; + + // We finally add the contents... + string str = (alias == null || all) ? main : string.Format("{0} AS {1}", main, alias); + _select = _select == null ? str : string.Format("{0}, {1}", _select, str); + } + + private void ParseSelectNode(object result, ref string column, ref string alias, ref bool all) + { + string main = null; + + DynamicParser.Node node = (DynamicParser.Node)result; + while (true) + { + // Support for the AS() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") + { + if (alias != null) + throw new ArgumentException(string.Format("Alias '{0}' is already set when parsing '{1}'.", alias, result)); + + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args == null) + throw new ArgumentNullException("arg", "AS() is not a parameterless method."); + + if (args.Length != 1) + throw new ArgumentException("AS() requires one and only one parameter: " + args.Sketch()); + + // Yes, we decorate columns + alias = Parse(args[0], rawstr: true, decorate: true, isMultiPart: false).Validated("Alias"); + + node = node.Host; + continue; + } + + // Support for the ALL() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "ALL") + { + if (all) + throw new ArgumentException(string.Format("Flag to select all columns is already set when parsing '{0}'.", result)); + + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null) + throw new ArgumentException("ALL() must be a parameterless virtual method, but found: " + args.Sketch()); + + all = true; + + node = node.Host; + continue; + } + + // Support for table and/or column specifications... + if (node is DynamicParser.Node.GetMember) + { + if (main != null) + throw new ArgumentException(string.Format("Main '{0}' is already set when parsing '{1}'.", main, result)); + + main = ((DynamicParser.Node.GetMember)node).Name; + + if (node.Host is DynamicParser.Node.GetMember) + { + // If leaf then decorate + main = Database.DecorateName(main); + + // Supporting multipart specifications... + node = node.Host; + + // Get table/alias name + string table = ((DynamicParser.Node.GetMember)node).Name; + bool isAlias = node.Host is DynamicParser.Node.Argument && IsTableAlias(table); + + if (isAlias) + main = string.Format("{0}.{1}", table, main); + else if (node.Host is DynamicParser.Node.GetMember) + { + node = node.Host; + main = string.Format("{0}.{1}.{2}", + Database.DecorateName(((DynamicParser.Node.GetMember)node).Name), + Database.DecorateName(table), main); + } + else + main = string.Format("{0}.{1}", Database.DecorateName(table), main); + } + else if (node.Host is DynamicParser.Node.Argument) + { + string table = ((DynamicParser.Node.Argument)node.Host).Name; + + if (IsTableAlias(table)) + main = string.Format("{0}.{1}", table, Database.DecorateName(main)); + else if (!IsTableAlias(main)) + main = Database.DecorateName(main); + } + else if (!(node.Host is DynamicParser.Node.Argument && IsTableAlias(main))) + main = Database.DecorateName(main); + + node = node.Host; + + continue; + } + + // Support for generic sources... + if (node is DynamicParser.Node.Invoke) + { + if (main != null) + throw new ArgumentException(string.Format("Main '{0}' is already set when parsing '{1}'.", main, result)); + + main = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); + + node = node.Host; + continue; + } + + // Just finished the parsing... + if (node is DynamicParser.Node.Argument) + { + if (string.IsNullOrEmpty(main) && IsTableAlias(node.Name)) + main = node.Name; + + break; + } + + // All others are assumed to be part of the main element... + if (main != null) throw new ArgumentException(string.Format("Main '{0}' is already set when parsing '{1}'.", main, result)); + main = Parse(node, pars: Parameters); + + break; + } + + column = main; + } + + #endregion Helpers + + #region IExtendedDisposable + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public override void Dispose() + { + base.Dispose(); + + _select = _from = _join = _groupby = _orderby = null; + } + + #endregion IExtendedDisposable + } + + /// Update query builder. + internal class DynamicUpdateQueryBuilder : DynamicModifyBuilder, IDynamicUpdateQueryBuilder, DynamicQueryBuilder.IQueryWithWhere + { + private string _columns; + + internal DynamicUpdateQueryBuilder(DynamicDatabase db) + : base(db) + { + } + + public DynamicUpdateQueryBuilder(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() + { + ITableInfo info = Tables.Single(); + return string.Format("UPDATE {0}{1} SET {2}{3}{4}", + string.IsNullOrEmpty(info.Owner) ? string.Empty : string.Format("{0}.", Database.DecorateName(info.Owner)), + Database.DecorateName(info.Name), _columns, + string.IsNullOrEmpty(WhereCondition) ? string.Empty : " WHERE ", + WhereCondition); + } + + #region Update + + /// Add update value or where condition using schema. + /// Update or where column name. + /// Column value. + /// Builder instance. + public virtual IDynamicUpdateQueryBuilder Update(string column, object value) + { + DynamicSchemaColumn? col = GetColumnFromSchema(column); + + if (!col.HasValue && SupportSchema) + throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", column)); + + if (col.HasValue && col.Value.IsKey) + Where(column, value); + else + Values(column, value); + + return this; + } + + /// Add update values and where condition columns using schema. + /// Set values or conditions as properties and values of an object. + /// Builder instance. + public virtual IDynamicUpdateQueryBuilder Update(object conditions) + { + if (conditions is DynamicColumn) + { + DynamicColumn column = (DynamicColumn)conditions; + + DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName); + + if (!col.HasValue && SupportSchema) + throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", column)); + + if (col.HasValue && col.Value.IsKey) + Where(column); + else + Values(column.ColumnName, column.Value); + + return this; + } + + IDictionary dict = conditions.ToDictionary(); + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType()); + + foreach (KeyValuePair con in dict) + { + if (mapper.Ignored.Contains(con.Key)) + continue; + + string colName = mapper != null ? mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key : con.Key; + DynamicSchemaColumn? col = GetColumnFromSchema(colName); + + if (!col.HasValue && SupportSchema) + throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", colName)); + + if (col.HasValue) + { + colName = col.Value.Name; + + if (col.Value.IsKey) + { + Where(colName, con.Value); + + continue; + } + } + + DynamicPropertyInvoker propMap = mapper.ColumnsMap.TryGetValue(colName.ToLower()); + if (propMap == null || propMap.Column == null || !propMap.Column.IsNoUpdate) + Values(colName, con.Value); + } + + return this; + } + + #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. + public virtual IDynamicUpdateQueryBuilder Set(params Func[] func) + { + if (func == null) + throw new ArgumentNullException("Array of specifications cannot be null."); + + int index = -1; + foreach (Func f in func) + { + index++; + if (f == null) + throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); + + object result = null; + + using (DynamicParser p = DynamicParser.Parse(f)) + { + result = p.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) + { + DynamicParser.Node.SetMember node = (DynamicParser.Node.SetMember)result; + + DynamicSchemaColumn? col = GetColumnFromSchema(node.Name); + main = Database.DecorateName(node.Name); + value = Parse(node.Value, ref col, pars: Parameters, nulls: true); + + str = string.Format("{0} = {1}", main, value); + _columns = _columns == null ? str : string.Format("{0}, {1}", _columns, str); + continue; + } + else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType) + { + Values(result); + continue; + } + + // Other specifications are considered invalid... + string 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); + } + } + + return this; + } + + /// Add insert fields. + /// Insert column. + /// Insert value. + /// Builder instance. + public virtual IDynamicUpdateQueryBuilder Values(string column, object value) + { + if (value is DynamicColumn) + { + DynamicColumn v = (DynamicColumn)value; + + if (string.IsNullOrEmpty(v.ColumnName)) + v.ColumnName = column; + + return Values(v); + } + + return Values(new DynamicColumn + { + ColumnName = column, + Value = value, + }); + } + + /// Add insert fields. + /// Set insert value as properties and values of an object. + /// Builder instance. + public virtual IDynamicUpdateQueryBuilder Values(object o) + { + if (o is DynamicColumn) + { + DynamicColumn column = (DynamicColumn)o; + DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName); + + string main = FixObjectName(column.ColumnName, onlyColumn: true); + string value = Parse(column.Value, ref col, pars: Parameters, nulls: true); + + string str = string.Format("{0} = {1}", main, value); + _columns = _columns == null ? str : string.Format("{0}, {1}", _columns, str); + + return this; + } + + IDictionary dict = o.ToDictionary(); + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(o.GetType()); + + if (mapper != null) + { + foreach (KeyValuePair con in dict) + if (!mapper.Ignored.Contains(con.Key)) + Values(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value); + } + else + foreach (KeyValuePair con in dict) + Values(con.Key, con.Value); + + return this; + } + + #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. + public virtual IDynamicUpdateQueryBuilder Where(Func func) + { + return this.InternalWhere(func); + } + + /// Add where condition. + /// Condition column with operator and value. + /// Builder instance. + public virtual IDynamicUpdateQueryBuilder Where(DynamicColumn column) + { + return this.InternalWhere(column); + } + + /// Add where condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + public virtual IDynamicUpdateQueryBuilder 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 IDynamicUpdateQueryBuilder 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 IDynamicUpdateQueryBuilder Where(object conditions, bool schema = false) + { + return this.InternalWhere(conditions, schema); + } + + #endregion Where + + #region IExtendedDisposable + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public override void Dispose() + { + base.Dispose(); + + _columns = null; + } + + #endregion IExtendedDisposable + } + + /// 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 WhereOpenBracketsCount { get; set; } + } + + /// Empty interface to allow having query builder implementation use universal approach. + internal interface IQueryWithHaving + { + /// Gets or sets the having condition. + string HavingCondition { get; set; } + + /// Gets or sets the amount of not closed brackets in having statement. + int HavingOpenBracketsCount { get; set; } + } + + private DynamicQueryBuilder _parent = null; + + #region TableInfo + + /// Table information. + internal class TableInfo : ITableInfo + { + /// + /// Initializes a new instance of the class. + /// + internal TableInfo() + { + IsDisposed = false; + } + + /// + /// Initializes a new instance of the class. + /// + /// The database. + /// The name of table. + /// The table alias. + /// The table owner. + /// The table should be used with nolock. + public TableInfo(DynamicDatabase db, string name, string alias = null, string owner = null, bool nolock = false) + : this() + { + Name = name; + Alias = alias; + Owner = owner; + NoLock = nolock; + + 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. + /// The table should be used with nolock. + public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null, bool nolock = false) + : this() + { + DynamicTypeMap 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; + NoLock = nolock; + + 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 alias. + public bool NoLock { get; internal set; } + + /// Gets or sets table schema. + public Dictionary Schema { get; internal set; } + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public virtual void Dispose() + { + IsDisposed = true; + + ////if (Schema != null) + //// Schema.Clear(); + + Owner = Name = Alias = null; + Schema = null; + } + } + + /// 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 + { + /// Initializes a new instance of the + /// class. + public Parameter() + { + IsDisposed = false; + } + + /// 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; } + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public virtual void Dispose() + { + IsDisposed = true; + + Name = null; + Schema = null; + } + } + + #endregion Parameter + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The database. + public DynamicQueryBuilder(DynamicDatabase db) + { + IsDisposed = false; + VirtualMode = false; + Tables = new List(); + Parameters = new Dictionary(); + OnCreateTemporaryParameter = new List>(); + OnCreateParameter = new List>(); + + WhereCondition = null; + WhereOpenBracketsCount = 0; + + Database = db; + if (Database != null) + Database.AddToCache(this); + + SupportSchema = (db.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema; + SupportNoLock = (db.Options & DynamicDatabaseOptions.SupportNoLock) == DynamicDatabaseOptions.SupportNoLock; + } + + /// 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 WhereOpenBracketsCount { 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 actions. + /// This is exposed to allow setting schema of column. + public List> OnCreateTemporaryParameter { get; set; } + + /// Gets or sets the on create real parameter actions. + /// This is exposed to allow modification of parameter. + public List> OnCreateParameter { get; set; } + + /// Gets a value indicating whether database supports standard schema. + public bool SupportSchema { get; private set; } + + /// Gets a value indicating whether database supports with no lock syntax. + public bool SupportNoLock { 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 (WhereOpenBracketsCount > 0) + { + WhereCondition += ")"; + WhereOpenBracketsCount--; + } + } + + // End not ended having statement + if (this is IQueryWithHaving) + { + IQueryWithHaving h = this as IQueryWithHaving; + + while (h.HavingOpenBracketsCount > 0) + { + h.HavingCondition += ")"; + h.HavingOpenBracketsCount--; + } + } + + 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.ForEach(x => x(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. + /// 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? c = null; + + return Parse(node, ref c, pars, rawstr, nulls, decorate, isMultiPart); + } + + /// 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. + /// This parameter is used to determine type of parameter used in query. + /// 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. + /// 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, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool rawstr = false, bool nulls = false, bool decorate = true, bool isMultiPart = true) + { + // 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, ref columnSchema, pars, decorate); + } + + // 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, ref columnSchema, pars, decorate); + } + + // If node is a delegate, parse it to create the logical tree... + if (node is Delegate) + { + using (DynamicParser p = DynamicParser.Parse((Delegate)node)) + { + node = p.Result; + + return Parse(node, ref columnSchema, pars, rawstr, decorate: decorate); // Intercept containers as in (x => "string") + } + } + + return Dispatch(node, ref columnSchema, pars, decorate, isMultiPart); + } + + private string Dispatch(object node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool decorate = true, bool isMultiPart = true) + { + 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, ref columnSchema, pars, decorate, isMultiPart); + else if (node is DynamicParser.Node.SetMember) return ParseSetMember((DynamicParser.Node.SetMember)node, ref columnSchema, pars, decorate, isMultiPart); + 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, ref columnSchema, pars); + else if (node is DynamicParser.Node.Invoke) return ParseInvoke((DynamicParser.Node.Invoke)node, ref columnSchema, 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) + return string.Format("({0})", str); + + // TODO: Make special condiion + ////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 (KeyValuePair 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, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool decorate = true, bool isMultiPart = true) + { + 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, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool decorate = true, bool isMultiPart = true) + { + 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, ref columnSchema, pars, nulls: true); + 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, ref columnSchema, pars); // Not nulls: left is assumed to be an object + string right = Parse(node.Right, ref columnSchema, pars, nulls: true); + return string.Format("({0} {1} {2})", left, op, right); + } + + protected virtual string ParseMethod(DynamicParser.Node.Method node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null) + { + string method = node.Name.ToUpper(); + string parent = node.Host == null ? null : Parse(node.Host, ref columnSchema, 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], ref columnSchema, 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()); + + object[] arguments = node.Arguments; + + if (arguments.Length == 1 && (arguments[0] is IEnumerable || arguments[0] is Array) && !(arguments[0] is byte[])) + { + IEnumerable 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], ref columnSchema, pars: pars), Parse(arguments[1], ref columnSchema, 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 (object arg in node.Arguments) + { + if (!firstParam) + sbin.Append(", "); + + if ((arg is IEnumerable || arg is Array) && !(arg is byte[])) + { + IEnumerable vals = arg as IEnumerable; + + if (vals == null && arg is Array) + vals = ((Array)arg).Cast() as IEnumerable; + + if (vals != null) + foreach (object val in vals) + { + if (!firstParam) + sbin.Append(", "); + else + firstParam = false; + + sbin.Append(Parse(val, ref columnSchema, pars: pars)); + } + else + sbin.Append(Parse(arg, ref columnSchema, pars: pars)); + } + else + sbin.Append(Parse(arg, ref columnSchema, pars: pars)); + + firstParam = false; + } + + return string.Format("{0} IN({1})", parent, sbin.ToString()); + } + + case "NOTIN": + { + 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 (object arg in node.Arguments) + { + if (!firstParam) + sbin.Append(", "); + + if ((arg is IEnumerable || arg is Array) && !(arg is byte[])) + { + IEnumerable vals = arg as IEnumerable; + + if (vals == null && arg is Array) + vals = ((Array)arg).Cast() as IEnumerable; + + if (vals != null) + foreach (object val in vals) + { + if (!firstParam) + sbin.Append(", "); + else + firstParam = false; + + sbin.Append(Parse(val, ref columnSchema, pars: pars)); + } + else + sbin.Append(Parse(arg, ref columnSchema, pars: pars)); + } + else + sbin.Append(Parse(arg, ref columnSchema, pars: pars)); + + firstParam = false; + } + + return string.Format("{0} NOT 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], ref columnSchema, 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], ref columnSchema, 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 "NOLOCK": + if (!SupportNoLock) + return parent; + + if (node.Arguments != null && node.Arguments.Length > 1) + throw new ArgumentException("NOLOCK method expects no arguments."); + + return string.Format("{0} {1}", parent, "WITH(NOLOCK)"); + + 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], ref columnSchema, pars: Parameters, nulls: true)); + + case "COUNT0": + if (node.Arguments != null && node.Arguments.Length > 0) + throw new ArgumentException("COUNT0 method doesn't expect arguments"); + + return "COUNT(0)"; + } + } + + // 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, ref columnSchema, pars, nulls: true)); // We don't accept raw strings here!!! + } + } + + sb.Append(")"); + return sb.ToString(); + } + + protected virtual string ParseInvoke(DynamicParser.Node.Invoke node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null) + { + // This is used as an especial syntax to merely concatenate its arguments. It is used as a way to extend the supported syntax without the need of treating all the possible cases... + if (node.Arguments == null || node.Arguments.Length == 0) + return string.Empty; + + StringBuilder sb = new StringBuilder(); + foreach (object arg in node.Arguments) + { + if (arg is string) + { + sb.Append((string)arg); + + if (node.Arguments.Length == 1 && !columnSchema.HasValue) + columnSchema = GetColumnFromSchema((string)arg); + } + else + sb.Append(Parse(arg, ref columnSchema, pars, rawstr: true, nulls: true)); + } + + return sb.ToString(); + } + + protected virtual string ParseConvert(DynamicParser.Node.Convert node, IDictionary pars = null) + { + // The cast mechanism is left for the specific database implementation, that should override this method + // as needed... + string r = Parse(node.Target, pars); + return r; + } + + protected virtual string ParseConstant(object node, IDictionary pars = null, DynamicSchemaColumn? columnSchema = null) + { + if (node == null && !VirtualMode) + return ParseNull(); + + if (pars != null) + { + bool wellKnownName = VirtualMode && node is String && ((String)node).StartsWith("[$") && ((String)node).EndsWith("]") && ((String)node).Length > 4; + + // If we have a list of parameters to store it, let's parametrize it + Parameter par = new Parameter() + { + Name = wellKnownName ? ((String)node).Substring(2, ((String)node).Length - 3) : Guid.NewGuid().ToString(), + Value = wellKnownName ? null : node, + WellKnown = wellKnownName, + Virtual = VirtualMode, + Schema = columnSchema, + }; + + // If we are adding parameter we inform external sources about this. + if (OnCreateTemporaryParameter != null) + OnCreateTemporaryParameter.ForEach(x => x(par)); + + pars.Add(par.Name, par); + + return string.Format("[${0}]", par.Name); + } + + return node.ToString(); // Last resort case + } + + protected virtual string ParseNull() + { + return "NULL"; // Override if needed + } + + #endregion Parser + + #region Helpers + + internal bool IsTableAlias(string name) + { + DynamicQueryBuilder builder = this; + + while (builder != null) + { + if (builder.Tables.Any(t => t.Alias == name)) + return true; + + builder = builder._parent; + } + + return false; + } + + internal bool IsTable(string name, string owner) + { + DynamicQueryBuilder builder = this; + + while (builder != null) + { + if ((string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower())) || + (!string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower() && + !string.IsNullOrEmpty(t.Owner) && t.Owner.ToLower() == owner.ToLower()))) + return true; + + builder = builder._parent; + } + + return false; + } + + internal string FixObjectName(string main, bool onlyColumn = false) + { + if (main.IndexOf("(") > 0 && main.IndexOf(")") > 0) + return main.FillStringWithVariables(f => string.Format("({0})", FixObjectNamePrivate(f, onlyColumn)), "(", ")"); + else + return FixObjectNamePrivate(main, onlyColumn); + } + + private string FixObjectNamePrivate(string f, bool onlyColumn = false) + { + IEnumerable objects = f.Split('.') + .Select(x => Database.StripName(x)); + + if (onlyColumn || objects.Count() == 1) + f = Database.DecorateName(objects.Last()); + else if (!IsTableAlias(objects.First())) + f = string.Join(".", objects.Select(o => Database.DecorateName(o))); + else + f = string.Format("{0}.{1}", objects.First(), string.Join(".", objects.Skip(1).Select(o => Database.DecorateName(o)))); + + return f; + } + + internal DynamicSchemaColumn? GetColumnFromSchema(string colName, DynamicTypeMap mapper = null, string table = null) + { + // This is tricky and will not always work unfortunetly. + ////if (colName.ContainsAny(StringExtensions.InvalidMultipartMemberChars)) + //// return null; + + // First we need to get real column name and it's owner if exist. + string[] parts = colName.Split('.'); + for (int i = 0; i < parts.Length; i++) + parts[i] = Database.StripName(parts[i]); + + string columnName = parts.Last(); + + // Get table name from mapper + string tableName = table; + + if (string.IsNullOrEmpty(tableName)) + { + tableName = (mapper != null && mapper.Table != null) ? mapper.Table.Name : string.Empty; + + if (parts.Length > 1 && string.IsNullOrEmpty(tableName)) + { + // OK, we have a multi part identifier, that's good, we can get table name + tableName = string.Join(".", parts.Take(parts.Length - 1)); + } + } + + // Try to get table info from cache + ITableInfo tableInfo = !string.IsNullOrEmpty(tableName) ? + Tables.FirstOrDefault(x => !string.IsNullOrEmpty(x.Alias) && x.Alias.ToLower() == tableName) ?? + Tables.FirstOrDefault(x => x.Name.ToLower() == tableName.ToLower()) ?? Tables.FirstOrDefault() : + this is DynamicModifyBuilder || Tables.Count == 1 ? Tables.FirstOrDefault() : null; + + // Try to get column from schema + if (tableInfo != null && tableInfo.Schema != null) + return tableInfo.Schema.TryGetNullable(columnName.ToLower()); + + // Well, we failed to find a column + return null; + } + + #endregion Helpers + + #region IExtendedDisposable + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public virtual void Dispose() + { + if (IsDisposed) + return; + + IsDisposed = true; + + if (Database != null) + Database.RemoveFromCache(this); + + if (Parameters != null) + { + foreach (KeyValuePair p in Parameters) + p.Value.Dispose(); + + Parameters.Clear(); + Parameters = null; + } + + if (Tables != null) + { + foreach (ITableInfo t in Tables) + if (t != null) + t.Dispose(); + + Tables.Clear(); + Tables = null; + } + + WhereCondition = null; + Database = null; + } + + #endregion IExtendedDisposable + } + + /// 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() + { + ITableInfo 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 (Func 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 (DynamicParser parser = DynamicParser.Parse(f)) + { + object 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) + { + DynamicParser.Node.SetMember node = (DynamicParser.Node.SetMember)result; + + DynamicSchemaColumn? col = GetColumnFromSchema(node.Name); + main = Database.DecorateName(node.Name); + value = Parse(node.Value, ref col, pars: Parameters, nulls: true); + + _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... + string 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) + { + DynamicColumn 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) + { + DynamicColumn column = (DynamicColumn)o; + DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName); + + string main = FixObjectName(column.ColumnName, onlyColumn: true); + string value = Parse(column.Value, ref col, pars: Parameters, nulls: true); + + _columns = _columns == null ? main : string.Format("{0}, {1}", _columns, main); + _values = _values == null ? value : string.Format("{0}, {1}", _values, value); + + return this; + } + + IDictionary dict = o.ToDictionary(); + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(o.GetType()); + + if (mapper != null) + { + foreach (KeyValuePair con in dict) + if (!mapper.Ignored.Contains(con.Key)) + { + string colName = mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key; + DynamicPropertyInvoker propMap = mapper.ColumnsMap.TryGetValue(colName.ToLower()); + + if (propMap == null || propMap.Column == null || !propMap.Column.IsNoInsert) + Insert(colName, con.Value); // TODO: This probably should get value from mapper + } + } + else + foreach (KeyValuePair con in dict) + Insert(con.Key, con.Value); + + return this; + } + + #endregion Insert + + #region IExtendedDisposable + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public override void Dispose() + { + base.Dispose(); + + _columns = _values = null; + } + + #endregion IExtendedDisposable + } + + /// 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 (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(this) + .ExecuteNonQuery(); + } + } + } + + /// 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() + { + ITableInfo 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 + } + } + } + + namespace Objects + { + + /// Base class for strong typed objects. + public class DynamicEntityBase + { + private Dictionary _changedFields = new Dictionary(); + private DynamicEntityState _dynamicEntityState = DynamicEntityState.Unknown; + + /// Occurs when object property is changing. + public event EventHandler PropertyChanging; + + /// Gets the state of the dynamic entity. + /// Current state of entity. + public virtual DynamicEntityState GetDynamicEntityState() { return _dynamicEntityState; } + + /// Sets the state of the dynamic entity. + /// Using this method will reset modified fields list. + /// The state. + public virtual void SetDynamicEntityState(DynamicEntityState state) + { + _dynamicEntityState = state; + + if (_changedFields != null) + _changedFields.Clear(); + } + + /// Called when object property is changing. + /// Name of the property. + /// The old property value. + /// The new property value. + protected virtual void OnPropertyChanging(string propertyName, object oldValue, object newValue) + { + OnPropertyChanging(new DynamicPropertyChangingEventArgs(propertyName, oldValue, newValue)); + } + + /// Raises the event. + /// The instance containing the event data. + protected virtual void OnPropertyChanging(DynamicPropertyChangingEventArgs e) + { + _changedFields[e.PropertyName] = e.NewValue; + if (PropertyChanging != null) + PropertyChanging(this, e); + } + + /// Validates this object instance. + /// Returns list of containing results of validation. + public virtual IList Validate() + { + return DynamicMapperCache.GetMapper(this.GetType()).ValidateObject(this); + } + + /// Saves this object to database. + /// The database. + /// Returns true if operation was successful. + /// Can be thrown when object is in invalid state. + public virtual bool Save(DynamicDatabase database) + { + switch (GetDynamicEntityState()) + { + default: + case DynamicEntityState.Unknown: + throw new InvalidOperationException("Unknown object state. Unable to decide whish action should be performed."); + + case DynamicEntityState.New: + return Insert(database); + + case DynamicEntityState.Existing: + if (IsModified()) + return Update(database); + + return true; + + case DynamicEntityState.ToBeDeleted: + return Delete(database); + + case DynamicEntityState.Deleted: + throw new InvalidOperationException("Unable to do any database action on deleted object."); + } + } + + /// Determines whether this instance is in existing state and fields was modified since this state was set modified. + /// Returns true if this instance is modified; otherwise, false. + public virtual bool IsModified() + { + if (GetDynamicEntityState() != DynamicEntityState.Existing) + return false; + + return _changedFields != null && _changedFields.Any(); + } + + #region Insert/Update/Delete + + /// Inserts this object to database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Insert(DynamicDatabase db) + { + using (var query = db.Insert(this.GetType())) + { + if (query + .Values(x => this) + .Execute() > 0) + { + _changedFields.Clear(); + SetDynamicEntityState(DynamicEntityState.Existing); + return true; + } + } + + return false; + } + + /// Updates this object in database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Update(DynamicDatabase db) + { + var t = GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + using (var query = db.Update(t)) + { + MakeQueryWhere(mapper, query); + + bool any = false; + + if (_changedFields.Any()) + { + foreach (var cf in _changedFields) + { + var cn = mapper.PropertyMap[cf.Key]; + var pm = mapper.ColumnsMap[cn.ToLower()]; + if (pm.Ignore) + continue; + + if (pm.Column != null) + { + if (pm.Column.IsKey || pm.Column.IsNoUpdate) + continue; + + if (!pm.Column.AllowNull && cf.Value == null) + continue; + } + + query.Values(cn, cf.Value); + any = true; + } + } + + if (!any) + foreach (var pmk in mapper.ColumnsMap) + { + var pm = pmk.Value; + var val = pm.Get(this); + var cn = pm.Name; + + if (pm.Ignore) + continue; + + if (pm.Column != null) + { + if (!string.IsNullOrEmpty(pm.Column.Name)) + cn = pm.Column.Name; + + if (pm.Column.IsKey) + continue; + + if (!pm.Column.AllowNull && val == null) + continue; + } + + query.Values(cn, val); + } + + if (query.Execute() == 0) + return false; + + SetDynamicEntityState(DynamicEntityState.Existing); + _changedFields.Clear(); + + return true; + } + } + + /// Deletes this object from database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Delete(DynamicDatabase db) + { + var t = this.GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + + using (var query = db.Delete(t)) + { + MakeQueryWhere(mapper, query); + + if (query.Execute() == 0) + return false; + + SetDynamicEntityState(DynamicEntityState.Deleted); + } + + return true; + } + + #endregion Insert/Update/Delete + + #region Select + + /// Refresh non key data from database. + /// The database. + /// All properties that are primary key values must be filled. + /// Returns true if operation was successful. + public virtual bool Refresh(DynamicDatabase db) + { + var t = this.GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + using (var query = db.From(t)) + { + MakeQueryWhere(mapper, query); + var o = (query.Execute() as IEnumerable).FirstOrDefault(); + + if (o == null) + return false; + + mapper.Map(o, this); + + SetDynamicEntityState(DynamicEntityState.Existing); + _changedFields.Clear(); + } + + return true; + } + + #endregion Select + + #region Query Helpers + + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicUpdateQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicDeleteQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicSelectQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + var v = cm.Value.Get(this); + + if (v == null) + throw new InvalidOperationException(String.Format("Class '{0}' have key columns {1} not filled with data.", + this.GetType().FullName, cm.Value.Name)); + + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + + #endregion Query Helpers + } + + /// Possible states of dynamic database objects. + public enum DynamicEntityState + { + /// Default state. This state will only permit to refresh data from database. + /// In this state repository will be unable to tell if object with this state should be added + /// or updated in database, but you can still manually perform update or insert on such object. + Unknown, + + /// This state should be set to new objects in database. + New, + + /// This state is ser when data is refreshed from database or object was loaded from repository. + Existing, + + /// You can set this state to an object if you want repository to perform delete from database. + ToBeDeleted, + + /// This state is set for objects that were deleted from database. + Deleted, + } + + /// Base repository class for specified object type. + /// Type of stored object. + public class DynamicRepositoryBase : IDisposable where T : DynamicEntityBase + { + private DynamicDatabase _database; + + /// Initializes a new instance of the class. + /// The database. + public DynamicRepositoryBase(DynamicDatabase database) + { + _database = database; + } + + /// Get all rows from database. + /// Objects enumerator. + public virtual IEnumerable GetAll() + { + using (var q = _database.From()) + return EnumerateQuery(q); + } + + /// Get rows from database by custom query. + /// The query. + /// Query must be based on object type. + /// Objects enumerator. + public virtual IEnumerable GetByQuery(IDynamicSelectQueryBuilder query) + { + return EnumerateQuery(query); + } + + private IEnumerable EnumerateQuery(IDynamicSelectQueryBuilder query, bool forceType = true) + { + if (forceType) + { + var mapper = DynamicMapperCache.GetMapper(typeof(T)); + + var tn = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + + if (!query.Tables.Any(t => t.Name == tn)) + throw new InvalidOperationException(string.Format("Query is not related to '{0}' class.", typeof(T).FullName)); + } + + foreach (var o in query.Execute()) + { + o.SetDynamicEntityState(DynamicEntityState.Existing); + yield return o; + } + } + + /// Saves single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Save(T element) + { + return element.Save(_database); + } + + /// Saves collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Save(IEnumerable element) + { + return element.All(x => x.Save(_database)); + } + + /// Insert single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Insert(T element) + { + return element.Insert(_database); + } + + /// Insert collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Insert(IEnumerable element) + { + return element.All(x => x.Insert(_database)); + } + + /// Update single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Update(T element) + { + return element.Update(_database); + } + + /// Update collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Update(IEnumerable element) + { + return element.All(x => x.Update(_database)); + } + + /// Delete single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Delete(T element) + { + return element.Delete(_database); + } + + /// Delete collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Delete(IEnumerable element) + { + return element.All(x => x.Delete(_database)); + } + + /// Releases unmanaged and - optionally - managed resources. + public virtual void Dispose() + { + _database = null; + } + } + + /// Class containing changed property data. + /// + public class DynamicPropertyChangingEventArgs : EventArgs + { + /// Gets the name of the property. + /// The name of the property. + public string PropertyName { get; private set; } + + /// Gets the old property value. + /// The old value. + public object OldValue { get; private set; } + + /// Gets the new property value. + /// The new value. + public object NewValue { get; private set; } + + /// Initializes a new instance of the class. + /// Name of the property. + /// The old property value. + /// The new property value. + public DynamicPropertyChangingEventArgs(string propertyName, object oldValue, object newValue) + { + PropertyName = propertyName; + OldValue = oldValue; + NewValue = newValue; + } + } + } +} + diff --git a/DynamORM.Net40.csproj b/DynamORM.Net40.csproj new file mode 100644 index 0000000..d84347f --- /dev/null +++ b/DynamORM.Net40.csproj @@ -0,0 +1,29 @@ + + + net40 + Dynamic Object-Relational Mapping library (amalgamated for .NET 4.0). + Copyright © RUSSEK Software 2012-2026 + RUSSEK Software + Grzegorz Russek + 1.9 + DynamORM + MIT + false + false + + + + + + + + + + + + + + + + +