/* * DynamORM - Dynamic Object-Relational Mapping library. * Copyright (c) 2012-2015, Grzegorz Russek (grzegorz.russek@gmail.com) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. * * See: http://opensource.org/licenses/bsd-license.php * * Supported preprocessor flags: * * DYNAMORM_OMMIT_OLDSYNTAX - Remove dynamic table functionality * * DYNAMORM_OMMIT_GENERICEXECUTION - Remove generic execution functionality * * DYNAMORM_OMMIT_TRYPARSE - Remove TryParse helpers (also applies DYNAMORM_OMMIT_GENERICEXECUTION) */ using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; using System.Data; using System.Data.Common; using System.Dynamic; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Text; using System.Text.RegularExpressions; using DynamORM.Builders; using DynamORM.Builders.Extensions; using DynamORM.Builders.Implementation; using DynamORM.Helpers; using DynamORM.Helpers.Dynamics; using DynamORM.Mapper; using DynamORM.Validation; [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 DataTable _schema; private int _fields; private int _rows; private int _position; private int _cachePos; private IList _names; private IDictionary _ordinals; private IList _types; private IList _cache; 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); for (int i = 0; i < firstDict.Keys.Count; i++) r._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]; r._cache.Add(val); if (r._types[c] == null && val != null) r._types[c] = val.GetType(); c++; } r._rows++; } for (int i = 0; i < firstDict.Keys.Count; i++) if (r._types[i] == null) r._types[i] = typeof(string); r._schema = new DataTable("DYNAMIC"); r._schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); r._schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); r._schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); r._schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); r._schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); r._schema.Columns.Add(new DataColumn("DataType", typeof(Type))); r._schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); r._schema.Columns.Add(new DataColumn("NativeType", typeof(int))); r._schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); r._schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); r._schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); r._schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); int ordinal = 0; DataRow dr = null; foreach (var column in firstDict.Keys) { dr = r._schema.NewRow(); dr[0] = column; dr[1] = ordinal; dr[2] = 0; dr[3] = 0; dr[4] = 0; dr[5] = r._types[ordinal]; dr[6] = r._types[ordinal].ToDbType(); dr[7] = r._types[ordinal].ToDbType(); dr[8] = true; dr[9] = false; dr[10] = false; dr[11] = false; r._schema.Rows.Add(dr); r._names.Add(dr[0].ToString()); r._ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); r._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; r._position = -1; r._cachePos = -1; 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; r._position = -1; r._cachePos = -1; return r; } private void InitDataReader(IDataReader reader, int offset = 0, int limit = -1, Func progress = null) { _schema = reader.GetSchemaTable(); RecordsAffected = reader.RecordsAffected; Init(reader.FieldCount); int i = 0; for (i = 0; i < _fields; i++) { _names.Add(reader.GetName(i)); _types.Add(reader.GetFieldType(i)); if (!_ordinals.ContainsKey(reader.GetName(i).ToUpper())) _ordinals.Add(reader.GetName(i).ToUpper(), i); } int current = 0; while (reader.Read()) { if (current < offset) { current++; continue; } for (i = 0; i < _fields; i++) _cache.Add(reader[i]); _rows++; current++; if (limit >= 0 && _rows >= limit) break; if (progress != null && !progress(this, _rows)) break; } IsClosed = false; _position = -1; _cachePos = -1; if (progress != null) progress(this, _rows); reader.Close(); } private void FillFromEnumerable(IEnumerable objects, DynamicTypeMap mapper) { foreach (var elem in objects) { foreach (var col in mapper.ColumnsMap) { object val = null; if (col.Value.Get != null) val = col.Value.Get(elem); _cache.Add(val); } _cache.Add(elem); _rows++; } } private void FillFromEnumerable(Type elementType, IEnumerable objects, DynamicTypeMap mapper) { foreach (var elem in objects) { foreach (var col in mapper.ColumnsMap) { object val = null; if (col.Value.Get != null) val = col.Value.Get(elem); _cache.Add(val); } _cache.Add(elem); _rows++; } } private void CreateSchemaTable(DynamicTypeMap mapper) { _schema = new DataTable("DYNAMIC"); _schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); _schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); _schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); _schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); _schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); _schema.Columns.Add(new DataColumn("DataType", typeof(Type))); _schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); _schema.Columns.Add(new DataColumn("NativeType", typeof(int))); _schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); _schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); _schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); _schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); int ordinal = 0; DataRow dr = null; foreach (var column in mapper.ColumnsMap) { dr = _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; _schema.Rows.Add(dr); _names.Add(dr[0].ToString()); _ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); _types.Add((Type)dr[5]); dr.AcceptChanges(); } dr = _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; _schema.Rows.Add(dr); _names.Add("#O"); _ordinals.Add("#O".ToUpper(), ordinal++); _types.Add(mapper.Type); dr.AcceptChanges(); } private void Init(int fieldCount) { _rows = 0; _fields = fieldCount; _names = new List(_fields); _ordinals = new Dictionary(_fields); _types = new List(_fields); _cache = new List(_fields * 100); } /// Sets the current position in reader. /// The position. public void SetPosition(int pos) { if (pos >= -1 && pos < _rows) { _position = pos; _cachePos = _position * _fields; } else throw new IndexOutOfRangeException(); } #endregion Helpers #region IDataReader Members /// Closes the System.Data.IDataReader Object. public void Close() { IsClosed = true; _position = _rows; _cachePos = -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 _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 _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() { _cachePos = (++_position) * _fields; return _position < _rows; } /// Advances the System.Data.IDataReader to the next record. /// Returns true if there are more rows; otherwise, false. public bool Read() { _cachePos = (++_position) * _fields; return _position < _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; private set; } #endregion IDataReader Members #region IDisposable Members /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. public void Dispose() { _names.Clear(); _types.Clear(); _cache.Clear(); _schema.Dispose(); } #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 _fields; } } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public bool GetBoolean(int i) { return (bool)_cache[_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) { return (byte)_cache[_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) { using (MemoryStream ms = new MemoryStream((byte[])_cache[_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) { return (char)_cache[_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) { using (MemoryStream ms = new MemoryStream((byte[])_cache[_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 _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) { return (DateTime)_cache[_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) { return (decimal)_cache[_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) { return (double)_cache[_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 _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) { return (float)_cache[_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) { return (Guid)_cache[_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) { return (short)_cache[_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) { return (int)_cache[_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) { return (long)_cache[_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 _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) { if (_ordinals.ContainsKey(name.ToUpper())) return _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) { return (string)_cache[_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) { return _cache[_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) { for (int i = 0; i < _fields; i++) values[i] = _cache[_cachePos + i]; return _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) { return _cache[_cachePos + i] == null || _cache[_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 { if (_ordinals.ContainsKey(name.ToUpper())) return _cache[_cachePos + _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 { return _cache[_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. public dynamic Procedures { get { if (_proc == null) { if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) throw new InvalidOperationException("Database connection desn'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. /// This instance to permit chaining. public virtual IDynamicSelectQueryBuilder From() { return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T))); } /// Adds to the FROM clause using . /// Type which can be represented in database. /// Table alias. /// This instance to permit chaining. public virtual IDynamicSelectQueryBuilder From(string alias) { 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) { DynamicCachedReader cache = null; using (IDbConnection con = Open()) using (IDbCommand cmd = con.CreateCommand()) { using (IDataReader rdr = cmd .SetCommand(sql) .AddParameters(this, args) .ExecuteReader()) 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) { DynamicCachedReader cache = null; using (IDbConnection con = Open()) using (IDbCommand cmd = con.CreateCommand()) { using (IDataReader rdr = cmd .SetCommand(builder) .ExecuteReader()) 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, /// 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(DateTimeOffset), DbType.DateTimeOffset }, { typeof(byte[]), DbType.Binary }, { typeof(byte?), DbType.Byte }, { typeof(sbyte?), DbType.SByte }, { typeof(short?), DbType.Int16 }, { typeof(ushort?), DbType.UInt16 }, { typeof(int?), DbType.Int32 }, { typeof(uint?), DbType.UInt32 }, { typeof(long?), DbType.Int64 }, { typeof(ulong?), DbType.UInt64 }, { typeof(float?), DbType.Single }, { typeof(double?), DbType.Double }, { typeof(decimal?), DbType.Decimal }, { typeof(bool?), DbType.Boolean }, { typeof(char?), DbType.StringFixedLength }, { typeof(Guid?), DbType.Guid }, { typeof(DateTime?), DbType.DateTime }, { typeof(DateTimeOffset?), DbType.DateTimeOffset } }; #endregion Type column map #region Command extensions /// Set connection on the fly. /// in which changes will be made. /// which will be set to instance. /// Returns edited instance. public static IDbCommand SetConnection(this IDbCommand command, IDbConnection connection) { command.Connection = connection; return command; } /// Set connection on the fly. /// in which changes will be made. /// which will be set to instance. /// Returns edited instance. public static IDbCommand SetTransaction(this IDbCommand command, IDbTransaction transaction) { command.Transaction = transaction; return command; } #region SetCommand /// Set properties on the fly. /// in which changes will be made. /// Indicates or specifies how the property is interpreted. /// The wait time before terminating the attempt to execute a command and generating an error. /// The text command to run against the data source. /// Arguments used to format command. /// Returns edited instance. public static IDbCommand SetCommand(this IDbCommand command, CommandType commandType, int commandTimeout, string commandText, params object[] args) { command.CommandType = commandType; command.CommandTimeout = commandTimeout; if (args != null && args.Length > 0) command.CommandText = string.Format(commandText, args); else command.CommandText = commandText; return command; } /// Set properties on the fly. /// in which changes will be made. /// The wait time before terminating the attempt to execute a command and generating an error. /// The text command to run against the data source. /// Arguments used to format command. /// Returns edited instance. public static IDbCommand SetCommand(this IDbCommand command, int commandTimeout, string commandText, params object[] args) { command.CommandTimeout = commandTimeout; if (args != null && args.Length > 0) command.CommandText = string.Format(commandText, args); else command.CommandText = commandText; return command; } /// Set properties on the fly. /// in which changes will be made. /// Indicates or specifies how the property is interpreted. /// The text command to run against the data source. /// Arguments used to format command. /// Returns edited instance. public static IDbCommand SetCommand(this IDbCommand command, CommandType commandType, string commandText, params object[] args) { command.CommandType = commandType; if (args != null && args.Length > 0) command.CommandText = string.Format(commandText, args); else command.CommandText = commandText; return command; } /// Set properties on the fly. /// in which changes will be made. /// The text command to run against the data source. /// Arguments used to format command. /// Returns edited instance. public static IDbCommand SetCommand(this IDbCommand command, string commandText, params object[] args) { if (args != null && args.Length > 0) command.CommandText = string.Format(commandText, args); else command.CommandText = commandText; return command; } /// Set properties on the fly. /// in which changes will be made. /// Command builder. /// Returns edited instance. public static IDbCommand SetCommand(this IDbCommand command, IDynamicQueryBuilder builder) { builder.FillCommand(command); return command; } #endregion SetCommand #region AddParameter /// Extension method for adding in a bunch of parameters. /// Command to handle. /// Database object required to get proper formatting. /// Items to add. /// Returns edited instance. public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, params object[] args) { if (args != null && args.Count() > 0) foreach (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 = ((IDictionary)item).Values.FirstOrDefault(); else p.Value = 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 = value == null ? DBNull.Value : value; } else if (value == null || value == DBNull.Value) p.Value = DBNull.Value; else { p.DbType = TypeMap.TryGetNullable(value.GetType()) ?? DbType.String; if (p.DbType == DbType.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 = value; } cmd.Parameters.Add(p); return cmd; } /// Extension for adding single parameter determining only type of object. /// Command to handle. /// Query builder containing schema. /// Column item to add. /// Returns edited instance. public static IDbCommand AddParameter(this IDbCommand cmd, IDynamicQueryBuilder builder, DynamicColumn item) { 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 : 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 = item.Value; } cmd.Parameters.Add(p); return cmd; } /// Add to on the fly. /// to which parameter will be added. /// The name of the . /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . /// The of the . /// The size of the parameter. /// Indicates the precision of numeric parameters. /// Indicates the scale of numeric parameters. /// The value of the . /// Returns edited instance. public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, byte precision, byte scale, object value) { IDbDataParameter param = command.CreateParameter(); param.ParameterName = parameterName; param.Direction = parameterDirection; param.DbType = databaseType; param.Size = size; param.Precision = precision; param.Scale = scale; param.Value = value; command.Parameters.Add(param); return command; } /// Add to on the fly. /// to which parameter will be added. /// The name of the . /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . /// The of the . /// The size of the parameter. /// Indicates the precision of numeric parameters. /// Indicates the scale of numeric parameters. /// Returns edited instance. public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, byte precision, byte scale) { IDbDataParameter param = command.CreateParameter(); param.ParameterName = parameterName; param.Direction = parameterDirection; param.DbType = databaseType; param.Size = size; param.Precision = precision; param.Scale = scale; command.Parameters.Add(param); return command; } /// Add to on the fly. /// to which parameter will be added. /// The name of the . /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . /// The of the . /// Indicates the precision of numeric parameters. /// Indicates the scale of numeric parameters. /// The value of the . /// Returns edited instance. public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, byte precision, byte scale, object value) { IDbDataParameter param = command.CreateParameter(); param.ParameterName = parameterName; param.Direction = parameterDirection; param.DbType = databaseType; param.Precision = precision; param.Scale = scale; param.Value = value; command.Parameters.Add(param); return command; } /// Add to on the fly. /// to which parameter will be added. /// The name of the . /// The of the . /// Indicates the precision of numeric parameters. /// Indicates the scale of numeric parameters. /// The value of the . /// Returns edited instance. public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, byte precision, byte scale, object value) { IDbDataParameter param = command.CreateParameter(); param.ParameterName = parameterName; param.DbType = databaseType; param.Precision = precision; param.Scale = scale; param.Value = value; command.Parameters.Add(param); return command; } /// Add to on the fly. /// to which parameter will be added. /// The name of the . /// The of the . /// Indicates the precision of numeric parameters. /// Indicates the scale of numeric parameters. /// Returns edited instance. public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, byte precision, byte scale) { IDbDataParameter param = command.CreateParameter(); param.ParameterName = parameterName; param.DbType = databaseType; param.Precision = precision; param.Scale = scale; command.Parameters.Add(param); return command; } /// Add to on the fly. /// to which parameter will be added. /// The name of the . /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . /// The of the . /// The size of the parameter. /// The value of the . /// Returns edited instance. public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, object value) { IDbDataParameter param = command.CreateParameter(); param.ParameterName = parameterName; param.Direction = parameterDirection; param.DbType = databaseType; param.Size = size; param.Value = value ?? 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 = 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 = 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 { ((IDbDataParameter)command.Parameters[parameterName]).Value = 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 { ((IDbDataParameter)command.Parameters[index]).Value = 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; 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 = dcv.Alias ?? dcv.ColumnName ?? (dcv.Schema.HasValue ? dcv.Schema.Value.Name : null) ?? (info.ArgumentNames.Count > i ? info.ArgumentNames[i] : i.ToString()); bool isOut = dcv.ParameterDirection == ParameterDirection.Output || dcv.ParameterDirection == ParameterDirection.ReturnValue; if (isOut || dcv.ParameterDirection == ParameterDirection.InputOutput) { 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), dcv.ParameterDirection, ds.Type, ds.Size, ds.Precision, ds.Scale, isOut ? DBNull.Value : dcv.Value); } else cmd.AddParameter( _db.GetParameterName(paramName), dcv.ParameterDirection, arg == null ? DbType.String : arg.GetType().ToDbType(), 0, isOut ? DBNull.Value : dcv.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_"); 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 ? DbType.String : arg.GetType().ToDbType(), 0, isOut ? 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].IsGenericEnumerable()) { Type argType = types[0].GetGenericArguments().First(); if (argType == typeof(object)) { IDataReader cache = null; using (IDataReader rdr = cmd.ExecuteReader()) 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(); IDataReader cache = null; using (IDataReader rdr = cmd.ExecuteReader()) cache = rdr.CachedReader(); while (cache.Read()) listInstance.Add(cache[0] == DBNull.Value ? defVal : argType.CastObject(cache[0])); 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(); IDataReader cache = null; using (IDataReader rdr = cmd.ExecuteReader()) cache = rdr.CachedReader(); while (cache.Read()) { if (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 konw what to do with this type: '{0}'.", argType.ToString())); IDataReader cache = null; using (IDataReader rdr = cmd.ExecuteReader()) cache = rdr.CachedReader(); mainResult = cache.EnumerateReader().MapEnumerable(argType).ToList(); } } else if (types[0].IsValueType || types[0] == typeof(string)) { mainResult = cmd.ExecuteScalar(); if (mainResult != DBNull.Value) mainResult = types[0].CastObject(mainResult); } else if (types[0] == typeof(Guid)) { mainResult = cmd.ExecuteScalar(); if (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 konw 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 mainResult = cmd.ExecuteNonQuery(); #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) { } /// Initializes a new instance of the /// class. /// The /// that holds the serialized object data about the exception being thrown. /// The /// that contains contextual information about the source or destination. public DynamicQueryException(SerializationInfo info, StreamingContext context) : base(info, context) { } /// When overridden in a derived class, sets the /// /// with information about the exception. /// The /// that holds the serialized object data about the exception being thrown. /// The /// that contains contextual information about the source or destination. /// /// /// /// public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); } } /// 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 = Database.StripName(owner); TableType = null; BuildAndCacheSchema(keys); } /// Initializes a new instance of the class. /// Database and connection management. /// Type describing table. /// Override keys in schema. public DynamicTable(DynamicDatabase database, Type type, string[] keys = null) { if (type == null) throw new ArgumentNullException("type", "Type can't be null."); IsDisposed = false; Database = database; TableType = type; 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 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); } } 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. public TableInfo(DynamicDatabase db, string name, string alias = null, string owner = null) : this() { Name = name; Alias = alias; Owner = owner; if (!name.ContainsAny(StringExtensions.InvalidMemberChars)) Schema = db.GetSchema(name, owner: owner); } /// /// Initializes a new instance of the class. /// /// The database. /// The type which can be mapped to database. /// The table alias. /// The table owner. public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null) : 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; Schema = db.GetSchema(type); } /// Gets or sets table owner name. public string Owner { get; internal set; } /// Gets or sets table name. public string Name { get; internal set; } /// Gets or sets table alias. public string Alias { get; internal set; } /// Gets or sets table schema. public Dictionary Schema { get; internal set; } /// 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; } /// 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; } /// /// 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 "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() { DynamicCachedReader cache = null; using (IDbConnection con = Database.Open()) using (IDbCommand cmd = con.CreateCommand()) { using (IDataReader rdr = cmd .SetCommand(this) .ExecuteReader()) 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 { DynamicCachedReader cache = null; 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()) 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) { DynamicCachedReader cache = null; using (IDbConnection con = Database.Open()) using (IDbCommand cmd = con.CreateCommand()) using (IDataReader rdr = cmd .SetCommand(this) .ExecuteReader()) 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; 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; } /*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) : new TableInfo(Database, type, alias, owner); 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); _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; 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 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) : new TableInfo(Database, tableType, alias, owner); 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 (!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 GetDataTableFromDataReader(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 - 1; 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); } /// 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; else throw e; } } /// /// 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) { } /// Initializes a new instance of the class. /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. protected DynamicMapperException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// 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()) attr.AllowNull = false; if (property.CanRead) Get = CreateGetter(property); if (property.CanWrite) Setter = CreateSetter(property); } private Func CreateGetter(PropertyInfo property) { if (!property.CanRead) return null; ParameterExpression 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; ParameterExpression objParm = Expression.Parameter(typeof(object), "o"); ParameterExpression 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) 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() { return EnumerateQuery(_database.From()); } /// 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; } } } }