diff --git a/AmalgamationTool/AmalgamationTool.csproj b/AmalgamationTool/AmalgamationTool.csproj index ce27200..8ddb6e2 100644 --- a/AmalgamationTool/AmalgamationTool.csproj +++ b/AmalgamationTool/AmalgamationTool.csproj @@ -1,63 +1,13 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {A64D2052-D0CD-488E-BF05-E5952615D926} - Exe - Properties - AmalgamationTool - AmalgamationTool - v4.8 - - - 512 - - - x86 - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - - - - - - - - - - True - - - - - - - \ No newline at end of file + + + Exe + net10.0 + enable + enable + latest + + + + + + diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs index bf9cdd8..e9ebcd5 100644 --- a/AmalgamationTool/DynamORM.Amalgamation.cs +++ b/AmalgamationTool/DynamORM.Amalgamation.cs @@ -3,6 +3,9 @@ * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) * All rights reserved. * + * Some of methods in this code file is based on Kerosene ORM solution + * for parsing dynamic lambda expressions by Moisés Barba Cebeira + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -24,14 +27,7 @@ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. - * - * See: http://opensource.org/licenses/bsd-license.php - * - * Supported preprocessor flags: - * * DYNAMORM_OMMIT_OLDSYNTAX - Remove dynamic table functionality - * * DYNAMORM_OMMIT_GENERICEXECUTION - Remove generic execution functionality - * * DYNAMORM_OMMIT_TRYPARSE - Remove TryParse helpers (also applies DYNAMORM_OMMIT_GENERICEXECUTION) -*/ + */ using DynamORM.Builders.Extensions; using DynamORM.Builders.Implementation; @@ -63,7 +59,5685 @@ using System; namespace DynamORM { + /// Cache data reader in memory. + public class DynamicCachedReader : DynamicObject, IDataReader + { + #region Constructor and Data + private class Data + { + internal DataTable _schema; + internal int _fields; + internal int _rows; + internal int _rowsAffected; + internal int _position = -1; + internal int _cachePos = -1; + + internal IList _names; + internal IDictionary _ordinals; + internal IList _types; + internal IList _cache; + } + private List _data = new List(); + private int _currentDataPosition = 0; + private bool _isDisposed; + + private DynamicCachedReader() + { + } + /// Initializes a new instance of the class. + /// Reader to cache. + /// The offset row. + /// The limit to number of tows. -1 is no limit. + /// The progress delegate. + public DynamicCachedReader(IDataReader reader, int offset = 0, int limit = -1, Func progress = null) + { + InitDataReader(reader, offset, limit, progress); + } + #endregion Constructor and Data + + #region Helpers + + /// Create data reader from dynamic enumerable. + /// List of objects. + /// Instance of containing objects data. + public static DynamicCachedReader FromDynamicEnumerable(IEnumerable objects) + { + var first = (objects as IEnumerable).FirstOrDefault(); + + if (first == null) + return null; + + var firstDict = first as IDictionary; + var r = new DynamicCachedReader(); + r.Init(firstDict.Keys.Count); + + r._data.Add(new Data()); + + var data = r._data[r._currentDataPosition]; + + for (int i = 0; i < firstDict.Keys.Count; i++) + data._types.Add(null); + + foreach (dynamic elem in (objects as IEnumerable)) + { + int c = 0; + var dict = elem as IDictionary; + + foreach (var k in firstDict.Keys) + { + object val = dict[k]; + + data._cache.Add(val); + + if (data._types[c] == null && val != null) + data._types[c] = val.GetType(); + + c++; + } + data._rows++; + } + for (int i = 0; i < firstDict.Keys.Count; i++) + if (data._types[i] == null) + data._types[i] = typeof(string); + + data._schema = new DataTable("DYNAMIC"); + data._schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); + data._schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); + data._schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); + data._schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); + data._schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); + data._schema.Columns.Add(new DataColumn("DataType", typeof(Type))); + data._schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); + data._schema.Columns.Add(new DataColumn("NativeType", typeof(int))); + data._schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); + + int ordinal = 0; + DataRow dr = null; + + foreach (var column in firstDict.Keys) + { + dr = data._schema.NewRow(); + + dr[0] = column; + dr[1] = ordinal; + dr[2] = 0; + dr[3] = 0; + dr[4] = 0; + dr[5] = data._types[ordinal]; + dr[6] = data._types[ordinal].ToDbType(); + dr[7] = data._types[ordinal].ToDbType(); + dr[8] = true; + dr[9] = false; + dr[10] = false; + dr[11] = false; + + data._schema.Rows.Add(dr); + + data._names.Add(dr[0].ToString()); + data._ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); + data._types.Add((Type)dr[5]); + + dr.AcceptChanges(); + } + dr.AcceptChanges(); + + return r; + } + /// Create data reader from enumerable. + /// Type of enumerated objects. + /// List of objects. + /// Instance of containing objects data. + public static DynamicCachedReader FromEnumerable(IEnumerable objects) + { + var mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidCastException(string.Format("Object type '{0}' can't be mapped.", typeof(T).FullName)); + + var r = new DynamicCachedReader(); + r.Init(mapper.ColumnsMap.Count + 1); + r.CreateSchemaTable(mapper); + r.FillFromEnumerable(objects, mapper); + + r.IsClosed = false; + + return r; + } + /// Create data reader from enumerable. + /// Type of enumerated objects. + /// List of objects. + /// Instance of containing objects data. + public static DynamicCachedReader FromEnumerable(Type elementType, IEnumerable objects) + { + var mapper = DynamicMapperCache.GetMapper(elementType); + + if (mapper == null) + throw new InvalidCastException(string.Format("Object type '{0}' can't be mapped.", elementType.FullName)); + + var r = new DynamicCachedReader(); + r.Init(mapper.ColumnsMap.Count + 1); + r.CreateSchemaTable(mapper); + r.FillFromEnumerable(elementType, objects, mapper); + + r.IsClosed = false; + + return r; + } + private void InitDataReader(IDataReader reader, int offset = 0, int limit = -1, Func progress = null) + { + do + { + Init(reader.FieldCount); + + var data = _data[_currentDataPosition]; + + data._schema = reader.GetSchemaTable(); + data._rowsAffected = reader.RecordsAffected; + + int i = 0; + + for (i = 0; i < data._fields; i++) + { + data._names.Add(reader.GetName(i)); + data._types.Add(reader.GetFieldType(i)); + + if (!data._ordinals.ContainsKey(reader.GetName(i).ToUpper())) + data._ordinals.Add(reader.GetName(i).ToUpper(), i); + } + int current = 0; + while (reader.Read()) + { + if (current < offset) + { + current++; + continue; + } + for (i = 0; i < data._fields; i++) + data._cache.Add(reader[i]); + + data._rows++; + current++; + + if (limit >= 0 && data._rows >= limit) + break; + + if (progress != null && !progress(this, data._rows)) + break; + } + IsClosed = false; + data._position = -1; + data._cachePos = -1; + + if (progress != null) + progress(this, data._rows); + } while (reader.NextResult()); + + reader.Close(); + } + private void FillFromEnumerable(IEnumerable objects, DynamicTypeMap mapper) + { + var data = _data[_currentDataPosition]; + foreach (var elem in objects) + { + foreach (var col in mapper.ColumnsMap) + { + object val = null; + + if (col.Value.Get != null) + val = col.Value.Get(elem); + + data._cache.Add(val); + } + data._cache.Add(elem); + + data._rows++; + } + } + private void FillFromEnumerable(Type elementType, IEnumerable objects, DynamicTypeMap mapper) + { + var data = _data[_currentDataPosition]; + + foreach (var elem in objects) + { + foreach (var col in mapper.ColumnsMap) + { + object val = null; + + if (col.Value.Get != null) + val = col.Value.Get(elem); + + data._cache.Add(val); + } + data._cache.Add(elem); + + data._rows++; + } + } + private void CreateSchemaTable(DynamicTypeMap mapper) + { + var data = _data[_currentDataPosition]; + data._schema = new DataTable("DYNAMIC"); + data._schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); + data._schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); + data._schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); + data._schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); + data._schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); + data._schema.Columns.Add(new DataColumn("DataType", typeof(Type))); + data._schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); + data._schema.Columns.Add(new DataColumn("NativeType", typeof(int))); + data._schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); + data._schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); + + int ordinal = 0; + DataRow dr = null; + + foreach (var column in mapper.ColumnsMap) + { + dr = data._schema.NewRow(); + + dr[0] = column.Value.Column.NullOr(x => x.Name ?? column.Value.Name, column.Value.Name); + dr[1] = ordinal; + dr[2] = column.Value.Column.NullOr(x => x.Size ?? int.MaxValue, int.MaxValue); + dr[3] = column.Value.Column.NullOr(x => x.Precision ?? 0, 0); + dr[4] = column.Value.Column.NullOr(x => x.Scale ?? 0, 0); + dr[5] = column.Value.Column.NullOr(x => x.Type.HasValue ? x.Type.Value.ToType() : column.Value.Type, column.Value.Type); + dr[6] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), column.Value.Type.ToDbType()); + dr[7] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), column.Value.Type.ToDbType()); + dr[8] = column.Value.Column.NullOr(x => x.IsKey, false) ? true : column.Value.Column.NullOr(x => x.AllowNull, true); + dr[9] = column.Value.Column.NullOr(x => x.IsUnique, false); + dr[10] = column.Value.Column.NullOr(x => x.IsKey, false); + dr[11] = false; + + data._schema.Rows.Add(dr); + + data._names.Add(dr[0].ToString()); + data._ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); + data._types.Add((Type)dr[5]); + + dr.AcceptChanges(); + } + dr = data._schema.NewRow(); + + dr[0] = "#O"; + dr[1] = ordinal; + dr[2] = int.MaxValue; + dr[3] = 0; + dr[4] = 0; + dr[5] = mapper.Type; + dr[6] = DbType.Object; + dr[7] = DbType.Object; + dr[8] = true; + dr[9] = false; + dr[10] = false; + dr[11] = false; + + data._schema.Rows.Add(dr); + + data._names.Add("#O"); + data._ordinals.Add("#O".ToUpper(), ordinal++); + data._types.Add(mapper.Type); + + dr.AcceptChanges(); + } + private void Init(int fieldCount) + { + _data.Add(new Data() + { + _rows = 0, + _fields = fieldCount, + _names = new List(fieldCount), + _ordinals = new Dictionary(fieldCount), + _types = new List(fieldCount), + _cache = new List(fieldCount * 100), + }); + + _currentDataPosition = _data.Count - 1; + } + /// Sets the current position in reader. + /// The position. + public void SetPosition(int pos) + { + if (pos >= -1 && pos < _data[_currentDataPosition]._rows) + { + _data[_currentDataPosition]._position = pos; + _data[_currentDataPosition]._cachePos = _data[_currentDataPosition]._position * _data[_currentDataPosition]._fields; + } + else + throw new IndexOutOfRangeException(); + } + #endregion Helpers + + #region IDataReader Members + + /// Closes the System.Data.IDataReader Object. + public void Close() + { + IsClosed = true; + _currentDataPosition = -1; + } + /// Gets a value indicating the depth of nesting for the current row. + /// This implementation use this field to indicate row count. + public int Depth + { + get { return _data[_currentDataPosition]._rows; } + } + /// Returns a System.Data.DataTable that describes the column metadata of the + /// System.Data.IDataReader.A System.Data.DataTable that describes + /// the column metadata. + /// The System.Data.IDataReader is closed. + public DataTable GetSchemaTable() + { + return _data[_currentDataPosition]._schema; + } + /// Gets a value indicating whether the data reader is closed. + public bool IsClosed { get; private set; } + + /// Advances the data reader to the next result, when reading the results of batch SQL statements. + /// Returns true if there are more rows; otherwise, false. + public bool NextResult() + { + _currentDataPosition++; + + return _data.Count < _currentDataPosition; + } + /// Advances the System.Data.IDataReader to the next record. + /// Returns true if there are more rows; otherwise, false. + public bool Read() + { + var data = _data[_currentDataPosition]; + + data._cachePos = (++data._position) * data._fields; + + return data._position < data._rows; + } + /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. + /// The number of rows changed, inserted, or deleted; 0 if no rows were affected or the statement + /// failed; and -1 for SELECT statements. + public int RecordsAffected { get { return _data[_currentDataPosition]._rowsAffected; } } + + #endregion IDataReader Members + + #region IDisposable Members + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + IsClosed = true; + + if (_data == null) + return; + + foreach (var data in _data) + { + if (data == null) + continue; + + if (data._names != null) + data._names.Clear(); + if (data._types != null) + data._types.Clear(); + if (data._cache != null) + data._cache.Clear(); + if (data._schema != null) + data._schema.Dispose(); + } + _data.Clear(); + } + #endregion IDisposable Members + + #region IDataRecord Members + + /// Gets the number of columns in the current row. + /// When not positioned in a valid record set, 0; otherwise, the number of columns in the current record. The default is -1. + public int FieldCount { get { return _data[_currentDataPosition]._fields; } } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public bool GetBoolean(int i) + { + var data = _data[_currentDataPosition]; + return (bool)data._cache[data._cachePos + i]; + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public byte GetByte(int i) + { + var data = _data[_currentDataPosition]; + return (byte)data._cache[data._cachePos + i]; + } + /// Reads a stream of bytes from the specified column offset into the buffer + /// as an array, starting at the given buffer offset. + /// The index of the field to find. + /// The index within the field from which to start the read operation. + /// The buffer into which to read the stream of bytes. + /// The index for buffer to start the read operation. + /// The number of bytes to read. + /// The actual number of bytes read. + public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + { + var data = _data[_currentDataPosition]; + + using (MemoryStream ms = new MemoryStream((byte[])data._cache[data._cachePos + i])) + return ms.Read(buffer, bufferoffset, length); + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public char GetChar(int i) + { + var data = _data[_currentDataPosition]; + return (char)data._cache[data._cachePos + i]; + } + /// Reads a stream of characters from the specified column offset into the buffer + /// as an array, starting at the given buffer offset. + /// The zero-based column ordinal. + /// The index within the row from which to start the read operation. + /// The buffer into which to read the stream of bytes. + /// The index for buffer to start the read operation. + /// The number of bytes to read. + /// The actual number of characters read. + public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) + { + var data = _data[_currentDataPosition]; + + using (MemoryStream ms = new MemoryStream((byte[])data._cache[data._cachePos + i])) + { + byte[] buff = new byte[buffer.Length]; + long ret = ms.Read(buff, bufferoffset, length); + + for (int n = bufferoffset; n < ret; n++) + buffer[n] = (char)buff[n]; + + return ret; + } + } + /// Returns an System.Data.IDataReader for the specified column ordinal. + /// The index of the field to find. + /// An System.Data.IDataReader. + public IDataReader GetData(int i) + { + return null; + } + /// Gets the data type information for the specified field. + /// The index of the field to find. + /// The data type information for the specified field. + public string GetDataTypeName(int i) + { + return _data[_currentDataPosition]._types[i].Name; + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public DateTime GetDateTime(int i) + { + var data = _data[_currentDataPosition]; + return (DateTime)data._cache[data._cachePos + i]; + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public decimal GetDecimal(int i) + { + var data = _data[_currentDataPosition]; + return (decimal)data._cache[data._cachePos + i]; + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public double GetDouble(int i) + { + var data = _data[_currentDataPosition]; + return (double)data._cache[data._cachePos + i]; + } + /// Gets the System.Type information corresponding to the type of System.Object + /// that would be returned from System.Data.IDataRecord.GetValue(System.Int32). + /// The index of the field to find. + /// The System.Type information corresponding to the type of System.Object that + /// would be returned from System.Data.IDataRecord.GetValue(System.Int32). + public Type GetFieldType(int i) + { + return _data[_currentDataPosition]._types[i]; + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public float GetFloat(int i) + { + var data = _data[_currentDataPosition]; + return (float)data._cache[data._cachePos + i]; + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public Guid GetGuid(int i) + { + var data = _data[_currentDataPosition]; + return (Guid)data._cache[data._cachePos + i]; + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public short GetInt16(int i) + { + var data = _data[_currentDataPosition]; + return (short)data._cache[data._cachePos + i]; + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public int GetInt32(int i) + { + var data = _data[_currentDataPosition]; + return (int)data._cache[data._cachePos + i]; + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public long GetInt64(int i) + { + var data = _data[_currentDataPosition]; + return (long)data._cache[data._cachePos + i]; + } + /// Gets the name for the field to find. + /// The index of the field to find. + /// The name of the field or the empty string (""), if there is no value to return. + public string GetName(int i) + { + return _data[_currentDataPosition]._names[i]; + } + /// Return the index of the named field. + /// The name of the field to find. + /// The index of the named field. + public int GetOrdinal(string name) + { + var data = _data[_currentDataPosition]; + + if (data._ordinals.ContainsKey(name.ToUpper())) + return data._ordinals[name.ToUpper()]; + + return -1; + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public string GetString(int i) + { + var data = _data[_currentDataPosition]; + + return (string)data._cache[data._cachePos + i]; + } + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public object GetValue(int i) + { + var data = _data[_currentDataPosition]; + + return data._cache[data._cachePos + i]; + } + /// Gets all the attribute fields in the collection for the current record. + /// An array of System.Object to copy the attribute fields into. + /// The number of instances of System.Object in the array. + public int GetValues(object[] values) + { + var data = _data[_currentDataPosition]; + + for (int i = 0; i < data._fields; i++) + values[i] = data._cache[data._cachePos + i]; + + return data._fields; + } + /// Return whether the specified field is set to null. + /// The index of the field to find. + /// Returns true if the specified field is set to null; otherwise, false. + public bool IsDBNull(int i) + { + var data = _data[_currentDataPosition]; + return data._cache[data._cachePos + i] == null || data._cache[data._cachePos + i] == DBNull.Value; + } + /// Gets or sets specified value in current record. + /// Name of column. + /// Value of specified column. + public object this[string name] + { + get + { + var data = _data[_currentDataPosition]; + + if (data._ordinals.ContainsKey(name.ToUpper())) + return data._cache[data._cachePos + data._ordinals[name.ToUpper()]]; + + throw new IndexOutOfRangeException(String.Format("Field '{0}' not found.", name)); + } + } + /// Gets or sets specified value in current record. + /// The index of the field to find. + /// Value of specified column. + public object this[int i] + { + get { + var data = _data[_currentDataPosition]; + return data._cache[data._cachePos + i]; + } + } + #endregion IDataRecord Members + } + /// 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() + { + if (IsDisposed) + return; + + var db = _db; + if (db == null) + { + IsDisposed = true; + return; + } + lock (db.SyncLock) + { + if (IsDisposed) + return; + + IsDisposed = true; + + if (_con != null) + { + List pool = db.CommandsPool.TryGetValue(_con.Connection); + + if (pool != null && pool.Contains(this)) + pool.Remove(this); + } + if (_command != null) + { + _command.Parameters.Clear(); + + _command.Dispose(); + _command = null; + } + _con = null; + _db = null; + } + } + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + #endregion IExtendedDisposable Members + } + /// Connection wrapper. + /// This class is only connection holder, connection is managed by + /// instance. + public class DynamicConnection : IDbConnection, IExtendedDisposable + { + private DynamicDatabase _db; + private bool _singleTransaction; + + /// Gets underlying connection. + internal IDbConnection Connection { get; private set; } + + /// Initializes a new instance of the class. + /// Database connection manager. + /// Active connection. + /// Are we using single transaction mode? I so... act correctly. + internal DynamicConnection(DynamicDatabase db, IDbConnection con, bool singleTransaction) + { + IsDisposed = false; + _db = db; + Connection = con; + _singleTransaction = singleTransaction; + } + /// Begins a database transaction. + /// One of the values. + /// Custom parameter describing transaction options. + /// This action is invoked when transaction is disposed. + /// Returns representation. + internal DynamicTransaction BeginTransaction(IsolationLevel? il, object custom, Action disposed) + { + return new DynamicTransaction(_db, this, _singleTransaction, il, disposed, null); + } + #region IDbConnection Members + + /// Creates and returns a Command object associated with the connection. + /// A Command object associated with the connection. + public IDbCommand CreateCommand() + { + return new DynamicCommand(this, _db); + } + /// Begins a database transaction. + /// Returns representation. + public IDbTransaction BeginTransaction() + { + return BeginTransaction(null, null, null); + } + /// Begins a database transaction with the specified + /// value. + /// One of the values. + /// Returns representation. + public IDbTransaction BeginTransaction(IsolationLevel il) + { + return BeginTransaction(il, null, null); + } + /// Begins a database transaction with the specified + /// value. + /// Custom parameter describing transaction options. + /// Returns representation. + public IDbTransaction BeginTransaction(object custom) + { + return BeginTransaction(null, custom, null); + } + /// Changes the current database for an open Connection object. + /// The name of the database to use in place of the current database. + /// This operation is not supported in DynamORM. and will throw . + /// Thrown always. + public void ChangeDatabase(string databaseName) + { + throw new NotSupportedException("This operation is not supported in DynamORM."); + } + /// Opens a database connection with the settings specified by + /// the ConnectionString property of the provider-specific + /// Connection object. + /// Does nothing. handles + /// opening connections. + public void Open() + { + } + /// Closes the connection to the database. + /// Does nothing. handles + /// closing connections. Only way to close it is to dispose connection. + /// It will close if this is multi connection configuration, otherwise + /// it will stay open until is not + /// disposed. + public void Close() + { + } + /// Gets or sets the string used to open a database. + /// Changing connection string operation is not supported in DynamORM. + /// and will throw . + /// Thrown always when set is attempted. + public string ConnectionString + { + get { return Connection.ConnectionString; } + set { throw new NotSupportedException("This operation is not supported in DynamORM."); } + } + /// Gets the time to wait while trying to establish a connection + /// before terminating the attempt and generating an error. + public int ConnectionTimeout + { + get { return Connection.ConnectionTimeout; } + } + /// Gets the name of the current database or the database + /// to be used after a connection is opened. + public string Database + { + get { return Connection.Database; } + } + /// Gets the current state of the connection. + public ConnectionState State + { + get { return Connection.State; } + } + #endregion IDbConnection Members + + #region IExtendedDisposable Members + + /// Performs application-defined tasks associated with freeing, + /// releasing, or resetting unmanaged resources. + public void Dispose() + { + if (IsDisposed) + return; + + var db = _db; + if (db != null && Connection != null) + db.Close(Connection); + + Connection = null; + _db = null; + IsDisposed = true; + } + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + #endregion IExtendedDisposable Members + } + /// Dynamic database is a class responsible for managing database. + public class DynamicDatabase : IExtendedDisposable + { + #region Internal fields and properties + + private DbProviderFactory _provider; + private DynamicProcedureInvoker _proc; + private string _connectionString; + private bool _singleConnection; + private bool _singleTransaction; + private string _leftDecorator = "\""; + private string _rightDecorator = "\""; + private bool _leftDecoratorIsInInvalidMembersChars = true; + private bool _rightDecoratorIsInInvalidMembersChars = true; + private string _parameterFormat = "@{0}"; + private int? _commandTimeout = null; + private long _poolStamp = 0; + + private DynamicConnection _tempConn = null; + + /// Provides lock object for this database instance. + internal readonly object SyncLock = new object(); + + /// Gets or sets timestamp of last transaction pool or configuration change. + /// This property is used to allow commands to determine if + /// they need to update transaction object or not. + internal long PoolStamp + { + get + { + long r = 0; + + lock (SyncLock) + r = _poolStamp; + + return r; + } + set + { + lock (SyncLock) + _poolStamp = value; + } + } + /// Gets pool of connections and transactions. + internal Dictionary> TransactionPool { get; private set; } + + /// Gets pool of connections and commands. + /// Pool should contain dynamic commands instead of native ones. + internal Dictionary> CommandsPool { get; private set; } + + /// Gets schema columns cache. + internal Dictionary> Schema { get; private set; } + + /// Gets active builders that weren't disposed. + internal List RemainingBuilders { get; private set; } + +#if !DYNAMORM_OMMIT_OLDSYNTAX + + /// Gets tables cache for this database instance. + internal Dictionary TablesCache { get; private set; } + +#endif + + #endregion Internal fields and properties + + #region Properties and Constructors + + /// Gets database options. + public DynamicDatabaseOptions Options { get; private set; } + + /// Gets or sets command timeout. + public int? CommandTimeout { get { return _commandTimeout; } set { _commandTimeout = value; _poolStamp = DateTime.Now.Ticks; } } + + /// Gets the database provider. + public DbProviderFactory Provider { get { return _provider; } } + + /// Gets the procedures invoker. + /// + /// + /// using (var db = GetORM()) + /// { + /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_Scalar AS SELECT 42;"); + /// var res0 = db.Procedures.sp_Exp_Scalar(); + /// var res1 = db.Procedures.sp_Exp_Scalar(); + /// + /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_ReturnInt AS RETURN 42;"); + /// var res2 = db.Procedures.sp_Exp_ReturnInt(); + /// + /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeData AS + /// SELECT 1 Id, 'Some Name 1' [Name], 'Some Desc 1' [Desc], GETDATE() [Date] + /// UNION ALL SELECT 2 Id, 'Some Name 2', 'Some Desc 2', GETDATE() [Date];"); + /// var res3 = db.Procedures.sp_Exp_SomeData(); + /// var res4 = db.Procedures.sp_Exp_SomeData>(); + /// + /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeInputAndOutput + /// @Name nvarchar(50), + /// @Result nvarchar(256) OUTPUT + /// AS + /// SELECT @Result = 'Hi, ' + @Name + ' your lucky number is 42!';"); + /// var res5 = db.Procedures.sp_Exp_SomeInputAndOutput(Name: "G4g4r1n", out_Result: new DynamicColumn + /// { + /// Schema = new DynamicSchemaColumn + /// { + /// Size = 256, + /// }, + /// }, ret_Return: 0); + /// var res6 = db.Procedures.sp_Exp_SomeInputAndOutput(Name: "G4g4r1n", out_Result: new DynamicSchemaColumn + /// { + /// Size = 256, + /// }, ret_Return: 0); + /// + /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeInputAndOutputWithDataAndReturn + /// @Name nvarchar(50), + /// @Result nvarchar(256) OUTPUT + /// AS + /// SELECT @Result = 'Hi, ' + @Name + ' your lucky number is 42!' + /// + /// SELECT 1 Id, 'Some Name 1' [Name], 'Some Desc 1' [Desc], GETDATE() [Date] + /// UNION ALL SELECT 2 Id, 'Some Name 2', 'Some Desc 2', GETDATE() [Date] + /// + /// RETURN 42;"); + /// var res7 = db.Procedures.sp_Exp_SomeInputAndOutputWithDataAndReturn, sp_Exp_SomeInputAndOutputWithDataAndReturn_Result>(Name: "G4g4r1n", out_Result: new DynamicColumn + /// { + /// Schema = new DynamicSchemaColumn + /// { + /// Size = 256, + /// }, + /// }, ret_Return: 0); + /// var res8 = db.Procedures.sp_Exp_SomeInputAndOutputWithDataAndReturn, sp_Exp_SomeInputAndOutputWithDataAndReturn_Result>(Name: "G4g4r1n", out_Result: new DynamicSchemaColumn + /// { + /// Size = 256, + /// }, ret_Return: 0); + /// } + /// + ///private class sp_Exp_SomeData_Result + ///{ + /// public virtual int Id { get; set; } + /// public virtual string Name { get; set; } + /// public virtual string Desc { get; set; } + /// public virtual DateTime Date { get; set; } + ///} + ///private class sp_Exp_SomeInputAndOutput_Result + ///{ + /// public virtual string Result { get; set; } + /// public virtual string Return { get; set; } + ///} + ///private class sp_Exp_SomeInputAndOutputWithDataAndReturn_Result + ///{ + /// public class Data + /// { + /// public virtual int Id { get; set; } + /// public virtual string Name { get; set; } + /// public virtual string Desc { get; set; } + /// public virtual DateTime Date { get; set; } + /// } + /// public virtual List sp_Exp_SomeInputAndOutputWithDataAndReturn { get; set; } + /// public virtual string Result { get; set; } + /// public virtual string Return { get; set; } + ///} + /// + /// + public dynamic Procedures + { + get + { + if (_proc == null) + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection doesn't support stored procedures."); + + _proc = new DynamicProcedureInvoker(this); + } + return _proc; + } + } + /// Gets or sets a value indicating whether + /// dump commands to console or not. + public bool DumpCommands { get; set; } + + /// Gets or sets the dump command delegate. + /// The dump command delegate. + public Action DumpCommandDelegate { get; set; } + +#if NETFRAMEWORK + // https://github.com/dotnet/runtime/issues/26229 + /// Initializes a new instance of the class. + /// Database provider by name. + /// Connection string to provided database. + /// Connection options. + public DynamicDatabase(string provider, string connectionString, DynamicDatabaseOptions options) + : this(DbProviderFactories.GetFactory(provider), connectionString, options) + { + } +#endif + + /// Initializes a new instance of the class. + /// Database provider. + /// Connection string to provided database. + /// Connection options. + public DynamicDatabase(DbProviderFactory provider, string connectionString, DynamicDatabaseOptions options) + { + IsDisposed = false; + _provider = provider; + + InitCommon(connectionString, options); + } + private DbProviderFactory FindDbProviderFactoryFromConnection(Type t) + { + foreach (var type in t.Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(DbProviderFactory)))) + { + DbProviderFactory provider = null; + bool dispose = false; + + var pi = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty); + if (pi != null) + provider = (DbProviderFactory)pi.GetValue(null, null); + else + { + var fi = type.GetField("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField); + if (fi != null) + provider = (DbProviderFactory)fi.GetValue(null); + } + if (provider == null) + { + var ci = type.GetConstructor(Type.EmptyTypes); + if (ci != null) + { + provider = ci.Invoke(null) as DbProviderFactory; + dispose = true; + } + } + try + { + if (provider != null) + { + using (var c = provider.CreateConnection()) + { + if (c.GetType() == t) + return provider; + } + } + } + finally + { + if (provider != null && dispose && provider is IDisposable) + ((IDisposable)provider).Dispose(); + } + } + return null; + } + /// Initializes a new instance of the class. + /// Active database connection. + /// Connection options. required. + public DynamicDatabase(IDbConnection connection, DynamicDatabaseOptions options) + { + // Try to find correct provider if possible + _provider = FindDbProviderFactoryFromConnection(connection.GetType()); + + IsDisposed = false; + InitCommon(connection.ConnectionString, options); + TransactionPool.Add(connection, new Stack()); + + if (!_singleConnection) + throw new InvalidOperationException("This constructor accepts only connections with DynamicDatabaseOptions.SingleConnection option."); + } + private void InitCommon(string connectionString, DynamicDatabaseOptions options) + { + _connectionString = connectionString; + Options = options; + + _singleConnection = (options & DynamicDatabaseOptions.SingleConnection) == DynamicDatabaseOptions.SingleConnection; + _singleTransaction = (options & DynamicDatabaseOptions.SingleTransaction) == DynamicDatabaseOptions.SingleTransaction; + DumpCommands = (options & DynamicDatabaseOptions.DumpCommands) == DynamicDatabaseOptions.DumpCommands; + + TransactionPool = new Dictionary>(); + CommandsPool = new Dictionary>(); + Schema = new Dictionary>(); + RemainingBuilders = new List(); +#if !DYNAMORM_OMMIT_OLDSYNTAX + TablesCache = new Dictionary(); +#endif + } + #endregion Properties and Constructors + + #region Table + +#if !DYNAMORM_OMMIT_OLDSYNTAX + + /// Gets dynamic table which is a simple ORM using dynamic objects. + /// The action with instance of as parameter. + /// Table name. + /// Override keys in schema. + /// Owner of the table. + public void Table(Action action, string table = "", string[] keys = null, string owner = "") + { + using (dynamic t = Table(table, keys, owner)) + action(t); + } + /// Gets dynamic table which is a simple ORM using dynamic objects. + /// Type used to determine table name. + /// The action with instance of as parameter. + /// Override keys in schema. + public void Table(Action action, string[] keys = null) + { + using (dynamic t = Table(keys)) + action(t); + } + /// Gets dynamic table which is a simple ORM using dynamic objects. + /// Table name. + /// Override keys in schema. + /// Owner of the table. + /// Instance of . + public dynamic Table(string table = "", string[] keys = null, string owner = "") + { + string key = string.Concat( + table == null ? string.Empty : table, + keys == null ? string.Empty : string.Join("_|_", keys)); + + DynamicTable dt = null; + lock (SyncLock) + dt = TablesCache.TryGetValue(key) ?? + TablesCache.AddAndPassValue(key, + new DynamicTable(this, table, owner, keys)); + + return dt; + } + /// Gets dynamic table which is a simple ORM using dynamic objects. + /// Type used to determine table name. + /// Override keys in schema. + /// Instance of . + public dynamic Table(string[] keys = null) + { + Type table = typeof(T); + string key = string.Concat( + table.FullName, + keys == null ? string.Empty : string.Join("_|_", keys)); + + DynamicTable dt = null; + lock (SyncLock) + dt = TablesCache.TryGetValue(key) ?? + TablesCache.AddAndPassValue(key, + new DynamicTable(this, table, keys)); + + return dt; + } + /// Removes cached table. + /// Disposed dynamic table. + internal void RemoveFromCache(DynamicTable dynamicTable) + { + foreach (KeyValuePair item in TablesCache.Where(kvp => kvp.Value == dynamicTable).ToList()) + TablesCache.Remove(item.Key); + } +#endif + + #endregion Table + + /// Adds cached builder. + /// New dynamic builder. + internal void AddToCache(IDynamicQueryBuilder builder) + { + lock (SyncLock) + RemainingBuilders.Add(builder); + } + /// Removes cached builder. + /// Disposed dynamic builder. + internal void RemoveFromCache(IDynamicQueryBuilder builder) + { + lock (SyncLock) + RemainingBuilders.Remove(builder); + } + #region From/Insert/Update/Delete + + /// + /// Adds to the FROM clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: x => "owner.Table AS Alias", where the alias part is optional. + /// - Resolve to an expression: x => x.owner.Table.As( x.Alias ), where the alias part is optional. + /// - Generic expression: x => x( expression ).As( x.Alias ), where the alias part is mandatory. In this + /// case the alias is not annotated. + /// + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder From(Func fn) + { + return new DynamicSelectQueryBuilder(this).From(fn); + } + /// Adds to the FROM clause using . + /// Type which can be represented in database. + /// Table alias. + /// use no lock. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder From(string alias = null, bool noLock = false) + { + // TODO: Make it more readable and maitainable + if (noLock) + { + if (string.IsNullOrEmpty(alias)) + return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).NoLock()); + else + return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias).NoLock()); + } + else + { + if (string.IsNullOrEmpty(alias)) + return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T))); + else + return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias)); + } + } + /// Adds to the FROM clause using . + /// Type which can be represented in database. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder From(Type t) + { + return new DynamicSelectQueryBuilder(this).From(x => x(t)); + } + /// + /// Adds to the INSERT INTO clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: x => "owner.Table". + /// - Resolve to a type: x => typeof(SomeClass). + /// - Resolve to an expression: x => x.owner.Table. + /// - Generic expression: x => x( expression ). Expression can + /// be or . + /// + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicInsertQueryBuilder Insert(Func func) + { + return new DynamicInsertQueryBuilder(this).Table(func); + } + /// Adds to the INSERT INTO clause using . + /// Type which can be represented in database. + /// This instance to permit chaining. + public virtual IDynamicInsertQueryBuilder Insert() + { + return new DynamicInsertQueryBuilder(this).Table(typeof(T)); + } + /// Adds to the INSERT INTO clause using . + /// Type which can be represented in database. + /// This instance to permit chaining. + public virtual IDynamicInsertQueryBuilder Insert(Type t) + { + return new DynamicInsertQueryBuilder(this).Table(t); + } + /// Bulk insert objects into database. + /// Type of objects to insert. + /// Enumerable containing instances of objects to insert. + /// Number of inserted rows. + public virtual int Insert(IEnumerable e) where T : class + { + return Insert(typeof(T), e); + } + /// Bulk insert objects into database. + /// Type of objects to insert. + /// Enumerable containing instances of objects to insert. + /// Number of inserted rows. + public virtual int Insert(Type t, IEnumerable e) + { + int affected = 0; + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); + + if (mapper != null) + { + using (IDbConnection con = Open()) + using (IDbTransaction tra = con.BeginTransaction()) + using (IDbCommand cmd = con.CreateCommand()) + { + try + { + Dictionary parameters = new Dictionary(); + + if (!string.IsNullOrEmpty(mapper.InsertCommandText)) + { + cmd.CommandText = mapper.InsertCommandText; + + foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values + .Where(di => !di.Ignore && di.InsertCommandParameter != null) + .OrderBy(di => di.InsertCommandParameter.Ordinal)) + { + IDbDataParameter para = cmd.CreateParameter(); + para.ParameterName = col.InsertCommandParameter.Name; + para.DbType = col.InsertCommandParameter.Type; + cmd.Parameters.Add(para); + + parameters[para] = col; + } + } + else + PrepareBatchInsert(t, mapper, cmd, parameters); + + foreach (var o in e) + { + foreach (KeyValuePair m in parameters) + m.Key.Value = m.Value.Get(o); + + affected += cmd.ExecuteNonQuery(); + } + tra.Commit(); + } + catch (Exception ex) + { + if (tra != null) + tra.Rollback(); + + affected = 0; + + StringBuilder problematicCommand = new StringBuilder(); + cmd.Dump(problematicCommand); + + throw new InvalidOperationException(problematicCommand.ToString(), ex); + } + } + } + return affected; + } + /// + /// Adds to the UPDATE clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: x => "owner.Table". + /// - Resolve to a type: x => typeof(SomeClass). + /// - Resolve to an expression: x => x.owner.Table. + /// - Generic expression: x => x( expression ). Expression can + /// be or . + /// + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicUpdateQueryBuilder Update(Func func) + { + return new DynamicUpdateQueryBuilder(this).Table(func); + } + /// Adds to the UPDATE clause using . + /// Type which can be represented in database. + /// This instance to permit chaining. + public virtual IDynamicUpdateQueryBuilder Update() + { + return new DynamicUpdateQueryBuilder(this).Table(typeof(T)); + } + /// Adds to the UPDATE clause using . + /// Type which can be represented in database. + /// This instance to permit chaining. + public virtual IDynamicUpdateQueryBuilder Update(Type t) + { + return new DynamicUpdateQueryBuilder(this).Table(t); + } + /// Bulk update objects in database. + /// Type of objects to update. + /// Enumerable containing instances of objects to update. + /// Number of updated rows. + public virtual int Update(IEnumerable e) where T : class + { + return Update(typeof(T), e); + } + /// Bulk update objects in database. + /// Type of objects to update. + /// Enumerable containing instances of objects to update. + /// Number of updated rows. + public virtual int Update(Type t, IEnumerable e) + { + int affected = 0; + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); + + if (mapper != null) + { + using (IDbConnection con = Open()) + using (IDbTransaction tra = con.BeginTransaction()) + using (IDbCommand cmd = con.CreateCommand()) + { + try + { + Dictionary parameters = new Dictionary(); + + if (!string.IsNullOrEmpty(mapper.UpdateCommandText)) + { + cmd.CommandText = mapper.UpdateCommandText; + + foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values + .Where(di => !di.Ignore && di.UpdateCommandParameter != null) + .OrderBy(di => di.UpdateCommandParameter.Ordinal)) + { + IDbDataParameter para = cmd.CreateParameter(); + para.ParameterName = col.UpdateCommandParameter.Name; + para.DbType = col.UpdateCommandParameter.Type; + cmd.Parameters.Add(para); + + parameters[para] = col; + } + } + else + PrepareBatchUpdate(t, mapper, cmd, parameters); + + foreach (var o in e) + { + foreach (KeyValuePair m in parameters) + m.Key.Value = m.Value.Get(o); + + affected += cmd.ExecuteNonQuery(); + } + tra.Commit(); + } + catch (Exception ex) + { + if (tra != null) + tra.Rollback(); + + affected = 0; + + StringBuilder problematicCommand = new StringBuilder(); + cmd.Dump(problematicCommand); + + throw new InvalidOperationException(problematicCommand.ToString(), ex); + } + } + } + return affected; + } + /// Bulk update or insert objects into database. + /// Type of objects to update or insert. + /// Enumerable containing instances of objects to update or insert. + /// Number of updated or inserted rows. + public virtual int UpdateOrInsert(IEnumerable e) where T : class + { + return UpdateOrInsert(typeof(T), e); + } + /// Bulk update or insert objects into database. + /// Type of objects to update or insert. + /// Enumerable containing instances of objects to update or insert. + /// Number of updated or inserted rows. + public virtual int UpdateOrInsert(Type t, IEnumerable e) + { + int affected = 0; + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); + + if (mapper != null) + { + using (IDbConnection con = Open()) + using (IDbTransaction tra = con.BeginTransaction()) + using (IDbCommand cmdUp = con.CreateCommand()) + using (IDbCommand cmdIn = con.CreateCommand()) + { + try + { + #region Update + + Dictionary parametersUp = new Dictionary(); + + if (!string.IsNullOrEmpty(mapper.UpdateCommandText)) + { + cmdUp.CommandText = mapper.UpdateCommandText; + + foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values + .Where(di => !di.Ignore && di.UpdateCommandParameter != null) + .OrderBy(di => di.UpdateCommandParameter.Ordinal)) + { + IDbDataParameter para = cmdUp.CreateParameter(); + para.ParameterName = col.UpdateCommandParameter.Name; + para.DbType = col.UpdateCommandParameter.Type; + cmdUp.Parameters.Add(para); + + parametersUp[para] = col; + } + } + else + PrepareBatchUpdate(t, mapper, cmdUp, parametersUp); + + #endregion Update + + #region Insert + + Dictionary parametersIn = new Dictionary(); + + if (!string.IsNullOrEmpty(mapper.InsertCommandText)) + { + cmdIn.CommandText = mapper.InsertCommandText; + + foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values + .Where(di => !di.Ignore && di.InsertCommandParameter != null) + .OrderBy(di => di.InsertCommandParameter.Ordinal)) + { + IDbDataParameter para = cmdIn.CreateParameter(); + para.ParameterName = col.InsertCommandParameter.Name; + para.DbType = col.InsertCommandParameter.Type; + cmdIn.Parameters.Add(para); + + parametersIn[para] = col; + } + } + else + PrepareBatchInsert(t, mapper, cmdIn, parametersIn); + + #endregion Insert + + foreach (var o in e) + { + foreach (KeyValuePair m in parametersUp) + m.Key.Value = m.Value.Get(o); + + int a = cmdUp.ExecuteNonQuery(); + if (a == 0) + { + foreach (KeyValuePair m in parametersIn) + m.Key.Value = m.Value.Get(o); + + a = cmdIn.ExecuteNonQuery(); + } + affected += a; + } + tra.Commit(); + } + catch (Exception ex) + { + if (tra != null) + tra.Rollback(); + + affected = 0; + + StringBuilder problematicCommand = new StringBuilder(); + cmdUp.Dump(problematicCommand); + + throw new InvalidOperationException(problematicCommand.ToString(), ex); + } + } + } + return affected; + } + /// + /// Adds to the DELETE FROM clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: x => "owner.Table". + /// - Resolve to a type: x => typeof(SomeClass). + /// - Resolve to an expression: x => x.owner.Table. + /// - Generic expression: x => x( expression ). Expression can + /// be or . + /// + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicDeleteQueryBuilder Delete(Func func) + { + return new DynamicDeleteQueryBuilder(this).Table(func); + } + /// Adds to the DELETE FROM clause using . + /// Type which can be represented in database. + /// This instance to permit chaining. + public virtual IDynamicDeleteQueryBuilder Delete() + { + return new DynamicDeleteQueryBuilder(this).Table(typeof(T)); + } + /// Adds to the DELETE FROM clause using . + /// Type which can be represented in database. + /// This instance to permit chaining. + public virtual IDynamicDeleteQueryBuilder Delete(Type t) + { + return new DynamicDeleteQueryBuilder(this).Table(t); + } + /// Bulk delete objects in database. + /// Type of objects to delete. + /// Enumerable containing instances of objects to delete. + /// Number of deleted rows. + public virtual int Delete(IEnumerable e) where T : class + { + return Delete(typeof(T), e); + } + /// Bulk delete objects in database. + /// Type of objects to delete. + /// Enumerable containing instances of objects to delete. + /// Number of deleted rows. + public virtual int Delete(Type t, IEnumerable e) + { + int affected = 0; + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); + + if (mapper != null) + { + using (IDbConnection con = Open()) + using (IDbTransaction tra = con.BeginTransaction()) + using (IDbCommand cmd = con.CreateCommand()) + { + try + { + Dictionary parameters = new Dictionary(); + + if (!string.IsNullOrEmpty(mapper.DeleteCommandText)) + { + cmd.CommandText = mapper.DeleteCommandText; + + foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values + .Where(di => !di.Ignore && di.DeleteCommandParameter != null) + .OrderBy(di => di.DeleteCommandParameter.Ordinal)) + { + IDbDataParameter para = cmd.CreateParameter(); + para.ParameterName = col.DeleteCommandParameter.Name; + para.DbType = col.DeleteCommandParameter.Type; + cmd.Parameters.Add(para); + + parameters[para] = col; + } + } + else + PrepareBatchDelete(t, mapper, cmd, parameters); + + foreach (var o in e) + { + foreach (KeyValuePair m in parameters) + m.Key.Value = m.Value.Get(o); + + affected += cmd.ExecuteNonQuery(); + } + tra.Commit(); + } + catch (Exception ex) + { + if (tra != null) + tra.Rollback(); + + affected = 0; + + StringBuilder problematicCommand = new StringBuilder(); + cmd.Dump(problematicCommand); + + throw new InvalidOperationException(problematicCommand.ToString(), ex); + } + } + } + return affected; + } + private void PrepareBatchInsert(DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + PrepareBatchInsert(typeof(T), mapper, cmd, parameters); + } + private void PrepareBatchInsert(Type t, DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + DynamicPropertyInvoker currentprop = null; + Dictionary temp = new Dictionary(); + Dictionary schema = this.GetSchema(t); + int ord = 0; + + IDynamicInsertQueryBuilder ib = Insert(t) + .SetVirtualMode(true) + .CreateTemporaryParameterAction(p => temp[p.Name] = currentprop) + .CreateParameterAction((p, cp) => + { + parameters[cp] = temp[p.Name]; + parameters[cp].InsertCommandParameter = new DynamicPropertyInvoker.ParameterSpec + { + Name = cp.ParameterName, + Type = cp.DbType, + Ordinal = ord++, + }; + }); + + foreach (KeyValuePair prop in mapper.PropertyMap) + if (!mapper.Ignored.Contains(prop.Key)) + { + string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; + currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); + + if (currentprop.Ignore || (currentprop.Column != null && currentprop.Column.IsNoInsert)) + continue; + + if (currentprop.Get != null) + ib.Insert(new DynamicColumn() + { + ColumnName = col, + Schema = schema == null ? null : schema.TryGetNullable(col.ToLower()), + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + } + ib.FillCommand(cmd); + + // Cache command + mapper.InsertCommandText = cmd.CommandText; + } + private void PrepareBatchUpdate(DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + PrepareBatchUpdate(typeof(T), mapper, cmd, parameters); + } + private void PrepareBatchUpdate(Type t, DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + DynamicPropertyInvoker currentprop = null; + Dictionary temp = new Dictionary(); + Dictionary schema = this.GetSchema(t); + int ord = 0; + + IDynamicUpdateQueryBuilder ib = Update(t) + .SetVirtualMode(true) + .CreateTemporaryParameterAction(p => temp[p.Name] = currentprop) + .CreateParameterAction((p, cp) => + { + parameters[cp] = temp[p.Name]; + parameters[cp].UpdateCommandParameter = new DynamicPropertyInvoker.ParameterSpec + { + Name = cp.ParameterName, + Type = cp.DbType, + Ordinal = ord++, + }; + }); + + foreach (KeyValuePair prop in mapper.PropertyMap) + if (!mapper.Ignored.Contains(prop.Key)) + { + string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; + currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); + + if (currentprop.Ignore) + continue; + + if (currentprop.Get != null) + { + DynamicSchemaColumn? colS = schema == null ? null : schema.TryGetNullable(col.ToLower()); + + if (colS.HasValue) + { + if (colS.Value.IsKey) + ib.Where(new DynamicColumn() + { + ColumnName = col, + Schema = colS, + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + else if (currentprop.Column == null || !currentprop.Column.IsNoUpdate) + ib.Values(new DynamicColumn() + { + ColumnName = col, + Schema = colS, + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + } + else if (currentprop.Column != null && currentprop.Column.IsKey) + ib.Where(new DynamicColumn() + { + ColumnName = col, + Schema = colS, + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + } + } + ib.FillCommand(cmd); + + // Cache command + mapper.UpdateCommandText = cmd.CommandText; + } + private void PrepareBatchDelete(DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + PrepareBatchDelete(typeof(T), mapper, cmd, parameters); + } + private void PrepareBatchDelete(Type t, DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) + { + DynamicPropertyInvoker currentprop = null; + Dictionary temp = new Dictionary(); + Dictionary schema = this.GetSchema(t); + int ord = 0; + + IDynamicDeleteQueryBuilder ib = Delete(t) + .SetVirtualMode(true) + .CreateTemporaryParameterAction(p => temp[p.Name] = currentprop) + .CreateParameterAction((p, cp) => + { + parameters[cp] = temp[p.Name]; + parameters[cp].DeleteCommandParameter = new DynamicPropertyInvoker.ParameterSpec + { + Name = cp.ParameterName, + Type = cp.DbType, + Ordinal = ord++, + }; + }); + + foreach (KeyValuePair prop in mapper.PropertyMap) + if (!mapper.Ignored.Contains(prop.Key)) + { + string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; + currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); + + if (currentprop.Ignore) + continue; + + if (currentprop.Get != null) + { + DynamicSchemaColumn? colS = schema == null ? null : schema.TryGetNullable(col.ToLower()); + + if (colS != null) + { + if (colS.Value.IsKey) + ib.Where(new DynamicColumn() + { + ColumnName = col, + Schema = colS, + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + } + else if (currentprop.Column != null && currentprop.Column.IsKey) + ib.Where(new DynamicColumn() + { + ColumnName = col, + Schema = colS, + Operator = DynamicColumn.CompareOperator.Eq, + Value = null, + VirtualColumn = true, + }); + } + } + ib.FillCommand(cmd); + + // Cache command + mapper.DeleteCommandText = cmd.CommandText; + } + #endregion From/Insert/Update/Delete + + #region Procedure + + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Number of affected rows. + public virtual int Procedure(string procName) + { + return Procedure(procName, (DynamicExpando)null); + } + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Arguments (parameters) in form of expando object. + /// Number of affected rows. + public virtual int Procedure(string procName, params object[] args) + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(this, args) + .ExecuteNonQuery(); + } + } + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Arguments (parameters) in form of expando object. + /// Number of affected rows. + public virtual int Procedure(string procName, DynamicExpando args) + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(this, args) + .ExecuteNonQuery(); + } + } + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Arguments (parameters) in form of expando object. + /// Number of affected rows. + public virtual int Procedure(string procName, ExpandoObject args) + { + if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(this, args) + .ExecuteNonQuery(); + } + } + #endregion Procedure + + #region Execute + + /// Execute non query. + /// SQL query containing numbered parameters in format provided by + /// methods. Also names should be formatted with + /// method. + /// Arguments (parameters). + /// Number of affected rows. + public virtual int Execute(string sql, params object[] args) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(sql).AddParameters(this, args) + .ExecuteNonQuery(); + } + } + /// Execute non query. + /// Command builder. + /// Number of affected rows. + public virtual int Execute(IDynamicQueryBuilder builder) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(builder) + .ExecuteNonQuery(); + } + } + /// Execute non query. + /// Command builders. + /// Number of affected rows. + public virtual int Execute(IDynamicQueryBuilder[] builders) + { + int ret = 0; + + using (IDbConnection con = Open()) + { + using (IDbTransaction trans = con.BeginTransaction()) + { + foreach (IDynamicQueryBuilder builder in builders) + using (IDbCommand cmd = con.CreateCommand()) + ret += cmd + .SetCommand(builder) + .ExecuteNonQuery(); + + trans.Commit(); + } + } + return ret; + } + #endregion Execute + + #region Scalar + + /// Returns a single result. + /// SQL query containing numbered parameters in format provided by + /// methods. Also names should be formatted with + /// method. + /// Arguments (parameters). + /// Result of a query. + public virtual object Scalar(string sql, params object[] args) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(sql).AddParameters(this, args) + .ExecuteScalar(); + } + } + /// Returns a single result. + /// Command builder. + /// Result of a query. + public virtual object Scalar(IDynamicQueryBuilder builder) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(builder) + .ExecuteScalar(); + } + } +#if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE + + /// Returns a single result. + /// What kind of result is expected. + /// SQL query containing numbered parameters in format provided by + /// methods. Also names should be formatted with + /// method. + /// Arguments (parameters). + /// Result of a query. + public virtual T ScalarAs(string sql, params object[] args) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(sql).AddParameters(this, args) + .ExecuteScalarAs(); + } + } + /// Returns a single result. + /// What kind of result is expected. + /// Command builder. + /// Default value. + /// Result of a query. + public virtual T ScalarAs(IDynamicQueryBuilder builder, T defaultValue = default(T)) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(builder) + .ExecuteScalarAs(defaultValue); + } + } +#endif + + #endregion Scalar + + #region Query + + /// Enumerate the reader and yield the result. + /// SQL query containing numbered parameters in format provided by + /// methods. Also names should be formatted with + /// method. + /// Arguments (parameters). + /// Enumerator of objects expanded from query. + public virtual IEnumerable Query(string sql, params object[] args) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(sql) + .AddParameters(this, args) + .ExecuteReader()) + using (IDataReader cache = new DynamicCachedReader(rdr)) + { + while (cache.Read()) + { + dynamic val = null; + + // Work around to avoid yield being in try...catch block: + // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch + try + { + val = cache.RowToDynamic(); + } + catch (ArgumentException argex) + { + StringBuilder sb = new StringBuilder(); + cmd.Dump(sb); + + throw new ArgumentException( + string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb), + argex.InnerException.NullOr(a => a, argex)); + } + yield return val; + } + } + } + } + /// Enumerate the reader and yield the result. + /// Command builder. + /// Enumerator of objects expanded from query. + public virtual IEnumerable Query(IDynamicQueryBuilder builder) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(builder) + .ExecuteReader()) + using (var cache = new DynamicCachedReader(rdr)) + while (cache.Read()) + { + dynamic val = null; + + // Work around to avoid yield being in try...catch block: + // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch + try + { + val = cache.RowToDynamic(); + } + catch (ArgumentException argex) + { + StringBuilder sb = new StringBuilder(); + cmd.Dump(sb); + + throw new ArgumentException( + string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb), + argex.InnerException.NullOr(a => a, argex)); + } + yield return val; + } + } + } + #endregion Query + + #region CachedQuery + + /// Enumerate the reader and yield the result. + /// SQL query containing numbered parameters in format provided by + /// methods. Also names should be formatted with + /// method. + /// Arguments (parameters). + /// Enumerator of objects expanded from query. + public virtual DynamicCachedReader CachedQuery(string sql, params object[] args) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(sql) + .AddParameters(this, args) + .ExecuteReader()) + return new DynamicCachedReader(rdr); + } + } + /// Enumerate the reader and yield the result. + /// Command builder. + /// Enumerator of objects expanded from query. + public virtual DynamicCachedReader CachedQuery(IDynamicQueryBuilder builder) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(builder) + .ExecuteReader()) + return new DynamicCachedReader(rdr); + } + } + #endregion Query + + #region Schema + + /// Builds query cache if necessary and returns it. + /// The builder containing query to read schema from. + /// Query schema. + public Dictionary GetQuerySchema(IDynamicSelectQueryBuilder builder) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand().SetCommand(builder)) + return ReadSchema(cmd) + .Distinct() + .ToDictionary(k => k.Name.ToLower(), k => k); + } + /// Builds query cache if necessary and returns it. + /// SQL query from which read schema. + /// SQL query arguments. + /// Query schema. + public Dictionary GetQuerySchema(string sql, params object[] args) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand().SetCommand(sql, args)) + return ReadSchema(cmd) + .Distinct() + .ToDictionary(k => k.Name.ToLower(), k => k); + } + /// Builds table cache if necessary and returns it. + /// Name of table for which build schema. + /// Owner of table for which build schema. + /// Table schema. + public Dictionary GetSchema(string table, string owner = null) + { + Dictionary schema = null; + + lock (SyncLock) + schema = Schema.TryGetValue(table.ToLower()) ?? + BuildAndCacheSchema(table, null, owner); + + return schema; + } + /// Builds table cache if necessary and returns it. + /// Type of table for which build schema. + /// Table schema or null if type was anonymous. + public Dictionary GetSchema() + { + if (typeof(T).IsAnonymous()) + return null; + + Dictionary schema = null; + + lock (SyncLock) + schema = Schema.TryGetValue(typeof(T).FullName) ?? + BuildAndCacheSchema(null, DynamicMapperCache.GetMapper()); + + return schema; + } + /// Builds table cache if necessary and returns it. + /// Type of table for which build schema. + /// Table schema or null if type was anonymous. + public Dictionary GetSchema(Type table) + { + if (table == null || table.IsAnonymous() || table.IsValueType) + return null; + + Dictionary schema = null; + + lock (SyncLock) + schema = Schema.TryGetValue(table.FullName) ?? + BuildAndCacheSchema(null, DynamicMapperCache.GetMapper(table)); + + return schema; + } + /// Clears the schema from cache. + /// Use this method to refresh table information. + /// Name of table for which clear schema. + /// Owner of table for which clear schema. + public void ClearSchema(string table = null, string owner = null) + { + lock (SyncLock) + if (Schema.ContainsKey(table.ToLower())) + Schema.Remove(table.ToLower()); + } + /// Clears the schema from cache. + /// Use this method to refresh table information. + /// Type of table for which clear schema. + public void ClearSchema() + { + ClearSchema(typeof(T)); + } + /// Clears the schema from cache. + /// Use this method to refresh table information. + /// Type of table for which clear schema. + public void ClearSchema(Type table) + { + lock (SyncLock) + if (Schema.ContainsKey(table.FullName)) + { + if (Schema[table.FullName] != null) + Schema[table.FullName].Clear(); + Schema.Remove(table.FullName); + } + } + /// Clears the all schemas from cache. + /// Use this method to refresh all table information. + public void ClearSchema() + { + lock (SyncLock) + { + foreach (KeyValuePair> s in Schema) + if (s.Value != null) + s.Value.Clear(); + + Schema.Clear(); + } + } + /// Get schema describing objects from reader. + /// Table from which extract column info. + /// Owner of table from which extract column info. + /// List of objects . + /// If your database doesn't get those values in upper case (like most of the databases) you should override this method. + protected virtual IList ReadSchema(string table, string owner) + { + using (IDbConnection con = Open()) + using (IDbCommand cmd = con.CreateCommand() + .SetCommand(string.Format("SELECT * FROM {0}{1} WHERE 1 = 0", + !string.IsNullOrEmpty(owner) ? string.Format("{0}.", DecorateName(owner)) : string.Empty, + DecorateName(table)))) + return ReadSchema(cmd) + .ToList(); + } + /// Get schema describing objects from reader. + /// Command containing query to execute. + /// List of objects . + /// If your database doesn't get those values in upper case (like most of the databases) you should override this method. + public virtual IEnumerable ReadSchema(IDbCommand cmd) + { + DataTable st = null; + + using (IDataReader rdr = cmd.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo)) + st = rdr.GetSchemaTable(); + + using (st) + foreach (DataRow col in st.Rows) + { + dynamic c = col.RowToDynamicUpper(); + + yield return new DynamicSchemaColumn + { + Name = c.COLUMNNAME, + Type = ReadSchemaType(c), + IsKey = c.ISKEY ?? false, + IsUnique = c.ISUNIQUE ?? false, + AllowNull = c.ALLOWNULL ?? false, + Size = (int)(c.COLUMNSIZE ?? 0), + Precision = (byte)(c.NUMERICPRECISION ?? 0), + Scale = (byte)(c.NUMERICSCALE ?? 0) + }; + } + } + /// Reads the type of column from the schema. + /// The schema. + /// Generic parameter type. + protected virtual DbType ReadSchemaType(dynamic schema) + { + Type type = (Type)schema.DATATYPE; + + // Small hack for SQL Server Provider + if (type == typeof(string) && Provider != null && Provider.GetType().Name == "SqlClientFactory") + { + var map = schema as IDictionary; + string typeName = (map.TryGetValue("DATATYPENAME") ?? string.Empty).ToString(); + + switch (typeName) + { + case "varchar": + return DbType.AnsiString; + + case "nvarchar": + return DbType.String; + } + } + return DynamicExtensions.TypeMap.TryGetNullable(type) ?? DbType.String; + } + private Dictionary BuildAndCacheSchema(string tableName, DynamicTypeMap mapper, string owner = null) + { + Dictionary schema = null; + + if (mapper != null) + { + tableName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + owner = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Owner) ? + null : mapper.Table.Owner; + } + bool databaseSchemaSupport = !string.IsNullOrEmpty(tableName) && + (Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema; + bool mapperSchema = mapper != null && mapper.Table != null && (mapper.Table.Override || !databaseSchemaSupport); + + #region Database schema + + if (databaseSchemaSupport && !Schema.ContainsKey(tableName.ToLower())) + { + schema = ReadSchema(tableName, owner) + .Where(x => x.Name != null) + .DistinctBy(x => x.Name.ToLower()) + .ToDictionary(k => k.Name.ToLower(), k => k); + + Schema[tableName.ToLower()] = schema; + } + #endregion Database schema + + #region Type schema + + if ((mapperSchema && !Schema.ContainsKey(mapper.Type.FullName)) || + (schema == null && mapper != null && !mapper.Type.IsAnonymous())) + { + // TODO: Ged rid of this monster below... + if (databaseSchemaSupport) + { + #region Merge with db schema + + schema = mapper.ColumnsMap.ToDictionary(k => k.Key, (v) => + { + DynamicSchemaColumn? col = Schema[tableName.ToLower()].TryGetNullable(v.Key); + + return new DynamicSchemaColumn + { + Name = DynamicExtensions.Coalesce( + v.Value.Column == null || string.IsNullOrEmpty(v.Value.Column.Name) ? null : v.Value.Column.Name, + col.HasValue && !string.IsNullOrEmpty(col.Value.Name) ? col.Value.Name : null, + v.Value.Name), + IsKey = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.IsKey : false, + col.HasValue ? col.Value.IsKey : false).Value, + Type = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.Type : null, + col.HasValue ? col.Value.Type : DynamicExtensions.TypeMap.TryGetNullable(v.Value.Type) ?? DbType.String).Value, + IsUnique = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.IsUnique : null, + col.HasValue ? col.Value.IsUnique : false).Value, + AllowNull = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.AllowNull : true, + col.HasValue ? col.Value.AllowNull : true).Value, + Size = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.Size : null, + col.HasValue ? col.Value.Size : 0).Value, + Precision = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.Precision : null, + col.HasValue ? col.Value.Precision : (byte)0).Value, + Scale = DynamicExtensions.CoalesceNullable( + v.Value.Column != null ? v.Value.Column.Scale : null, + col.HasValue ? col.Value.Scale : (byte)0).Value, + }; + }); + + #endregion Merge with db schema + } + else + { + #region MapEnumerable based only on type + + schema = mapper.ColumnsMap.ToDictionary(k => k.Key, + v => new DynamicSchemaColumn + { + Name = DynamicExtensions.Coalesce(v.Value.Column == null || string.IsNullOrEmpty(v.Value.Column.Name) ? null : v.Value.Column.Name, v.Value.Name), + IsKey = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.IsKey : false, false).Value, + Type = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Type : null, DynamicExtensions.TypeMap.TryGetNullable(v.Value.Type) ?? DbType.String).Value, + IsUnique = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.IsUnique : null, false).Value, + AllowNull = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.AllowNull : true, true).Value, + Size = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Size : null, 0).Value, + Precision = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Precision : null, 0).Value, + Scale = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Scale : null, 0).Value, + }); + + #endregion MapEnumerable based only on type + } + } + if (mapper != null && schema != null) + Schema[mapper.Type.FullName] = schema; + + #endregion Type schema + + return schema; + } + #endregion Schema + + #region Decorators + + /// Gets or sets left side decorator for database objects. + public string LeftDecorator + { + get { return _leftDecorator; } + set + { + _leftDecorator = value; + _leftDecoratorIsInInvalidMembersChars = + _leftDecorator.Length == 1 && StringExtensions.InvalidMemberChars.Contains(_leftDecorator[0]); + } + } + /// Gets or sets right side decorator for database objects. + public string RightDecorator + { + get { return _rightDecorator; } + set + { + _rightDecorator = value; + _rightDecoratorIsInInvalidMembersChars = + _rightDecorator.Length == 1 && StringExtensions.InvalidMemberChars.Contains(_rightDecorator[0]); + } + } + /// Gets or sets parameter name format. + public string ParameterFormat { get { return _parameterFormat; } set { _parameterFormat = value; } } + + /// Decorate string representing name of database object. + /// Name of database object. + /// Decorated name of database object. + public string DecorateName(string name) + { + return String.Concat(_leftDecorator, name, _rightDecorator); + } + /// Strip string representing name of database object from decorators. + /// Decorated name of database object. + /// Not decorated name of database object. + public string StripName(string name) + { + string res = name.Trim(StringExtensions.InvalidMemberChars); + + if (!_leftDecoratorIsInInvalidMembersChars && res.StartsWith(_leftDecorator)) + res = res.Substring(_leftDecorator.Length); + + if (!_rightDecoratorIsInInvalidMembersChars && res.EndsWith(_rightDecorator)) + res = res.Substring(0, res.Length - _rightDecorator.Length); + + return res; + } + /// Decorate string representing name of database object. + /// String builder to which add decorated name. + /// Name of database object. + public void DecorateName(StringBuilder sb, string name) + { + sb.Append(_leftDecorator); + sb.Append(name); + sb.Append(_rightDecorator); + } + /// Get database parameter name. + /// Friendly parameter name or number. + /// Formatted parameter name. + public string GetParameterName(object parameter) + { + return String.Format(_parameterFormat, parameter).Replace(" ", "_"); + } + /// Get database parameter name. + /// String builder to which add parameter name. + /// Friendly parameter name or number. + public void GetParameterName(StringBuilder sb, object parameter) + { + sb.AppendFormat(_parameterFormat, parameter.ToString().Replace(" ", "_")); + } + /// Dumps the command into console output. + /// The command to dump. + public virtual void DumpCommand(IDbCommand cmd) + { + if (DumpCommandDelegate != null) + DumpCommandDelegate(cmd, cmd.DumpToString()); + else + cmd.Dump(Console.Out); + } + #endregion Decorators + + #region Connection + + /// Open managed connection. + /// Opened connection. + public IDbConnection Open() + { + IDbConnection conn = null; + DynamicConnection ret = null; + bool opened = false; + + lock (SyncLock) + { + if (_tempConn == null) + { + if (TransactionPool.Count == 0 || !_singleConnection) + { + conn = _provider.CreateConnection(); + conn.ConnectionString = _connectionString; + conn.Open(); + opened = true; + + TransactionPool.Add(conn, new Stack()); + CommandsPool.Add(conn, new List()); + } + else + { + conn = TransactionPool.Keys.First(); + + if (conn.State != ConnectionState.Open) + { + conn.Open(); + opened = true; + } + } + ret = new DynamicConnection(this, conn, _singleTransaction); + } + else + ret = _tempConn; + } + if (opened) + ExecuteInitCommands(ret); + + return ret; + } + /// Close connection if we are allowed to. + /// Connection to manage. + internal void Close(IDbConnection connection) + { + if (connection == null) + return; + + if (!_singleConnection && connection != null && TransactionPool.ContainsKey(connection)) + { + // Close all commands + if (CommandsPool.ContainsKey(connection)) + { + List tmp = CommandsPool[connection].ToList(); + tmp.ForEach(cmd => cmd.Dispose()); + + CommandsPool[connection].Clear(); + } + // Rollback remaining transactions + while (TransactionPool[connection].Count > 0) + { + IDbTransaction trans = TransactionPool[connection].Pop(); + trans.Rollback(); + trans.Dispose(); + } + // Close connection + if (connection.State == ConnectionState.Open) + connection.Close(); + + // remove from pools + lock (SyncLock) + { + TransactionPool.Remove(connection); + CommandsPool.Remove(connection); + } + // Set stamp + _poolStamp = DateTime.Now.Ticks; + + // Dispose the corpse + connection.Dispose(); + connection = null; + } + } + /// Gets or sets contains commands executed when connection is opened. + public List InitCommands { get; set; } + + private void ExecuteInitCommands(IDbConnection conn) + { + if (InitCommands != null) + using (IDbCommand command = conn.CreateCommand()) + foreach (string commandText in InitCommands) + command + .SetCommand(commandText) + .ExecuteNonQuery(); + } + #endregion Connection + + #region Transaction + + /// Begins a global database transaction. + /// Using this method connection is set to single open + /// connection until all transactions are finished. + /// Returns representation. + public IDbTransaction BeginTransaction() + { + _tempConn = Open() as DynamicConnection; + + return _tempConn.BeginTransaction(null, null, () => + { + Stack t = TransactionPool.TryGetValue(_tempConn.Connection); + + if (t == null | t.Count == 0) + { + _tempConn.Dispose(); + _tempConn = null; + } + }); + } + /// Begins a database transaction with the specified + /// value. + /// One of the values. + /// Returns representation. + public IDbTransaction BeginTransaction(IsolationLevel il) + { + _tempConn = Open() as DynamicConnection; + + return _tempConn.BeginTransaction(il, null, () => + { + Stack t = TransactionPool.TryGetValue(_tempConn.Connection); + + if (t == null | t.Count == 0) + { + _tempConn.Dispose(); + _tempConn = null; + } + }); + } + /// Begins a database transaction with the specified + /// value. + /// Custom parameter describing transaction options. + /// Returns representation. + public IDbTransaction BeginTransaction(object custom) + { + _tempConn = Open() as DynamicConnection; + + return _tempConn.BeginTransaction(null, custom, () => + { + Stack t = TransactionPool.TryGetValue(_tempConn.Connection); + + if (t == null | t.Count == 0) + { + _tempConn.Dispose(); + _tempConn = null; + } + }); + } + #endregion Transaction + + #region IExtendedDisposable Members + + /// Performs application-defined tasks associated with freeing, + /// releasing, or resetting unmanaged resources. + public void Dispose() + { + if (IsDisposed) + return; + +#if !DYNAMORM_OMMIT_OLDSYNTAX + List tables = TablesCache.Values.ToList(); + TablesCache.Clear(); + + tables.ForEach(t => t.Dispose()); + tables.Clear(); + tables = null; +#endif + + foreach (KeyValuePair> con in TransactionPool) + { + // Close all commands + if (CommandsPool.ContainsKey(con.Key)) + { + List tmp = CommandsPool[con.Key].ToList(); + tmp.ForEach(cmd => cmd.Dispose()); + + CommandsPool[con.Key].Clear(); + tmp.Clear(); + CommandsPool[con.Key] = tmp = null; + } + // Rollback remaining transactions + while (con.Value.Count > 0) + { + IDbTransaction trans = con.Value.Pop(); + trans.Rollback(); + trans.Dispose(); + } + // Close connection + if (con.Key.State == ConnectionState.Open) + con.Key.Close(); + + // Dispose it + con.Key.Dispose(); + } + while (RemainingBuilders.Count > 0) + RemainingBuilders.First().Dispose(); + + // Clear pools + lock (SyncLock) + { + TransactionPool.Clear(); + CommandsPool.Clear(); + RemainingBuilders.Clear(); + + TransactionPool = null; + CommandsPool = null; + RemainingBuilders = null; + } + ClearSchema(); + if (_proc != null) + _proc.Dispose(); + + _proc = null; + _tempConn = null; + IsDisposed = true; + } + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + #endregion IExtendedDisposable Members + } + /// Represents database connection options. + [Flags] + [System.Reflection.ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)] + public enum DynamicDatabaseOptions + { + /// No specific options. + None = 0x00000000, + + /// Only single persistent database connection. + SingleConnection = 0x00000001, + + /// Only one transaction. + SingleTransaction = 0x00000002, + + /// Database supports top syntax (SELECT TOP x ... FROM ...). + SupportTop = 0x00000080, + + /// Database supports limit offset syntax (SELECT ... FROM ... LIMIT x OFFSET y). + SupportLimitOffset = 0x00000040, + + /// Database supports limit offset syntax (SELECT FIRST x SKIP y ... FROM ...). + SupportFirstSkip = 0x00000020, + + /// Database support standard schema. + SupportSchema = 0x00000010, + + /// Database support stored procedures (EXEC procedure ...). + SupportStoredProcedures = 0x00000100, + + /// Database support stored procedures results (Return value). + SupportStoredProceduresResult = 0x00000200, + + /// Database support with no lock syntax. + SupportNoLock = 0x00001000, + + /// Debug option allowing to enable command dumps by default. + DumpCommands = 0x01000000, + } + /// Dynamic expando is a simple and temporary class to resolve memory leaks inside ExpandoObject. + public class DynamicExpando : DynamicObject, IDictionary, ICollection>, IEnumerable>, IEnumerable + { + /// Class containing information about last accessed property of dynamic object. + public class PropertyAccess + { + /// Enum describing type of access to object. + public enum TypeOfAccess + { + /// Get member. + Get, + + /// Set member. + Set, + } + /// Gets the type of operation. + public TypeOfAccess Operation { get; internal set; } + + /// Gets the name of property. + public string Name { get; internal set; } + + /// Gets the type from binder. + public Type RequestedType { get; internal set; } + + /// Gets the type of value stored in object. + public Type Type { get; internal set; } + + /// Gets the value stored in object. + public object Value { get; internal set; } + + /// Gets the last access time. + public long Ticks { get; internal set; } + } + private Dictionary _data = new Dictionary(); + + private PropertyAccess _lastProp = new PropertyAccess(); + + /// Initializes a new instance of the class. + public DynamicExpando() + { + } + /// Gets the last accesses property. + /// Description of last accessed property. + public PropertyAccess GetLastAccessesProperty() + { + return _lastProp; + } + /// Tries to get member value. + /// Returns true, if get member was tried, false otherwise. + /// The context binder. + /// The invocation result. + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + result = _data.TryGetValue(binder.Name); + + _lastProp.Operation = PropertyAccess.TypeOfAccess.Get; + _lastProp.RequestedType = binder.ReturnType; + _lastProp.Name = binder.Name; + _lastProp.Value = result; + _lastProp.Type = result == null ? typeof(void) : result.GetType(); + _lastProp.Ticks = DateTime.Now.Ticks; + + return true; + } + /// Tries to set member. + /// Returns true, if set member was tried, false otherwise. + /// The context binder. + /// Value which will be set. + public override bool TrySetMember(SetMemberBinder binder, object value) + { + _data[binder.Name] = value; + + _lastProp.Operation = PropertyAccess.TypeOfAccess.Set; + _lastProp.RequestedType = binder.ReturnType; + _lastProp.Name = binder.Name; + _lastProp.Value = value; + _lastProp.Type = value == null ? typeof(void) : value.GetType(); + _lastProp.Ticks = DateTime.Now.Ticks; + + return true; + } + #region IDictionary implementation + + bool IDictionary.ContainsKey(string key) + { + return _data.ContainsKey(key); + } + void IDictionary.Add(string key, object value) + { + _data.Add(key, value); + } + bool IDictionary.Remove(string key) + { + return _data.Remove(key); + } + bool IDictionary.TryGetValue(string key, out object value) + { + return _data.TryGetValue(key, out value); + } + object IDictionary.this[string index] { get { return _data[index]; } set { _data[index] = value; } } + + ICollection IDictionary.Keys { get { return _data.Keys; } } + + ICollection IDictionary.Values { get { return _data.Values; } } + + #endregion IDictionary implementation + + #region ICollection implementation + + void ICollection>.Add(KeyValuePair item) + { + ((ICollection>)_data).Add(item); + } + void ICollection>.Clear() + { + _data.Clear(); + } + bool ICollection>.Contains(KeyValuePair item) + { + return ((ICollection>)_data).Contains(item); + } + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((ICollection>)_data).CopyTo(array, arrayIndex); + } + bool ICollection>.Remove(KeyValuePair item) + { + return ((ICollection>)_data).Remove(item); + } + int ICollection>.Count { get { return _data.Count; } } + + bool ICollection>.IsReadOnly { get { return ((ICollection>)_data).IsReadOnly; } } + + #endregion ICollection implementation + + #region IEnumerable implementation + + IEnumerator> IEnumerable>.GetEnumerator() + { + return _data.GetEnumerator(); + } + #endregion IEnumerable implementation + + #region IEnumerable implementation + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_data).GetEnumerator(); + } + #endregion IEnumerable implementation + } + /// Extension to ORM objects. + public static class DynamicExtensions + { + #region Type column map + + /// MapEnumerable of .NET types to . + public static readonly Dictionary TypeMap = new Dictionary() + { + { typeof(byte), DbType.Byte }, + { typeof(sbyte), DbType.SByte }, + { typeof(short), DbType.Int16 }, + { typeof(ushort), DbType.UInt16 }, + { typeof(int), DbType.Int32 }, + { typeof(uint), DbType.UInt32 }, + { typeof(long), DbType.Int64 }, + { typeof(ulong), DbType.UInt64 }, + { typeof(float), DbType.Single }, + { typeof(double), DbType.Double }, + { typeof(decimal), DbType.Decimal }, + { typeof(bool), DbType.Boolean }, + { typeof(string), DbType.String }, + { typeof(char), DbType.StringFixedLength }, + { typeof(Guid), DbType.Guid }, + { typeof(DateTime), DbType.DateTime }, + { typeof(TimeSpan), DbType.Time }, + { typeof(DateTimeOffset), DbType.DateTimeOffset }, + { typeof(byte[]), DbType.Binary }, + { typeof(byte?), DbType.Byte }, + { typeof(sbyte?), DbType.SByte }, + { typeof(short?), DbType.Int16 }, + { typeof(ushort?), DbType.UInt16 }, + { typeof(int?), DbType.Int32 }, + { typeof(uint?), DbType.UInt32 }, + { typeof(long?), DbType.Int64 }, + { typeof(ulong?), DbType.UInt64 }, + { typeof(float?), DbType.Single }, + { typeof(double?), DbType.Double }, + { typeof(decimal?), DbType.Decimal }, + { typeof(bool?), DbType.Boolean }, + { typeof(char?), DbType.StringFixedLength }, + { typeof(Guid?), DbType.Guid }, + { typeof(DateTime?), DbType.DateTime }, + { typeof(TimeSpan?), DbType.Time }, + { typeof(DateTimeOffset?), DbType.DateTimeOffset }, + }; + + #endregion Type column map + + #region Command extensions + + /// Set connection on the fly. + /// in which changes will be made. + /// which will be set to instance. + /// Returns edited instance. + public static IDbCommand SetConnection(this IDbCommand command, IDbConnection connection) + { + command.Connection = connection; + + return command; + } + /// Set connection on the fly. + /// in which changes will be made. + /// which will be set to instance. + /// Returns edited instance. + public static IDbCommand SetTransaction(this IDbCommand command, IDbTransaction transaction) + { + command.Transaction = transaction; + + return command; + } + #region SetCommand + + /// Set properties on the fly. + /// in which changes will be made. + /// Indicates or specifies how the property is interpreted. + /// The wait time before terminating the attempt to execute a command and generating an error. + /// The text command to run against the data source. + /// Arguments used to format command. + /// Returns edited instance. + public static IDbCommand SetCommand(this IDbCommand command, CommandType commandType, int commandTimeout, string commandText, params object[] args) + { + command.CommandType = commandType; + command.CommandTimeout = commandTimeout; + + if (args != null && args.Length > 0) + command.CommandText = string.Format(commandText, args); + else + command.CommandText = commandText; + + return command; + } + /// Set properties on the fly. + /// in which changes will be made. + /// The wait time before terminating the attempt to execute a command and generating an error. + /// The text command to run against the data source. + /// Arguments used to format command. + /// Returns edited instance. + public static IDbCommand SetCommand(this IDbCommand command, int commandTimeout, string commandText, params object[] args) + { + command.CommandTimeout = commandTimeout; + + if (args != null && args.Length > 0) + command.CommandText = string.Format(commandText, args); + else + command.CommandText = commandText; + + return command; + } + /// Set properties on the fly. + /// in which changes will be made. + /// Indicates or specifies how the property is interpreted. + /// The text command to run against the data source. + /// Arguments used to format command. + /// Returns edited instance. + public static IDbCommand SetCommand(this IDbCommand command, CommandType commandType, string commandText, params object[] args) + { + command.CommandType = commandType; + + if (args != null && args.Length > 0) + command.CommandText = string.Format(commandText, args); + else + command.CommandText = commandText; + + return command; + } + /// Set properties on the fly. + /// in which changes will be made. + /// The text command to run against the data source. + /// Arguments used to format command. + /// Returns edited instance. + public static IDbCommand SetCommand(this IDbCommand command, string commandText, params object[] args) + { + if (args != null && args.Length > 0) + command.CommandText = string.Format(commandText, args); + else + command.CommandText = commandText; + + return command; + } + /// Set properties on the fly. + /// in which changes will be made. + /// Command builder. + /// Returns edited instance. + public static IDbCommand SetCommand(this IDbCommand command, IDynamicQueryBuilder builder) + { + builder.FillCommand(command); + + return command; + } + #endregion SetCommand + + #region AddParameter + + /// Extension method for adding in a bunch of parameters. + /// Command to handle. + /// Database object required to get proper formatting. + /// Items to add. + /// Returns edited instance. + public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, params object[] args) + { + if (args != null && args.Count() > 0) + foreach (object item in args) + { + if (item is DynamicExpando) + cmd.AddParameters(database, (DynamicExpando)item); + else if (item is ExpandoObject) + cmd.AddParameters(database, (ExpandoObject)item); + else + cmd.AddParameter(database, item); + } + return cmd; + } + /// Extension method for adding in a bunch of parameters. + /// Command to handle. + /// Database object required to get proper formatting. + /// Items to add in an expando object. + /// Returns edited instance. + public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, ExpandoObject args) + { + if (args != null && args.Count() > 0) + foreach (KeyValuePair item in args.ToDictionary()) + cmd.AddParameter(database, item.Key, item.Value); + + return cmd; + } + /// Extension method for adding in a bunch of parameters. + /// Command to handle. + /// Database object required to get proper formatting. + /// Items to add in an expando object. + /// Returns edited instance. + public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, DynamicExpando args) + { + if (args != null && args.Count() > 0) + foreach (KeyValuePair item in args.ToDictionary()) + cmd.AddParameter(database, item.Key, item.Value); + + return cmd; + } + /// Extension for adding single parameter determining only type of object. + /// Command to handle. + /// Database object required to get proper formatting. + /// Items to add. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand cmd, DynamicDatabase database, object item) + { + return cmd.AddParameter(database, database.GetParameterName(cmd.Parameters.Count), item); + } + /// Extension for adding single parameter determining only type of object. + /// Command to handle. + /// Database object required to get proper formatting. + /// Name of parameter. + /// Items to add. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand cmd, DynamicDatabase database, string name, object item) + { + IDbDataParameter p = cmd.CreateParameter(); + p.ParameterName = name; + + if (item == null || item == DBNull.Value) + p.Value = DBNull.Value; + else + { + Type type = item.GetType(); + + p.DbType = TypeMap.TryGetNullable(type) ?? DbType.String; + + if (type == typeof(DynamicExpando) || type == typeof(ExpandoObject)) + p.Value = CorrectValue(p.DbType, ((IDictionary)item).Values.FirstOrDefault()); + else + p.Value = CorrectValue(p.DbType, item); + + if (p.DbType == DbType.String) + p.Size = item.ToString().Length > 4000 ? -1 : 4000; + else if (p.DbType == DbType.AnsiString) + p.Size = item.ToString().Length > 8000 ? -1 : 8000; + } + cmd.Parameters.Add(p); + + return cmd; + } + /// Extension for adding single parameter determining only type of object. + /// Command to handle. + /// Query builder containing schema. + /// Column schema to use. + /// Parameter value. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand cmd, IDynamicQueryBuilder builder, DynamicSchemaColumn? col, object value) + { + IDbDataParameter p = cmd.CreateParameter(); + p.ParameterName = builder.Database.GetParameterName(cmd.Parameters.Count); + + if (col.HasValue) + { + p.DbType = col.Value.Type; + + if ((builder.Database.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema) + { + p.Size = col.Value.Size; + p.Precision = col.Value.Precision; + p.Scale = col.Value.Scale; + + // Quick fix - review that + // Quick fix 2 - use item.Schema in that case + if (p.Scale > p.Precision) + p.Scale = 4; + } + p.Value = CorrectValue(p.DbType, value); + } + else if (value == null || value == DBNull.Value) + p.Value = DBNull.Value; + else + { + p.DbType = TypeMap.TryGetNullable(value.GetType()) ?? DbType.String; + + if (p.DbType == DbType.AnsiString) + p.Size = value.ToString().Length > 8000 ? -1 : 8000; + else if (p.DbType == DbType.String) + p.Size = value.ToString().Length > 4000 ? -1 : 4000; + + p.Value = CorrectValue(p.DbType, value); + } + cmd.Parameters.Add(p); + + return cmd; + } + private static object CorrectValue(DbType type, object value) + { + if (value == null || value == DBNull.Value) + return DBNull.Value; + + if ((type == DbType.String || type == DbType.AnsiString || type == DbType.StringFixedLength || type == DbType.AnsiStringFixedLength) && + !(value is string)) + return value.ToString(); + else if (type == DbType.Guid && value is string) + return Guid.Parse(value.ToString()); + else if (type == DbType.Guid && value is byte[] && ((byte[])value).Length == 16) + return new Guid((byte[])value); + else if (type == DbType.DateTime && value is TimeSpan) // HACK: This is specific for SQL Server, to be verified with other databases + return DateTime.Today.Add((TimeSpan)value); + + return value; + } + /// Extension for adding single parameter determining only type of object. + /// Command to handle. + /// Query builder containing schema. + /// Column item to add. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand cmd, IDynamicQueryBuilder builder, DynamicColumn item) + { + IDbDataParameter p = cmd.CreateParameter(); + p.ParameterName = builder.Database.GetParameterName(cmd.Parameters.Count); + + DynamicSchemaColumn? col = item.Schema ?? (builder as DynamicQueryBuilder) + .NullOr(b => b.GetColumnFromSchema(item.ColumnName), + builder.Tables.FirstOrDefault() + .NullOr(t => t.Schema + .NullOr(s => s.TryGetNullable(item.ColumnName.ToLower()), null), null)); + + if (col.HasValue) + { + p.DbType = col.Value.Type; + + if (builder.SupportSchema) + { + p.Size = col.Value.Size; + p.Precision = col.Value.Precision; + p.Scale = col.Value.Scale; + + // Quick fix - review that + // Quick fix 2 - use item.Schema in that case + if (p.Scale > p.Precision) + p.Scale = 4; + } + p.Value = item.Value == null ? DBNull.Value : CorrectValue(p.DbType, item.Value); + } + else if (item.Value == null || item.Value == DBNull.Value) + p.Value = DBNull.Value; + else + { + p.DbType = TypeMap.TryGetNullable(item.Value.GetType()) ?? DbType.String; + + if (p.DbType == DbType.AnsiString) + p.Size = item.Value.ToString().Length > 8000 ? -1 : 8000; + else if (p.DbType == DbType.String) + p.Size = item.Value.ToString().Length > 4000 ? -1 : 4000; + + p.Value = CorrectValue(p.DbType, item.Value); + } + cmd.Parameters.Add(p); + + return cmd; + } + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . + /// The of the . + /// The size of the parameter. + /// Indicates the precision of numeric parameters. + /// Indicates the scale of numeric parameters. + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, byte precision, byte scale, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.Direction = parameterDirection; + param.DbType = databaseType; + param.Size = size; + param.Precision = precision; + param.Scale = scale; + param.Value = CorrectValue(param.DbType, value); + command.Parameters.Add(param); + + return command; + } + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . + /// The of the . + /// The size of the parameter. + /// Indicates the precision of numeric parameters. + /// Indicates the scale of numeric parameters. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, byte precision, byte scale) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.Direction = parameterDirection; + param.DbType = databaseType; + param.Size = size; + param.Precision = precision; + param.Scale = scale; + command.Parameters.Add(param); + + return command; + } + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . + /// The of the . + /// Indicates the precision of numeric parameters. + /// Indicates the scale of numeric parameters. + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, byte precision, byte scale, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.Direction = parameterDirection; + param.DbType = databaseType; + param.Precision = precision; + param.Scale = scale; + param.Value = CorrectValue(param.DbType, value); + command.Parameters.Add(param); + + return command; + } + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// Indicates the precision of numeric parameters. + /// Indicates the scale of numeric parameters. + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, byte precision, byte scale, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + param.Precision = precision; + param.Scale = scale; + param.Value = CorrectValue(param.DbType, value); + command.Parameters.Add(param); + + return command; + } + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// Indicates the precision of numeric parameters. + /// Indicates the scale of numeric parameters. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, byte precision, byte scale) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + param.Precision = precision; + param.Scale = scale; + command.Parameters.Add(param); + + return command; + } + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . + /// The of the . + /// The size of the parameter. + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.Direction = parameterDirection; + param.DbType = databaseType; + param.Size = size; + param.Value = CorrectValue(param.DbType, value ?? DBNull.Value); + command.Parameters.Add(param); + + return command; + } + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// The size of the parameter. + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, int size, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + param.Size = size; + param.Value = CorrectValue(param.DbType, value ?? DBNull.Value); + command.Parameters.Add(param); + + return command; + } + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// The value of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, object value) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + param.Value = CorrectValue(param.DbType, value ?? DBNull.Value); + command.Parameters.Add(param); + + return command; + } + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// The size of the parameter. + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, int size) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + param.Size = size; + command.Parameters.Add(param); + + return command; + } + /// Add to on the fly. + /// to which parameter will be added. + /// The name of the . + /// The of the . + /// Returns edited instance. + public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType) + { + IDbDataParameter param = command.CreateParameter(); + param.ParameterName = parameterName; + param.DbType = databaseType; + command.Parameters.Add(param); + + return command; + } + #endregion AddParameter + + #region SetParameter + + /// Set value for on the fly. + /// to which parameter will be added. + /// The name of the . + /// Value to set on this parameter. + /// Returns edited instance. + public static IDbCommand SetParameter(this IDbCommand command, string parameterName, object value) + { + try + { + var p = ((IDbDataParameter)command.Parameters[parameterName]); + p.Value = CorrectValue(p.DbType, value); + } + catch (Exception ex) + { + throw new ArgumentException(string.Format("Error setting parameter {0} in command {1}", parameterName, command.CommandText ?? string.Empty), ex); + } + return command; + } + /// Set value for on the fly. + /// to which parameter will be added. + /// Index of the . + /// Value to set on this parameter. + /// Returns edited instance. + public static IDbCommand SetParameter(this IDbCommand command, int index, object value) + { + try + { + var p = ((IDbDataParameter)command.Parameters[index]); + p.Value = CorrectValue(p.DbType, value); + } + catch (Exception ex) + { + throw new ArgumentException(string.Format("Error setting parameter {0} in command {1}", index, command.CommandText ?? string.Empty), ex); + } + return command; + } + #endregion SetParameter + + #region Generic Execution + +#if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE + + /// Execute scalar and return string if possible. + /// Type to parse to. + /// which will be executed. + /// Returns resulting instance of T from query. + public static T ExecuteScalarAs(this IDbCommand command) + { + return ExecuteScalarAs(command, default(T), null); + } + /// Execute scalar and return string if possible. + /// Type to parse to. + /// which will be executed. + /// Handler of a try parse method. + /// Returns resulting instance of T from query. + public static T ExecuteScalarAs(this IDbCommand command, DynamicExtensions.TryParseHandler handler) + { + return ExecuteScalarAs(command, default(T), handler); + } + /// Execute scalar and return string if possible. + /// Type to parse to. + /// which will be executed. + /// Default result value. + /// Returns resulting instance of T from query. + public static T ExecuteScalarAs(this IDbCommand command, T defaultValue) + { + return ExecuteScalarAs(command, defaultValue, null); + } + /// Execute scalar and return string if possible. + /// Type to parse to. + /// which will be executed. + /// Default result value. + /// Handler of a try parse method. + /// Returns resulting instance of T from query. + public static T ExecuteScalarAs(this IDbCommand command, T defaultValue, DynamicExtensions.TryParseHandler handler) + { + T ret = defaultValue; + + object o = command.ExecuteScalar(); + + if (o is T) + return (T)o; + else if (o != DBNull.Value && o != null) + { + if (handler != null) + ret = o.ToString().TryParseDefault(defaultValue, handler); + else if (o is IConvertible && typeof(T).GetInterfaces().Any(i => i == typeof(IConvertible))) + ret = (T)Convert.ChangeType(o, typeof(T)); + else if (typeof(T) == typeof(Guid)) + { + if (o.GetType() == typeof(byte[])) + ret = (T)(object)new Guid((byte[])o); + else + ret = (T)(object)Guid.Parse(o.ToString()); + } + else if (typeof(T) == typeof(string)) + ret = (T)(o.ToString() as object); + else if (typeof(T) == typeof(object)) + ret = (T)o; + else + { + MethodInfo method = typeof(T).GetMethod( + "TryParse", + new Type[] + { + typeof(string), + Type.GetType(string.Format("{0}&", typeof(T).FullName)) + }); + + if (method != null) + ret = o.ToString().TryParseDefault(defaultValue, delegate(string v, out T r) + { + r = defaultValue; + return (bool)method.Invoke(null, new object[] { v, r }); + }); + else + throw new InvalidOperationException("Provided type can't be parsed using generic approach."); + } + } + return ret; + } + /// Execute enumerator of specified type. + /// Type to parse to. + /// which will be executed. + /// Default result value. + /// Handler of a try parse method. + /// Returns enumerator of specified type from query. + public static IEnumerable ExecuteEnumeratorOf(this IDbCommand command, T defaultValue, DynamicExtensions.TryParseHandler handler) where T : struct + { + using (IDataReader reader = command.ExecuteReader()) + { + MethodInfo method = typeof(T).GetMethod( + "TryParse", + new[] + { + typeof(string), + Type.GetType(string.Format("{0}&", typeof(T).FullName)) + }); + + while (reader.Read()) + { + T ret = defaultValue; + + if (!reader.IsDBNull(0)) + { + object o = reader.GetValue(0); + + if (o is T) + ret = (T)o; + else if (o != DBNull.Value) + { + if (handler != null) + ret = o.ToString().TryParseDefault(defaultValue, handler); + else if (o is IConvertible && typeof(T).GetInterfaces().Any(i => i == typeof(IConvertible))) + ret = (T)Convert.ChangeType(o, typeof(T)); + else if (typeof(T) == typeof(Guid)) + { + if (o.GetType() == typeof(byte[])) + ret = (T)(object)new Guid((byte[])o); + else + ret = (T)(object)Guid.Parse(o.ToString()); + } + else if (typeof(T) == typeof(string)) + ret = (T)(o.ToString() as object); + else if (typeof(T) == typeof(object)) + ret = (T)o; + else if (method != null) + ret = o.ToString().TryParseDefault(defaultValue, delegate(string v, out T r) + { + r = defaultValue; + return (bool)method.Invoke(null, new object[] { v, r }); + }); + else + throw new InvalidOperationException("Provided type can't be parsed using generic approach."); + } + } + yield return ret; + } + } + } +#endif + + #endregion Generic Execution + + /// Dump command into string. + /// Command to dump. + /// Returns dumped instance in string form. + public static string DumpToString(this IDbCommand command) + { + StringBuilder sb = new StringBuilder(); + command.Dump(sb); + return sb.ToString(); + } + /// Dump command into text writer. + /// Command to dump. + /// Builder to which write output. + /// Returns dumped instance. + public static IDbCommand Dump(this IDbCommand command, StringBuilder buider) + { + using (StringWriter sw = new StringWriter(buider)) + return command.Dump(sw); + } + /// Dump command into text writer. + /// Command to dump. + /// Writer to which write output. + /// Returns dumped instance. + public static IDbCommand Dump(this IDbCommand command, TextWriter writer) + { + try + { + writer.WriteLine("Type: {0}; Timeout: {1}; Query: {2}", command.CommandType, command.CommandTimeout, command.CommandText); + + if (command.Parameters.Count > 0) + { + writer.WriteLine("Parameters:"); + + foreach (IDbDataParameter param in command.Parameters) + { + writer.WriteLine(" '{0}' ({1} (s:{2} p:{3} s:{4})) = '{5}' ({6});", + param.ParameterName, + param.DbType, + param.Scale, + param.Precision, + param.Scale, + param.Value is byte[] ? ConvertByteArrayToHexString((byte[])param.Value) : param.Value ?? "NULL", + param.Value != null ? param.Value.GetType().Name : "DBNull"); + } + writer.WriteLine(); + } + } + catch (NullReferenceException) + { + writer.WriteLine("Command disposed."); + } + return command; + } + /// Convert byte array to hex formatted string without separators. + /// Byte Array Data. + /// Hex string representation of byte array. + private static string ConvertByteArrayToHexString(byte[] data) + { + return ConvertByteArrayToHexString(data, 0); + } + /// Convert byte array to hex formatted string. + /// Byte Array Data. + /// Put '-' each separatorEach characters. + /// Hex string representation of byte array. + private static string ConvertByteArrayToHexString(byte[] data, int separatorEach) + { + int len = data.Length * 2; + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + for (int i = 0; i < len; i++) + { + sb.AppendFormat("{0:X}", data[(i / 2)] >> (((i % 2) == 0) ? 4 : 0) & 0x0F); + if ((separatorEach > 0) && ((i + 1) % separatorEach == 0)) + sb.AppendFormat("-"); + } + return sb.ToString(); + } + #endregion Command extensions + + #region Dynamic builders extensions + + /// Turns an to a Dynamic list of things. + /// Ready to execute builder. + /// List of things. + public static List ToList(this IDynamicSelectQueryBuilder b) + { + return b.Execute().ToList(); + } + /// Turns an to a Dynamic list of things with specified type. + /// Type of object to map on. + /// Ready to execute builder. + /// List of things. + public static List ToList(this IDynamicSelectQueryBuilder b) where T : class + { + return b.Execute().ToList(); + } + /// Sets the on create temporary parameter action. + /// Class implementing interface. + /// The builder on which set delegate. + /// Action to invoke. + /// Returns instance of builder on which action is set. + public static T CreateTemporaryParameterAction(this T b, Action a) where T : IDynamicQueryBuilder + { + if (a != null) + { + if (b.OnCreateTemporaryParameter == null) + b.OnCreateTemporaryParameter = new List>(); + + b.OnCreateTemporaryParameter.Add(a); + } + return b; + } + /// Sets the on create real parameter action. + /// Class implementing interface. + /// The builder on which set delegate. + /// Action to invoke. + /// Returns instance of builder on which action is set. + public static T CreateParameterAction(this T b, Action a) where T : IDynamicQueryBuilder + { + if (a != null) + { + if (b.OnCreateParameter == null) + b.OnCreateParameter = new List>(); + + b.OnCreateParameter.Add(a); + } + return b; + } + /// Sets the virtual mode on builder. + /// Class implementing interface. + /// The builder on which set virtual mode. + /// Virtual mode. + /// Returns instance of builder on which virtual mode is set. + public static T SetVirtualMode(this T b, bool virtualMode) where T : IDynamicQueryBuilder + { + b.VirtualMode = virtualMode; + return b; + } + /// Creates sub query that can be used inside of from/join/expressions. + /// Class implementing interface. + /// The builder that will be parent of new sub query. + /// Instance of sub query. + public static IDynamicSelectQueryBuilder SubQuery(this T b) where T : IDynamicQueryBuilder + { + return new DynamicSelectQueryBuilder(b.Database, b as DynamicQueryBuilder); + } + /// Creates sub query that can be used inside of from/join/expressions. + /// Class implementing interface. + /// The builder that will be parent of new sub query. + /// The specification for sub query. + /// The specification for sub query. + /// Instance of sub query. + public static IDynamicSelectQueryBuilder SubQuery(this T b, Func fn, params Func[] func) where T : IDynamicQueryBuilder + { + return new DynamicSelectQueryBuilder(b.Database, b as DynamicQueryBuilder).From(fn, func); + } + /// Creates sub query that can be used inside of from/join/expressions. + /// Class implementing interface. + /// The builder that will be parent of new sub query. + /// First argument is parent query, second one is a sub query. + /// This instance to permit chaining. + public static T SubQuery(this T b, Action subquery) where T : IDynamicQueryBuilder + { + IDynamicSelectQueryBuilder sub = b.SubQuery(); + + subquery(b, sub); + try + { + (b as DynamicQueryBuilder).ParseCommand(sub as DynamicQueryBuilder, b.Parameters); + } + catch (ArgumentException) + { + // This might occur if join was made to subquery + } + return b; + } + /// Creates sub query that can be used inside of from/join/expressions. + /// Class implementing interface. + /// The builder that will be parent of new sub query. + /// First argument is parent query, second one is a sub query. + /// The specification for sub query. + /// The specification for sub query. + /// This instance to permit chaining. + public static T SubQuery(this T b, Action subquery, Func fn, params Func[] func) where T : IDynamicQueryBuilder + { + IDynamicSelectQueryBuilder sub = b.SubQuery(fn, func); + + subquery(b, sub); + + (b as DynamicQueryBuilder).ParseCommand(sub as DynamicQueryBuilder, b.Parameters); + + return b; + } + #endregion Dynamic builders extensions + + #region Dynamic extensions + + /// Turns an to a Dynamic list of things. + /// Reader from which read data. + /// List of things. + public static List ToList(this IDataReader r) + { + List result = new List(); + + while (r.Read()) + result.Add(r.RowToDynamic()); + + return result; + } + /// Turns the dictionary into an ExpandoObject. + /// Dictionary to convert. + /// Converted dictionary. + public static dynamic ToDynamic(this IDictionary d) + { + DynamicExpando result = new DynamicExpando(); + IDictionary dict = (IDictionary)result; + + foreach (KeyValuePair prop in d) + dict.Add(prop.Key, prop.Value); + + return result; + } + /// Turns the dictionary into an ExpandoObject. + /// Dictionary to convert. + /// Converted dictionary. + public static dynamic ToExpando(this IDictionary d) + { + ExpandoObject result = new ExpandoObject(); + IDictionary dict = (IDictionary)result; + + foreach (KeyValuePair prop in d) + dict.Add(prop.Key, prop.Value); + + return result; + } + /// Turns the object into an ExpandoObject. + /// Object to convert. + /// Converted object. + public static dynamic ToDynamic(this object o) + { + Type ot = o.GetType(); + + if (ot == typeof(DynamicExpando) || ot == typeof(ExpandoObject)) + return o; + + DynamicExpando result = new DynamicExpando(); + IDictionary dict = (IDictionary)result; + + if (o is IDictionary) + ((IDictionary)o) + .ToList() + .ForEach(kvp => dict.Add(kvp.Key, kvp.Value)); + else if (ot == typeof(NameValueCollection) || ot.IsSubclassOf(typeof(NameValueCollection))) + { + NameValueCollection nameValue = (NameValueCollection)o; + nameValue.Cast() + .Select(key => new KeyValuePair(key, nameValue[key])) + .ToList() + .ForEach(i => dict.Add(i)); + } + else + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(ot); + + if (mapper != null) + { + foreach (DynamicPropertyInvoker item in mapper.ColumnsMap.Values) + if (item.Get != null) + dict.Add(item.Name, item.Get(o)); + } + else + { + PropertyInfo[] props = ot.GetProperties(); + + foreach (PropertyInfo item in props) + if (item.CanRead) + dict.Add(item.Name, item.GetValue(o, null)); + } + } + return result; + } + /// Turns the object into an ExpandoObject. + /// Object to convert. + /// Converted object. + public static dynamic ToExpando(this object o) + { + Type ot = o.GetType(); + + if (ot == typeof(ExpandoObject) || ot == typeof(DynamicExpando)) + return o; + + ExpandoObject result = new ExpandoObject(); + IDictionary dict = (IDictionary)result; + + if (o is IDictionary) + ((IDictionary)o) + .ToList() + .ForEach(kvp => dict.Add(kvp.Key, kvp.Value)); + else if (ot == typeof(NameValueCollection) || ot.IsSubclassOf(typeof(NameValueCollection))) + { + NameValueCollection nameValue = (NameValueCollection)o; + nameValue.Cast() + .Select(key => new KeyValuePair(key, nameValue[key])) + .ToList() + .ForEach(i => dict.Add(i)); + } + else + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(ot); + + if (mapper != null) + { + foreach (DynamicPropertyInvoker item in mapper.ColumnsMap.Values) + if (item.Get != null) + dict.Add(item.Name, item.Get(o)); + } + else + { + PropertyInfo[] props = ot.GetProperties(); + + foreach (PropertyInfo item in props) + if (item.CanRead) + dict.Add(item.Name, item.GetValue(o, null)); + } + } + return result; + } + /// Convert data row row into dynamic object. + /// DataRow from which read. + /// Generated dynamic object. + public static dynamic RowToDynamic(this DataRow r) + { + dynamic e = new DynamicExpando(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + ((IDictionary)e).Add(r.Table.Columns[i].ColumnName, r.IsNull(i) ? null : r[i]); + + return e; + } + /// Convert data row row into dynamic object. + /// DataRow from which read. + /// Generated dynamic object. + public static dynamic RowToExpando(this DataRow r) + { + dynamic e = new ExpandoObject(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + ((IDictionary)e).Add(r.Table.Columns[i].ColumnName, r.IsNull(i) ? null : r[i]); + + return e; + } + /// Convert data row row into dynamic object (upper case key). + /// DataRow from which read. + /// Generated dynamic object. + public static dynamic RowToDynamicUpper(this DataRow r) + { + dynamic e = new DynamicExpando(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + ((IDictionary)e).Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); + + return e; + } + /// Convert data row row into dynamic object (upper case key). + /// DataRow from which read. + /// Generated dynamic object. + public static dynamic RowToExpandoUpper(this DataRow r) + { + // ERROR: Memory leak + dynamic e = new ExpandoObject(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + ((IDictionary)e).Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); + + return e; + } + internal static Dictionary RowToDynamicUpperDict(this DataRow r) + { + dynamic e = new Dictionary(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + e.Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); + + return e; + } + /// Convert reader row into dynamic object. + /// Reader from which read. + /// Generated dynamic object. + public static dynamic RowToDynamic(this IDataReader r) + { + dynamic e = new DynamicExpando(); + IDictionary d = e as IDictionary; + + int c = r.FieldCount; + for (int i = 0; i < c; i++) + try + { + d.Add(r.GetName(i), r.IsDBNull(i) ? null : r[i]); + } + catch (ArgumentException argex) + { + throw new ArgumentException( + string.Format("Field '{0}' is defined more than once in a query.", r.GetName(i)), "Column name or alias", argex); + } + return e; + } + /// Convert reader row into dynamic object. + /// Reader from which read. + /// Generated dynamic object. + public static dynamic RowToExpando(this IDataReader r) + { + dynamic e = new ExpandoObject(); + IDictionary d = e as IDictionary; + + int c = r.FieldCount; + for (int i = 0; i < c; i++) + try + { + d.Add(r.GetName(i), r.IsDBNull(i) ? null : r[i]); + } + catch (ArgumentException argex) + { + throw new ArgumentException( + string.Format("Field '{0}' is defined more than once in a query.", r.GetName(i)), "Column name or alias", argex); + } + return e; + } + /// Turns the object into a Dictionary. + /// Object to convert. + /// Resulting dictionary. + public static IDictionary ToDictionary(this ExpandoObject o) + { + return (IDictionary)o; + } + /// Turns the object into a Dictionary. + /// Object to convert. + /// Resulting dictionary. + public static IDictionary ToDictionary(this DynamicExpando o) + { + return (IDictionary)o; + } + /// Turns the object into a Dictionary. + /// Object to convert. + /// Resulting dictionary. + public static IDictionary ToDictionary(this object o) + { + return o is IDictionary ? + (IDictionary)o : + (IDictionary)o.ToDynamic(); + } + #endregion Dynamic extensions + + #region Type extensions + + /// Check if type is anonymous. + /// Type to test. + /// Returns true if type is anonymous. + public static bool IsAnonymous(this Type type) + { + // HACK: The only way to detect anonymous types right now. + return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false) + && type.IsGenericType + && (type.Name.Contains("AnonymousType") || type.Name.Contains("AnonType")) + && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) + && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; + } + /// Check if type implements IEnumerable<> interface. + /// Type to check. + /// Returns true if it does. + public static bool IsGenericEnumerable(this Type type) + { + return type.IsGenericType && type.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + } + /// Check if type implements IEnumerable<> interface. + /// Type to check. + /// Returns true if it does. + public static bool IsNullableType(this Type type) + { + Type generic = type.IsGenericType ? type.GetGenericTypeDefinition() : null; + + if (generic != null && generic.Equals(typeof(Nullable<>)) && (type.IsClass || type.IsValueType || type.IsEnum)) + return true; + + return false; + } + /// Check if type is collection of any kind. + /// Type to check. + /// Returns true if it is. + public static bool IsCollection(this Type type) + { + if (!type.IsArray) + return type.IsGenericEnumerable(); + + return true; + } + /// Check if type is collection of value types like int. + /// Type to check. + /// Returns true if it is. + public static bool IsCollectionOfValueTypes(this Type type) + { + if (type.IsArray) + return type.GetElementType().IsValueType; + else + { + if (type.IsGenericType && type.GetInterfaces().Any(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + { + Type[] gt = type.GetGenericArguments(); + + return (gt.Length == 1) && gt[0].IsValueType; + } + } + return false; + } + /// Gets corresponding to the + /// provided . + /// The type to be converted. + /// Returns corresponding to the + /// provided . + public static DbType ToDbType(this Type t) + { + return TypeMap.TryGetNullable(t) ?? DbType.Object; + } + /// Gets corresponding to the + /// provided . + /// The type to be converted. + /// Returns corresponding to the + /// provided . + public static Type ToType(this DbType dbt) + { + if (dbt == DbType.String) + return typeof(string); + + foreach (KeyValuePair tdbt in TypeMap) + if (tdbt.Value == dbt) + return tdbt.Key; + + return typeof(object); + } + /// Determines whether the specified value is has only ASCII chars. + /// The value to check. + /// Returns true if the specified value has only ASCII cars; otherwise, false. + public static bool IsASCII(this string value) + { + return Encoding.UTF8.GetByteCount(value) == value.Length; + } + #endregion Type extensions + + #region IDictionary extensions + + /// Gets the value associated with the specified key. + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// Dictionary to probe. + /// The key whose value to get. + /// Nullable type containing value or null if key was not found. + public static TValue? TryGetNullable(this IDictionary dict, TKey key) where TValue : struct + { + TValue val; + + if (key != null && dict.TryGetValue(key, out val)) + return val; + + return null; + } + /// Gets the value associated with the specified key. + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// Dictionary to probe. + /// The key whose value to get. + /// Instance of object or null if not found. + public static TValue TryGetValue(this IDictionary dict, TKey key) where TValue : class + { + TValue val; + + if (key != null && dict != null && dict.TryGetValue(key, out val)) + return val; + + return default(TValue); + } + /// Adds element to dictionary and returns added value. + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// Dictionary to which add value. + /// The key under which value value will be added. + /// Value to add. + /// Instance of object or null if not found. + public static TValue AddAndPassValue(this IDictionary dict, TKey key, TValue value) + { + dict.Add(key, value); + return value; + } + #endregion IDictionary extensions + + #region IDataReader extensions + + /// Gets the information corresponding + /// to the type of that would be returned from + /// . + /// The data reader. + /// The index of the field to find. + /// The information corresponding to the + /// type of that would be returned from + /// . + public static DbType GetFieldDbType(this IDataReader r, int i) + { + return TypeMap.TryGetNullable(r.GetFieldType(i)) ?? DbType.String; + } + internal static IEnumerable EnumerateReader(this IDataReader r) + { + while (r.Read()) + yield return r.RowToDynamic(); + } + /// Creates cached reader object from non cached reader. + /// The reader to cache. + /// The offset row. + /// The limit to number of tows. -1 is no limit. + /// The progress delegate. + /// Returns new instance of cached reader or current instance of a reader. + public static IDataReader CachedReader(this IDataReader r, int offset = 0, int limit = -1, Func progress = null) + { + if (r is DynamicCachedReader) + return r; + + return new DynamicCachedReader(r, offset, limit, progress); + } + #endregion IDataReader extensions + + #region Mapper extensions + + /// MapEnumerable object enumerator into specified type. + /// Type to which columnMap results. + /// Source enumerator. + /// Enumerator of specified type. + public static IEnumerable MapEnumerable(this IEnumerable enumerable) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + foreach (object item in enumerable) + yield return (T)mapper.Create(item); + } + /// MapEnumerable object item into specified type. + /// Type to which columnMap results. + /// Source object. + /// Item of specified type. + public static T Map(this object item) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + return (T)mapper.Create(item); + } + /// MapEnumerable object enumerator into specified type using property names. + /// Type to which columnMap results. + /// Source enumerator. + /// Enumerator of specified type. + public static IEnumerable MapEnumerableByProperty(this IEnumerable enumerable) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + foreach (object item in enumerable) + yield return (T)mapper.CreateByProperty(item); + } + /// MapEnumerable object item into specified type using property names. + /// Type to which columnMap results. + /// Source object. + /// Item of specified type. + public static T MapByProperty(this object item) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + return (T)mapper.CreateByProperty(item); + } + /// Fill object of specified type with data from source object. + /// Type to which columnMap results. + /// Item to which columnMap data. + /// Item from which extract data. + /// Filled item. + public static T Fill(this T item, object source) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + mapper.Map(source, item); + + return item; + } + /// Fill object of specified type with data from source object using property names. + /// Type to which columnMap results. + /// Item to which columnMap data. + /// Item from which extract data. + /// Filled item. + public static T FillByProperty(this T item, object source) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + mapper.MapByProperty(source, item); + + return item; + } + /// MapEnumerable object enumerator into specified type. + /// Source enumerator. + /// Type to which columnMap results. + /// Enumerator of specified type. + public static IEnumerable MapEnumerable(this IEnumerable enumerable, Type type) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + foreach (object item in enumerable) + yield return mapper.Create(item); + } + /// MapEnumerable object item into specified type. + /// Source object. + /// Type to which columnMap results. + /// Item of specified type. + public static object Map(this object item, Type type) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + return mapper.Create(item); + } + /// MapEnumerable object enumerator into specified type using property names. + /// Source enumerator. + /// Type to which columnMap results. + /// Enumerator of specified type. + public static IEnumerable MapEnumerableByProperty(this IEnumerable enumerable, Type type) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + foreach (object item in enumerable) + yield return mapper.CreateByProperty(item); + } + /// MapEnumerable object item into specified type using property names. + /// Source object. + /// Type to which columnMap results. + /// Item of specified type. + public static object MapByProperty(this object item, Type type) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + return mapper.CreateByProperty(item); + } + /// Converts the elements of an + /// to the specified type. + /// The type to convert the elements of source to. + /// The that + /// contains the elements to be converted. + /// An enumerator that contains each element of + /// the source sequence converted to the specified type. + /// Thrown when + /// source is null. + /// An element in the + /// sequence cannot be cast to type T or enumerator + /// is not . + public static IEnumerable CastEnumerable(this object enumerator) + { + return (enumerator as System.Collections.IEnumerable).Cast(); + } + #endregion Mapper extensions + + #region TryParse extensions + +#if !DYNAMORM_OMMIT_TRYPARSE + + /// Generic try parse. + /// Type to parse to. + /// Value to parse. + /// Handler of a try parse method. + /// Returns true if conversion was successful. + public static T? TryParse(this string value, TryParseHandler handler) where T : struct + { + if (String.IsNullOrEmpty(value)) + return null; + + T result; + + if (handler(value, out result)) + return result; + + return null; + } + /// Generic try parse with default value. + /// Type to parse to. + /// Value to parse. + /// Default value of a result. + /// Handler of a try parse method. + /// Returns true if conversion was successful. + public static T TryParseDefault(this string value, T defaultValue, TryParseHandler handler) + { + if (String.IsNullOrEmpty(value)) + return defaultValue; + + T result; + + if (handler(value, out result)) + return result; + + return defaultValue; + } + /// Delegate from try parse function of a type. + /// Type which implements this function. + /// Value to parse. + /// Resulting value. + /// Returns true if conversion was successful. + public delegate bool TryParseHandler(string value, out T result); + +#endif + + #endregion TryParse extensions + + #region Coalesce - besicaly not an extensions + + /// Select first not null value. + /// Type to return. + /// Values to check. + /// First not null or default value. + public static T Coalesce(params T[] vals) where T : class + { + return vals.FirstOrDefault(v => v != null); + } + /// Select first not null value. + /// Type to return. + /// Values to check. + /// First not null or default value. + public static T? CoalesceNullable(params T?[] vals) where T : struct + { + return vals.FirstOrDefault(v => v != null); + } + #endregion Coalesce - besicaly not an extensions + } + /// Dynamic procedure invoker. + /// Unfortunately I can use out and ref to + /// return parameters, . + /// But see example for workaround. If there aren't any return parameters execution will return scalar value. + /// Scalar result is not converted to provided generic type (if any). For output results there is possibility to map to provided class. + /// You still can use out, return and both way parameters by providing variable prefix: + /// dynamic res = db.Procedures.sp_Test_Scalar_In_Out(inp: Guid.NewGuid(), out_outp: Guid.Empty); + /// Console.Out.WriteLine(res.outp); + /// Prefixes: out_, ret_, both_. Result will contain field without prefix. + /// Here is an example with result class: + /// public class ProcResult { [Column("outp")] public Guid Output { get; set; } } + /// ProcResult res4 = db.Procedures.sp_Test_Scalar_In_Out<ProcResult>(inp: Guid.NewGuid(), out_outp: Guid.Empty) as ProcResult; + /// As you can se, you can use mapper to do job for you. + public class DynamicProcedureInvoker : DynamicObject, IDisposable + { + private DynamicDatabase _db; + private List _prefixes; + private bool _isDisposed; + + internal DynamicProcedureInvoker(DynamicDatabase db, List prefixes = null) + { + _prefixes = prefixes; + _db = db; + } + /// This is where the magic begins. + /// Binder to create owner. + /// Binder invoke result. + /// Returns true if invoke was performed. + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + List pref = new List(); + + if (_prefixes != null) + pref.AddRange(_prefixes); + + pref.Add(binder.Name); + + result = new DynamicProcedureInvoker(_db, pref); + + return true; + } + /// This is where the magic begins. + /// Binder to invoke. + /// Binder arguments. + /// Binder invoke result. + /// Returns true if invoke was performed. + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + // parse the method + CallInfo info = binder.CallInfo; + + // Get generic types + IList types = binder.GetGenericTypeArguments(); + + Dictionary retParams = null; + + using (IDbConnection con = _db.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + if (_prefixes == null || _prefixes.Count == 0) + cmd.SetCommand(CommandType.StoredProcedure, binder.Name); + else + cmd.SetCommand(CommandType.StoredProcedure, string.Format("{0}.{1}", string.Join(".", _prefixes), binder.Name)); + + #region Prepare arguments + + int alen = args.Length; + bool retIsAdded = false; + + if (alen > 0) + { + for (int i = 0; i < alen; i++) + { + object arg = args[i]; + + if (arg is DynamicExpando) + cmd.AddParameters(_db, (DynamicExpando)arg); + else if (arg is ExpandoObject) + cmd.AddParameters(_db, (ExpandoObject)arg); + else if (arg is DynamicColumn) + { + var dcv = (DynamicColumn)arg; + + string paramName = i.ToString(); + bool isOut = false; + bool isRet = false; + bool isBoth = false; + + if (info.ArgumentNames.Count > i) + { + isOut = info.ArgumentNames[i].StartsWith("out_"); + isRet = info.ArgumentNames[i].StartsWith("ret_"); + isBoth = info.ArgumentNames[i].StartsWith("both_"); + + paramName = isOut || isRet ? + info.ArgumentNames[i].Substring(4) : + isBoth ? info.ArgumentNames[i].Substring(5) : + info.ArgumentNames[i]; + } + paramName = dcv.Alias ?? dcv.ColumnName ?? + (dcv.Schema.HasValue ? dcv.Schema.Value.Name : null) ?? + paramName; + + if (!isOut && !isRet && !isBoth) + { + isOut = dcv.ParameterDirection == ParameterDirection.Output; + isRet = dcv.ParameterDirection == ParameterDirection.ReturnValue; + isBoth = dcv.ParameterDirection == ParameterDirection.InputOutput; + } + if (isRet) + retIsAdded = true; + + if (isOut || isRet || isBoth) + { + if (retParams == null) + retParams = new Dictionary(); + retParams.Add(paramName, cmd.Parameters.Count); + } + if (dcv.Schema != null) + { + var ds = dcv.Schema.Value; + cmd.AddParameter( + _db.GetParameterName(paramName), + isOut ? ParameterDirection.Output : + isRet ? ParameterDirection.ReturnValue : + isBoth ? ParameterDirection.InputOutput : + ParameterDirection.Input, + ds.Type, ds.Size, ds.Precision, ds.Scale, + (isOut || isRet) ? DBNull.Value : dcv.Value); + } + else + cmd.AddParameter( + _db.GetParameterName(paramName), + isOut ? ParameterDirection.Output : + isRet ? ParameterDirection.ReturnValue : + isBoth ? ParameterDirection.InputOutput : + ParameterDirection.Input, + arg == null ? DbType.String : arg.GetType().ToDbType(), + isRet ? 4 : 0, + (isOut || isRet) ? DBNull.Value : dcv.Value); + } + else if (arg is DynamicSchemaColumn) + { + var dsc = (DynamicSchemaColumn)arg; + + string paramName = i.ToString(); + bool isOut = false; + bool isRet = false; + bool isBoth = false; + + if (info.ArgumentNames.Count > i) + { + isOut = info.ArgumentNames[i].StartsWith("out_"); + isRet = info.ArgumentNames[i].StartsWith("ret_"); + isBoth = info.ArgumentNames[i].StartsWith("both_"); + + paramName = isOut || isRet ? + info.ArgumentNames[i].Substring(4) : + isBoth ? info.ArgumentNames[i].Substring(5) : + info.ArgumentNames[i]; + } + paramName = dsc.Name ?? paramName; + + if (isRet) + retIsAdded = true; + + if (isOut || isRet || isBoth) + { + if (retParams == null) + retParams = new Dictionary(); + retParams.Add(paramName, cmd.Parameters.Count); + } + cmd.AddParameter( + _db.GetParameterName(paramName), + isOut ? ParameterDirection.Output : + isRet ? ParameterDirection.ReturnValue : + isBoth ? ParameterDirection.InputOutput : + ParameterDirection.Input, + dsc.Type, dsc.Size, dsc.Precision, dsc.Scale, + DBNull.Value); + } + else + { + if (info.ArgumentNames.Count > i && !string.IsNullOrEmpty(info.ArgumentNames[i])) + { + bool isOut = info.ArgumentNames[i].StartsWith("out_"); + bool isRet = info.ArgumentNames[i].StartsWith("ret_"); + bool isBoth = info.ArgumentNames[i].StartsWith("both_"); + + if (isRet) + retIsAdded = true; + + string paramName = isOut || isRet ? + info.ArgumentNames[i].Substring(4) : + isBoth ? info.ArgumentNames[i].Substring(5) : + info.ArgumentNames[i]; + + if (isOut || isBoth || isRet) + { + if (retParams == null) + retParams = new Dictionary(); + retParams.Add(paramName, cmd.Parameters.Count); + } + cmd.AddParameter( + _db.GetParameterName(paramName), + isOut ? ParameterDirection.Output : + isRet ? ParameterDirection.ReturnValue : + isBoth ? ParameterDirection.InputOutput : + ParameterDirection.Input, + arg == null ? isRet ? DbType.Int32 : DbType.String : arg.GetType().ToDbType(), + isRet ? 4 : 0, + (isOut || isRet) ? DBNull.Value : arg); + } + else + cmd.AddParameter(_db, arg); + } + } + } + #endregion Prepare arguments + + #region Get main result + + object mainResult = null; + + if (types.Count > 0) + { + mainResult = types[0].GetDefaultValue(); + + if (types[0] == typeof(IDataReader)) + { + using (IDataReader rdr = cmd.ExecuteReader()) + mainResult = rdr.CachedReader(); + } + else if (types[0] == typeof(DataTable)) + { + using (IDataReader rdr = cmd.ExecuteReader()) + using (IDataReader cache = rdr.CachedReader()) + mainResult = cache.ToDataTable(binder.Name); + } + else if (types[0].IsGenericEnumerable()) + { + Type argType = types[0].GetGenericArguments().First(); + if (argType == typeof(object)) + { + using (IDataReader rdr = cmd.ExecuteReader()) + using (IDataReader cache = rdr.CachedReader()) + mainResult = cache.EnumerateReader().ToList(); + } + else if (argType.IsValueType || argType == typeof(string)) + { + Type listType = typeof(List<>).MakeGenericType(new Type[] { argType }); + IList listInstance = (IList)Activator.CreateInstance(listType); + + object defVal = listType.GetDefaultValue(); + + using (IDataReader rdr = cmd.ExecuteReader()) + using (IDataReader cache = rdr.CachedReader()) + while (cache.Read()) + listInstance.Add(cache[0] != null && cache[0] != DBNull.Value ? argType.CastObject(cache[0]) : defVal); + + mainResult = listInstance; + } + else if (argType == typeof(Guid)) + { + Type listType = typeof(List<>).MakeGenericType(new Type[] { argType }); + IList listInstance = (IList)Activator.CreateInstance(listType); + + using (IDataReader rdr = cmd.ExecuteReader()) + using (IDataReader cache = rdr.CachedReader()) + while (cache.Read()) + { + if (cache[0] != null && cache[0] != DBNull.Value && Guid.TryParse(cache[0].ToString(), out Guid g)) + listInstance.Add(g); + } + mainResult = listInstance; + } + else + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(argType); + if (mapper == null) + throw new InvalidCastException(string.Format("Don't know what to do with this type: '{0}'.", argType.ToString())); + + using (IDataReader rdr = cmd.ExecuteReader()) + using (IDataReader cache = rdr.CachedReader()) + { + var lt = typeof(List<>); + var ltc = lt.MakeGenericType(argType); + var instance = Activator.CreateInstance(ltc) as IList; + + foreach (var item in cache.EnumerateReader()) + instance.Add(DynamicExtensions.Map(item, argType)); + + mainResult = instance; + } + //mainResult = cache.EnumerateReader().MapEnumerable(argType).ToList(); + } + } + else if (types[0].IsValueType || types[0] == typeof(string)) + { + mainResult = cmd.ExecuteScalar(); + if (mainResult != null && mainResult != DBNull.Value) + mainResult = types[0].CastObject(mainResult); + } + else if (types[0] == typeof(Guid)) + { + mainResult = cmd.ExecuteScalar(); + if (mainResult != null && mainResult != DBNull.Value && Guid.TryParse(mainResult.ToString(), out Guid g)) + mainResult = g; + } + else + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(types[0]); + if (mapper == null) + throw new InvalidCastException(string.Format("Don't know what to do with this type: '{0}'.", types[0].ToString())); + + using (IDataReader rdr = cmd.ExecuteReader()) + if (rdr.Read()) + mainResult = (rdr.RowToDynamic() as object).Map(types[0]); + else + mainResult = null; + } + } + else + { + var returnName = _db.GetParameterName("___result___"); + if ((_db.Options & DynamicDatabaseOptions.SupportStoredProceduresResult) == DynamicDatabaseOptions.SupportStoredProceduresResult) + { + if (!retIsAdded) + cmd.AddParameter(returnName, ParameterDirection.ReturnValue, DbType.Int32, 4, 0, 0, DBNull.Value); + } + mainResult = cmd.ExecuteNonQuery(); + + IDbDataParameter returnParam = null; + if (!retIsAdded) + { + if ((_db.Options & DynamicDatabaseOptions.SupportStoredProceduresResult) == DynamicDatabaseOptions.SupportStoredProceduresResult) + returnParam = cmd.Parameters[returnName] as IDbDataParameter; + } + else + { + foreach (var e in cmd.Parameters) + { + var p = e as IDbDataParameter; + if (p != null && p.Direction == ParameterDirection.ReturnValue) + { + returnParam = p; + break; + } + } + } + if (returnParam != null && returnParam.Value != null && returnParam.Value != DBNull.Value) + mainResult = returnParam.Value; + } + #endregion Get main result + + #region Handle out params + + if (retParams != null) + { + Dictionary res = new Dictionary(); + + if (mainResult != null) + { + if (mainResult == DBNull.Value) + res.Add(binder.Name, null); + else + res.Add(binder.Name, mainResult); + } + foreach (KeyValuePair pos in retParams) + res.Add(pos.Key, ((IDbDataParameter)cmd.Parameters[pos.Value]).Value); + + if (types.Count > 1) + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(types[1]); + + if (mapper != null) + result = mapper.Create(res.ToDynamic()); + else + result = res.ToDynamic(); + } + else + result = res.ToDynamic(); + } + else + result = mainResult; + + #endregion Handle out params + } + return true; + } + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + _db = null; + _prefixes = null; + } + } + /// Dynamic query exception. + [Serializable] + public class DynamicQueryException : Exception, ISerializable + { + /// Initializes a new instance of the + /// class. + /// The command which failed. + public DynamicQueryException(IDbCommand command = null) + : base(string.Format("Error executing command.{0}{1}", Environment.NewLine, command != null ? command.DumpToString() : string.Empty)) + { + } + /// Initializes a new instance of the + /// class. + /// The message. + /// The command which failed. + public DynamicQueryException(string message, IDbCommand command = null) + : base(string.Format("{0}{1}{2}", message, Environment.NewLine, command != null ? command.DumpToString() : string.Empty)) + { + } + /// Initializes a new instance of the + /// class. + /// The inner exception. + /// The command which failed. + public DynamicQueryException(Exception innerException, IDbCommand command = null) + : base(string.Format("Error executing command.{0}{1}", Environment.NewLine, command != null ? command.DumpToString() : string.Empty), innerException) + { + } + /// Initializes a new instance of the + /// class. + /// The message. + /// The inner exception. + /// The command which failed. + public DynamicQueryException(string message, Exception innerException, IDbCommand command = null) + : base(string.Format("{0}{1}{2}", message, Environment.NewLine, command != null ? command.DumpToString() : string.Empty), innerException) + { + } + } + /// Stores information about column from database schema. + public struct DynamicSchemaColumn + { + /// Gets or sets column name. + public string Name { get; set; } + + /// Gets or sets column type. + public DbType Type { get; set; } + + /// Gets or sets a value indicating whether column is a key. + public bool IsKey { get; set; } + + /// Gets or sets a value indicating whether column should have unique value. + public bool IsUnique { get; set; } + + /// Gets or sets a value indicating whether column allows null or not. + public bool AllowNull { get; set; } + + /// Gets or sets column size. + public int Size { get; set; } + + /// Gets or sets column precision. + public byte Precision { get; set; } + + /// Gets or sets column scale. + public byte Scale { get; set; } + } #if !DYNAMORM_OMMIT_OLDSYNTAX /// Dynamic table is a simple ORM using dynamic objects. @@ -253,7 +5927,6 @@ namespace DynamORM 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; } @@ -261,7 +5934,6 @@ namespace DynamORM private DynamicTable() { } - /// Initializes a new instance of the class. /// Database and connection management. /// Table name. @@ -277,7 +5949,6 @@ namespace DynamORM BuildAndCacheSchema(keys); } - /// Initializes a new instance of the class. /// Database and connection management. /// Type describing table. @@ -301,10 +5972,8 @@ namespace DynamORM OwnerName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Owner) ? null : mapper.Table.Owner; } - BuildAndCacheSchema(keys); } - #region Schema private void BuildAndCacheSchema(string[] keys) @@ -327,7 +5996,6 @@ namespace DynamORM keys = k.ToArray(); } } - if (schema != null) { if (keys == null) @@ -345,7 +6013,6 @@ namespace DynamORM }); } } - #endregion Fill currrent table schema #region Build ad-hock schema @@ -355,10 +6022,8 @@ namespace DynamORM 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 @@ -373,7 +6038,6 @@ namespace DynamORM { return Database.Query(sql, args); } - /// Enumerate the reader and yield the result. /// Command builder. /// Enumerator of objects expanded from query. @@ -381,7 +6045,6 @@ namespace DynamORM { return Database.Query(builder); } - /// Create new . /// New instance. public virtual IDynamicSelectQueryBuilder Query() @@ -394,7 +6057,6 @@ namespace DynamORM return builder; } - /// Returns a single result. /// SQL query containing numbered parameters in format provided by /// methods. Also names should be formatted with @@ -405,7 +6067,6 @@ namespace DynamORM { return Database.Scalar(sql, args); } - /// Returns a single result. /// Command builder. /// Result of a query. @@ -413,7 +6074,6 @@ namespace DynamORM { return Database.Scalar(builder); } - #if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE /// Returns a single result. @@ -427,7 +6087,6 @@ namespace DynamORM { return Database.ScalarAs(sql, args); } - /// Returns a single result. /// What kind of result is expected. /// Command builder. @@ -437,7 +6096,6 @@ namespace DynamORM { return Database.ScalarAs(builder, defaultValue); } - #endif /// Execute stored procedure. @@ -447,7 +6105,6 @@ namespace DynamORM { return Database.Procedure(procName); } - /// Execute stored procedure. /// Name of stored procedure to execute. /// Arguments (parameters) in form of expando object. @@ -456,7 +6113,6 @@ namespace DynamORM { return Database.Procedure(procName, args); } - /// Execute stored procedure. /// Name of stored procedure to execute. /// Arguments (parameters) in form of expando object. @@ -465,7 +6121,6 @@ namespace DynamORM { return Database.Procedure(procName, args); } - /// Execute stored procedure. /// Name of stored procedure to execute. /// Arguments (parameters) in form of expando object. @@ -474,7 +6129,6 @@ namespace DynamORM { return Database.Procedure(procName, args); } - /// Execute non query. /// SQL query containing numbered parameters in format provided by /// methods. Also names should be formatted with @@ -485,7 +6139,6 @@ namespace DynamORM { return Database.Execute(sql, args); } - /// Execute non query. /// Command builder. /// Number of affected rows. @@ -493,7 +6146,6 @@ namespace DynamORM { return Database.Execute(builder); } - /// Execute non query. /// Command builders. /// Number of affected rows. @@ -501,7 +6153,6 @@ namespace DynamORM { return Database.Execute(builders); } - #endregion Basic Queries #region Insert @@ -512,7 +6163,6 @@ namespace DynamORM { 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 @@ -524,7 +6174,6 @@ namespace DynamORM .Insert(o) .Execute(); } - #endregion Insert #region Update @@ -535,7 +6184,6 @@ namespace DynamORM { 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 @@ -550,7 +6198,6 @@ namespace DynamORM .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 @@ -562,7 +6209,6 @@ namespace DynamORM .Update(o) .Execute(); } - #endregion Update #region Delete @@ -573,7 +6219,6 @@ namespace DynamORM { 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 @@ -587,7 +6232,6 @@ namespace DynamORM .Where(o, schema) .Execute(); } - #endregion Delete #region Universal Dynamic Invoker @@ -633,10 +6277,8 @@ namespace DynamORM result = DynamicQuery(args, info, op, types); break; } - return true; } - private object DynamicInsert(object[] args, CallInfo info, IList types) { DynamicInsertQueryBuilder builder = new DynamicInsertQueryBuilder(this.Database); @@ -679,11 +6321,9 @@ namespace DynamORM } } } - // Execute return Execute(builder); } - private object DynamicUpdate(object[] args, CallInfo info, IList types) { DynamicUpdateQueryBuilder builder = new DynamicUpdateQueryBuilder(this.Database); @@ -734,11 +6374,9 @@ namespace DynamORM } } } - // Execute return Execute(builder); } - private object DynamicDelete(object[] args, CallInfo info, IList types) { DynamicDeleteQueryBuilder builder = new DynamicDeleteQueryBuilder(this.Database); @@ -785,11 +6423,9 @@ namespace DynamORM } } } - // Execute return Execute(builder); } - private object DynamicQuery(object[] args, CallInfo info, string op, IList types) { object result; @@ -862,7 +6498,6 @@ namespace DynamORM }).ToArray()); else goto default; } - break; case "where": @@ -887,7 +6522,6 @@ namespace DynamORM } } } - if (op == "Count" && !builder.HasSelectColumns) { result = Scalar(builder.Select(x => x.Count())); @@ -921,7 +6555,6 @@ namespace DynamORM else if ((Database.Options & DynamicDatabaseOptions.SupportTop) == DynamicDatabaseOptions.SupportTop) builder.Top(1); } - if (op == "Scalar") { if (!builder.HasSelectColumns) @@ -953,10 +6586,8 @@ namespace DynamORM } } } - return result; } - private void HandleTypeArgument(object[] args, CallInfo info, ref IList types, T builder, int i) where T : DynamicQueryBuilder { if (args != null) @@ -966,11 +6597,9 @@ namespace DynamORM 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 @@ -988,10 +6617,8 @@ namespace DynamORM Database.RemoveFromCache(this); Database = null; } - IsDisposed = true; } - /// Gets a value indicating whether this instance is disposed. public bool IsDisposed { get; private set; } @@ -1012,5889 +6639,9 @@ namespace DynamORM TableType = this.TableType }; } - #endregion ICloneable Members } - #endif - - /// Helper class to easy manage command. - public class DynamicCommand : IDbCommand, IExtendedDisposable - { - private IDbCommand _command; - private int? _commandTimeout = null; - private DynamicConnection _con; - private DynamicDatabase _db; - ////private long _poolStamp = 0; - - /// Initializes a new instance of the class. - /// The connection. - /// The database manager. - internal DynamicCommand(DynamicConnection con, DynamicDatabase db) - { - IsDisposed = false; - _con = con; - _db = db; - - lock (_db.SyncLock) - { - if (!_db.CommandsPool.ContainsKey(_con.Connection)) - throw new InvalidOperationException("Can't create command using disposed connection."); - else - { - _command = _con.Connection.CreateCommand(); - _db.CommandsPool[_con.Connection].Add(this); - } - } - } - - /// Initializes a new instance of the class. - /// The database manager. - /// Used internally to create command without context. - internal DynamicCommand(DynamicDatabase db) - { - IsDisposed = false; - _db = db; - _command = db.Provider.CreateCommand(); - } - - /// Prepare command for execution. - /// Returns edited instance. - internal IDbCommand PrepareForExecution() - { - // TODO: Fix that - ////if (_poolStamp < _db.PoolStamp) - { - _command.CommandTimeout = _commandTimeout ?? _db.CommandTimeout ?? _command.CommandTimeout; - - if (_db.TransactionPool[_command.Connection].Count > 0) - _command.Transaction = _db.TransactionPool[_command.Connection].Peek(); - else - _command.Transaction = null; - - ////_poolStamp = _db.PoolStamp; - } - - if (_db.DumpCommands) - _db.DumpCommand(_command); - - return _command; - } - - #region IDbCommand Members - - /// - /// Attempts to cancels the execution of an . - /// - public void Cancel() - { - _command.Cancel(); - } - - /// - /// Gets or sets the text command to run against the data source. - /// - /// The text command to execute. The default value is an empty string (""). - public string CommandText { get { return _command.CommandText; } set { _command.CommandText = value; } } - - /// - /// Gets or sets the wait time before terminating the attempt to execute a command and generating an error. - /// - /// The time (in seconds) to wait for the command to execute. The default value is 30 seconds. - /// The property value assigned is less than 0. - public int CommandTimeout { get { return _commandTimeout ?? _command.CommandTimeout; } set { _commandTimeout = value; } } - - /// Gets or sets how the property is interpreted. - public CommandType CommandType { get { return _command.CommandType; } set { _command.CommandType = value; } } - - /// Gets or sets the - /// used by this instance of the . - /// The connection to the data source. - public IDbConnection Connection - { - get { return _con; } - - set - { - _con = value as DynamicConnection; - - if (_con != null) - { - ////_poolStamp = 0; - _command.Connection = _con.Connection; - } - else if (value == null) - { - _command.Transaction = null; - _command.Connection = null; - } - else - throw new InvalidOperationException("Can't assign direct IDbConnection implementation. This property accepts only DynamORM implementation of IDbConnection."); - } - } - - /// Creates a new instance of an - /// object. - /// An object. - public IDbDataParameter CreateParameter() - { - return _command.CreateParameter(); - } - - /// Executes an SQL statement against the Connection object of a - /// data provider, and returns the number of rows affected. - /// The number of rows affected. - public int ExecuteNonQuery() - { - try - { - return PrepareForExecution().ExecuteNonQuery(); - } - catch (Exception ex) - { - throw new DynamicQueryException(ex, this); - } - } - - /// Executes the - /// against the , - /// and builds an using one - /// of the values. - /// One of the - /// values. - /// An object. - public IDataReader ExecuteReader(CommandBehavior behavior) - { - try - { - return PrepareForExecution().ExecuteReader(behavior); - } - catch (Exception ex) - { - throw new DynamicQueryException(ex, this); - } - } - - /// Executes the - /// against the and - /// builds an . - /// An object. - public IDataReader ExecuteReader() - { - try - { - return PrepareForExecution().ExecuteReader(); - } - catch (Exception ex) - { - throw new DynamicQueryException(ex, this); - } - } - - /// Executes the query, and returns the first column of the - /// first row in the result set returned by the query. Extra columns or - /// rows are ignored. - /// The first column of the first row in the result set. - public object ExecuteScalar() - { - try - { - return PrepareForExecution().ExecuteScalar(); - } - catch (Exception ex) - { - throw new DynamicQueryException(ex, this); - } - } - - /// Gets the . - public IDataParameterCollection Parameters - { - get { return _command.Parameters; } - } - - /// Creates a prepared (or compiled) version of the command on the data source. - public void Prepare() - { - try - { - _command.Prepare(); - } - catch (Exception ex) - { - throw new DynamicQueryException("Error preparing command.", ex, this); - } - } - - /// Gets or sets the transaction within which the Command - /// object of a data provider executes. - /// It's does nothing, transaction is peeked from transaction - /// pool of a connection. This is only a dummy. - public IDbTransaction Transaction { get { return null; } set { } } - - /// Gets or sets how command results are applied to the - /// when used by the - /// method of a . - /// One of the values. The default is - /// Both unless the command is automatically generated. Then the default is None. - /// The value entered was not one of the - /// values. - public UpdateRowSource UpdatedRowSource { get { return _command.UpdatedRowSource; } set { _command.UpdatedRowSource = value; } } - - #endregion IDbCommand Members - - #region IExtendedDisposable Members - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public void Dispose() - { - if (IsDisposed) - return; - - var db = _db; - if (db == null) - { - IsDisposed = true; - return; - } - - lock (db.SyncLock) - { - if (IsDisposed) - return; - - IsDisposed = true; - - if (_con != null) - { - List pool = db.CommandsPool.TryGetValue(_con.Connection); - - if (pool != null && pool.Contains(this)) - pool.Remove(this); - } - - if (_command != null) - { - _command.Parameters.Clear(); - - _command.Dispose(); - _command = null; - } - - _con = null; - _db = null; - } - } - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - #endregion IExtendedDisposable Members - } - - /// Connection wrapper. - /// This class is only connection holder, connection is managed by - /// instance. - public class DynamicConnection : IDbConnection, IExtendedDisposable - { - private DynamicDatabase _db; - private bool _singleTransaction; - - /// Gets underlying connection. - internal IDbConnection Connection { get; private set; } - - /// Initializes a new instance of the class. - /// Database connection manager. - /// Active connection. - /// Are we using single transaction mode? I so... act correctly. - internal DynamicConnection(DynamicDatabase db, IDbConnection con, bool singleTransaction) - { - IsDisposed = false; - _db = db; - Connection = con; - _singleTransaction = singleTransaction; - } - - /// Begins a database transaction. - /// One of the values. - /// Custom parameter describing transaction options. - /// This action is invoked when transaction is disposed. - /// Returns representation. - internal DynamicTransaction BeginTransaction(IsolationLevel? il, object custom, Action disposed) - { - return new DynamicTransaction(_db, this, _singleTransaction, il, disposed, null); - } - - #region IDbConnection Members - - /// Creates and returns a Command object associated with the connection. - /// A Command object associated with the connection. - public IDbCommand CreateCommand() - { - return new DynamicCommand(this, _db); - } - - /// Begins a database transaction. - /// Returns representation. - public IDbTransaction BeginTransaction() - { - return BeginTransaction(null, null, null); - } - - /// Begins a database transaction with the specified - /// value. - /// One of the values. - /// Returns representation. - public IDbTransaction BeginTransaction(IsolationLevel il) - { - return BeginTransaction(il, null, null); - } - - /// Begins a database transaction with the specified - /// value. - /// Custom parameter describing transaction options. - /// Returns representation. - public IDbTransaction BeginTransaction(object custom) - { - return BeginTransaction(null, custom, null); - } - - /// Changes the current database for an open Connection object. - /// The name of the database to use in place of the current database. - /// This operation is not supported in DynamORM. and will throw . - /// Thrown always. - public void ChangeDatabase(string databaseName) - { - throw new NotSupportedException("This operation is not supported in DynamORM."); - } - - /// Opens a database connection with the settings specified by - /// the ConnectionString property of the provider-specific - /// Connection object. - /// Does nothing. handles - /// opening connections. - public void Open() - { - } - - /// Closes the connection to the database. - /// Does nothing. handles - /// closing connections. Only way to close it is to dispose connection. - /// It will close if this is multi connection configuration, otherwise - /// it will stay open until is not - /// disposed. - public void Close() - { - } - - /// Gets or sets the string used to open a database. - /// Changing connection string operation is not supported in DynamORM. - /// and will throw . - /// Thrown always when set is attempted. - public string ConnectionString - { - get { return Connection.ConnectionString; } - set { throw new NotSupportedException("This operation is not supported in DynamORM."); } - } - - /// Gets the time to wait while trying to establish a connection - /// before terminating the attempt and generating an error. - public int ConnectionTimeout - { - get { return Connection.ConnectionTimeout; } - } - - /// Gets the name of the current database or the database - /// to be used after a connection is opened. - public string Database - { - get { return Connection.Database; } - } - - /// Gets the current state of the connection. - public ConnectionState State - { - get { return Connection.State; } - } - - #endregion IDbConnection Members - - #region IExtendedDisposable Members - - /// Performs application-defined tasks associated with freeing, - /// releasing, or resetting unmanaged resources. - public void Dispose() - { - if (IsDisposed) - return; - - var db = _db; - if (db != null && Connection != null) - db.Close(Connection); - - Connection = null; - _db = null; - IsDisposed = true; - } - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - #endregion IExtendedDisposable Members - } - - /// Small utility class to manage single columns. - public class DynamicColumn - { - #region Enums - - /// Order By Order. - public enum SortOrder - { - /// Ascending order. - Asc, - - /// Descending order. - Desc - } - - /// Dynamic query operators. - public enum CompareOperator - { - /// Equals operator (default). - Eq, - - /// Not equal operator. - Not, - - /// Like operator. - Like, - - /// Not like operator. - NotLike, - - /// In operator. - In, - - /// Less than operator. - Lt, - - /// Less or equal operator. - Lte, - - /// Greater than operator. - Gt, - - /// Greater or equal operator. - Gte, - - /// Between two values. - Between, - } - - #endregion Enums - - #region Constructors - - /// Initializes a new instance of the class. - public DynamicColumn() - { - ParameterDirection = ParameterDirection.Input; - } - - /// Initializes a new instance of the class. - /// Constructor provided for easier object creation in queries. - /// Name of column to set. - public DynamicColumn(string columnName) - : this() - { - ColumnName = columnName; - } - - /// Initializes a new instance of the class. - /// Constructor provided for easier object creation in queries. - /// Name of column to set. - /// Compare column to value(s) operator. - /// Parameter value(s). - public DynamicColumn(string columnName, CompareOperator oper, object value) - : this(columnName) - { - Operator = oper; - Value = value; - } - - #endregion Constructors - - #region Properties - - /// Gets or sets column name. - public string ColumnName { get; set; } - - /// Gets or sets column alias. - /// Select specific. - public string Alias { get; set; } - - /// Gets or sets aggregate function used on column. - /// Select specific. - public string Aggregate { get; set; } - - /// Gets or sets order direction. - public SortOrder Order { get; set; } - - /// Gets or sets parameter direction when used in procedure invocation. - public ParameterDirection ParameterDirection { get; set; } - - /// Gets or sets value for parameters. - public object Value { get; set; } - - /// Gets or sets condition operator. - public CompareOperator Operator { get; set; } - - /// Gets or sets a value indicating whether this condition will be treated as or condition. - public bool Or { get; set; } - - /// Gets or sets a value indicating whether start new block in where statement. - public bool BeginBlock { get; set; } - - /// Gets or sets a value indicating whether end existing block in where statement. - public bool EndBlock { get; set; } - - /// Gets or sets a value indicating whether set parameters for null values. - public bool? VirtualColumn { get; set; } - - /// Gets or sets schema representation of a column. - /// Workaround to providers issues which sometimes pass wrong - /// data o schema. For example decimal has precision of 255 in SQL - /// server. - public DynamicSchemaColumn? Schema { get; set; } - - #endregion Properties - - #region Query creation helpers - - #region Operators - - private DynamicColumn SetOperatorAndValue(CompareOperator c, object v) - { - Operator = c; - Value = v; - - return this; - } - - /// Helper method setting - /// to and - /// to provided value. - /// Value of parameter to set. - /// Returns self. - public DynamicColumn Eq(object value) - { - return SetOperatorAndValue(CompareOperator.Eq, value); - } - - /// Helper method setting - /// to and - /// to provided value. - /// Value of parameter to set. - /// Returns self. - public DynamicColumn Not(object value) - { - return SetOperatorAndValue(CompareOperator.Not, value); - } - - /// Helper method setting - /// to and - /// to provided value. - /// Value of parameter to set. - /// Returns self. - public DynamicColumn Like(object value) - { - return SetOperatorAndValue(CompareOperator.Like, value); - } - - /// Helper method setting - /// to and - /// to provided value. - /// Value of parameter to set. - /// Returns self. - public DynamicColumn NotLike(object value) - { - return SetOperatorAndValue(CompareOperator.NotLike, value); - } - - /// Helper method setting - /// to and - /// to provided value. - /// Value of parameter to set. - /// Returns self. - public DynamicColumn Greater(object value) - { - return SetOperatorAndValue(CompareOperator.Gt, value); - } - - /// Helper method setting - /// to and - /// to provided value. - /// Value of parameter to set. - /// Returns self. - public DynamicColumn Less(object value) - { - return SetOperatorAndValue(CompareOperator.Lt, value); - } - - /// Helper method setting - /// to and - /// to provided value. - /// Value of parameter to set. - /// Returns self. - public DynamicColumn GreaterOrEqual(object value) - { - return SetOperatorAndValue(CompareOperator.Gte, value); - } - - /// Helper method setting - /// to and - /// to provided value. - /// Value of parameter to set. - /// Returns self. - public DynamicColumn LessOrEqual(object value) - { - return SetOperatorAndValue(CompareOperator.Lte, value); - } - - /// Helper method setting - /// to and - /// to provided values. - /// Value of from parameter to set. - /// Value of to parameter to set. - /// Returns self. - public DynamicColumn Between(object from, object to) - { - return SetOperatorAndValue(CompareOperator.Between, new[] { from, to }); - } - - /// Helper method setting - /// to and - /// to provided values. - /// Values of parameters to set. - /// Returns self. - public DynamicColumn In(IEnumerable values) - { - return SetOperatorAndValue(CompareOperator.In, values); - } - - /// Helper method setting - /// to and - /// to provided values. - /// Values of parameters to set. - /// Returns self. - public DynamicColumn In(params object[] values) - { - if (values.Length == 1 && (values[0].GetType().IsCollection() || values[0] is IEnumerable)) - return SetOperatorAndValue(CompareOperator.In, values[0]); - - return SetOperatorAndValue(CompareOperator.In, values); - } - - #endregion Operators - - #region Order - - /// Helper method setting - /// to .. - /// Returns self. - public DynamicColumn Asc() - { - Order = SortOrder.Asc; - return this; - } - - /// Helper method setting - /// to .. - /// Returns self. - public DynamicColumn Desc() - { - Order = SortOrder.Desc; - return this; - } - - #endregion Order - - #region Other - - /// Helper method setting - /// - /// to provided name. - /// Name to set. - /// Returns self. - public DynamicColumn SetName(string name) - { - ColumnName = name; - return this; - } - - /// Helper method setting - /// - /// to provided alias. - /// Alias to set. - /// Returns self. - public DynamicColumn SetAlias(string alias) - { - Alias = alias; - return this; - } - - /// Helper method setting - /// - /// to provided aggregate. - /// Aggregate to set. - /// Returns self. - public DynamicColumn SetAggregate(string aggregate) - { - Aggregate = aggregate; - return this; - } - - /// Sets the begin block flag. - /// If set to true [begin]. - /// Returns self. - public DynamicColumn SetBeginBlock(bool begin = true) - { - BeginBlock = begin; - return this; - } - - /// Sets the end block flag. - /// If set to true [end]. - /// Returns self. - public DynamicColumn SetEndBlock(bool end = true) - { - EndBlock = end; - return this; - } - - /// Sets the or flag. - /// If set to true [or]. - /// Returns self. - public DynamicColumn SetOr(bool or = true) - { - Or = or; - return this; - } - - /// Sets the virtual column. - /// Set virtual column value. - /// Returns self. - public DynamicColumn SetVirtualColumn(bool? virt) - { - VirtualColumn = virt; - return this; - } - - #endregion Other - - #endregion Query creation helpers - - #region Parsing - - /// Parse column for select query. - /// Column format consist of Column Name, Alias and - /// Aggregate function in this order separated by ':'. - /// Column string. - /// Instance of . - public static DynamicColumn ParseSelectColumn(string column) - { - // Split column description - string[] parts = column.Split(':'); - - if (parts.Length > 0) - { - DynamicColumn ret = new DynamicColumn() { ColumnName = parts[0] }; - - if (parts.Length > 1) - ret.Alias = parts[1]; - - if (parts.Length > 2) - ret.Aggregate = parts[2]; - - return ret; - } - - return null; - } - - /// Parse column for order by in query. - /// Column format consist of Column Name and - /// Direction in this order separated by ':'. - /// Column string. - /// Instance of . - public static DynamicColumn ParseOrderByColumn(string column) - { - // Split column description - string[] parts = column.Split(':'); - - if (parts.Length > 0) - { - DynamicColumn ret = new DynamicColumn() { ColumnName = parts[0] }; - - if (parts.Length > 1) - ret.Order = parts[1].ToLower() == "d" || parts[1].ToLower() == "desc" ? SortOrder.Desc : SortOrder.Asc; - - if (parts.Length > 2) - ret.Alias = parts[2]; - - return ret; - } - - return null; - } - - #endregion Parsing - - #region ToSQL - - internal string ToSQLSelectColumn(DynamicDatabase db) - { - StringBuilder sb = new StringBuilder(); - ToSQLSelectColumn(db, sb); - return sb.ToString(); - } - - internal void ToSQLSelectColumn(DynamicDatabase db, StringBuilder sb) - { - string column = ColumnName == "*" ? "*" : ColumnName; - - if (column != "*" && - (column.IndexOf(db.LeftDecorator) == -1 || column.IndexOf(db.RightDecorator) == -1) && - (column.IndexOf('(') == -1 || column.IndexOf(')') == -1)) - column = db.DecorateName(column); - - string alias = Alias; - - if (!string.IsNullOrEmpty(Aggregate)) - { - sb.AppendFormat("{0}({1})", Aggregate, column); - - alias = string.IsNullOrEmpty(alias) ? - ColumnName == "*" ? Guid.NewGuid().ToString() : ColumnName : - alias; - } - else - sb.Append(column); - - if (!string.IsNullOrEmpty(alias)) - sb.AppendFormat(" AS {0}", alias); - } - - internal string ToSQLGroupByColumn(DynamicDatabase db) - { - StringBuilder sb = new StringBuilder(); - ToSQLGroupByColumn(db, sb); - return sb.ToString(); - } - - internal void ToSQLGroupByColumn(DynamicDatabase db, StringBuilder sb) - { - sb.Append(db.DecorateName(ColumnName)); - } - - internal string ToSQLOrderByColumn(DynamicDatabase db) - { - StringBuilder sb = new StringBuilder(); - ToSQLOrderByColumn(db, sb); - return sb.ToString(); - } - - internal void ToSQLOrderByColumn(DynamicDatabase db, StringBuilder sb) - { - if (!string.IsNullOrEmpty(Alias)) - sb.Append(Alias); - else - sb.Append(db.DecorateName(ColumnName)); - - sb.AppendFormat(" {0}", Order.ToString().ToUpper()); - } - - #endregion ToSQL - } - - /// Dynamic database is a class responsible for managing database. - public class DynamicDatabase : IExtendedDisposable - { - #region Internal fields and properties - - private DbProviderFactory _provider; - private DynamicProcedureInvoker _proc; - private string _connectionString; - private bool _singleConnection; - private bool _singleTransaction; - private string _leftDecorator = "\""; - private string _rightDecorator = "\""; - private bool _leftDecoratorIsInInvalidMembersChars = true; - private bool _rightDecoratorIsInInvalidMembersChars = true; - private string _parameterFormat = "@{0}"; - private int? _commandTimeout = null; - private long _poolStamp = 0; - - private DynamicConnection _tempConn = null; - - /// Provides lock object for this database instance. - internal readonly object SyncLock = new object(); - - /// Gets or sets timestamp of last transaction pool or configuration change. - /// This property is used to allow commands to determine if - /// they need to update transaction object or not. - internal long PoolStamp - { - get - { - long r = 0; - - lock (SyncLock) - r = _poolStamp; - - return r; - } - - set - { - lock (SyncLock) - _poolStamp = value; - } - } - - /// Gets pool of connections and transactions. - internal Dictionary> TransactionPool { get; private set; } - - /// Gets pool of connections and commands. - /// Pool should contain dynamic commands instead of native ones. - internal Dictionary> CommandsPool { get; private set; } - - /// Gets schema columns cache. - internal Dictionary> Schema { get; private set; } - - /// Gets active builders that weren't disposed. - internal List RemainingBuilders { get; private set; } - -#if !DYNAMORM_OMMIT_OLDSYNTAX - - /// Gets tables cache for this database instance. - internal Dictionary TablesCache { get; private set; } - -#endif - - #endregion Internal fields and properties - - #region Properties and Constructors - - /// Gets database options. - public DynamicDatabaseOptions Options { get; private set; } - - /// Gets or sets command timeout. - public int? CommandTimeout { get { return _commandTimeout; } set { _commandTimeout = value; _poolStamp = DateTime.Now.Ticks; } } - - /// Gets the database provider. - public DbProviderFactory Provider { get { return _provider; } } - - /// Gets the procedures invoker. - /// - /// - /// using (var db = GetORM()) - /// { - /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_Scalar AS SELECT 42;"); - /// var res0 = db.Procedures.sp_Exp_Scalar(); - /// var res1 = db.Procedures.sp_Exp_Scalar(); - /// - /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_ReturnInt AS RETURN 42;"); - /// var res2 = db.Procedures.sp_Exp_ReturnInt(); - /// - /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeData AS - /// SELECT 1 Id, 'Some Name 1' [Name], 'Some Desc 1' [Desc], GETDATE() [Date] - /// UNION ALL SELECT 2 Id, 'Some Name 2', 'Some Desc 2', GETDATE() [Date];"); - /// var res3 = db.Procedures.sp_Exp_SomeData(); - /// var res4 = db.Procedures.sp_Exp_SomeData>(); - /// - /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeInputAndOutput - /// @Name nvarchar(50), - /// @Result nvarchar(256) OUTPUT - /// AS - /// SELECT @Result = 'Hi, ' + @Name + ' your lucky number is 42!';"); - /// var res5 = db.Procedures.sp_Exp_SomeInputAndOutput(Name: "G4g4r1n", out_Result: new DynamicColumn - /// { - /// Schema = new DynamicSchemaColumn - /// { - /// Size = 256, - /// }, - /// }, ret_Return: 0); - /// var res6 = db.Procedures.sp_Exp_SomeInputAndOutput(Name: "G4g4r1n", out_Result: new DynamicSchemaColumn - /// { - /// Size = 256, - /// }, ret_Return: 0); - /// - /// db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeInputAndOutputWithDataAndReturn - /// @Name nvarchar(50), - /// @Result nvarchar(256) OUTPUT - /// AS - /// SELECT @Result = 'Hi, ' + @Name + ' your lucky number is 42!' - /// - /// SELECT 1 Id, 'Some Name 1' [Name], 'Some Desc 1' [Desc], GETDATE() [Date] - /// UNION ALL SELECT 2 Id, 'Some Name 2', 'Some Desc 2', GETDATE() [Date] - /// - /// RETURN 42;"); - /// var res7 = db.Procedures.sp_Exp_SomeInputAndOutputWithDataAndReturn, sp_Exp_SomeInputAndOutputWithDataAndReturn_Result>(Name: "G4g4r1n", out_Result: new DynamicColumn - /// { - /// Schema = new DynamicSchemaColumn - /// { - /// Size = 256, - /// }, - /// }, ret_Return: 0); - /// var res8 = db.Procedures.sp_Exp_SomeInputAndOutputWithDataAndReturn, sp_Exp_SomeInputAndOutputWithDataAndReturn_Result>(Name: "G4g4r1n", out_Result: new DynamicSchemaColumn - /// { - /// Size = 256, - /// }, ret_Return: 0); - /// } - /// - ///private class sp_Exp_SomeData_Result - ///{ - /// public virtual int Id { get; set; } - /// public virtual string Name { get; set; } - /// public virtual string Desc { get; set; } - /// public virtual DateTime Date { get; set; } - ///} - ///private class sp_Exp_SomeInputAndOutput_Result - ///{ - /// public virtual string Result { get; set; } - /// public virtual string Return { get; set; } - ///} - ///private class sp_Exp_SomeInputAndOutputWithDataAndReturn_Result - ///{ - /// public class Data - /// { - /// public virtual int Id { get; set; } - /// public virtual string Name { get; set; } - /// public virtual string Desc { get; set; } - /// public virtual DateTime Date { get; set; } - /// } - /// public virtual List sp_Exp_SomeInputAndOutputWithDataAndReturn { get; set; } - /// public virtual string Result { get; set; } - /// public virtual string Return { get; set; } - ///} - /// - /// - public dynamic Procedures - { - get - { - if (_proc == null) - { - if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) - throw new InvalidOperationException("Database connection doesn't support stored procedures."); - - _proc = new DynamicProcedureInvoker(this); - } - - return _proc; - } - } - - /// Gets or sets a value indicating whether - /// dump commands to console or not. - public bool DumpCommands { get; set; } - - /// Gets or sets the dump command delegate. - /// The dump command delegate. - public Action DumpCommandDelegate { get; set; } - -#if NETFRAMEWORK - // https://github.com/dotnet/runtime/issues/26229 - /// Initializes a new instance of the class. - /// Database provider by name. - /// Connection string to provided database. - /// Connection options. - public DynamicDatabase(string provider, string connectionString, DynamicDatabaseOptions options) - : this(DbProviderFactories.GetFactory(provider), connectionString, options) - { - } -#endif - - /// Initializes a new instance of the class. - /// Database provider. - /// Connection string to provided database. - /// Connection options. - public DynamicDatabase(DbProviderFactory provider, string connectionString, DynamicDatabaseOptions options) - { - IsDisposed = false; - _provider = provider; - - InitCommon(connectionString, options); - } - - private DbProviderFactory FindDbProviderFactoryFromConnection(Type t) - { - foreach (var type in t.Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(DbProviderFactory)))) - { - DbProviderFactory provider = null; - bool dispose = false; - - var pi = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty); - if (pi != null) - provider = (DbProviderFactory)pi.GetValue(null, null); - else - { - var fi = type.GetField("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField); - if (fi != null) - provider = (DbProviderFactory)fi.GetValue(null); - } - - if (provider == null) - { - var ci = type.GetConstructor(Type.EmptyTypes); - if (ci != null) - { - provider = ci.Invoke(null) as DbProviderFactory; - dispose = true; - } - } - - try - { - if (provider != null) - { - using (var c = provider.CreateConnection()) - { - if (c.GetType() == t) - return provider; - } - } - } - finally - { - if (provider != null && dispose && provider is IDisposable) - ((IDisposable)provider).Dispose(); - } - } - - return null; - } - - /// Initializes a new instance of the class. - /// Active database connection. - /// Connection options. required. - public DynamicDatabase(IDbConnection connection, DynamicDatabaseOptions options) - { - // Try to find correct provider if possible - _provider = FindDbProviderFactoryFromConnection(connection.GetType()); - - IsDisposed = false; - InitCommon(connection.ConnectionString, options); - TransactionPool.Add(connection, new Stack()); - - if (!_singleConnection) - throw new InvalidOperationException("This constructor accepts only connections with DynamicDatabaseOptions.SingleConnection option."); - } - - private void InitCommon(string connectionString, DynamicDatabaseOptions options) - { - _connectionString = connectionString; - Options = options; - - _singleConnection = (options & DynamicDatabaseOptions.SingleConnection) == DynamicDatabaseOptions.SingleConnection; - _singleTransaction = (options & DynamicDatabaseOptions.SingleTransaction) == DynamicDatabaseOptions.SingleTransaction; - DumpCommands = (options & DynamicDatabaseOptions.DumpCommands) == DynamicDatabaseOptions.DumpCommands; - - TransactionPool = new Dictionary>(); - CommandsPool = new Dictionary>(); - Schema = new Dictionary>(); - RemainingBuilders = new List(); -#if !DYNAMORM_OMMIT_OLDSYNTAX - TablesCache = new Dictionary(); -#endif - } - - #endregion Properties and Constructors - - #region Table - -#if !DYNAMORM_OMMIT_OLDSYNTAX - - /// Gets dynamic table which is a simple ORM using dynamic objects. - /// The action with instance of as parameter. - /// Table name. - /// Override keys in schema. - /// Owner of the table. - public void Table(Action action, string table = "", string[] keys = null, string owner = "") - { - using (dynamic t = Table(table, keys, owner)) - action(t); - } - - /// Gets dynamic table which is a simple ORM using dynamic objects. - /// Type used to determine table name. - /// The action with instance of as parameter. - /// Override keys in schema. - public void Table(Action action, string[] keys = null) - { - using (dynamic t = Table(keys)) - action(t); - } - - /// Gets dynamic table which is a simple ORM using dynamic objects. - /// Table name. - /// Override keys in schema. - /// Owner of the table. - /// Instance of . - public dynamic Table(string table = "", string[] keys = null, string owner = "") - { - string key = string.Concat( - table == null ? string.Empty : table, - keys == null ? string.Empty : string.Join("_|_", keys)); - - DynamicTable dt = null; - lock (SyncLock) - dt = TablesCache.TryGetValue(key) ?? - TablesCache.AddAndPassValue(key, - new DynamicTable(this, table, owner, keys)); - - return dt; - } - - /// Gets dynamic table which is a simple ORM using dynamic objects. - /// Type used to determine table name. - /// Override keys in schema. - /// Instance of . - public dynamic Table(string[] keys = null) - { - Type table = typeof(T); - string key = string.Concat( - table.FullName, - keys == null ? string.Empty : string.Join("_|_", keys)); - - DynamicTable dt = null; - lock (SyncLock) - dt = TablesCache.TryGetValue(key) ?? - TablesCache.AddAndPassValue(key, - new DynamicTable(this, table, keys)); - - return dt; - } - - /// Removes cached table. - /// Disposed dynamic table. - internal void RemoveFromCache(DynamicTable dynamicTable) - { - foreach (KeyValuePair item in TablesCache.Where(kvp => kvp.Value == dynamicTable).ToList()) - TablesCache.Remove(item.Key); - } - -#endif - - #endregion Table - - /// Adds cached builder. - /// New dynamic builder. - internal void AddToCache(IDynamicQueryBuilder builder) - { - lock (SyncLock) - RemainingBuilders.Add(builder); - } - - /// Removes cached builder. - /// Disposed dynamic builder. - internal void RemoveFromCache(IDynamicQueryBuilder builder) - { - lock (SyncLock) - RemainingBuilders.Remove(builder); - } - - #region From/Insert/Update/Delete - - /// - /// Adds to the FROM clause the contents obtained by parsing the dynamic lambda expressions given. The supported - /// formats are: - /// - Resolve to a string: x => "owner.Table AS Alias", where the alias part is optional. - /// - Resolve to an expression: x => x.owner.Table.As( x.Alias ), where the alias part is optional. - /// - Generic expression: x => x( expression ).As( x.Alias ), where the alias part is mandatory. In this - /// case the alias is not annotated. - /// - /// The specification. - /// This instance to permit chaining. - public virtual IDynamicSelectQueryBuilder From(Func fn) - { - return new DynamicSelectQueryBuilder(this).From(fn); - } - - /// Adds to the FROM clause using . - /// Type which can be represented in database. - /// Table alias. - /// use no lock. - /// This instance to permit chaining. - public virtual IDynamicSelectQueryBuilder From(string alias = null, bool noLock = false) - { - // TODO: Make it more readable and maitainable - if (noLock) - { - if (string.IsNullOrEmpty(alias)) - return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).NoLock()); - else - return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias).NoLock()); - } - else - { - if (string.IsNullOrEmpty(alias)) - return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T))); - else - return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)).As(alias)); - } - } - - /// Adds to the FROM clause using . - /// Type which can be represented in database. - /// This instance to permit chaining. - public virtual IDynamicSelectQueryBuilder From(Type t) - { - return new DynamicSelectQueryBuilder(this).From(x => x(t)); - } - - /// - /// Adds to the INSERT INTO clause the contents obtained by parsing the dynamic lambda expressions given. The supported - /// formats are: - /// - Resolve to a string: x => "owner.Table". - /// - Resolve to a type: x => typeof(SomeClass). - /// - Resolve to an expression: x => x.owner.Table. - /// - Generic expression: x => x( expression ). Expression can - /// be or . - /// - /// The specification. - /// This instance to permit chaining. - public virtual IDynamicInsertQueryBuilder Insert(Func func) - { - return new DynamicInsertQueryBuilder(this).Table(func); - } - - /// Adds to the INSERT INTO clause using . - /// Type which can be represented in database. - /// This instance to permit chaining. - public virtual IDynamicInsertQueryBuilder Insert() - { - return new DynamicInsertQueryBuilder(this).Table(typeof(T)); - } - - /// Adds to the INSERT INTO clause using . - /// Type which can be represented in database. - /// This instance to permit chaining. - public virtual IDynamicInsertQueryBuilder Insert(Type t) - { - return new DynamicInsertQueryBuilder(this).Table(t); - } - - /// Bulk insert objects into database. - /// Type of objects to insert. - /// Enumerable containing instances of objects to insert. - /// Number of inserted rows. - public virtual int Insert(IEnumerable e) where T : class - { - return Insert(typeof(T), e); - } - - /// Bulk insert objects into database. - /// Type of objects to insert. - /// Enumerable containing instances of objects to insert. - /// Number of inserted rows. - public virtual int Insert(Type t, IEnumerable e) - { - int affected = 0; - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); - - if (mapper != null) - { - using (IDbConnection con = Open()) - using (IDbTransaction tra = con.BeginTransaction()) - using (IDbCommand cmd = con.CreateCommand()) - { - try - { - Dictionary parameters = new Dictionary(); - - if (!string.IsNullOrEmpty(mapper.InsertCommandText)) - { - cmd.CommandText = mapper.InsertCommandText; - - foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values - .Where(di => !di.Ignore && di.InsertCommandParameter != null) - .OrderBy(di => di.InsertCommandParameter.Ordinal)) - { - IDbDataParameter para = cmd.CreateParameter(); - para.ParameterName = col.InsertCommandParameter.Name; - para.DbType = col.InsertCommandParameter.Type; - cmd.Parameters.Add(para); - - parameters[para] = col; - } - } - else - PrepareBatchInsert(t, mapper, cmd, parameters); - - foreach (var o in e) - { - foreach (KeyValuePair m in parameters) - m.Key.Value = m.Value.Get(o); - - affected += cmd.ExecuteNonQuery(); - } - - tra.Commit(); - } - catch (Exception ex) - { - if (tra != null) - tra.Rollback(); - - affected = 0; - - StringBuilder problematicCommand = new StringBuilder(); - cmd.Dump(problematicCommand); - - throw new InvalidOperationException(problematicCommand.ToString(), ex); - } - } - } - - return affected; - } - - /// - /// Adds to the UPDATE clause the contents obtained by parsing the dynamic lambda expressions given. The supported - /// formats are: - /// - Resolve to a string: x => "owner.Table". - /// - Resolve to a type: x => typeof(SomeClass). - /// - Resolve to an expression: x => x.owner.Table. - /// - Generic expression: x => x( expression ). Expression can - /// be or . - /// - /// The specification. - /// This instance to permit chaining. - public virtual IDynamicUpdateQueryBuilder Update(Func func) - { - return new DynamicUpdateQueryBuilder(this).Table(func); - } - - /// Adds to the UPDATE clause using . - /// Type which can be represented in database. - /// This instance to permit chaining. - public virtual IDynamicUpdateQueryBuilder Update() - { - return new DynamicUpdateQueryBuilder(this).Table(typeof(T)); - } - - /// Adds to the UPDATE clause using . - /// Type which can be represented in database. - /// This instance to permit chaining. - public virtual IDynamicUpdateQueryBuilder Update(Type t) - { - return new DynamicUpdateQueryBuilder(this).Table(t); - } - - /// Bulk update objects in database. - /// Type of objects to update. - /// Enumerable containing instances of objects to update. - /// Number of updated rows. - public virtual int Update(IEnumerable e) where T : class - { - return Update(typeof(T), e); - } - - /// Bulk update objects in database. - /// Type of objects to update. - /// Enumerable containing instances of objects to update. - /// Number of updated rows. - public virtual int Update(Type t, IEnumerable e) - { - int affected = 0; - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); - - if (mapper != null) - { - using (IDbConnection con = Open()) - using (IDbTransaction tra = con.BeginTransaction()) - using (IDbCommand cmd = con.CreateCommand()) - { - try - { - Dictionary parameters = new Dictionary(); - - if (!string.IsNullOrEmpty(mapper.UpdateCommandText)) - { - cmd.CommandText = mapper.UpdateCommandText; - - foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values - .Where(di => !di.Ignore && di.UpdateCommandParameter != null) - .OrderBy(di => di.UpdateCommandParameter.Ordinal)) - { - IDbDataParameter para = cmd.CreateParameter(); - para.ParameterName = col.UpdateCommandParameter.Name; - para.DbType = col.UpdateCommandParameter.Type; - cmd.Parameters.Add(para); - - parameters[para] = col; - } - } - else - PrepareBatchUpdate(t, mapper, cmd, parameters); - - foreach (var o in e) - { - foreach (KeyValuePair m in parameters) - m.Key.Value = m.Value.Get(o); - - affected += cmd.ExecuteNonQuery(); - } - - tra.Commit(); - } - catch (Exception ex) - { - if (tra != null) - tra.Rollback(); - - affected = 0; - - StringBuilder problematicCommand = new StringBuilder(); - cmd.Dump(problematicCommand); - - throw new InvalidOperationException(problematicCommand.ToString(), ex); - } - } - } - - return affected; - } - - /// Bulk update or insert objects into database. - /// Type of objects to update or insert. - /// Enumerable containing instances of objects to update or insert. - /// Number of updated or inserted rows. - public virtual int UpdateOrInsert(IEnumerable e) where T : class - { - return UpdateOrInsert(typeof(T), e); - } - - /// Bulk update or insert objects into database. - /// Type of objects to update or insert. - /// Enumerable containing instances of objects to update or insert. - /// Number of updated or inserted rows. - public virtual int UpdateOrInsert(Type t, IEnumerable e) - { - int affected = 0; - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); - - if (mapper != null) - { - using (IDbConnection con = Open()) - using (IDbTransaction tra = con.BeginTransaction()) - using (IDbCommand cmdUp = con.CreateCommand()) - using (IDbCommand cmdIn = con.CreateCommand()) - { - try - { - #region Update - - Dictionary parametersUp = new Dictionary(); - - if (!string.IsNullOrEmpty(mapper.UpdateCommandText)) - { - cmdUp.CommandText = mapper.UpdateCommandText; - - foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values - .Where(di => !di.Ignore && di.UpdateCommandParameter != null) - .OrderBy(di => di.UpdateCommandParameter.Ordinal)) - { - IDbDataParameter para = cmdUp.CreateParameter(); - para.ParameterName = col.UpdateCommandParameter.Name; - para.DbType = col.UpdateCommandParameter.Type; - cmdUp.Parameters.Add(para); - - parametersUp[para] = col; - } - } - else - PrepareBatchUpdate(t, mapper, cmdUp, parametersUp); - - #endregion Update - - #region Insert - - Dictionary parametersIn = new Dictionary(); - - if (!string.IsNullOrEmpty(mapper.InsertCommandText)) - { - cmdIn.CommandText = mapper.InsertCommandText; - - foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values - .Where(di => !di.Ignore && di.InsertCommandParameter != null) - .OrderBy(di => di.InsertCommandParameter.Ordinal)) - { - IDbDataParameter para = cmdIn.CreateParameter(); - para.ParameterName = col.InsertCommandParameter.Name; - para.DbType = col.InsertCommandParameter.Type; - cmdIn.Parameters.Add(para); - - parametersIn[para] = col; - } - } - else - PrepareBatchInsert(t, mapper, cmdIn, parametersIn); - - #endregion Insert - - foreach (var o in e) - { - foreach (KeyValuePair m in parametersUp) - m.Key.Value = m.Value.Get(o); - - int a = cmdUp.ExecuteNonQuery(); - if (a == 0) - { - foreach (KeyValuePair m in parametersIn) - m.Key.Value = m.Value.Get(o); - - a = cmdIn.ExecuteNonQuery(); - } - - affected += a; - } - - tra.Commit(); - } - catch (Exception ex) - { - if (tra != null) - tra.Rollback(); - - affected = 0; - - StringBuilder problematicCommand = new StringBuilder(); - cmdUp.Dump(problematicCommand); - - throw new InvalidOperationException(problematicCommand.ToString(), ex); - } - } - } - - return affected; - } - - /// - /// Adds to the DELETE FROM clause the contents obtained by parsing the dynamic lambda expressions given. The supported - /// formats are: - /// - Resolve to a string: x => "owner.Table". - /// - Resolve to a type: x => typeof(SomeClass). - /// - Resolve to an expression: x => x.owner.Table. - /// - Generic expression: x => x( expression ). Expression can - /// be or . - /// - /// The specification. - /// This instance to permit chaining. - public virtual IDynamicDeleteQueryBuilder Delete(Func func) - { - return new DynamicDeleteQueryBuilder(this).Table(func); - } - - /// Adds to the DELETE FROM clause using . - /// Type which can be represented in database. - /// This instance to permit chaining. - public virtual IDynamicDeleteQueryBuilder Delete() - { - return new DynamicDeleteQueryBuilder(this).Table(typeof(T)); - } - - /// Adds to the DELETE FROM clause using . - /// Type which can be represented in database. - /// This instance to permit chaining. - public virtual IDynamicDeleteQueryBuilder Delete(Type t) - { - return new DynamicDeleteQueryBuilder(this).Table(t); - } - - /// Bulk delete objects in database. - /// Type of objects to delete. - /// Enumerable containing instances of objects to delete. - /// Number of deleted rows. - public virtual int Delete(IEnumerable e) where T : class - { - return Delete(typeof(T), e); - } - - /// Bulk delete objects in database. - /// Type of objects to delete. - /// Enumerable containing instances of objects to delete. - /// Number of deleted rows. - public virtual int Delete(Type t, IEnumerable e) - { - int affected = 0; - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(t); - - if (mapper != null) - { - using (IDbConnection con = Open()) - using (IDbTransaction tra = con.BeginTransaction()) - using (IDbCommand cmd = con.CreateCommand()) - { - try - { - Dictionary parameters = new Dictionary(); - - if (!string.IsNullOrEmpty(mapper.DeleteCommandText)) - { - cmd.CommandText = mapper.DeleteCommandText; - - foreach (DynamicPropertyInvoker col in mapper.ColumnsMap.Values - .Where(di => !di.Ignore && di.DeleteCommandParameter != null) - .OrderBy(di => di.DeleteCommandParameter.Ordinal)) - { - IDbDataParameter para = cmd.CreateParameter(); - para.ParameterName = col.DeleteCommandParameter.Name; - para.DbType = col.DeleteCommandParameter.Type; - cmd.Parameters.Add(para); - - parameters[para] = col; - } - } - else - PrepareBatchDelete(t, mapper, cmd, parameters); - - foreach (var o in e) - { - foreach (KeyValuePair m in parameters) - m.Key.Value = m.Value.Get(o); - - affected += cmd.ExecuteNonQuery(); - } - - tra.Commit(); - } - catch (Exception ex) - { - if (tra != null) - tra.Rollback(); - - affected = 0; - - StringBuilder problematicCommand = new StringBuilder(); - cmd.Dump(problematicCommand); - - throw new InvalidOperationException(problematicCommand.ToString(), ex); - } - } - } - - return affected; - } - - private void PrepareBatchInsert(DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) - { - PrepareBatchInsert(typeof(T), mapper, cmd, parameters); - } - - private void PrepareBatchInsert(Type t, DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) - { - DynamicPropertyInvoker currentprop = null; - Dictionary temp = new Dictionary(); - Dictionary schema = this.GetSchema(t); - int ord = 0; - - IDynamicInsertQueryBuilder ib = Insert(t) - .SetVirtualMode(true) - .CreateTemporaryParameterAction(p => temp[p.Name] = currentprop) - .CreateParameterAction((p, cp) => - { - parameters[cp] = temp[p.Name]; - parameters[cp].InsertCommandParameter = new DynamicPropertyInvoker.ParameterSpec - { - Name = cp.ParameterName, - Type = cp.DbType, - Ordinal = ord++, - }; - }); - - foreach (KeyValuePair prop in mapper.PropertyMap) - if (!mapper.Ignored.Contains(prop.Key)) - { - string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; - currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); - - if (currentprop.Ignore || (currentprop.Column != null && currentprop.Column.IsNoInsert)) - continue; - - if (currentprop.Get != null) - ib.Insert(new DynamicColumn() - { - ColumnName = col, - Schema = schema == null ? null : schema.TryGetNullable(col.ToLower()), - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - } - - ib.FillCommand(cmd); - - // Cache command - mapper.InsertCommandText = cmd.CommandText; - } - - private void PrepareBatchUpdate(DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) - { - PrepareBatchUpdate(typeof(T), mapper, cmd, parameters); - } - - private void PrepareBatchUpdate(Type t, DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) - { - DynamicPropertyInvoker currentprop = null; - Dictionary temp = new Dictionary(); - Dictionary schema = this.GetSchema(t); - int ord = 0; - - IDynamicUpdateQueryBuilder ib = Update(t) - .SetVirtualMode(true) - .CreateTemporaryParameterAction(p => temp[p.Name] = currentprop) - .CreateParameterAction((p, cp) => - { - parameters[cp] = temp[p.Name]; - parameters[cp].UpdateCommandParameter = new DynamicPropertyInvoker.ParameterSpec - { - Name = cp.ParameterName, - Type = cp.DbType, - Ordinal = ord++, - }; - }); - - foreach (KeyValuePair prop in mapper.PropertyMap) - if (!mapper.Ignored.Contains(prop.Key)) - { - string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; - currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); - - if (currentprop.Ignore) - continue; - - if (currentprop.Get != null) - { - DynamicSchemaColumn? colS = schema == null ? null : schema.TryGetNullable(col.ToLower()); - - if (colS.HasValue) - { - if (colS.Value.IsKey) - ib.Where(new DynamicColumn() - { - ColumnName = col, - Schema = colS, - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - else if (currentprop.Column == null || !currentprop.Column.IsNoUpdate) - ib.Values(new DynamicColumn() - { - ColumnName = col, - Schema = colS, - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - } - else if (currentprop.Column != null && currentprop.Column.IsKey) - ib.Where(new DynamicColumn() - { - ColumnName = col, - Schema = colS, - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - } - } - - ib.FillCommand(cmd); - - // Cache command - mapper.UpdateCommandText = cmd.CommandText; - } - - private void PrepareBatchDelete(DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) - { - PrepareBatchDelete(typeof(T), mapper, cmd, parameters); - } - - private void PrepareBatchDelete(Type t, DynamicTypeMap mapper, IDbCommand cmd, Dictionary parameters) - { - DynamicPropertyInvoker currentprop = null; - Dictionary temp = new Dictionary(); - Dictionary schema = this.GetSchema(t); - int ord = 0; - - IDynamicDeleteQueryBuilder ib = Delete(t) - .SetVirtualMode(true) - .CreateTemporaryParameterAction(p => temp[p.Name] = currentprop) - .CreateParameterAction((p, cp) => - { - parameters[cp] = temp[p.Name]; - parameters[cp].DeleteCommandParameter = new DynamicPropertyInvoker.ParameterSpec - { - Name = cp.ParameterName, - Type = cp.DbType, - Ordinal = ord++, - }; - }); - - foreach (KeyValuePair prop in mapper.PropertyMap) - if (!mapper.Ignored.Contains(prop.Key)) - { - string col = mapper.PropertyMap.TryGetValue(prop.Key) ?? prop.Key; - currentprop = mapper.ColumnsMap.TryGetValue(col.ToLower()); - - if (currentprop.Ignore) - continue; - - if (currentprop.Get != null) - { - DynamicSchemaColumn? colS = schema == null ? null : schema.TryGetNullable(col.ToLower()); - - if (colS != null) - { - if (colS.Value.IsKey) - ib.Where(new DynamicColumn() - { - ColumnName = col, - Schema = colS, - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - } - else if (currentprop.Column != null && currentprop.Column.IsKey) - ib.Where(new DynamicColumn() - { - ColumnName = col, - Schema = colS, - Operator = DynamicColumn.CompareOperator.Eq, - Value = null, - VirtualColumn = true, - }); - } - } - - ib.FillCommand(cmd); - - // Cache command - mapper.DeleteCommandText = cmd.CommandText; - } - - #endregion From/Insert/Update/Delete - - #region Procedure - - /// Execute stored procedure. - /// Name of stored procedure to execute. - /// Number of affected rows. - public virtual int Procedure(string procName) - { - return Procedure(procName, (DynamicExpando)null); - } - - /// Execute stored procedure. - /// Name of stored procedure to execute. - /// Arguments (parameters) in form of expando object. - /// Number of affected rows. - public virtual int Procedure(string procName, params object[] args) - { - if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) - throw new InvalidOperationException("Database connection desn't support stored procedures."); - - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(CommandType.StoredProcedure, procName) - .AddParameters(this, args) - .ExecuteNonQuery(); - } - } - - /// Execute stored procedure. - /// Name of stored procedure to execute. - /// Arguments (parameters) in form of expando object. - /// Number of affected rows. - public virtual int Procedure(string procName, DynamicExpando args) - { - if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) - throw new InvalidOperationException("Database connection desn't support stored procedures."); - - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(CommandType.StoredProcedure, procName) - .AddParameters(this, args) - .ExecuteNonQuery(); - } - } - - /// Execute stored procedure. - /// Name of stored procedure to execute. - /// Arguments (parameters) in form of expando object. - /// Number of affected rows. - public virtual int Procedure(string procName, ExpandoObject args) - { - if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) - throw new InvalidOperationException("Database connection desn't support stored procedures."); - - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(CommandType.StoredProcedure, procName) - .AddParameters(this, args) - .ExecuteNonQuery(); - } - } - - #endregion Procedure - - #region Execute - - /// Execute non query. - /// SQL query containing numbered parameters in format provided by - /// methods. Also names should be formatted with - /// method. - /// Arguments (parameters). - /// Number of affected rows. - public virtual int Execute(string sql, params object[] args) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(sql).AddParameters(this, args) - .ExecuteNonQuery(); - } - } - - /// Execute non query. - /// Command builder. - /// Number of affected rows. - public virtual int Execute(IDynamicQueryBuilder builder) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(builder) - .ExecuteNonQuery(); - } - } - - /// Execute non query. - /// Command builders. - /// Number of affected rows. - public virtual int Execute(IDynamicQueryBuilder[] builders) - { - int ret = 0; - - using (IDbConnection con = Open()) - { - using (IDbTransaction trans = con.BeginTransaction()) - { - foreach (IDynamicQueryBuilder builder in builders) - using (IDbCommand cmd = con.CreateCommand()) - ret += cmd - .SetCommand(builder) - .ExecuteNonQuery(); - - trans.Commit(); - } - } - - return ret; - } - - #endregion Execute - - #region Scalar - - /// Returns a single result. - /// SQL query containing numbered parameters in format provided by - /// methods. Also names should be formatted with - /// method. - /// Arguments (parameters). - /// Result of a query. - public virtual object Scalar(string sql, params object[] args) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(sql).AddParameters(this, args) - .ExecuteScalar(); - } - } - - /// Returns a single result. - /// Command builder. - /// Result of a query. - public virtual object Scalar(IDynamicQueryBuilder builder) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(builder) - .ExecuteScalar(); - } - } - -#if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE - - /// Returns a single result. - /// What kind of result is expected. - /// SQL query containing numbered parameters in format provided by - /// methods. Also names should be formatted with - /// method. - /// Arguments (parameters). - /// Result of a query. - public virtual T ScalarAs(string sql, params object[] args) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(sql).AddParameters(this, args) - .ExecuteScalarAs(); - } - } - - /// Returns a single result. - /// What kind of result is expected. - /// Command builder. - /// Default value. - /// Result of a query. - public virtual T ScalarAs(IDynamicQueryBuilder builder, T defaultValue = default(T)) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - return cmd - .SetCommand(builder) - .ExecuteScalarAs(defaultValue); - } - } - -#endif - - #endregion Scalar - - #region Query - - /// Enumerate the reader and yield the result. - /// SQL query containing numbered parameters in format provided by - /// methods. Also names should be formatted with - /// method. - /// Arguments (parameters). - /// Enumerator of objects expanded from query. - public virtual IEnumerable Query(string sql, params object[] args) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(sql) - .AddParameters(this, args) - .ExecuteReader()) - using (IDataReader cache = new DynamicCachedReader(rdr)) - { - while (cache.Read()) - { - dynamic val = null; - - // Work around to avoid yield being in try...catch block: - // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch - try - { - val = cache.RowToDynamic(); - } - catch (ArgumentException argex) - { - StringBuilder sb = new StringBuilder(); - cmd.Dump(sb); - - throw new ArgumentException( - string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb), - argex.InnerException.NullOr(a => a, argex)); - } - - yield return val; - } - } - } - } - - /// Enumerate the reader and yield the result. - /// Command builder. - /// Enumerator of objects expanded from query. - public virtual IEnumerable Query(IDynamicQueryBuilder builder) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(builder) - .ExecuteReader()) - using (var cache = new DynamicCachedReader(rdr)) - while (cache.Read()) - { - dynamic val = null; - - // Work around to avoid yield being in try...catch block: - // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch - try - { - val = cache.RowToDynamic(); - } - catch (ArgumentException argex) - { - StringBuilder sb = new StringBuilder(); - cmd.Dump(sb); - - throw new ArgumentException( - string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb), - argex.InnerException.NullOr(a => a, argex)); - } - - yield return val; - } - } - } - - #endregion Query - - #region CachedQuery - - /// Enumerate the reader and yield the result. - /// SQL query containing numbered parameters in format provided by - /// methods. Also names should be formatted with - /// method. - /// Arguments (parameters). - /// Enumerator of objects expanded from query. - public virtual DynamicCachedReader CachedQuery(string sql, params object[] args) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(sql) - .AddParameters(this, args) - .ExecuteReader()) - return new DynamicCachedReader(rdr); - } - } - - /// Enumerate the reader and yield the result. - /// Command builder. - /// Enumerator of objects expanded from query. - public virtual DynamicCachedReader CachedQuery(IDynamicQueryBuilder builder) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(builder) - .ExecuteReader()) - return new DynamicCachedReader(rdr); - } - } - - #endregion Query - - #region Schema - - /// Builds query cache if necessary and returns it. - /// The builder containing query to read schema from. - /// Query schema. - public Dictionary GetQuerySchema(IDynamicSelectQueryBuilder builder) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand().SetCommand(builder)) - return ReadSchema(cmd) - .Distinct() - .ToDictionary(k => k.Name.ToLower(), k => k); - } - - /// Builds query cache if necessary and returns it. - /// SQL query from which read schema. - /// SQL query arguments. - /// Query schema. - public Dictionary GetQuerySchema(string sql, params object[] args) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand().SetCommand(sql, args)) - return ReadSchema(cmd) - .Distinct() - .ToDictionary(k => k.Name.ToLower(), k => k); - } - - /// Builds table cache if necessary and returns it. - /// Name of table for which build schema. - /// Owner of table for which build schema. - /// Table schema. - public Dictionary GetSchema(string table, string owner = null) - { - Dictionary schema = null; - - lock (SyncLock) - schema = Schema.TryGetValue(table.ToLower()) ?? - BuildAndCacheSchema(table, null, owner); - - return schema; - } - - /// Builds table cache if necessary and returns it. - /// Type of table for which build schema. - /// Table schema or null if type was anonymous. - public Dictionary GetSchema() - { - if (typeof(T).IsAnonymous()) - return null; - - Dictionary schema = null; - - lock (SyncLock) - schema = Schema.TryGetValue(typeof(T).FullName) ?? - BuildAndCacheSchema(null, DynamicMapperCache.GetMapper()); - - return schema; - } - - /// Builds table cache if necessary and returns it. - /// Type of table for which build schema. - /// Table schema or null if type was anonymous. - public Dictionary GetSchema(Type table) - { - if (table == null || table.IsAnonymous() || table.IsValueType) - return null; - - Dictionary schema = null; - - lock (SyncLock) - schema = Schema.TryGetValue(table.FullName) ?? - BuildAndCacheSchema(null, DynamicMapperCache.GetMapper(table)); - - return schema; - } - - /// Clears the schema from cache. - /// Use this method to refresh table information. - /// Name of table for which clear schema. - /// Owner of table for which clear schema. - public void ClearSchema(string table = null, string owner = null) - { - lock (SyncLock) - if (Schema.ContainsKey(table.ToLower())) - Schema.Remove(table.ToLower()); - } - - /// Clears the schema from cache. - /// Use this method to refresh table information. - /// Type of table for which clear schema. - public void ClearSchema() - { - ClearSchema(typeof(T)); - } - - /// Clears the schema from cache. - /// Use this method to refresh table information. - /// Type of table for which clear schema. - public void ClearSchema(Type table) - { - lock (SyncLock) - if (Schema.ContainsKey(table.FullName)) - { - if (Schema[table.FullName] != null) - Schema[table.FullName].Clear(); - Schema.Remove(table.FullName); - } - } - - /// Clears the all schemas from cache. - /// Use this method to refresh all table information. - public void ClearSchema() - { - lock (SyncLock) - { - foreach (KeyValuePair> s in Schema) - if (s.Value != null) - s.Value.Clear(); - - Schema.Clear(); - } - } - - /// Get schema describing objects from reader. - /// Table from which extract column info. - /// Owner of table from which extract column info. - /// List of objects . - /// If your database doesn't get those values in upper case (like most of the databases) you should override this method. - protected virtual IList ReadSchema(string table, string owner) - { - using (IDbConnection con = Open()) - using (IDbCommand cmd = con.CreateCommand() - .SetCommand(string.Format("SELECT * FROM {0}{1} WHERE 1 = 0", - !string.IsNullOrEmpty(owner) ? string.Format("{0}.", DecorateName(owner)) : string.Empty, - DecorateName(table)))) - return ReadSchema(cmd) - .ToList(); - } - - /// Get schema describing objects from reader. - /// Command containing query to execute. - /// List of objects . - /// If your database doesn't get those values in upper case (like most of the databases) you should override this method. - public virtual IEnumerable ReadSchema(IDbCommand cmd) - { - DataTable st = null; - - using (IDataReader rdr = cmd.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo)) - st = rdr.GetSchemaTable(); - - using (st) - foreach (DataRow col in st.Rows) - { - dynamic c = col.RowToDynamicUpper(); - - yield return new DynamicSchemaColumn - { - Name = c.COLUMNNAME, - Type = ReadSchemaType(c), - IsKey = c.ISKEY ?? false, - IsUnique = c.ISUNIQUE ?? false, - AllowNull = c.ALLOWNULL ?? false, - Size = (int)(c.COLUMNSIZE ?? 0), - Precision = (byte)(c.NUMERICPRECISION ?? 0), - Scale = (byte)(c.NUMERICSCALE ?? 0) - }; - } - } - - /// Reads the type of column from the schema. - /// The schema. - /// Generic parameter type. - protected virtual DbType ReadSchemaType(dynamic schema) - { - Type type = (Type)schema.DATATYPE; - - // Small hack for SQL Server Provider - if (type == typeof(string) && Provider != null && Provider.GetType().Name == "SqlClientFactory") - { - var map = schema as IDictionary; - string typeName = (map.TryGetValue("DATATYPENAME") ?? string.Empty).ToString(); - - switch (typeName) - { - case "varchar": - return DbType.AnsiString; - - case "nvarchar": - return DbType.String; - } - } - - return DynamicExtensions.TypeMap.TryGetNullable(type) ?? DbType.String; - } - - private Dictionary BuildAndCacheSchema(string tableName, DynamicTypeMap mapper, string owner = null) - { - Dictionary schema = null; - - if (mapper != null) - { - tableName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? - mapper.Type.Name : mapper.Table.Name; - owner = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Owner) ? - null : mapper.Table.Owner; - } - - bool databaseSchemaSupport = !string.IsNullOrEmpty(tableName) && - (Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema; - bool mapperSchema = mapper != null && mapper.Table != null && (mapper.Table.Override || !databaseSchemaSupport); - - #region Database schema - - if (databaseSchemaSupport && !Schema.ContainsKey(tableName.ToLower())) - { - schema = ReadSchema(tableName, owner) - .Where(x => x.Name != null) - .DistinctBy(x => x.Name.ToLower()) - .ToDictionary(k => k.Name.ToLower(), k => k); - - Schema[tableName.ToLower()] = schema; - } - - #endregion Database schema - - #region Type schema - - if ((mapperSchema && !Schema.ContainsKey(mapper.Type.FullName)) || - (schema == null && mapper != null && !mapper.Type.IsAnonymous())) - { - // TODO: Ged rid of this monster below... - if (databaseSchemaSupport) - { - #region Merge with db schema - - schema = mapper.ColumnsMap.ToDictionary(k => k.Key, (v) => - { - DynamicSchemaColumn? col = Schema[tableName.ToLower()].TryGetNullable(v.Key); - - return new DynamicSchemaColumn - { - Name = DynamicExtensions.Coalesce( - v.Value.Column == null || string.IsNullOrEmpty(v.Value.Column.Name) ? null : v.Value.Column.Name, - col.HasValue && !string.IsNullOrEmpty(col.Value.Name) ? col.Value.Name : null, - v.Value.Name), - IsKey = DynamicExtensions.CoalesceNullable( - v.Value.Column != null ? v.Value.Column.IsKey : false, - col.HasValue ? col.Value.IsKey : false).Value, - Type = DynamicExtensions.CoalesceNullable( - v.Value.Column != null ? v.Value.Column.Type : null, - col.HasValue ? col.Value.Type : DynamicExtensions.TypeMap.TryGetNullable(v.Value.Type) ?? DbType.String).Value, - IsUnique = DynamicExtensions.CoalesceNullable( - v.Value.Column != null ? v.Value.Column.IsUnique : null, - col.HasValue ? col.Value.IsUnique : false).Value, - AllowNull = DynamicExtensions.CoalesceNullable( - v.Value.Column != null ? v.Value.Column.AllowNull : true, - col.HasValue ? col.Value.AllowNull : true).Value, - Size = DynamicExtensions.CoalesceNullable( - v.Value.Column != null ? v.Value.Column.Size : null, - col.HasValue ? col.Value.Size : 0).Value, - Precision = DynamicExtensions.CoalesceNullable( - v.Value.Column != null ? v.Value.Column.Precision : null, - col.HasValue ? col.Value.Precision : (byte)0).Value, - Scale = DynamicExtensions.CoalesceNullable( - v.Value.Column != null ? v.Value.Column.Scale : null, - col.HasValue ? col.Value.Scale : (byte)0).Value, - }; - }); - - #endregion Merge with db schema - } - else - { - #region MapEnumerable based only on type - - schema = mapper.ColumnsMap.ToDictionary(k => k.Key, - v => new DynamicSchemaColumn - { - Name = DynamicExtensions.Coalesce(v.Value.Column == null || string.IsNullOrEmpty(v.Value.Column.Name) ? null : v.Value.Column.Name, v.Value.Name), - IsKey = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.IsKey : false, false).Value, - Type = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Type : null, DynamicExtensions.TypeMap.TryGetNullable(v.Value.Type) ?? DbType.String).Value, - IsUnique = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.IsUnique : null, false).Value, - AllowNull = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.AllowNull : true, true).Value, - Size = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Size : null, 0).Value, - Precision = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Precision : null, 0).Value, - Scale = DynamicExtensions.CoalesceNullable(v.Value.Column != null ? v.Value.Column.Scale : null, 0).Value, - }); - - #endregion MapEnumerable based only on type - } - } - - if (mapper != null && schema != null) - Schema[mapper.Type.FullName] = schema; - - #endregion Type schema - - return schema; - } - - #endregion Schema - - #region Decorators - - /// Gets or sets left side decorator for database objects. - public string LeftDecorator - { - get { return _leftDecorator; } - set - { - _leftDecorator = value; - _leftDecoratorIsInInvalidMembersChars = - _leftDecorator.Length == 1 && StringExtensions.InvalidMemberChars.Contains(_leftDecorator[0]); - } - } - - /// Gets or sets right side decorator for database objects. - public string RightDecorator - { - get { return _rightDecorator; } - set - { - _rightDecorator = value; - _rightDecoratorIsInInvalidMembersChars = - _rightDecorator.Length == 1 && StringExtensions.InvalidMemberChars.Contains(_rightDecorator[0]); - } - } - - /// Gets or sets parameter name format. - public string ParameterFormat { get { return _parameterFormat; } set { _parameterFormat = value; } } - - /// Decorate string representing name of database object. - /// Name of database object. - /// Decorated name of database object. - public string DecorateName(string name) - { - return String.Concat(_leftDecorator, name, _rightDecorator); - } - - /// Strip string representing name of database object from decorators. - /// Decorated name of database object. - /// Not decorated name of database object. - public string StripName(string name) - { - string res = name.Trim(StringExtensions.InvalidMemberChars); - - if (!_leftDecoratorIsInInvalidMembersChars && res.StartsWith(_leftDecorator)) - res = res.Substring(_leftDecorator.Length); - - if (!_rightDecoratorIsInInvalidMembersChars && res.EndsWith(_rightDecorator)) - res = res.Substring(0, res.Length - _rightDecorator.Length); - - return res; - } - - /// Decorate string representing name of database object. - /// String builder to which add decorated name. - /// Name of database object. - public void DecorateName(StringBuilder sb, string name) - { - sb.Append(_leftDecorator); - sb.Append(name); - sb.Append(_rightDecorator); - } - - /// Get database parameter name. - /// Friendly parameter name or number. - /// Formatted parameter name. - public string GetParameterName(object parameter) - { - return String.Format(_parameterFormat, parameter).Replace(" ", "_"); - } - - /// Get database parameter name. - /// String builder to which add parameter name. - /// Friendly parameter name or number. - public void GetParameterName(StringBuilder sb, object parameter) - { - sb.AppendFormat(_parameterFormat, parameter.ToString().Replace(" ", "_")); - } - - /// Dumps the command into console output. - /// The command to dump. - public virtual void DumpCommand(IDbCommand cmd) - { - if (DumpCommandDelegate != null) - DumpCommandDelegate(cmd, cmd.DumpToString()); - else - cmd.Dump(Console.Out); - } - - #endregion Decorators - - #region Connection - - /// Open managed connection. - /// Opened connection. - public IDbConnection Open() - { - IDbConnection conn = null; - DynamicConnection ret = null; - bool opened = false; - - lock (SyncLock) - { - if (_tempConn == null) - { - if (TransactionPool.Count == 0 || !_singleConnection) - { - conn = _provider.CreateConnection(); - conn.ConnectionString = _connectionString; - conn.Open(); - opened = true; - - TransactionPool.Add(conn, new Stack()); - CommandsPool.Add(conn, new List()); - } - else - { - conn = TransactionPool.Keys.First(); - - if (conn.State != ConnectionState.Open) - { - conn.Open(); - opened = true; - } - } - - ret = new DynamicConnection(this, conn, _singleTransaction); - } - else - ret = _tempConn; - } - - if (opened) - ExecuteInitCommands(ret); - - return ret; - } - - /// Close connection if we are allowed to. - /// Connection to manage. - internal void Close(IDbConnection connection) - { - if (connection == null) - return; - - if (!_singleConnection && connection != null && TransactionPool.ContainsKey(connection)) - { - // Close all commands - if (CommandsPool.ContainsKey(connection)) - { - List tmp = CommandsPool[connection].ToList(); - tmp.ForEach(cmd => cmd.Dispose()); - - CommandsPool[connection].Clear(); - } - - // Rollback remaining transactions - while (TransactionPool[connection].Count > 0) - { - IDbTransaction trans = TransactionPool[connection].Pop(); - trans.Rollback(); - trans.Dispose(); - } - - // Close connection - if (connection.State == ConnectionState.Open) - connection.Close(); - - // remove from pools - lock (SyncLock) - { - TransactionPool.Remove(connection); - CommandsPool.Remove(connection); - } - - // Set stamp - _poolStamp = DateTime.Now.Ticks; - - // Dispose the corpse - connection.Dispose(); - connection = null; - } - } - - /// Gets or sets contains commands executed when connection is opened. - public List InitCommands { get; set; } - - private void ExecuteInitCommands(IDbConnection conn) - { - if (InitCommands != null) - using (IDbCommand command = conn.CreateCommand()) - foreach (string commandText in InitCommands) - command - .SetCommand(commandText) - .ExecuteNonQuery(); - } - - #endregion Connection - - #region Transaction - - /// Begins a global database transaction. - /// Using this method connection is set to single open - /// connection until all transactions are finished. - /// Returns representation. - public IDbTransaction BeginTransaction() - { - _tempConn = Open() as DynamicConnection; - - return _tempConn.BeginTransaction(null, null, () => - { - Stack t = TransactionPool.TryGetValue(_tempConn.Connection); - - if (t == null | t.Count == 0) - { - _tempConn.Dispose(); - _tempConn = null; - } - }); - } - - /// Begins a database transaction with the specified - /// value. - /// One of the values. - /// Returns representation. - public IDbTransaction BeginTransaction(IsolationLevel il) - { - _tempConn = Open() as DynamicConnection; - - return _tempConn.BeginTransaction(il, null, () => - { - Stack t = TransactionPool.TryGetValue(_tempConn.Connection); - - if (t == null | t.Count == 0) - { - _tempConn.Dispose(); - _tempConn = null; - } - }); - } - - /// Begins a database transaction with the specified - /// value. - /// Custom parameter describing transaction options. - /// Returns representation. - public IDbTransaction BeginTransaction(object custom) - { - _tempConn = Open() as DynamicConnection; - - return _tempConn.BeginTransaction(null, custom, () => - { - Stack t = TransactionPool.TryGetValue(_tempConn.Connection); - - if (t == null | t.Count == 0) - { - _tempConn.Dispose(); - _tempConn = null; - } - }); - } - - #endregion Transaction - - #region IExtendedDisposable Members - - /// Performs application-defined tasks associated with freeing, - /// releasing, or resetting unmanaged resources. - public void Dispose() - { - if (IsDisposed) - return; - -#if !DYNAMORM_OMMIT_OLDSYNTAX - List tables = TablesCache.Values.ToList(); - TablesCache.Clear(); - - tables.ForEach(t => t.Dispose()); - tables.Clear(); - tables = null; -#endif - - foreach (KeyValuePair> con in TransactionPool) - { - // Close all commands - if (CommandsPool.ContainsKey(con.Key)) - { - List tmp = CommandsPool[con.Key].ToList(); - tmp.ForEach(cmd => cmd.Dispose()); - - CommandsPool[con.Key].Clear(); - tmp.Clear(); - CommandsPool[con.Key] = tmp = null; - } - - // Rollback remaining transactions - while (con.Value.Count > 0) - { - IDbTransaction trans = con.Value.Pop(); - trans.Rollback(); - trans.Dispose(); - } - - // Close connection - if (con.Key.State == ConnectionState.Open) - con.Key.Close(); - - // Dispose it - con.Key.Dispose(); - } - - while (RemainingBuilders.Count > 0) - RemainingBuilders.First().Dispose(); - - // Clear pools - lock (SyncLock) - { - TransactionPool.Clear(); - CommandsPool.Clear(); - RemainingBuilders.Clear(); - - TransactionPool = null; - CommandsPool = null; - RemainingBuilders = null; - } - - ClearSchema(); - if (_proc != null) - _proc.Dispose(); - - _proc = null; - _tempConn = null; - IsDisposed = true; - } - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - #endregion IExtendedDisposable Members - } - - /// Dynamic query exception. - [Serializable] - public class DynamicQueryException : Exception, ISerializable - { - /// Initializes a new instance of the - /// class. - /// The command which failed. - public DynamicQueryException(IDbCommand command = null) - : base(string.Format("Error executing command.{0}{1}", Environment.NewLine, command != null ? command.DumpToString() : string.Empty)) - { - } - - /// Initializes a new instance of the - /// class. - /// The message. - /// The command which failed. - public DynamicQueryException(string message, IDbCommand command = null) - : base(string.Format("{0}{1}{2}", message, Environment.NewLine, command != null ? command.DumpToString() : string.Empty)) - { - } - - /// Initializes a new instance of the - /// class. - /// The inner exception. - /// The command which failed. - public DynamicQueryException(Exception innerException, IDbCommand command = null) - : base(string.Format("Error executing command.{0}{1}", Environment.NewLine, command != null ? command.DumpToString() : string.Empty), innerException) - { - } - - /// Initializes a new instance of the - /// class. - /// The message. - /// The inner exception. - /// The command which failed. - public DynamicQueryException(string message, Exception innerException, IDbCommand command = null) - : base(string.Format("{0}{1}{2}", message, Environment.NewLine, command != null ? command.DumpToString() : string.Empty), innerException) - { - } - } - - /// Cache data reader in memory. - public class DynamicCachedReader : DynamicObject, IDataReader - { - #region Constructor and Data - - private class Data - { - internal DataTable _schema; - internal int _fields; - internal int _rows; - internal int _rowsAffected; - internal int _position = -1; - internal int _cachePos = -1; - - internal IList _names; - internal IDictionary _ordinals; - internal IList _types; - internal IList _cache; - } - - private List _data = new List(); - private int _currentDataPosition = 0; - private bool _isDisposed; - - private DynamicCachedReader() - { - } - - /// Initializes a new instance of the class. - /// Reader to cache. - /// The offset row. - /// The limit to number of tows. -1 is no limit. - /// The progress delegate. - public DynamicCachedReader(IDataReader reader, int offset = 0, int limit = -1, Func progress = null) - { - InitDataReader(reader, offset, limit, progress); - } - - #endregion Constructor and Data - - #region Helpers - - /// Create data reader from dynamic enumerable. - /// List of objects. - /// Instance of containing objects data. - public static DynamicCachedReader FromDynamicEnumerable(IEnumerable objects) - { - var first = (objects as IEnumerable).FirstOrDefault(); - - if (first == null) - return null; - - var firstDict = first as IDictionary; - var r = new DynamicCachedReader(); - r.Init(firstDict.Keys.Count); - - r._data.Add(new Data()); - - var data = r._data[r._currentDataPosition]; - - for (int i = 0; i < firstDict.Keys.Count; i++) - data._types.Add(null); - - foreach (dynamic elem in (objects as IEnumerable)) - { - int c = 0; - var dict = elem as IDictionary; - - foreach (var k in firstDict.Keys) - { - object val = dict[k]; - - data._cache.Add(val); - - if (data._types[c] == null && val != null) - data._types[c] = val.GetType(); - - c++; - } - - data._rows++; - } - - for (int i = 0; i < firstDict.Keys.Count; i++) - if (data._types[i] == null) - data._types[i] = typeof(string); - - data._schema = new DataTable("DYNAMIC"); - data._schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); - data._schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); - data._schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); - data._schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); - data._schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); - data._schema.Columns.Add(new DataColumn("DataType", typeof(Type))); - data._schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); - data._schema.Columns.Add(new DataColumn("NativeType", typeof(int))); - data._schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); - - int ordinal = 0; - DataRow dr = null; - - foreach (var column in firstDict.Keys) - { - dr = data._schema.NewRow(); - - dr[0] = column; - dr[1] = ordinal; - dr[2] = 0; - dr[3] = 0; - dr[4] = 0; - dr[5] = data._types[ordinal]; - dr[6] = data._types[ordinal].ToDbType(); - dr[7] = data._types[ordinal].ToDbType(); - dr[8] = true; - dr[9] = false; - dr[10] = false; - dr[11] = false; - - data._schema.Rows.Add(dr); - - data._names.Add(dr[0].ToString()); - data._ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); - data._types.Add((Type)dr[5]); - - dr.AcceptChanges(); - } - - dr.AcceptChanges(); - - return r; - } - - /// Create data reader from enumerable. - /// Type of enumerated objects. - /// List of objects. - /// Instance of containing objects data. - public static DynamicCachedReader FromEnumerable(IEnumerable objects) - { - var mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidCastException(string.Format("Object type '{0}' can't be mapped.", typeof(T).FullName)); - - var r = new DynamicCachedReader(); - r.Init(mapper.ColumnsMap.Count + 1); - r.CreateSchemaTable(mapper); - r.FillFromEnumerable(objects, mapper); - - r.IsClosed = false; - - return r; - } - - /// Create data reader from enumerable. - /// Type of enumerated objects. - /// List of objects. - /// Instance of containing objects data. - public static DynamicCachedReader FromEnumerable(Type elementType, IEnumerable objects) - { - var mapper = DynamicMapperCache.GetMapper(elementType); - - if (mapper == null) - throw new InvalidCastException(string.Format("Object type '{0}' can't be mapped.", elementType.FullName)); - - var r = new DynamicCachedReader(); - r.Init(mapper.ColumnsMap.Count + 1); - r.CreateSchemaTable(mapper); - r.FillFromEnumerable(elementType, objects, mapper); - - r.IsClosed = false; - - return r; - } - - private void InitDataReader(IDataReader reader, int offset = 0, int limit = -1, Func progress = null) - { - do - { - Init(reader.FieldCount); - - var data = _data[_currentDataPosition]; - - data._schema = reader.GetSchemaTable(); - data._rowsAffected = reader.RecordsAffected; - - int i = 0; - - for (i = 0; i < data._fields; i++) - { - data._names.Add(reader.GetName(i)); - data._types.Add(reader.GetFieldType(i)); - - if (!data._ordinals.ContainsKey(reader.GetName(i).ToUpper())) - data._ordinals.Add(reader.GetName(i).ToUpper(), i); - } - - int current = 0; - while (reader.Read()) - { - if (current < offset) - { - current++; - continue; - } - - for (i = 0; i < data._fields; i++) - data._cache.Add(reader[i]); - - data._rows++; - current++; - - if (limit >= 0 && data._rows >= limit) - break; - - if (progress != null && !progress(this, data._rows)) - break; - } - - IsClosed = false; - data._position = -1; - data._cachePos = -1; - - if (progress != null) - progress(this, data._rows); - } while (reader.NextResult()); - - reader.Close(); - } - - private void FillFromEnumerable(IEnumerable objects, DynamicTypeMap mapper) - { - var data = _data[_currentDataPosition]; - foreach (var elem in objects) - { - foreach (var col in mapper.ColumnsMap) - { - object val = null; - - if (col.Value.Get != null) - val = col.Value.Get(elem); - - data._cache.Add(val); - } - - data._cache.Add(elem); - - data._rows++; - } - } - - private void FillFromEnumerable(Type elementType, IEnumerable objects, DynamicTypeMap mapper) - { - var data = _data[_currentDataPosition]; - - foreach (var elem in objects) - { - foreach (var col in mapper.ColumnsMap) - { - object val = null; - - if (col.Value.Get != null) - val = col.Value.Get(elem); - - data._cache.Add(val); - } - - data._cache.Add(elem); - - data._rows++; - } - } - - private void CreateSchemaTable(DynamicTypeMap mapper) - { - var data = _data[_currentDataPosition]; - data._schema = new DataTable("DYNAMIC"); - data._schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); - data._schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); - data._schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); - data._schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); - data._schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); - data._schema.Columns.Add(new DataColumn("DataType", typeof(Type))); - data._schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); - data._schema.Columns.Add(new DataColumn("NativeType", typeof(int))); - data._schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); - data._schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); - - int ordinal = 0; - DataRow dr = null; - - foreach (var column in mapper.ColumnsMap) - { - dr = data._schema.NewRow(); - - dr[0] = column.Value.Column.NullOr(x => x.Name ?? column.Value.Name, column.Value.Name); - dr[1] = ordinal; - dr[2] = column.Value.Column.NullOr(x => x.Size ?? int.MaxValue, int.MaxValue); - dr[3] = column.Value.Column.NullOr(x => x.Precision ?? 0, 0); - dr[4] = column.Value.Column.NullOr(x => x.Scale ?? 0, 0); - dr[5] = column.Value.Column.NullOr(x => x.Type.HasValue ? x.Type.Value.ToType() : column.Value.Type, column.Value.Type); - dr[6] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), column.Value.Type.ToDbType()); - dr[7] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), column.Value.Type.ToDbType()); - dr[8] = column.Value.Column.NullOr(x => x.IsKey, false) ? true : column.Value.Column.NullOr(x => x.AllowNull, true); - dr[9] = column.Value.Column.NullOr(x => x.IsUnique, false); - dr[10] = column.Value.Column.NullOr(x => x.IsKey, false); - dr[11] = false; - - data._schema.Rows.Add(dr); - - data._names.Add(dr[0].ToString()); - data._ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); - data._types.Add((Type)dr[5]); - - dr.AcceptChanges(); - } - - dr = data._schema.NewRow(); - - dr[0] = "#O"; - dr[1] = ordinal; - dr[2] = int.MaxValue; - dr[3] = 0; - dr[4] = 0; - dr[5] = mapper.Type; - dr[6] = DbType.Object; - dr[7] = DbType.Object; - dr[8] = true; - dr[9] = false; - dr[10] = false; - dr[11] = false; - - data._schema.Rows.Add(dr); - - data._names.Add("#O"); - data._ordinals.Add("#O".ToUpper(), ordinal++); - data._types.Add(mapper.Type); - - dr.AcceptChanges(); - } - - private void Init(int fieldCount) - { - _data.Add(new Data() - { - _rows = 0, - _fields = fieldCount, - _names = new List(fieldCount), - _ordinals = new Dictionary(fieldCount), - _types = new List(fieldCount), - _cache = new List(fieldCount * 100), - }); - - _currentDataPosition = _data.Count - 1; - } - - /// Sets the current position in reader. - /// The position. - public void SetPosition(int pos) - { - if (pos >= -1 && pos < _data[_currentDataPosition]._rows) - { - _data[_currentDataPosition]._position = pos; - _data[_currentDataPosition]._cachePos = _data[_currentDataPosition]._position * _data[_currentDataPosition]._fields; - } - else - throw new IndexOutOfRangeException(); - } - - #endregion Helpers - - #region IDataReader Members - - /// Closes the System.Data.IDataReader Object. - public void Close() - { - IsClosed = true; - _currentDataPosition = -1; - } - - /// Gets a value indicating the depth of nesting for the current row. - /// This implementation use this field to indicate row count. - public int Depth - { - get { return _data[_currentDataPosition]._rows; } - } - - /// Returns a System.Data.DataTable that describes the column metadata of the - /// System.Data.IDataReader.A System.Data.DataTable that describes - /// the column metadata. - /// The System.Data.IDataReader is closed. - public DataTable GetSchemaTable() - { - return _data[_currentDataPosition]._schema; - } - - /// Gets a value indicating whether the data reader is closed. - public bool IsClosed { get; private set; } - - /// Advances the data reader to the next result, when reading the results of batch SQL statements. - /// Returns true if there are more rows; otherwise, false. - public bool NextResult() - { - _currentDataPosition++; - - return _data.Count < _currentDataPosition; - } - - /// Advances the System.Data.IDataReader to the next record. - /// Returns true if there are more rows; otherwise, false. - public bool Read() - { - var data = _data[_currentDataPosition]; - - data._cachePos = (++data._position) * data._fields; - - return data._position < data._rows; - } - - /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. - /// The number of rows changed, inserted, or deleted; 0 if no rows were affected or the statement - /// failed; and -1 for SELECT statements. - public int RecordsAffected { get { return _data[_currentDataPosition]._rowsAffected; } } - - #endregion IDataReader Members - - #region IDisposable Members - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public void Dispose() - { - if (_isDisposed) - return; - - _isDisposed = true; - IsClosed = true; - - if (_data == null) - return; - - foreach (var data in _data) - { - if (data == null) - continue; - - if (data._names != null) - data._names.Clear(); - if (data._types != null) - data._types.Clear(); - if (data._cache != null) - data._cache.Clear(); - if (data._schema != null) - data._schema.Dispose(); - } - - _data.Clear(); - } - - #endregion IDisposable Members - - #region IDataRecord Members - - /// Gets the number of columns in the current row. - /// When not positioned in a valid record set, 0; otherwise, the number of columns in the current record. The default is -1. - public int FieldCount { get { return _data[_currentDataPosition]._fields; } } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public bool GetBoolean(int i) - { - var data = _data[_currentDataPosition]; - return (bool)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public byte GetByte(int i) - { - var data = _data[_currentDataPosition]; - return (byte)data._cache[data._cachePos + i]; - } - - /// Reads a stream of bytes from the specified column offset into the buffer - /// as an array, starting at the given buffer offset. - /// The index of the field to find. - /// The index within the field from which to start the read operation. - /// The buffer into which to read the stream of bytes. - /// The index for buffer to start the read operation. - /// The number of bytes to read. - /// The actual number of bytes read. - public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) - { - var data = _data[_currentDataPosition]; - - using (MemoryStream ms = new MemoryStream((byte[])data._cache[data._cachePos + i])) - return ms.Read(buffer, bufferoffset, length); - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public char GetChar(int i) - { - var data = _data[_currentDataPosition]; - return (char)data._cache[data._cachePos + i]; - } - - /// Reads a stream of characters from the specified column offset into the buffer - /// as an array, starting at the given buffer offset. - /// The zero-based column ordinal. - /// The index within the row from which to start the read operation. - /// The buffer into which to read the stream of bytes. - /// The index for buffer to start the read operation. - /// The number of bytes to read. - /// The actual number of characters read. - public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) - { - var data = _data[_currentDataPosition]; - - using (MemoryStream ms = new MemoryStream((byte[])data._cache[data._cachePos + i])) - { - byte[] buff = new byte[buffer.Length]; - long ret = ms.Read(buff, bufferoffset, length); - - for (int n = bufferoffset; n < ret; n++) - buffer[n] = (char)buff[n]; - - return ret; - } - } - - /// Returns an System.Data.IDataReader for the specified column ordinal. - /// The index of the field to find. - /// An System.Data.IDataReader. - public IDataReader GetData(int i) - { - return null; - } - - /// Gets the data type information for the specified field. - /// The index of the field to find. - /// The data type information for the specified field. - public string GetDataTypeName(int i) - { - return _data[_currentDataPosition]._types[i].Name; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public DateTime GetDateTime(int i) - { - var data = _data[_currentDataPosition]; - return (DateTime)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public decimal GetDecimal(int i) - { - var data = _data[_currentDataPosition]; - return (decimal)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public double GetDouble(int i) - { - var data = _data[_currentDataPosition]; - return (double)data._cache[data._cachePos + i]; - } - - /// Gets the System.Type information corresponding to the type of System.Object - /// that would be returned from System.Data.IDataRecord.GetValue(System.Int32). - /// The index of the field to find. - /// The System.Type information corresponding to the type of System.Object that - /// would be returned from System.Data.IDataRecord.GetValue(System.Int32). - public Type GetFieldType(int i) - { - return _data[_currentDataPosition]._types[i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public float GetFloat(int i) - { - var data = _data[_currentDataPosition]; - return (float)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public Guid GetGuid(int i) - { - var data = _data[_currentDataPosition]; - return (Guid)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public short GetInt16(int i) - { - var data = _data[_currentDataPosition]; - return (short)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public int GetInt32(int i) - { - var data = _data[_currentDataPosition]; - return (int)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public long GetInt64(int i) - { - var data = _data[_currentDataPosition]; - return (long)data._cache[data._cachePos + i]; - } - - /// Gets the name for the field to find. - /// The index of the field to find. - /// The name of the field or the empty string (""), if there is no value to return. - public string GetName(int i) - { - return _data[_currentDataPosition]._names[i]; - } - - /// Return the index of the named field. - /// The name of the field to find. - /// The index of the named field. - public int GetOrdinal(string name) - { - var data = _data[_currentDataPosition]; - - if (data._ordinals.ContainsKey(name.ToUpper())) - return data._ordinals[name.ToUpper()]; - - return -1; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public string GetString(int i) - { - var data = _data[_currentDataPosition]; - - return (string)data._cache[data._cachePos + i]; - } - - /// Return the value of the specified field. - /// The index of the field to find. - /// Field value upon return. - public object GetValue(int i) - { - var data = _data[_currentDataPosition]; - - return data._cache[data._cachePos + i]; - } - - /// Gets all the attribute fields in the collection for the current record. - /// An array of System.Object to copy the attribute fields into. - /// The number of instances of System.Object in the array. - public int GetValues(object[] values) - { - var data = _data[_currentDataPosition]; - - for (int i = 0; i < data._fields; i++) - values[i] = data._cache[data._cachePos + i]; - - return data._fields; - } - - /// Return whether the specified field is set to null. - /// The index of the field to find. - /// Returns true if the specified field is set to null; otherwise, false. - public bool IsDBNull(int i) - { - var data = _data[_currentDataPosition]; - return data._cache[data._cachePos + i] == null || data._cache[data._cachePos + i] == DBNull.Value; - } - - /// Gets or sets specified value in current record. - /// Name of column. - /// Value of specified column. - public object this[string name] - { - get - { - var data = _data[_currentDataPosition]; - - if (data._ordinals.ContainsKey(name.ToUpper())) - return data._cache[data._cachePos + data._ordinals[name.ToUpper()]]; - - throw new IndexOutOfRangeException(String.Format("Field '{0}' not found.", name)); - } - } - - /// Gets or sets specified value in current record. - /// The index of the field to find. - /// Value of specified column. - public object this[int i] - { - get { - var data = _data[_currentDataPosition]; - return data._cache[data._cachePos + i]; - } - } - - #endregion IDataRecord Members - } - - /// Dynamic procedure invoker. - /// Unfortunately I can use out and ref to - /// return parameters, . - /// But see example for workaround. If there aren't any return parameters execution will return scalar value. - /// Scalar result is not converted to provided generic type (if any). For output results there is possibility to map to provided class. - /// You still can use out, return and both way parameters by providing variable prefix: - /// dynamic res = db.Procedures.sp_Test_Scalar_In_Out(inp: Guid.NewGuid(), out_outp: Guid.Empty); - /// Console.Out.WriteLine(res.outp); - /// Prefixes: out_, ret_, both_. Result will contain field without prefix. - /// Here is an example with result class: - /// public class ProcResult { [Column("outp")] public Guid Output { get; set; } } - /// ProcResult res4 = db.Procedures.sp_Test_Scalar_In_Out<ProcResult>(inp: Guid.NewGuid(), out_outp: Guid.Empty) as ProcResult; - /// As you can se, you can use mapper to do job for you. - public class DynamicProcedureInvoker : DynamicObject, IDisposable - { - private DynamicDatabase _db; - private List _prefixes; - private bool _isDisposed; - - internal DynamicProcedureInvoker(DynamicDatabase db, List prefixes = null) - { - _prefixes = prefixes; - _db = db; - } - - /// This is where the magic begins. - /// Binder to create owner. - /// Binder invoke result. - /// Returns true if invoke was performed. - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - List pref = new List(); - - if (_prefixes != null) - pref.AddRange(_prefixes); - - pref.Add(binder.Name); - - result = new DynamicProcedureInvoker(_db, pref); - - return true; - } - - /// This is where the magic begins. - /// Binder to invoke. - /// Binder arguments. - /// Binder invoke result. - /// Returns true if invoke was performed. - public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) - { - // parse the method - CallInfo info = binder.CallInfo; - - // Get generic types - IList types = binder.GetGenericTypeArguments(); - - Dictionary retParams = null; - - using (IDbConnection con = _db.Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - if (_prefixes == null || _prefixes.Count == 0) - cmd.SetCommand(CommandType.StoredProcedure, binder.Name); - else - cmd.SetCommand(CommandType.StoredProcedure, string.Format("{0}.{1}", string.Join(".", _prefixes), binder.Name)); - - #region Prepare arguments - - int alen = args.Length; - bool retIsAdded = false; - - if (alen > 0) - { - for (int i = 0; i < alen; i++) - { - object arg = args[i]; - - if (arg is DynamicExpando) - cmd.AddParameters(_db, (DynamicExpando)arg); - else if (arg is ExpandoObject) - cmd.AddParameters(_db, (ExpandoObject)arg); - else if (arg is DynamicColumn) - { - var dcv = (DynamicColumn)arg; - - string paramName = i.ToString(); - bool isOut = false; - bool isRet = false; - bool isBoth = false; - - if (info.ArgumentNames.Count > i) - { - isOut = info.ArgumentNames[i].StartsWith("out_"); - isRet = info.ArgumentNames[i].StartsWith("ret_"); - isBoth = info.ArgumentNames[i].StartsWith("both_"); - - paramName = isOut || isRet ? - info.ArgumentNames[i].Substring(4) : - isBoth ? info.ArgumentNames[i].Substring(5) : - info.ArgumentNames[i]; - } - - paramName = dcv.Alias ?? dcv.ColumnName ?? - (dcv.Schema.HasValue ? dcv.Schema.Value.Name : null) ?? - paramName; - - if (!isOut && !isRet && !isBoth) - { - isOut = dcv.ParameterDirection == ParameterDirection.Output; - isRet = dcv.ParameterDirection == ParameterDirection.ReturnValue; - isBoth = dcv.ParameterDirection == ParameterDirection.InputOutput; - } - - if (isRet) - retIsAdded = true; - - if (isOut || isRet || isBoth) - { - if (retParams == null) - retParams = new Dictionary(); - retParams.Add(paramName, cmd.Parameters.Count); - } - - if (dcv.Schema != null) - { - var ds = dcv.Schema.Value; - cmd.AddParameter( - _db.GetParameterName(paramName), - isOut ? ParameterDirection.Output : - isRet ? ParameterDirection.ReturnValue : - isBoth ? ParameterDirection.InputOutput : - ParameterDirection.Input, - ds.Type, ds.Size, ds.Precision, ds.Scale, - (isOut || isRet) ? DBNull.Value : dcv.Value); - } - else - cmd.AddParameter( - _db.GetParameterName(paramName), - isOut ? ParameterDirection.Output : - isRet ? ParameterDirection.ReturnValue : - isBoth ? ParameterDirection.InputOutput : - ParameterDirection.Input, - arg == null ? DbType.String : arg.GetType().ToDbType(), - isRet ? 4 : 0, - (isOut || isRet) ? DBNull.Value : dcv.Value); - } - else if (arg is DynamicSchemaColumn) - { - var dsc = (DynamicSchemaColumn)arg; - - string paramName = i.ToString(); - bool isOut = false; - bool isRet = false; - bool isBoth = false; - - if (info.ArgumentNames.Count > i) - { - isOut = info.ArgumentNames[i].StartsWith("out_"); - isRet = info.ArgumentNames[i].StartsWith("ret_"); - isBoth = info.ArgumentNames[i].StartsWith("both_"); - - paramName = isOut || isRet ? - info.ArgumentNames[i].Substring(4) : - isBoth ? info.ArgumentNames[i].Substring(5) : - info.ArgumentNames[i]; - } - - paramName = dsc.Name ?? paramName; - - if (isRet) - retIsAdded = true; - - if (isOut || isRet || isBoth) - { - if (retParams == null) - retParams = new Dictionary(); - retParams.Add(paramName, cmd.Parameters.Count); - } - - cmd.AddParameter( - _db.GetParameterName(paramName), - isOut ? ParameterDirection.Output : - isRet ? ParameterDirection.ReturnValue : - isBoth ? ParameterDirection.InputOutput : - ParameterDirection.Input, - dsc.Type, dsc.Size, dsc.Precision, dsc.Scale, - DBNull.Value); - } - else - { - if (info.ArgumentNames.Count > i && !string.IsNullOrEmpty(info.ArgumentNames[i])) - { - bool isOut = info.ArgumentNames[i].StartsWith("out_"); - bool isRet = info.ArgumentNames[i].StartsWith("ret_"); - bool isBoth = info.ArgumentNames[i].StartsWith("both_"); - - if (isRet) - retIsAdded = true; - - string paramName = isOut || isRet ? - info.ArgumentNames[i].Substring(4) : - isBoth ? info.ArgumentNames[i].Substring(5) : - info.ArgumentNames[i]; - - if (isOut || isBoth || isRet) - { - if (retParams == null) - retParams = new Dictionary(); - retParams.Add(paramName, cmd.Parameters.Count); - } - - cmd.AddParameter( - _db.GetParameterName(paramName), - isOut ? ParameterDirection.Output : - isRet ? ParameterDirection.ReturnValue : - isBoth ? ParameterDirection.InputOutput : - ParameterDirection.Input, - arg == null ? isRet ? DbType.Int32 : DbType.String : arg.GetType().ToDbType(), - isRet ? 4 : 0, - (isOut || isRet) ? DBNull.Value : arg); - } - else - cmd.AddParameter(_db, arg); - } - } - } - - #endregion Prepare arguments - - #region Get main result - - object mainResult = null; - - if (types.Count > 0) - { - mainResult = types[0].GetDefaultValue(); - - if (types[0] == typeof(IDataReader)) - { - using (IDataReader rdr = cmd.ExecuteReader()) - mainResult = rdr.CachedReader(); - } - else if (types[0] == typeof(DataTable)) - { - using (IDataReader rdr = cmd.ExecuteReader()) - using (IDataReader cache = rdr.CachedReader()) - mainResult = cache.ToDataTable(binder.Name); - } - else if (types[0].IsGenericEnumerable()) - { - Type argType = types[0].GetGenericArguments().First(); - if (argType == typeof(object)) - { - using (IDataReader rdr = cmd.ExecuteReader()) - using (IDataReader cache = rdr.CachedReader()) - mainResult = cache.EnumerateReader().ToList(); - } - else if (argType.IsValueType || argType == typeof(string)) - { - Type listType = typeof(List<>).MakeGenericType(new Type[] { argType }); - IList listInstance = (IList)Activator.CreateInstance(listType); - - object defVal = listType.GetDefaultValue(); - - using (IDataReader rdr = cmd.ExecuteReader()) - using (IDataReader cache = rdr.CachedReader()) - while (cache.Read()) - listInstance.Add(cache[0] != null && cache[0] != DBNull.Value ? argType.CastObject(cache[0]) : defVal); - - mainResult = listInstance; - } - else if (argType == typeof(Guid)) - { - Type listType = typeof(List<>).MakeGenericType(new Type[] { argType }); - IList listInstance = (IList)Activator.CreateInstance(listType); - - object defVal = listType.GetDefaultValue(); - - using (IDataReader rdr = cmd.ExecuteReader()) - using (IDataReader cache = rdr.CachedReader()) - while (cache.Read()) - { - if (cache[0] != null && cache[0] != DBNull.Value && Guid.TryParse(cache[0].ToString(), out Guid g)) - listInstance.Add(g); - } - - mainResult = listInstance; - } - else - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(argType); - if (mapper == null) - throw new InvalidCastException(string.Format("Don't know what to do with this type: '{0}'.", argType.ToString())); - - using (IDataReader rdr = cmd.ExecuteReader()) - using (IDataReader cache = rdr.CachedReader()) - { - var lt = typeof(List<>); - var ltc = lt.MakeGenericType(argType); - var instance = Activator.CreateInstance(ltc) as IList; - - foreach (var item in cache.EnumerateReader()) - instance.Add(DynamicExtensions.Map(item, argType)); - - mainResult = instance; - } - - //mainResult = cache.EnumerateReader().MapEnumerable(argType).ToList(); - } - } - else if (types[0].IsValueType || types[0] == typeof(string)) - { - mainResult = cmd.ExecuteScalar(); - if (mainResult != null && mainResult != DBNull.Value) - mainResult = types[0].CastObject(mainResult); - } - else if (types[0] == typeof(Guid)) - { - mainResult = cmd.ExecuteScalar(); - if (mainResult != null && mainResult != DBNull.Value && Guid.TryParse(mainResult.ToString(), out Guid g)) - mainResult = g; - } - else - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(types[0]); - if (mapper == null) - throw new InvalidCastException(string.Format("Don't know what to do with this type: '{0}'.", types[0].ToString())); - - using (IDataReader rdr = cmd.ExecuteReader()) - if (rdr.Read()) - mainResult = (rdr.RowToDynamic() as object).Map(types[0]); - else - mainResult = null; - } - } - else - { - var returnName = _db.GetParameterName("___result___"); - if ((_db.Options & DynamicDatabaseOptions.SupportStoredProceduresResult) == DynamicDatabaseOptions.SupportStoredProceduresResult) - { - if (!retIsAdded) - cmd.AddParameter(returnName, ParameterDirection.ReturnValue, DbType.Int32, 4, 0, 0, DBNull.Value); - } - - mainResult = cmd.ExecuteNonQuery(); - - IDbDataParameter returnParam = null; - if (!retIsAdded) - { - if ((_db.Options & DynamicDatabaseOptions.SupportStoredProceduresResult) == DynamicDatabaseOptions.SupportStoredProceduresResult) - returnParam = cmd.Parameters[returnName] as IDbDataParameter; - } - else - { - foreach (var e in cmd.Parameters) - { - var p = e as IDbDataParameter; - if (p != null && p.Direction == ParameterDirection.ReturnValue) - { - returnParam = p; - break; - } - } - } - - if (returnParam != null && returnParam.Value != null && returnParam.Value != DBNull.Value) - mainResult = returnParam.Value; - } - - #endregion Get main result - - #region Handle out params - - if (retParams != null) - { - Dictionary res = new Dictionary(); - - if (mainResult != null) - { - if (mainResult == DBNull.Value) - res.Add(binder.Name, null); - else - res.Add(binder.Name, mainResult); - } - - foreach (KeyValuePair pos in retParams) - res.Add(pos.Key, ((IDbDataParameter)cmd.Parameters[pos.Value]).Value); - - if (types.Count > 1) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(types[1]); - - if (mapper != null) - result = mapper.Create(res.ToDynamic()); - else - result = res.ToDynamic(); - } - else - result = res.ToDynamic(); - } - else - result = mainResult; - - #endregion Handle out params - } - - return true; - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public void Dispose() - { - if (_isDisposed) - return; - - _isDisposed = true; - _db = null; - _prefixes = null; - } - } - - /// Extension to ORM objects. - public static class DynamicExtensions - { - #region Type column map - - /// MapEnumerable of .NET types to . - public static readonly Dictionary TypeMap = new Dictionary() - { - { typeof(byte), DbType.Byte }, - { typeof(sbyte), DbType.SByte }, - { typeof(short), DbType.Int16 }, - { typeof(ushort), DbType.UInt16 }, - { typeof(int), DbType.Int32 }, - { typeof(uint), DbType.UInt32 }, - { typeof(long), DbType.Int64 }, - { typeof(ulong), DbType.UInt64 }, - { typeof(float), DbType.Single }, - { typeof(double), DbType.Double }, - { typeof(decimal), DbType.Decimal }, - { typeof(bool), DbType.Boolean }, - { typeof(string), DbType.String }, - { typeof(char), DbType.StringFixedLength }, - { typeof(Guid), DbType.Guid }, - { typeof(DateTime), DbType.DateTime }, - { typeof(TimeSpan), DbType.Time }, - { typeof(DateTimeOffset), DbType.DateTimeOffset }, - { typeof(byte[]), DbType.Binary }, - { typeof(byte?), DbType.Byte }, - { typeof(sbyte?), DbType.SByte }, - { typeof(short?), DbType.Int16 }, - { typeof(ushort?), DbType.UInt16 }, - { typeof(int?), DbType.Int32 }, - { typeof(uint?), DbType.UInt32 }, - { typeof(long?), DbType.Int64 }, - { typeof(ulong?), DbType.UInt64 }, - { typeof(float?), DbType.Single }, - { typeof(double?), DbType.Double }, - { typeof(decimal?), DbType.Decimal }, - { typeof(bool?), DbType.Boolean }, - { typeof(char?), DbType.StringFixedLength }, - { typeof(Guid?), DbType.Guid }, - { typeof(DateTime?), DbType.DateTime }, - { typeof(TimeSpan?), DbType.Time }, - { typeof(DateTimeOffset?), DbType.DateTimeOffset }, - }; - - #endregion Type column map - - #region Command extensions - - /// Set connection on the fly. - /// in which changes will be made. - /// which will be set to instance. - /// Returns edited instance. - public static IDbCommand SetConnection(this IDbCommand command, IDbConnection connection) - { - command.Connection = connection; - - return command; - } - - /// Set connection on the fly. - /// in which changes will be made. - /// which will be set to instance. - /// Returns edited instance. - public static IDbCommand SetTransaction(this IDbCommand command, IDbTransaction transaction) - { - command.Transaction = transaction; - - return command; - } - - #region SetCommand - - /// Set properties on the fly. - /// in which changes will be made. - /// Indicates or specifies how the property is interpreted. - /// The wait time before terminating the attempt to execute a command and generating an error. - /// The text command to run against the data source. - /// Arguments used to format command. - /// Returns edited instance. - public static IDbCommand SetCommand(this IDbCommand command, CommandType commandType, int commandTimeout, string commandText, params object[] args) - { - command.CommandType = commandType; - command.CommandTimeout = commandTimeout; - - if (args != null && args.Length > 0) - command.CommandText = string.Format(commandText, args); - else - command.CommandText = commandText; - - return command; - } - - /// Set properties on the fly. - /// in which changes will be made. - /// The wait time before terminating the attempt to execute a command and generating an error. - /// The text command to run against the data source. - /// Arguments used to format command. - /// Returns edited instance. - public static IDbCommand SetCommand(this IDbCommand command, int commandTimeout, string commandText, params object[] args) - { - command.CommandTimeout = commandTimeout; - - if (args != null && args.Length > 0) - command.CommandText = string.Format(commandText, args); - else - command.CommandText = commandText; - - return command; - } - - /// Set properties on the fly. - /// in which changes will be made. - /// Indicates or specifies how the property is interpreted. - /// The text command to run against the data source. - /// Arguments used to format command. - /// Returns edited instance. - public static IDbCommand SetCommand(this IDbCommand command, CommandType commandType, string commandText, params object[] args) - { - command.CommandType = commandType; - - if (args != null && args.Length > 0) - command.CommandText = string.Format(commandText, args); - else - command.CommandText = commandText; - - return command; - } - - /// Set properties on the fly. - /// in which changes will be made. - /// The text command to run against the data source. - /// Arguments used to format command. - /// Returns edited instance. - public static IDbCommand SetCommand(this IDbCommand command, string commandText, params object[] args) - { - if (args != null && args.Length > 0) - command.CommandText = string.Format(commandText, args); - else - command.CommandText = commandText; - - return command; - } - - /// Set properties on the fly. - /// in which changes will be made. - /// Command builder. - /// Returns edited instance. - public static IDbCommand SetCommand(this IDbCommand command, IDynamicQueryBuilder builder) - { - builder.FillCommand(command); - - return command; - } - - #endregion SetCommand - - #region AddParameter - - /// Extension method for adding in a bunch of parameters. - /// Command to handle. - /// Database object required to get proper formatting. - /// Items to add. - /// Returns edited instance. - public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, params object[] args) - { - if (args != null && args.Count() > 0) - foreach (object item in args) - { - if (item is DynamicExpando) - cmd.AddParameters(database, (DynamicExpando)item); - else if (item is ExpandoObject) - cmd.AddParameters(database, (ExpandoObject)item); - else - cmd.AddParameter(database, item); - } - - return cmd; - } - - /// Extension method for adding in a bunch of parameters. - /// Command to handle. - /// Database object required to get proper formatting. - /// Items to add in an expando object. - /// Returns edited instance. - public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, ExpandoObject args) - { - if (args != null && args.Count() > 0) - foreach (KeyValuePair item in args.ToDictionary()) - cmd.AddParameter(database, item.Key, item.Value); - - return cmd; - } - - /// Extension method for adding in a bunch of parameters. - /// Command to handle. - /// Database object required to get proper formatting. - /// Items to add in an expando object. - /// Returns edited instance. - public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, DynamicExpando args) - { - if (args != null && args.Count() > 0) - foreach (KeyValuePair item in args.ToDictionary()) - cmd.AddParameter(database, item.Key, item.Value); - - return cmd; - } - - /// Extension for adding single parameter determining only type of object. - /// Command to handle. - /// Database object required to get proper formatting. - /// Items to add. - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand cmd, DynamicDatabase database, object item) - { - return cmd.AddParameter(database, database.GetParameterName(cmd.Parameters.Count), item); - } - - /// Extension for adding single parameter determining only type of object. - /// Command to handle. - /// Database object required to get proper formatting. - /// Name of parameter. - /// Items to add. - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand cmd, DynamicDatabase database, string name, object item) - { - IDbDataParameter p = cmd.CreateParameter(); - p.ParameterName = name; - - if (item == null || item == DBNull.Value) - p.Value = DBNull.Value; - else - { - Type type = item.GetType(); - - p.DbType = TypeMap.TryGetNullable(type) ?? DbType.String; - - if (type == typeof(DynamicExpando) || type == typeof(ExpandoObject)) - p.Value = CorrectValue(p.DbType, ((IDictionary)item).Values.FirstOrDefault()); - else - p.Value = CorrectValue(p.DbType, item); - - if (p.DbType == DbType.String) - p.Size = item.ToString().Length > 4000 ? -1 : 4000; - else if (p.DbType == DbType.AnsiString) - p.Size = item.ToString().Length > 8000 ? -1 : 8000; - } - - cmd.Parameters.Add(p); - - return cmd; - } - - /// Extension for adding single parameter determining only type of object. - /// Command to handle. - /// Query builder containing schema. - /// Column schema to use. - /// Parameter value. - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand cmd, IDynamicQueryBuilder builder, DynamicSchemaColumn? col, object value) - { - IDbDataParameter p = cmd.CreateParameter(); - p.ParameterName = builder.Database.GetParameterName(cmd.Parameters.Count); - - if (col.HasValue) - { - p.DbType = col.Value.Type; - - if ((builder.Database.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema) - { - p.Size = col.Value.Size; - p.Precision = col.Value.Precision; - p.Scale = col.Value.Scale; - - // Quick fix - review that - // Quick fix 2 - use item.Schema in that case - if (p.Scale > p.Precision) - p.Scale = 4; - } - - p.Value = CorrectValue(p.DbType, value); - } - else if (value == null || value == DBNull.Value) - p.Value = DBNull.Value; - else - { - p.DbType = TypeMap.TryGetNullable(value.GetType()) ?? DbType.String; - - if (p.DbType == DbType.AnsiString) - p.Size = value.ToString().Length > 8000 ? -1 : 8000; - else if (p.DbType == DbType.String) - p.Size = value.ToString().Length > 4000 ? -1 : 4000; - - p.Value = CorrectValue(p.DbType, value); - } - - cmd.Parameters.Add(p); - - return cmd; - } - - private static object CorrectValue(DbType type, object value) - { - if (value == null || value == DBNull.Value) - return DBNull.Value; - - if ((type == DbType.String || type == DbType.AnsiString || type == DbType.StringFixedLength || type == DbType.AnsiStringFixedLength) && - !(value is string)) - return value.ToString(); - else if (type == DbType.Guid && value is string) - return Guid.Parse(value.ToString()); - else if (type == DbType.Guid && value is byte[] && ((byte[])value).Length == 16) - return new Guid((byte[])value); - else if (type == DbType.DateTime && value is TimeSpan) // HACK: This is specific for SQL Server, to be verified with other databases - return DateTime.Today.Add((TimeSpan)value); - - return value; - } - - /// Extension for adding single parameter determining only type of object. - /// Command to handle. - /// Query builder containing schema. - /// Column item to add. - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand cmd, IDynamicQueryBuilder builder, DynamicColumn item) - { - IDbDataParameter p = cmd.CreateParameter(); - p.ParameterName = builder.Database.GetParameterName(cmd.Parameters.Count); - - DynamicSchemaColumn? col = item.Schema ?? (builder as DynamicQueryBuilder) - .NullOr(b => b.GetColumnFromSchema(item.ColumnName), - builder.Tables.FirstOrDefault() - .NullOr(t => t.Schema - .NullOr(s => s.TryGetNullable(item.ColumnName.ToLower()), null), null)); - - if (col.HasValue) - { - p.DbType = col.Value.Type; - - if (builder.SupportSchema) - { - p.Size = col.Value.Size; - p.Precision = col.Value.Precision; - p.Scale = col.Value.Scale; - - // Quick fix - review that - // Quick fix 2 - use item.Schema in that case - if (p.Scale > p.Precision) - p.Scale = 4; - } - - p.Value = item.Value == null ? DBNull.Value : CorrectValue(p.DbType, item.Value); - } - else if (item.Value == null || item.Value == DBNull.Value) - p.Value = DBNull.Value; - else - { - p.DbType = TypeMap.TryGetNullable(item.Value.GetType()) ?? DbType.String; - - if (p.DbType == DbType.AnsiString) - p.Size = item.Value.ToString().Length > 8000 ? -1 : 8000; - else if (p.DbType == DbType.String) - p.Size = item.Value.ToString().Length > 4000 ? -1 : 4000; - - p.Value = CorrectValue(p.DbType, item.Value); - } - - cmd.Parameters.Add(p); - - return cmd; - } - - /// Add to on the fly. - /// to which parameter will be added. - /// The name of the . - /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . - /// The of the . - /// The size of the parameter. - /// Indicates the precision of numeric parameters. - /// Indicates the scale of numeric parameters. - /// The value of the . - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, byte precision, byte scale, object value) - { - IDbDataParameter param = command.CreateParameter(); - param.ParameterName = parameterName; - param.Direction = parameterDirection; - param.DbType = databaseType; - param.Size = size; - param.Precision = precision; - param.Scale = scale; - param.Value = CorrectValue(param.DbType, value); - command.Parameters.Add(param); - - return command; - } - - /// Add to on the fly. - /// to which parameter will be added. - /// The name of the . - /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . - /// The of the . - /// The size of the parameter. - /// Indicates the precision of numeric parameters. - /// Indicates the scale of numeric parameters. - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, byte precision, byte scale) - { - IDbDataParameter param = command.CreateParameter(); - param.ParameterName = parameterName; - param.Direction = parameterDirection; - param.DbType = databaseType; - param.Size = size; - param.Precision = precision; - param.Scale = scale; - command.Parameters.Add(param); - - return command; - } - - /// Add to on the fly. - /// to which parameter will be added. - /// The name of the . - /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . - /// The of the . - /// Indicates the precision of numeric parameters. - /// Indicates the scale of numeric parameters. - /// The value of the . - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, byte precision, byte scale, object value) - { - IDbDataParameter param = command.CreateParameter(); - param.ParameterName = parameterName; - param.Direction = parameterDirection; - param.DbType = databaseType; - param.Precision = precision; - param.Scale = scale; - param.Value = CorrectValue(param.DbType, value); - command.Parameters.Add(param); - - return command; - } - - /// Add to on the fly. - /// to which parameter will be added. - /// The name of the . - /// The of the . - /// Indicates the precision of numeric parameters. - /// Indicates the scale of numeric parameters. - /// The value of the . - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, byte precision, byte scale, object value) - { - IDbDataParameter param = command.CreateParameter(); - param.ParameterName = parameterName; - param.DbType = databaseType; - param.Precision = precision; - param.Scale = scale; - param.Value = CorrectValue(param.DbType, value); - command.Parameters.Add(param); - - return command; - } - - /// Add to on the fly. - /// to which parameter will be added. - /// The name of the . - /// The of the . - /// Indicates the precision of numeric parameters. - /// Indicates the scale of numeric parameters. - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, byte precision, byte scale) - { - IDbDataParameter param = command.CreateParameter(); - param.ParameterName = parameterName; - param.DbType = databaseType; - param.Precision = precision; - param.Scale = scale; - command.Parameters.Add(param); - - return command; - } - - /// Add to on the fly. - /// to which parameter will be added. - /// The name of the . - /// Value indicating whether the parameter is input-only, output-only, bidirectional, or a stored procedure return value . - /// The of the . - /// The size of the parameter. - /// The value of the . - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand command, string parameterName, ParameterDirection parameterDirection, DbType databaseType, int size, object value) - { - IDbDataParameter param = command.CreateParameter(); - param.ParameterName = parameterName; - param.Direction = parameterDirection; - param.DbType = databaseType; - param.Size = size; - param.Value = CorrectValue(param.DbType, value ?? DBNull.Value); - command.Parameters.Add(param); - - return command; - } - - /// Add to on the fly. - /// to which parameter will be added. - /// The name of the . - /// The of the . - /// The size of the parameter. - /// The value of the . - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, int size, object value) - { - IDbDataParameter param = command.CreateParameter(); - param.ParameterName = parameterName; - param.DbType = databaseType; - param.Size = size; - param.Value = CorrectValue(param.DbType, value ?? DBNull.Value); - command.Parameters.Add(param); - - return command; - } - - /// Add to on the fly. - /// to which parameter will be added. - /// The name of the . - /// The of the . - /// The value of the . - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, object value) - { - IDbDataParameter param = command.CreateParameter(); - param.ParameterName = parameterName; - param.DbType = databaseType; - param.Value = CorrectValue(param.DbType, value ?? DBNull.Value); - command.Parameters.Add(param); - - return command; - } - - /// Add to on the fly. - /// to which parameter will be added. - /// The name of the . - /// The of the . - /// The size of the parameter. - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType, int size) - { - IDbDataParameter param = command.CreateParameter(); - param.ParameterName = parameterName; - param.DbType = databaseType; - param.Size = size; - command.Parameters.Add(param); - - return command; - } - - /// Add to on the fly. - /// to which parameter will be added. - /// The name of the . - /// The of the . - /// Returns edited instance. - public static IDbCommand AddParameter(this IDbCommand command, string parameterName, DbType databaseType) - { - IDbDataParameter param = command.CreateParameter(); - param.ParameterName = parameterName; - param.DbType = databaseType; - command.Parameters.Add(param); - - return command; - } - - #endregion AddParameter - - #region SetParameter - - /// Set value for on the fly. - /// to which parameter will be added. - /// The name of the . - /// Value to set on this parameter. - /// Returns edited instance. - public static IDbCommand SetParameter(this IDbCommand command, string parameterName, object value) - { - try - { - var p = ((IDbDataParameter)command.Parameters[parameterName]); - p.Value = CorrectValue(p.DbType, value); - } - catch (Exception ex) - { - throw new ArgumentException(string.Format("Error setting parameter {0} in command {1}", parameterName, command.CommandText ?? string.Empty), ex); - } - - return command; - } - - /// Set value for on the fly. - /// to which parameter will be added. - /// Index of the . - /// Value to set on this parameter. - /// Returns edited instance. - public static IDbCommand SetParameter(this IDbCommand command, int index, object value) - { - try - { - var p = ((IDbDataParameter)command.Parameters[index]); - p.Value = CorrectValue(p.DbType, value); - } - catch (Exception ex) - { - throw new ArgumentException(string.Format("Error setting parameter {0} in command {1}", index, command.CommandText ?? string.Empty), ex); - } - - return command; - } - - #endregion SetParameter - - #region Generic Execution - -#if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE - - /// Execute scalar and return string if possible. - /// Type to parse to. - /// which will be executed. - /// Returns resulting instance of T from query. - public static T ExecuteScalarAs(this IDbCommand command) - { - return ExecuteScalarAs(command, default(T), null); - } - - /// Execute scalar and return string if possible. - /// Type to parse to. - /// which will be executed. - /// Handler of a try parse method. - /// Returns resulting instance of T from query. - public static T ExecuteScalarAs(this IDbCommand command, DynamicExtensions.TryParseHandler handler) - { - return ExecuteScalarAs(command, default(T), handler); - } - - /// Execute scalar and return string if possible. - /// Type to parse to. - /// which will be executed. - /// Default result value. - /// Returns resulting instance of T from query. - public static T ExecuteScalarAs(this IDbCommand command, T defaultValue) - { - return ExecuteScalarAs(command, defaultValue, null); - } - - /// Execute scalar and return string if possible. - /// Type to parse to. - /// which will be executed. - /// Default result value. - /// Handler of a try parse method. - /// Returns resulting instance of T from query. - public static T ExecuteScalarAs(this IDbCommand command, T defaultValue, DynamicExtensions.TryParseHandler handler) - { - T ret = defaultValue; - - object o = command.ExecuteScalar(); - - if (o is T) - return (T)o; - else if (o != DBNull.Value && o != null) - { - if (handler != null) - ret = o.ToString().TryParseDefault(defaultValue, handler); - else if (o is IConvertible && typeof(T).GetInterfaces().Any(i => i == typeof(IConvertible))) - ret = (T)Convert.ChangeType(o, typeof(T)); - else if (typeof(T) == typeof(Guid)) - { - if (o.GetType() == typeof(byte[])) - ret = (T)(object)new Guid((byte[])o); - else - ret = (T)(object)Guid.Parse(o.ToString()); - } - else if (typeof(T) == typeof(string)) - ret = (T)(o.ToString() as object); - else if (typeof(T) == typeof(object)) - ret = (T)o; - else - { - MethodInfo method = typeof(T).GetMethod( - "TryParse", - new Type[] - { - typeof(string), - Type.GetType(string.Format("{0}&", typeof(T).FullName)) - }); - - if (method != null) - ret = o.ToString().TryParseDefault(defaultValue, delegate(string v, out T r) - { - r = defaultValue; - return (bool)method.Invoke(null, new object[] { v, r }); - }); - else - throw new InvalidOperationException("Provided type can't be parsed using generic approach."); - } - } - - return ret; - } - - /// Execute enumerator of specified type. - /// Type to parse to. - /// which will be executed. - /// Default result value. - /// Handler of a try parse method. - /// Returns enumerator of specified type from query. - public static IEnumerable ExecuteEnumeratorOf(this IDbCommand command, T defaultValue, DynamicExtensions.TryParseHandler handler) where T : struct - { - using (IDataReader reader = command.ExecuteReader()) - { - MethodInfo method = typeof(T).GetMethod( - "TryParse", - new[] - { - typeof(string), - Type.GetType(string.Format("{0}&", typeof(T).FullName)) - }); - - while (reader.Read()) - { - T ret = defaultValue; - - if (!reader.IsDBNull(0)) - { - object o = reader.GetValue(0); - - if (o is T) - ret = (T)o; - else if (o != DBNull.Value) - { - if (handler != null) - ret = o.ToString().TryParseDefault(defaultValue, handler); - else if (o is IConvertible && typeof(T).GetInterfaces().Any(i => i == typeof(IConvertible))) - ret = (T)Convert.ChangeType(o, typeof(T)); - else if (typeof(T) == typeof(Guid)) - { - if (o.GetType() == typeof(byte[])) - ret = (T)(object)new Guid((byte[])o); - else - ret = (T)(object)Guid.Parse(o.ToString()); - } - else if (typeof(T) == typeof(string)) - ret = (T)(o.ToString() as object); - else if (typeof(T) == typeof(object)) - ret = (T)o; - else if (method != null) - ret = o.ToString().TryParseDefault(defaultValue, delegate(string v, out T r) - { - r = defaultValue; - return (bool)method.Invoke(null, new object[] { v, r }); - }); - else - throw new InvalidOperationException("Provided type can't be parsed using generic approach."); - } - } - - yield return ret; - } - } - } - -#endif - - #endregion Generic Execution - - /// Dump command into string. - /// Command to dump. - /// Returns dumped instance in string form. - public static string DumpToString(this IDbCommand command) - { - StringBuilder sb = new StringBuilder(); - command.Dump(sb); - return sb.ToString(); - } - - /// Dump command into text writer. - /// Command to dump. - /// Builder to which write output. - /// Returns dumped instance. - public static IDbCommand Dump(this IDbCommand command, StringBuilder buider) - { - using (StringWriter sw = new StringWriter(buider)) - return command.Dump(sw); - } - - /// Dump command into text writer. - /// Command to dump. - /// Writer to which write output. - /// Returns dumped instance. - public static IDbCommand Dump(this IDbCommand command, TextWriter writer) - { - try - { - writer.WriteLine("Type: {0}; Timeout: {1}; Query: {2}", command.CommandType, command.CommandTimeout, command.CommandText); - - if (command.Parameters.Count > 0) - { - writer.WriteLine("Parameters:"); - - foreach (IDbDataParameter param in command.Parameters) - { - writer.WriteLine(" '{0}' ({1} (s:{2} p:{3} s:{4})) = '{5}' ({6});", - param.ParameterName, - param.DbType, - param.Scale, - param.Precision, - param.Scale, - param.Value is byte[] ? ConvertByteArrayToHexString((byte[])param.Value) : param.Value ?? "NULL", - param.Value != null ? param.Value.GetType().Name : "DBNull"); - } - - writer.WriteLine(); - } - } - catch (NullReferenceException) - { - writer.WriteLine("Command disposed."); - } - - return command; - } - - /// Convert byte array to hex formatted string without separators. - /// Byte Array Data. - /// Hex string representation of byte array. - private static string ConvertByteArrayToHexString(byte[] data) - { - return ConvertByteArrayToHexString(data, 0); - } - - /// Convert byte array to hex formatted string. - /// Byte Array Data. - /// Put '-' each separatorEach characters. - /// Hex string representation of byte array. - private static string ConvertByteArrayToHexString(byte[] data, int separatorEach) - { - int len = data.Length * 2; - System.Text.StringBuilder sb = new System.Text.StringBuilder(); - for (int i = 0; i < len; i++) - { - sb.AppendFormat("{0:X}", data[(i / 2)] >> (((i % 2) == 0) ? 4 : 0) & 0x0F); - if ((separatorEach > 0) && ((i + 1) % separatorEach == 0)) - sb.AppendFormat("-"); - } - - return sb.ToString(); - } - - #endregion Command extensions - - #region Dynamic builders extensions - - /// Turns an to a Dynamic list of things. - /// Ready to execute builder. - /// List of things. - public static List ToList(this IDynamicSelectQueryBuilder b) - { - return b.Execute().ToList(); - } - - /// Turns an to a Dynamic list of things with specified type. - /// Type of object to map on. - /// Ready to execute builder. - /// List of things. - public static List ToList(this IDynamicSelectQueryBuilder b) where T : class - { - return b.Execute().ToList(); - } - - /// Sets the on create temporary parameter action. - /// Class implementing interface. - /// The builder on which set delegate. - /// Action to invoke. - /// Returns instance of builder on which action is set. - public static T CreateTemporaryParameterAction(this T b, Action a) where T : IDynamicQueryBuilder - { - if (a != null) - { - if (b.OnCreateTemporaryParameter == null) - b.OnCreateTemporaryParameter = new List>(); - - b.OnCreateTemporaryParameter.Add(a); - } - - return b; - } - - /// Sets the on create real parameter action. - /// Class implementing interface. - /// The builder on which set delegate. - /// Action to invoke. - /// Returns instance of builder on which action is set. - public static T CreateParameterAction(this T b, Action a) where T : IDynamicQueryBuilder - { - if (a != null) - { - if (b.OnCreateParameter == null) - b.OnCreateParameter = new List>(); - - b.OnCreateParameter.Add(a); - } - - return b; - } - - /// Sets the virtual mode on builder. - /// Class implementing interface. - /// The builder on which set virtual mode. - /// Virtual mode. - /// Returns instance of builder on which virtual mode is set. - public static T SetVirtualMode(this T b, bool virtualMode) where T : IDynamicQueryBuilder - { - b.VirtualMode = virtualMode; - return b; - } - - /// Creates sub query that can be used inside of from/join/expressions. - /// Class implementing interface. - /// The builder that will be parent of new sub query. - /// Instance of sub query. - public static IDynamicSelectQueryBuilder SubQuery(this T b) where T : IDynamicQueryBuilder - { - return new DynamicSelectQueryBuilder(b.Database, b as DynamicQueryBuilder); - } - - /// Creates sub query that can be used inside of from/join/expressions. - /// Class implementing interface. - /// The builder that will be parent of new sub query. - /// The specification for sub query. - /// The specification for sub query. - /// Instance of sub query. - public static IDynamicSelectQueryBuilder SubQuery(this T b, Func fn, params Func[] func) where T : IDynamicQueryBuilder - { - return new DynamicSelectQueryBuilder(b.Database, b as DynamicQueryBuilder).From(fn, func); - } - - /// Creates sub query that can be used inside of from/join/expressions. - /// Class implementing interface. - /// The builder that will be parent of new sub query. - /// First argument is parent query, second one is a sub query. - /// This instance to permit chaining. - public static T SubQuery(this T b, Action subquery) where T : IDynamicQueryBuilder - { - IDynamicSelectQueryBuilder sub = b.SubQuery(); - - subquery(b, sub); - try - { - (b as DynamicQueryBuilder).ParseCommand(sub as DynamicQueryBuilder, b.Parameters); - } - catch (ArgumentException) - { - // This might occur if join was made to subquery - } - - return b; - } - - /// Creates sub query that can be used inside of from/join/expressions. - /// Class implementing interface. - /// The builder that will be parent of new sub query. - /// First argument is parent query, second one is a sub query. - /// The specification for sub query. - /// The specification for sub query. - /// This instance to permit chaining. - public static T SubQuery(this T b, Action subquery, Func fn, params Func[] func) where T : IDynamicQueryBuilder - { - IDynamicSelectQueryBuilder sub = b.SubQuery(fn, func); - - subquery(b, sub); - - (b as DynamicQueryBuilder).ParseCommand(sub as DynamicQueryBuilder, b.Parameters); - - return b; - } - - #endregion Dynamic builders extensions - - #region Dynamic extensions - - /// Turns an to a Dynamic list of things. - /// Reader from which read data. - /// List of things. - public static List ToList(this IDataReader r) - { - List result = new List(); - - while (r.Read()) - result.Add(r.RowToDynamic()); - - return result; - } - - /// Turns the dictionary into an ExpandoObject. - /// Dictionary to convert. - /// Converted dictionary. - public static dynamic ToDynamic(this IDictionary d) - { - DynamicExpando result = new DynamicExpando(); - IDictionary dict = (IDictionary)result; - - foreach (KeyValuePair prop in d) - dict.Add(prop.Key, prop.Value); - - return result; - } - - /// Turns the dictionary into an ExpandoObject. - /// Dictionary to convert. - /// Converted dictionary. - public static dynamic ToExpando(this IDictionary d) - { - ExpandoObject result = new ExpandoObject(); - IDictionary dict = (IDictionary)result; - - foreach (KeyValuePair prop in d) - dict.Add(prop.Key, prop.Value); - - return result; - } - - /// Turns the object into an ExpandoObject. - /// Object to convert. - /// Converted object. - public static dynamic ToDynamic(this object o) - { - Type ot = o.GetType(); - - if (ot == typeof(DynamicExpando) || ot == typeof(ExpandoObject)) - return o; - - DynamicExpando result = new DynamicExpando(); - IDictionary dict = (IDictionary)result; - - if (o is IDictionary) - ((IDictionary)o) - .ToList() - .ForEach(kvp => dict.Add(kvp.Key, kvp.Value)); - else if (ot == typeof(NameValueCollection) || ot.IsSubclassOf(typeof(NameValueCollection))) - { - NameValueCollection nameValue = (NameValueCollection)o; - nameValue.Cast() - .Select(key => new KeyValuePair(key, nameValue[key])) - .ToList() - .ForEach(i => dict.Add(i)); - } - else - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(ot); - - if (mapper != null) - { - foreach (DynamicPropertyInvoker item in mapper.ColumnsMap.Values) - if (item.Get != null) - dict.Add(item.Name, item.Get(o)); - } - else - { - PropertyInfo[] props = ot.GetProperties(); - - foreach (PropertyInfo item in props) - if (item.CanRead) - dict.Add(item.Name, item.GetValue(o, null)); - } - } - - return result; - } - - /// Turns the object into an ExpandoObject. - /// Object to convert. - /// Converted object. - public static dynamic ToExpando(this object o) - { - Type ot = o.GetType(); - - if (ot == typeof(ExpandoObject) || ot == typeof(DynamicExpando)) - return o; - - ExpandoObject result = new ExpandoObject(); - IDictionary dict = (IDictionary)result; - - if (o is IDictionary) - ((IDictionary)o) - .ToList() - .ForEach(kvp => dict.Add(kvp.Key, kvp.Value)); - else if (ot == typeof(NameValueCollection) || ot.IsSubclassOf(typeof(NameValueCollection))) - { - NameValueCollection nameValue = (NameValueCollection)o; - nameValue.Cast() - .Select(key => new KeyValuePair(key, nameValue[key])) - .ToList() - .ForEach(i => dict.Add(i)); - } - else - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(ot); - - if (mapper != null) - { - foreach (DynamicPropertyInvoker item in mapper.ColumnsMap.Values) - if (item.Get != null) - dict.Add(item.Name, item.Get(o)); - } - else - { - PropertyInfo[] props = ot.GetProperties(); - - foreach (PropertyInfo item in props) - if (item.CanRead) - dict.Add(item.Name, item.GetValue(o, null)); - } - } - - return result; - } - - /// Convert data row row into dynamic object. - /// DataRow from which read. - /// Generated dynamic object. - public static dynamic RowToDynamic(this DataRow r) - { - dynamic e = new DynamicExpando(); - - int len = r.Table.Columns.Count; - - for (int i = 0; i < len; i++) - ((IDictionary)e).Add(r.Table.Columns[i].ColumnName, r.IsNull(i) ? null : r[i]); - - return e; - } - - /// Convert data row row into dynamic object. - /// DataRow from which read. - /// Generated dynamic object. - public static dynamic RowToExpando(this DataRow r) - { - dynamic e = new ExpandoObject(); - - int len = r.Table.Columns.Count; - - for (int i = 0; i < len; i++) - ((IDictionary)e).Add(r.Table.Columns[i].ColumnName, r.IsNull(i) ? null : r[i]); - - return e; - } - - /// Convert data row row into dynamic object (upper case key). - /// DataRow from which read. - /// Generated dynamic object. - public static dynamic RowToDynamicUpper(this DataRow r) - { - dynamic e = new DynamicExpando(); - - int len = r.Table.Columns.Count; - - for (int i = 0; i < len; i++) - ((IDictionary)e).Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); - - return e; - } - - /// Convert data row row into dynamic object (upper case key). - /// DataRow from which read. - /// Generated dynamic object. - public static dynamic RowToExpandoUpper(this DataRow r) - { - // ERROR: Memory leak - dynamic e = new ExpandoObject(); - - int len = r.Table.Columns.Count; - - for (int i = 0; i < len; i++) - ((IDictionary)e).Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); - - return e; - } - - internal static Dictionary RowToDynamicUpperDict(this DataRow r) - { - dynamic e = new Dictionary(); - - int len = r.Table.Columns.Count; - - for (int i = 0; i < len; i++) - e.Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); - - return e; - } - - /// Convert reader row into dynamic object. - /// Reader from which read. - /// Generated dynamic object. - public static dynamic RowToDynamic(this IDataReader r) - { - dynamic e = new DynamicExpando(); - IDictionary d = e as IDictionary; - - int c = r.FieldCount; - for (int i = 0; i < c; i++) - try - { - d.Add(r.GetName(i), r.IsDBNull(i) ? null : r[i]); - } - catch (ArgumentException argex) - { - throw new ArgumentException( - string.Format("Field '{0}' is defined more than once in a query.", r.GetName(i)), "Column name or alias", argex); - } - - return e; - } - - /// Convert reader row into dynamic object. - /// Reader from which read. - /// Generated dynamic object. - public static dynamic RowToExpando(this IDataReader r) - { - dynamic e = new ExpandoObject(); - IDictionary d = e as IDictionary; - - int c = r.FieldCount; - for (int i = 0; i < c; i++) - try - { - d.Add(r.GetName(i), r.IsDBNull(i) ? null : r[i]); - } - catch (ArgumentException argex) - { - throw new ArgumentException( - string.Format("Field '{0}' is defined more than once in a query.", r.GetName(i)), "Column name or alias", argex); - } - - return e; - } - - /// Turns the object into a Dictionary. - /// Object to convert. - /// Resulting dictionary. - public static IDictionary ToDictionary(this ExpandoObject o) - { - return (IDictionary)o; - } - - /// Turns the object into a Dictionary. - /// Object to convert. - /// Resulting dictionary. - public static IDictionary ToDictionary(this DynamicExpando o) - { - return (IDictionary)o; - } - - /// Turns the object into a Dictionary. - /// Object to convert. - /// Resulting dictionary. - public static IDictionary ToDictionary(this object o) - { - return o is IDictionary ? - (IDictionary)o : - (IDictionary)o.ToDynamic(); - } - - #endregion Dynamic extensions - - #region Type extensions - - /// Check if type is anonymous. - /// Type to test. - /// Returns true if type is anonymous. - public static bool IsAnonymous(this Type type) - { - // HACK: The only way to detect anonymous types right now. - return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false) - && type.IsGenericType - && (type.Name.Contains("AnonymousType") || type.Name.Contains("AnonType")) - && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) - && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; - } - - /// Check if type implements IEnumerable<> interface. - /// Type to check. - /// Returns true if it does. - public static bool IsGenericEnumerable(this Type type) - { - return type.IsGenericType && type.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); - } - - /// Check if type implements IEnumerable<> interface. - /// Type to check. - /// Returns true if it does. - public static bool IsNullableType(this Type type) - { - Type generic = type.IsGenericType ? type.GetGenericTypeDefinition() : null; - - if (generic != null && generic.Equals(typeof(Nullable<>)) && (type.IsClass || type.IsValueType || type.IsEnum)) - return true; - - return false; - } - - /// Check if type is collection of any kind. - /// Type to check. - /// Returns true if it is. - public static bool IsCollection(this Type type) - { - if (!type.IsArray) - return type.IsGenericEnumerable(); - - return true; - } - - /// Check if type is collection of value types like int. - /// Type to check. - /// Returns true if it is. - public static bool IsCollectionOfValueTypes(this Type type) - { - if (type.IsArray) - return type.GetElementType().IsValueType; - else - { - if (type.IsGenericType && type.GetInterfaces().Any(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>))) - { - Type[] gt = type.GetGenericArguments(); - - return (gt.Length == 1) && gt[0].IsValueType; - } - } - - return false; - } - - /// Gets corresponding to the - /// provided . - /// The type to be converted. - /// Returns corresponding to the - /// provided . - public static DbType ToDbType(this Type t) - { - return TypeMap.TryGetNullable(t) ?? DbType.Object; - } - - /// Gets corresponding to the - /// provided . - /// The type to be converted. - /// Returns corresponding to the - /// provided . - public static Type ToType(this DbType dbt) - { - if (dbt == DbType.String) - return typeof(string); - - foreach (KeyValuePair tdbt in TypeMap) - if (tdbt.Value == dbt) - return tdbt.Key; - - return typeof(object); - } - - /// Determines whether the specified value is has only ASCII chars. - /// The value to check. - /// Returns true if the specified value has only ASCII cars; otherwise, false. - public static bool IsASCII(this string value) - { - return Encoding.UTF8.GetByteCount(value) == value.Length; - } - - #endregion Type extensions - - #region IDictionary extensions - - /// Gets the value associated with the specified key. - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - /// Dictionary to probe. - /// The key whose value to get. - /// Nullable type containing value or null if key was not found. - public static TValue? TryGetNullable(this IDictionary dict, TKey key) where TValue : struct - { - TValue val; - - if (key != null && dict.TryGetValue(key, out val)) - return val; - - return null; - } - - /// Gets the value associated with the specified key. - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - /// Dictionary to probe. - /// The key whose value to get. - /// Instance of object or null if not found. - public static TValue TryGetValue(this IDictionary dict, TKey key) where TValue : class - { - TValue val; - - if (key != null && dict != null && dict.TryGetValue(key, out val)) - return val; - - return default(TValue); - } - - /// Adds element to dictionary and returns added value. - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - /// Dictionary to which add value. - /// The key under which value value will be added. - /// Value to add. - /// Instance of object or null if not found. - public static TValue AddAndPassValue(this IDictionary dict, TKey key, TValue value) - { - dict.Add(key, value); - return value; - } - - #endregion IDictionary extensions - - #region IDataReader extensions - - /// Gets the information corresponding - /// to the type of that would be returned from - /// . - /// The data reader. - /// The index of the field to find. - /// The information corresponding to the - /// type of that would be returned from - /// . - public static DbType GetFieldDbType(this IDataReader r, int i) - { - return TypeMap.TryGetNullable(r.GetFieldType(i)) ?? DbType.String; - } - - internal static IEnumerable EnumerateReader(this IDataReader r) - { - while (r.Read()) - yield return r.RowToDynamic(); - } - - /// Creates cached reader object from non cached reader. - /// The reader to cache. - /// The offset row. - /// The limit to number of tows. -1 is no limit. - /// The progress delegate. - /// Returns new instance of cached reader or current instance of a reader. - public static IDataReader CachedReader(this IDataReader r, int offset = 0, int limit = -1, Func progress = null) - { - if (r is DynamicCachedReader) - return r; - - return new DynamicCachedReader(r, offset, limit, progress); - } - - #endregion IDataReader extensions - - #region Mapper extensions - - /// MapEnumerable object enumerator into specified type. - /// Type to which columnMap results. - /// Source enumerator. - /// Enumerator of specified type. - public static IEnumerable MapEnumerable(this IEnumerable enumerable) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - foreach (object item in enumerable) - yield return (T)mapper.Create(item); - } - - /// MapEnumerable object item into specified type. - /// Type to which columnMap results. - /// Source object. - /// Item of specified type. - public static T Map(this object item) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - return (T)mapper.Create(item); - } - - /// MapEnumerable object enumerator into specified type using property names. - /// Type to which columnMap results. - /// Source enumerator. - /// Enumerator of specified type. - public static IEnumerable MapEnumerableByProperty(this IEnumerable enumerable) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - foreach (object item in enumerable) - yield return (T)mapper.CreateByProperty(item); - } - - /// MapEnumerable object item into specified type using property names. - /// Type to which columnMap results. - /// Source object. - /// Item of specified type. - public static T MapByProperty(this object item) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - return (T)mapper.CreateByProperty(item); - } - - /// Fill object of specified type with data from source object. - /// Type to which columnMap results. - /// Item to which columnMap data. - /// Item from which extract data. - /// Filled item. - public static T Fill(this T item, object source) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - mapper.Map(source, item); - - return item; - } - - /// Fill object of specified type with data from source object using property names. - /// Type to which columnMap results. - /// Item to which columnMap data. - /// Item from which extract data. - /// Filled item. - public static T FillByProperty(this T item, object source) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - mapper.MapByProperty(source, item); - - return item; - } - - /// MapEnumerable object enumerator into specified type. - /// Source enumerator. - /// Type to which columnMap results. - /// Enumerator of specified type. - public static IEnumerable MapEnumerable(this IEnumerable enumerable, Type type) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - foreach (object item in enumerable) - yield return mapper.Create(item); - } - - /// MapEnumerable object item into specified type. - /// Source object. - /// Type to which columnMap results. - /// Item of specified type. - public static object Map(this object item, Type type) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - return mapper.Create(item); - } - - /// MapEnumerable object enumerator into specified type using property names. - /// Source enumerator. - /// Type to which columnMap results. - /// Enumerator of specified type. - public static IEnumerable MapEnumerableByProperty(this IEnumerable enumerable, Type type) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - foreach (object item in enumerable) - yield return mapper.CreateByProperty(item); - } - - /// MapEnumerable object item into specified type using property names. - /// Source object. - /// Type to which columnMap results. - /// Item of specified type. - public static object MapByProperty(this object item, Type type) - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - return mapper.CreateByProperty(item); - } - - /// Converts the elements of an - /// to the specified type. - /// The type to convert the elements of source to. - /// The that - /// contains the elements to be converted. - /// An enumerator that contains each element of - /// the source sequence converted to the specified type. - /// Thrown when - /// source is null. - /// An element in the - /// sequence cannot be cast to type T or enumerator - /// is not . - public static IEnumerable CastEnumerable(this object enumerator) - { - return (enumerator as System.Collections.IEnumerable).Cast(); - } - - #endregion Mapper extensions - - #region TryParse extensions - -#if !DYNAMORM_OMMIT_TRYPARSE - - /// Generic try parse. - /// Type to parse to. - /// Value to parse. - /// Handler of a try parse method. - /// Returns true if conversion was successful. - public static T? TryParse(this string value, TryParseHandler handler) where T : struct - { - if (String.IsNullOrEmpty(value)) - return null; - - T result; - - if (handler(value, out result)) - return result; - - return null; - } - - /// Generic try parse with default value. - /// Type to parse to. - /// Value to parse. - /// Default value of a result. - /// Handler of a try parse method. - /// Returns true if conversion was successful. - public static T TryParseDefault(this string value, T defaultValue, TryParseHandler handler) - { - if (String.IsNullOrEmpty(value)) - return defaultValue; - - T result; - - if (handler(value, out result)) - return result; - - return defaultValue; - } - - /// Delegate from try parse function of a type. - /// Type which implements this function. - /// Value to parse. - /// Resulting value. - /// Returns true if conversion was successful. - public delegate bool TryParseHandler(string value, out T result); - -#endif - - #endregion TryParse extensions - - #region Coalesce - besicaly not an extensions - - /// Select first not null value. - /// Type to return. - /// Values to check. - /// First not null or default value. - public static T Coalesce(params T[] vals) where T : class - { - return vals.FirstOrDefault(v => v != null); - } - - /// Select first not null value. - /// Type to return. - /// Values to check. - /// First not null or default value. - public static T? CoalesceNullable(params T?[] vals) where T : struct - { - return vals.FirstOrDefault(v => v != null); - } - - #endregion Coalesce - besicaly not an extensions - } - - /// Represents database connection options. - [Flags] - [System.Reflection.ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)] - public enum DynamicDatabaseOptions - { - /// No specific options. - None = 0x00000000, - - /// Only single persistent database connection. - SingleConnection = 0x00000001, - - /// Only one transaction. - SingleTransaction = 0x00000002, - - /// Database supports top syntax (SELECT TOP x ... FROM ...). - SupportTop = 0x00000080, - - /// Database supports limit offset syntax (SELECT ... FROM ... LIMIT x OFFSET y). - SupportLimitOffset = 0x00000040, - - /// Database supports limit offset syntax (SELECT FIRST x SKIP y ... FROM ...). - SupportFirstSkip = 0x00000020, - - /// Database support standard schema. - SupportSchema = 0x00000010, - - /// Database support stored procedures (EXEC procedure ...). - SupportStoredProcedures = 0x00000100, - - /// Database support stored procedures results (Return value). - SupportStoredProceduresResult = 0x00000200, - - /// Database support with no lock syntax. - SupportNoLock = 0x00001000, - - /// Debug option allowing to enable command dumps by default. - DumpCommands = 0x01000000, - } - /// Helper class to easy manage transaction. public class DynamicTransaction : IDbTransaction, IExtendedDisposable { @@ -6954,13 +6701,11 @@ namespace DynamORM 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() { @@ -6982,14 +6727,12 @@ namespace DynamORM 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() { @@ -7011,20 +6754,17 @@ namespace DynamORM 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; } @@ -7047,5067 +6787,160 @@ namespace DynamORM _con = null; _db = null; } - /// Gets a value indicating whether this instance is disposed. public bool IsDisposed { get { return !_isOperational; } } #endregion IExtendedDisposable Members } - - /// Dynamic expando is a simple and temporary class to resolve memory leaks inside ExpandoObject. - public class DynamicExpando : DynamicObject, IDictionary, ICollection>, IEnumerable>, IEnumerable - { - /// Class containing information about last accessed property of dynamic object. - public class PropertyAccess - { - /// Enum describing type of access to object. - public enum TypeOfAccess - { - /// Get member. - Get, - - /// Set member. - Set, - } - - /// Gets the type of operation. - public TypeOfAccess Operation { get; internal set; } - - /// Gets the name of property. - public string Name { get; internal set; } - - /// Gets the type from binder. - public Type RequestedType { get; internal set; } - - /// Gets the type of value stored in object. - public Type Type { get; internal set; } - - /// Gets the value stored in object. - public object Value { get; internal set; } - - /// Gets the last access time. - public long Ticks { get; internal set; } - } - - private Dictionary _data = new Dictionary(); - - private PropertyAccess _lastProp = new PropertyAccess(); - - /// Initializes a new instance of the class. - public DynamicExpando() - { - } - - /// Gets the last accesses property. - /// Description of last accessed property. - public PropertyAccess GetLastAccessesProperty() - { - return _lastProp; - } - - /// Tries to get member value. - /// Returns true, if get member was tried, false otherwise. - /// The context binder. - /// The invocation result. - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - result = _data.TryGetValue(binder.Name); - - _lastProp.Operation = PropertyAccess.TypeOfAccess.Get; - _lastProp.RequestedType = binder.ReturnType; - _lastProp.Name = binder.Name; - _lastProp.Value = result; - _lastProp.Type = result == null ? typeof(void) : result.GetType(); - _lastProp.Ticks = DateTime.Now.Ticks; - - return true; - } - - /// Tries to set member. - /// Returns true, if set member was tried, false otherwise. - /// The context binder. - /// Value which will be set. - public override bool TrySetMember(SetMemberBinder binder, object value) - { - _data[binder.Name] = value; - - _lastProp.Operation = PropertyAccess.TypeOfAccess.Set; - _lastProp.RequestedType = binder.ReturnType; - _lastProp.Name = binder.Name; - _lastProp.Value = value; - _lastProp.Type = value == null ? typeof(void) : value.GetType(); - _lastProp.Ticks = DateTime.Now.Ticks; - - return true; - } - - #region IDictionary implementation - - bool IDictionary.ContainsKey(string key) - { - return _data.ContainsKey(key); - } - - void IDictionary.Add(string key, object value) - { - _data.Add(key, value); - } - - bool IDictionary.Remove(string key) - { - return _data.Remove(key); - } - - bool IDictionary.TryGetValue(string key, out object value) - { - return _data.TryGetValue(key, out value); - } - - object IDictionary.this[string index] { get { return _data[index]; } set { _data[index] = value; } } - - ICollection IDictionary.Keys { get { return _data.Keys; } } - - ICollection IDictionary.Values { get { return _data.Values; } } - - #endregion IDictionary implementation - - #region ICollection implementation - - void ICollection>.Add(KeyValuePair item) - { - ((ICollection>)_data).Add(item); - } - - void ICollection>.Clear() - { - _data.Clear(); - } - - bool ICollection>.Contains(KeyValuePair item) - { - return ((ICollection>)_data).Contains(item); - } - - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - { - ((ICollection>)_data).CopyTo(array, arrayIndex); - } - - bool ICollection>.Remove(KeyValuePair item) - { - return ((ICollection>)_data).Remove(item); - } - - int ICollection>.Count { get { return _data.Count; } } - - bool ICollection>.IsReadOnly { get { return ((ICollection>)_data).IsReadOnly; } } - - #endregion ICollection implementation - - #region IEnumerable implementation - - IEnumerator> IEnumerable>.GetEnumerator() - { - return _data.GetEnumerator(); - } - - #endregion IEnumerable implementation - - #region IEnumerable implementation - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)_data).GetEnumerator(); - } - - #endregion IEnumerable implementation - } - - /// Stores information about column from database schema. - public struct DynamicSchemaColumn - { - /// Gets or sets column name. - public string Name { get; set; } - - /// Gets or sets column type. - public DbType Type { get; set; } - - /// Gets or sets a value indicating whether column is a key. - public bool IsKey { get; set; } - - /// Gets or sets a value indicating whether column should have unique value. - public bool IsUnique { get; set; } - - /// Gets or sets a value indicating whether column allows null or not. - public bool AllowNull { get; set; } - - /// Gets or sets column size. - public int Size { get; set; } - - /// Gets or sets column precision. - public byte Precision { get; set; } - - /// Gets or sets column scale. - public byte Scale { get; set; } - } - - namespace Validation - { - - /// Validation result enum. - public enum ValidateResult - { - /// The valid value. - Valid, - - /// The value is missing. - ValueIsMissing, - - /// The value too small. - ValueTooSmall, - - /// The value too large. - ValueTooLarge, - - /// The too few elements in collection. - TooFewElementsInCollection, - - /// The too many elements in collection. - TooManyElementsInCollection, - - /// The value too short. - ValueTooShort, - - /// The value too long. - ValueTooLong, - - /// The value don't match pattern. - ValueDontMatchPattern, - - /// The not supported. - NotSupported, - } - - /// Required attribute can be used to validate fields in objects using mapper class. - [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] - public class RequiredAttribute : Attribute - { - /// Gets or sets minimum value or length of field. - public decimal? Min { get; set; } - - /// Gets or sets maximum value or length of field. - public decimal? Max { get; set; } - - /// Gets or sets pattern to verify. - public Regex Pattern { get; set; } - - /// Gets or sets a value indicating whether property value is required or not. - public bool Required { get; set; } - - /// Gets or sets a value indicating whether this is an element requirement. - public bool ElementRequirement { get; set; } - - /// Initializes a new instance of the class. - /// This field will be required. - public RequiredAttribute(bool required = true) - { - Required = required; - } - - /// Initializes a new instance of the class. - /// Limiting value to set. - /// Whether set maximum parameter (true) or minimum parameter (false). - /// This field will be required. - public RequiredAttribute(float val, bool max, bool required = true) - { - if (max) - Max = (decimal)val; - else - Min = (decimal)val; - Required = required; - } - - /// Initializes a new instance of the class. - /// Minimum value to set. - /// Maximum value to set. - /// This field will be required. - public RequiredAttribute(float min, float max, bool required = true) - { - Min = (decimal)min; - Max = (decimal)max; - Required = required; - } - - /// Initializes a new instance of the class. - /// Minimum value to set. - /// Maximum value to set. - /// Pattern to check. - /// This field will be required. - public RequiredAttribute(float min, float max, string pattern, bool required = true) - { - Min = (decimal)min; - Max = (decimal)max; - Pattern = new Regex(pattern, RegexOptions.Compiled); - Required = required; - } - - internal ValidateResult ValidateSimpleValue(DynamicPropertyInvoker dpi, object val) - { - return ValidateSimpleValue(dpi.Type, dpi.IsGnericEnumerable, val); - } - - internal ValidateResult ValidateSimpleValue(Type type, bool isGnericEnumerable, object val) - { - if (val == null) - { - if (Required) - return ValidateResult.ValueIsMissing; - else - return ValidateResult.Valid; - } - - if (type.IsValueType) - { - if (val is decimal || val is long || val is int || val is float || val is double || val is short || val is byte || - val is decimal? || val is long? || val is int? || val is float? || val is double? || val is short? || val is byte?) - { - decimal dec = Convert.ToDecimal(val); - - if (Min.HasValue && Min.Value > dec) - return ValidateResult.ValueTooSmall; - - if (Max.HasValue && Max.Value < dec) - return ValidateResult.ValueTooLarge; - - return ValidateResult.Valid; - } - else - { - var str = val.ToString(); - - if (Min.HasValue && Min.Value > str.Length) - return ValidateResult.ValueTooShort; - - if (Max.HasValue && Max.Value < str.Length) - return ValidateResult.ValueTooLong; - - if (Pattern != null && !Pattern.IsMatch(str)) - return ValidateResult.ValueDontMatchPattern; - - return ValidateResult.Valid; - } - } - else if (type.IsArray || isGnericEnumerable) - { - int? cnt = null; - - var list = val as IEnumerable; - if (list != null) - cnt = list.Count(); - else - { - var enumerable = val as IEnumerable; - if (enumerable != null) - cnt = enumerable.Cast().Count(); - } - - if (Min.HasValue && Min.Value > cnt) - return ValidateResult.TooFewElementsInCollection; - - if (Max.HasValue && Max.Value < cnt) - return ValidateResult.TooManyElementsInCollection; - - return ValidateResult.Valid; - } - else if (type == typeof(string)) - { - var str = (string)val; - - if (Min.HasValue && Min.Value > str.Length) - return ValidateResult.ValueTooShort; - - if (Max.HasValue && Max.Value < str.Length) - return ValidateResult.ValueTooLong; - - if (Pattern != null && !Pattern.IsMatch(str)) - return ValidateResult.ValueDontMatchPattern; - - return ValidateResult.Valid; - } - - return ValidateResult.NotSupported; - } - } - - /// Validation result. - public class ValidationResult - { - /// Gets the property invoker. - public DynamicPropertyInvoker Property { get; internal set; } - - /// Gets the requirement definition. - public RequiredAttribute Requirement { get; internal set; } - - /// Gets the value that is broken. - public object Value { get; internal set; } - - /// Gets the result. - public ValidateResult Result { get;internal set;} - } - } - - namespace Mapper - { - - /// Allows to add table name to class. - [AttributeUsage(AttributeTargets.Class)] - public class TableAttribute : Attribute - { - /// Gets or sets table owner name. - public string Owner { get; set; } - - /// Gets or sets name. - public string Name { get; set; } - - /// Gets or sets a value indicating whether override database - /// schema values. - /// If database doesn't support schema, you still have to - /// set this to true to get schema from type. - public bool Override { get; set; } - } - - /// Class with mapper cache. - public static class DynamicMapperCache - { - private static readonly object SyncLock = new object(); - private static Dictionary _cache = new Dictionary(); - - /// Get type mapper. - /// Type of mapper. - /// Type mapper. - public static DynamicTypeMap GetMapper() - { - return GetMapper(typeof(T)); - } - - /// Get type mapper. - /// Type of mapper. - /// Type mapper. - public static DynamicTypeMap GetMapper(Type type) - { - if (type == null) - return null; - /*if (type.IsAnonymous()) - return null;*/ - - DynamicTypeMap mapper = null; - - lock (SyncLock) - { - if (!_cache.TryGetValue(type, out mapper)) - { - mapper = new DynamicTypeMap(type); - - if (mapper != null) - _cache.Add(type, mapper); - } - } - - return mapper; - } - } - - /// Allows to add table name to class. - [AttributeUsage(AttributeTargets.Property)] - public class ColumnAttribute : Attribute - { - /// Gets or sets name. - public string Name { get; set; } - - /// Gets or sets column type. - /// Used when overriding schema. - public DbType? Type { get; set; } - - /// Gets or sets a value indicating whether column is a key. - public bool IsKey { get; set; } - - /// Gets or sets a value indicating whether column allows null or not. - /// Information only. - public bool AllowNull { get; set; } - - /// Gets or sets a value indicating whether column should have unique value. - /// Used when overriding schema. - public bool? IsUnique { get; set; } - - /// Gets or sets column size. - /// Used when overriding schema. - public int? Size { get; set; } - - /// Gets or sets column precision. - /// Used when overriding schema. - public byte? Precision { get; set; } - - /// Gets or sets column scale. - /// Used when overriding schema. - public byte? Scale { get; set; } - - /// Gets or sets a value indicating whether this column is no allowed to be inserted. - /// This is only a suggestion to automated mapping. - public bool IsNoInsert { get; set; } - - /// Gets or sets a value indicating whether this column is no allowed to be updated. - /// This is only a suggestion to automated mapping. - public bool IsNoUpdate { get; set; } - - #region Constructors - - /// Initializes a new instance of the class. - public ColumnAttribute() - { - AllowNull = true; - } - - /// Initializes a new instance of the class. - /// Name of column. - public ColumnAttribute(string name) - : this() - { - Name = name; - } - - /// Initializes a new instance of the class. - /// Set column as a key column. - public ColumnAttribute(bool isKey) - : this() - { - IsKey = isKey; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - public ColumnAttribute(string name, bool isKey) - : this(name) - { - IsKey = isKey; - } - - /// Initializes a new instance of the class. - /// Set column as a key column. - /// Set column type. - public ColumnAttribute(bool isKey, DbType type) - : this(isKey) - { - Type = type; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - /// Set column type. - public ColumnAttribute(string name, bool isKey, DbType type) - : this(name, isKey) - { - Type = type; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - /// Set column type. - /// Set column value size. - public ColumnAttribute(string name, bool isKey, DbType type, int size) - : this(name, isKey, type) - { - Size = size; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - /// Set column type. - /// Set column value precision. - /// Set column value scale. - public ColumnAttribute(string name, bool isKey, DbType type, byte precision, byte scale) - : this(name, isKey, type) - { - Precision = precision; - Scale = scale; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - /// Set column type. - /// Set column value size. - /// Set column value precision. - /// Set column value scale. - public ColumnAttribute(string name, bool isKey, DbType type, int size, byte precision, byte scale) - : this(name, isKey, type, precision, scale) - { - Size = size; - } - - /// Initializes a new instance of the class. - /// Name of column. - /// Set column as a key column. - /// Set column has unique value. - /// Set column type. - /// Set column value size. - /// Set column value precision. - /// Set column value scale. - public ColumnAttribute(string name, bool isKey, bool isUnique, DbType type, int size, byte precision, byte scale) - : this(name, isKey, type, size, precision, scale) - { - IsUnique = isUnique; - } - - #endregion Constructors - } - - /// Type cast helper. - public static class DynamicCast - { - /// Gets the default value. - /// The type. - /// Default instance. - public static object GetDefaultValue(this Type type) - { - return type.IsValueType ? TypeDefaults.GetOrAdd(type, t => Activator.CreateInstance(t)) : null; - } - - /// Casts the object to this type. - /// The type to which cast value. - /// The value to cast. - /// Value casted to new type. - public static object CastObject(this Type type, object val) - { - return GetConverter(type, val)(val); - } - - private static readonly ConcurrentDictionary TypeDefaults = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary> TypeAsCasts = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> TypeConvert = new ConcurrentDictionary>(); - private static readonly ParameterExpression ConvParameter = Expression.Parameter(typeof(object), "val"); - - [MethodImpl(MethodImplOptions.Synchronized)] - private static Func GetConverter(Type targetType, object val) - { - Func fn; - - if (!targetType.IsValueType && !val.GetType().IsValueType) - { - if (!TypeAsCasts.TryGetValue(targetType, out fn)) - { - UnaryExpression instanceCast = Expression.TypeAs(ConvParameter, targetType); - - fn = Expression.Lambda>(Expression.TypeAs(instanceCast, typeof(object)), ConvParameter).Compile(); - TypeAsCasts.AddOrUpdate(targetType, fn, (t, f) => fn); - } - } - else - { - var fromType = val != null ? val.GetType() : typeof(object); - var key = new PairOfTypes(fromType, targetType); - if (TypeConvert.TryGetValue(key, out fn)) - return fn; - - fn = (Func)Expression.Lambda(Expression.Convert(Expression.Convert(Expression.Convert(ConvParameter, fromType), targetType), typeof(object)), ConvParameter).Compile(); - TypeConvert.AddOrUpdate(key, fn, (t, f) => fn); - } - - return fn; - } - - private class PairOfTypes - { - private readonly Type _first; - private readonly Type _second; - - public PairOfTypes(Type first, Type second) - { - this._first = first; - this._second = second; - } - - public override int GetHashCode() - { - return (31 * _first.GetHashCode()) + _second.GetHashCode(); - } - - public override bool Equals(object obj) - { - if (obj == this) - return true; - - var other = obj as PairOfTypes; - if (other == null) - return false; - - return _first.Equals(other._first) - && _second.Equals(other._second); - } - } - } - - /// Allows to add ignore action to property. - /// Property still get's mapped from output. - [AttributeUsage(AttributeTargets.Property)] - public class IgnoreAttribute : Attribute - { - } - - /// Dynamic property invoker. - public class DynamicPropertyInvoker - { - internal class ParameterSpec - { - public string Name { get; set; } - - public DbType Type { get; set; } - - public int Ordinal { get; set; } - } - - /// Gets the array type of property if main type is a form of collection. - public Type ArrayType { get; private set; } - - /// Gets a value indicating whether this property is in fact a generic list. - public bool IsGnericEnumerable { get; private set; } - - /// Gets the type of property. - public Type Type { get; private set; } - - /// Gets value getter. - public Func Get { get; private set; } - - /// Gets value setter. - public Action Setter { get; private set; } - - /// Gets the property information. - public PropertyInfo PropertyInfo { get; private set; } - - /// Gets name of property. - public string Name { get; private set; } - - /// Gets type column description. - public ColumnAttribute Column { get; private set; } - - /// Gets type list of property requirements. - public List Requirements { get; private set; } - - /// Gets a value indicating whether this is ignored in some cases. - public bool Ignore { get; private set; } - - /// Gets a value indicating whether this instance hold data contract type. - public bool IsDataContract { get; private set; } - - /// Initializes a new instance of the class. - /// Property info to be invoked in the future. - /// Column attribute if exist. - public DynamicPropertyInvoker(PropertyInfo property, ColumnAttribute attr) - { - PropertyInfo = property; - Name = property.Name; - Type = property.PropertyType; - - object[] ignore = property.GetCustomAttributes(typeof(IgnoreAttribute), false); - Requirements = property.GetCustomAttributes(typeof(RequiredAttribute), false).Cast().ToList(); - - Ignore = ignore != null && ignore.Length > 0; - - IsGnericEnumerable = Type.IsGenericEnumerable(); - - ArrayType = Type.IsArray ? Type.GetElementType() : - IsGnericEnumerable ? Type.GetGenericArguments().First() : - Type; - - IsDataContract = ArrayType.GetCustomAttributes(false).Any(x => x.GetType().Name == "DataContractAttribute"); - - if (ArrayType.IsArray) - throw new InvalidOperationException("Jagged arrays are not supported"); - - if (ArrayType.IsGenericEnumerable()) - throw new InvalidOperationException("Enumerables of enumerables are not supported"); - - Column = attr; - - if (attr != null && attr.AllowNull && !Type.IsNullableType() && Type != typeof(string)) - attr.AllowNull = false; - - if (property.CanRead) - Get = CreateGetter(property); - - if (property.CanWrite) - Setter = CreateSetter(property); - } - - private Func CreateGetter(PropertyInfo property) - { - if (property == null || !property.CanRead) - return null; - - var objParm = Expression.Parameter(typeof(object), "o"); - var target = property.DeclaringType.IsValueType - ? (Expression)Expression.Convert(objParm, property.DeclaringType) - : Expression.TypeAs(objParm, property.DeclaringType); - - return Expression.Lambda>( - Expression.Convert( - Expression.Property( - target, - property.Name), - typeof(object)), objParm).Compile(); - } - - private Action CreateSetter(PropertyInfo property) - { - if (!property.CanWrite) - return null; - - var objParm = Expression.Parameter(typeof(object), "o"); - var valueParm = Expression.Parameter(typeof(object), "value"); - - return Expression.Lambda>( - Expression.Assign( - Expression.Property( - Expression.Convert(objParm, property.DeclaringType), - property.Name), - Expression.Convert(valueParm, property.PropertyType)), - objParm, valueParm).Compile(); - } - - /// Sets the specified value to destination object. - /// The destination object. - /// The value. - public void Set(object dest, object val, bool byProperty = false) - { - object value = null; - - try - { - if (!Type.IsAssignableFrom(val.GetType())) - { - if (Type.IsArray || IsGnericEnumerable) - { - if (val != null) - { - if (val is IEnumerable) - { - var lst = (val as IEnumerable).Select(x => GetElementVal(ArrayType, x, byProperty)).ToList(); - - value = Array.CreateInstance(ArrayType, lst.Count); - - int i = 0; - foreach (var e in lst) - ((Array)value).SetValue(e, i++); - } - else - { - value = Array.CreateInstance(ArrayType, 1); - ((Array)value).SetValue(GetElementVal(ArrayType, val, byProperty), 0); - } - } - else - value = Array.CreateInstance(ArrayType, 0); - } - else - value = GetElementVal(Type, val, byProperty); - } - else - value = val; - - Setter(dest, value); - } - catch (Exception ex) - { - throw new DynamicMapperException( - string.Format("Error trying to convert and set value '{0}' of type '{1}' to type '{2}' in object of type '{3}'", - val == null ? string.Empty : val.ToString(), val.GetType(), Type.FullName, dest.GetType().FullName), - ex); - } - } - - private object GetElementVal(System.Type etype, object val, bool byProperty) - { - bool nullable = etype.IsGenericType && etype.GetGenericTypeDefinition() == typeof(Nullable<>); - Type type = Nullable.GetUnderlyingType(etype) ?? etype; - - if (val == null && type.IsValueType) - { - if (nullable) - return null; - else - return Activator.CreateInstance(Type); - } - else if ((val == null && !type.IsValueType) || (val != null && type == val.GetType())) - return val; - else if (type.IsEnum && val.GetType().IsValueType) - return Enum.ToObject(type, val); - else if (type.IsEnum) - try - { - return Enum.Parse(type, val.ToString()); - } - catch (ArgumentException) - { - if (nullable) - return null; - - throw; - } - else if (Type == typeof(string) && val.GetType() == typeof(Guid)) - return val.ToString(); - else if (Type == typeof(Guid)) - { - if (val.GetType() == typeof(byte[])) - return new Guid((byte[])val); - else if (val.GetType() == typeof(string)) - { - Guid g; - return Guid.TryParse((string)val, out g) ? g : Guid.Empty; - } - else return (nullable) ? null : (object)Guid.Empty; - } - else if (!typeof(IConvertible).IsAssignableFrom(type) && (IsDataContract || (!type.IsValueType && val is IDictionary))) - { - if (byProperty) - return val.MapByProperty(type); - - return val.Map(type); - } - else - try - { - return Convert.ChangeType(val, type); - } - catch - { - if (nullable) - return null; - - throw; - } - } - - #region Type command cache - - internal ParameterSpec InsertCommandParameter { get; set; } - - internal ParameterSpec UpdateCommandParameter { get; set; } - - internal ParameterSpec DeleteCommandParameter { get; set; } - - #endregion Type command cache - } - - /// Exception thrown when mapper fails to set or get a property. - /// - public class DynamicMapperException : Exception - { - /// Initializes a new instance of the class. - public DynamicMapperException() - { - } - - /// Initializes a new instance of the class. - /// The message that describes the error. - public DynamicMapperException(string message) : base(message) - { - } - - /// Initializes a new instance of the class. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public DynamicMapperException(string message, Exception innerException) : base(message, innerException) - { - } - } - - /// Represents type columnMap. - public class DynamicTypeMap - { - /// Gets mapper destination type creator. - public Type Type { get; private set; } - - /// Gets type table description. - public TableAttribute Table { get; private set; } - - /// Gets object creator. - public Func Creator { get; private set; } - - /// Gets map of columns to properties. - /// Key: Column name (lower), Value: . - public Dictionary ColumnsMap { get; private set; } - - /// Gets map of properties to column. - /// Key: Property name, Value: Column name. - public Dictionary PropertyMap { get; private set; } - - /// Gets list of ignored properties. - public List Ignored { get; private set; } - - /// Initializes a new instance of the class. - /// Type to which columnMap objects. - public DynamicTypeMap(Type type) - { - Type = type; - - object[] attr = type.GetCustomAttributes(typeof(TableAttribute), false); - - if (attr != null && attr.Length > 0) - Table = (TableAttribute)attr[0]; - - Creator = CreateCreator(); - CreateColumnAndPropertyMap(); - } - - private void CreateColumnAndPropertyMap() - { - Dictionary columnMap = new Dictionary(); - Dictionary propertyMap = new Dictionary(); - List ignored = new List(); - - foreach (PropertyInfo pi in GetAllMembers(Type).Where(x => x is PropertyInfo).Cast()) - { - // Skip indexers - if (pi.GetIndexParameters().Any()) - continue; - - ColumnAttribute attr = null; - - object[] attrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true); - - if (attrs != null && attrs.Length > 0) - attr = (ColumnAttribute)attrs[0]; - - string col = attr == null || string.IsNullOrEmpty(attr.Name) ? pi.Name : attr.Name; - - DynamicPropertyInvoker val = new DynamicPropertyInvoker(pi, attr); - columnMap.Add(col.ToLower(), val); - - propertyMap.Add(pi.Name, col); - - if (val.Ignore) - ignored.Add(pi.Name); - } - - ColumnsMap = columnMap; - PropertyMap = propertyMap; - - Ignored = ignored; ////columnMap.Where(i => i.Value.Ignore).Select(i => i.Value.Name).ToList(); - } - - private Func CreateCreator() - { - var c = Type.GetConstructor(Type.EmptyTypes); - if (c == null) - c = Type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null); - - if (c != null) - return Expression.Lambda>(Expression.New(Type)).Compile(); - - return null; - } - - /// Create object of type and fill values from source. - /// Object containing values that will be mapped to newly created object. - /// New object of type with matching values from source. - public object Create(object source) - { - return Map(source, Creator()); - } - - /// Create object of type and fill values from source using property names. - /// Object containing values that will be mapped to newly created object. - /// New object of type with matching values from source. - public object CreateByProperty(object source) - { - return MapByProperty(source, Creator()); - } - - /// Fill values from source to object in destination. - /// Object containing values that will be mapped to newly created object. - /// Object of type to which copy values from source. - /// Object of type with matching values from source. - public object Map(object source, object destination) - { - DynamicPropertyInvoker dpi = null; - - foreach (KeyValuePair item in source.ToDictionary()) - { - if (ColumnsMap.TryGetValue(item.Key.ToLower(), out dpi) && item.Value != null) - if (dpi.Setter != null) - dpi.Set(destination, item.Value); - } - - return destination; - } - - /// Fill values from source to object in destination using property names. - /// Object containing values that will be mapped to newly created object. - /// Object of type to which copy values from source. - /// Object of type with matching values from source. - public object MapByProperty(object source, object destination) - { - string cn = null; - DynamicPropertyInvoker dpi = null; - - foreach (KeyValuePair item in source.ToDictionary()) - { - if (PropertyMap.TryGetValue(item.Key, out cn) && item.Value != null) - if (ColumnsMap.TryGetValue(cn.ToLower(), out dpi) && item.Value != null) - if (dpi.Setter != null) - dpi.Set(destination, item.Value, true); - } - - return destination; - } - - /// Validates the object. - /// The value. - /// List of not valid results. - public IList ValidateObject(object val) - { - var result = new List(); - - if (val == null || val.GetType() != Type) - return null; - - foreach (var prop in ColumnsMap.Values) - { - if (prop.Requirements == null || !prop.Requirements.Any()) - continue; - - var v = prop.Get(val); - - foreach (var r in prop.Requirements.Where(x => !x.ElementRequirement)) - { - var valid = r.ValidateSimpleValue(prop, v); - - if (valid == ValidateResult.Valid) - { - if (prop.Type.IsArray || prop.IsGnericEnumerable) - { - var map = DynamicMapperCache.GetMapper(prop.ArrayType); - - var list = v as IEnumerable; - - if (list == null) - { - var enumerable = v as IEnumerable; - if (enumerable != null) - list = enumerable.Cast(); - } - - if (list != null) - foreach (var item in list) - { - if (prop.Requirements.Any(x => x.ElementRequirement)) - { - foreach (var re in prop.Requirements.Where(x => x.ElementRequirement)) - { - var validelem = re.ValidateSimpleValue(prop.ArrayType, prop.ArrayType.IsGenericEnumerable(), item); - - if (validelem == ValidateResult.NotSupported) - { - result.AddRange(map.ValidateObject(item)); - break; - } - else if (validelem != ValidateResult.Valid) - result.Add(new ValidationResult() - { - Property = prop, - Requirement = r, - Value = item, - Result = validelem, - }); - } - } - else - result.AddRange(map.ValidateObject(item)); - } - } - - continue; - } - - if (valid == ValidateResult.NotSupported) - { - result.AddRange(DynamicMapperCache.GetMapper(prop.Type).ValidateObject(v)); - continue; - } - - result.Add(new ValidationResult() - { - Property = prop, - Requirement = r, - Value = v, - Result = valid, - }); - } - } - - return result; - } - - private IEnumerable GetAllMembers(Type type) - { - if (type.IsInterface) - { - List members = new List(); - List considered = new List(); - Queue queue = new Queue(); - - considered.Add(type); - queue.Enqueue(type); - - while (queue.Count > 0) - { - Type subType = queue.Dequeue(); - foreach (Type subInterface in subType.GetInterfaces()) - { - if (considered.Contains(subInterface)) continue; - - considered.Add(subInterface); - queue.Enqueue(subInterface); - } - - MemberInfo[] typeProperties = subType.GetMembers( - BindingFlags.FlattenHierarchy - | BindingFlags.Public - | BindingFlags.Instance); - - IEnumerable newPropertyInfos = typeProperties - .Where(x => !members.Contains(x)); - - members.InsertRange(0, newPropertyInfos); - } - - return members; - } - - return type.GetMembers(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance); - } - - #region Type command cache - - internal string InsertCommandText { get; set; } - - internal string UpdateCommandText { get; set; } - - internal string DeleteCommandText { get; set; } - - #endregion Type command cache - } - } - - namespace Helpers - { - - /// Framework detection and specific implementations. - public static class FrameworkTools - { - #region Mono or .NET Framework detection - - /// This is pretty simple trick. - private static bool _isMono = Type.GetType("Mono.Runtime") != null; - - /// Gets a value indicating whether application is running under mono runtime. - public static bool IsMono { get { return _isMono; } } - - #endregion Mono or .NET Framework detection - - static FrameworkTools() - { - _frameworkTypeArgumentsGetter = CreateTypeArgumentsGetter(); - } - - #region GetGenericTypeArguments - - private static Func> _frameworkTypeArgumentsGetter = null; - - private static Func> CreateTypeArgumentsGetter() - { - // HACK: Creating binders assuming types are correct... this may fail. - if (IsMono) - { - Type binderType = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder"); - - if (binderType != null) - { - ParameterExpression param = Expression.Parameter(typeof(InvokeMemberBinder), "o"); - - try - { - return Expression.Lambda>>( - Expression.TypeAs( - Expression.Field( - Expression.TypeAs(param, binderType), "typeArguments"), - typeof(IList)), param).Compile(); - } - catch - { - } - - PropertyInfo prop = binderType.GetProperty("TypeArguments"); - - if (!prop.CanRead) - return null; - - return Expression.Lambda>>( - Expression.TypeAs( - Expression.Property( - Expression.TypeAs(param, binderType), prop.Name), - typeof(IList)), param).Compile(); - } - } - else - { - Type inter = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); - - if (inter != null) - { - PropertyInfo prop = inter.GetProperty("TypeArguments"); - - if (!prop.CanRead) - return null; - - ParameterExpression objParm = Expression.Parameter(typeof(InvokeMemberBinder), "o"); - - return Expression.Lambda>>( - Expression.TypeAs( - Expression.Property( - Expression.TypeAs(objParm, inter), prop.Name), - typeof(IList)), objParm).Compile(); - } - } - - return null; - } - - /// Extension method allowing to easily extract generic type - /// arguments from assuming that it - /// inherits from - /// Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder - /// in .NET Framework or - /// Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder - /// under Mono. - /// Binder from which get type arguments. - /// This is generally a bad solution, but there is no other - /// currently so we have to go with it. - /// List of types passed as generic parameters. - public static IList GetGenericTypeArguments(this InvokeMemberBinder binder) - { - // First try to use delegate if exist - if (_frameworkTypeArgumentsGetter != null) - return _frameworkTypeArgumentsGetter(binder); - - if (_isMono) - { - // HACK: Using Reflection - // In mono this is trivial. - - // First we get field info. - FieldInfo field = binder.GetType().GetField("typeArguments", BindingFlags.Instance | - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); - - // If this was a success get and return it's value - if (field != null) - return field.GetValue(binder) as IList; - else - { - PropertyInfo prop = binder.GetType().GetProperty("TypeArguments"); - - // If we have a property, return it's value - if (prop != null) - return prop.GetValue(binder, null) as IList; - } - } - else - { - // HACK: Using Reflection - // In this case, we need more aerobic :D - - // First, get the interface - Type inter = binder.GetType().GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); - - if (inter != null) - { - // Now get property. - PropertyInfo prop = inter.GetProperty("TypeArguments"); - - // If we have a property, return it's value - if (prop != null) - return prop.GetValue(binder, null) as IList; - } - } - - // Sadly return null if failed. - return null; - } - - #endregion GetGenericTypeArguments - } - - /// Extends interface. - public interface IFinalizerDisposable : IExtendedDisposable - { - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - void Dispose(bool disposing); - } - - /// Class contains unclassified extensions. - internal static class UnclassifiedExtensions - { - /// Easy way to use conditional value. - /// Includes . - /// Input object type to check. - /// Result type. - /// The object to check. - /// The select function. - /// The else value. - /// Selected value or default value. - /// It lets you do this: - /// var lname = thingy.NullOr(t => t.Name).NullOr(n => n.ToLower()); - /// which is more fluent and (IMO) easier to read than this: - /// var lname = (thingy != null ? thingy.Name : null) != null ? thingy.Name.ToLower() : null; - /// - public static R NullOr(this T obj, Func func, R elseValue = default(R)) where T : class - { - return obj != null && obj != DBNull.Value ? - func(obj) : elseValue; - } - - /// Easy way to use conditional value. - /// Includes . - /// Input object type to check. - /// Result type. - /// The object to check. - /// The select function. - /// The else value function. - /// Selected value or default value. - /// It lets you do this: - /// var lname = thingy.NullOr(t => t.Name).NullOr(n => n.ToLower()); - /// which is more fluent and (IMO) easier to read than this: - /// var lname = (thingy != null ? thingy.Name : null) != null ? thingy.Name.ToLower() : null; - /// - public static R NullOrFn(this T obj, Func func, Func elseFunc = null) where T : class - { - // Old if to avoid recurency. - return obj != null && obj != DBNull.Value ? - func(obj) : elseFunc != null ? elseFunc() : default(R); - } - - #if !NET6_0_OR_GREATER - /// Simple distinct by selector extension. - /// The enumerator of elements distinct by specified selector. - /// Source collection. - /// Distinct key selector. - /// The enumerable element type parameter. - /// The selector type parameter. - public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) - { - HashSet seenKeys = new HashSet(); - foreach (R element in source) - if (seenKeys.Add(keySelector(element))) - yield return element; - } - #endif - } - - /// Extensions for data reader handling. - public static class DataReaderExtensions - { - /// Gets the data table from data reader. - /// The data reader. - /// The name to give the table. If tableName is null or an empty string, a default name is given when added to the System.Data.DataTableCollection. - /// The namespace for the XML representation of the data stored in the DataTable. - /// - public static DataTable ToDataTable(this IDataReader r, string name = null, string nameSpace = null) - { - DataTable schemaTable = r.GetSchemaTable(); - DataTable resultTable = new DataTable(name, nameSpace); - - foreach (DataRow col in schemaTable.Rows) - { - dynamic c = col.RowToDynamicUpper(); - - DataColumn dataColumn = new DataColumn(); - dataColumn.ColumnName = c.COLUMNNAME; - dataColumn.DataType = (Type)c.DATATYPE; - dataColumn.ReadOnly = true; - dataColumn.Unique = c.ISUNIQUE; - - resultTable.Columns.Add(dataColumn); - } - - while (r.Read()) - { - DataRow row = resultTable.NewRow(); - for (int i = 0; i < resultTable.Columns.Count; i++) - row[i] = r[i]; - - resultTable.Rows.Add(row); - } - - return resultTable; - } - } - - public static class ReaderExtensions - { - public static bool? GetBooleanIfNotNull(this IDataReader r, string name, bool? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetBoolean(ord); - } - - public static byte? GetByteIfNotNull(this IDataReader r, string name, byte? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetByte(ord); - } - - public static char? GetCharIfNotNull(this IDataReader r, string name, char? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetChar(ord); - } - - public static DateTime? GetDateTimeIfNotNull(this IDataReader r, string name, DateTime? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetDateTime(ord); - } - - public static decimal? GetDecimalIfNotNull(this IDataReader r, string name, decimal? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetDecimal(ord); - } - - public static double? GetDoubleIfNotNull(this IDataReader r, string name, double? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetDouble(ord); - } - - public static float? GetFloatIfNotNull(this IDataReader r, string name, float? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetFloat(ord); - } - - public static Guid? GetGuidIfNotNull(this IDataReader r, string name, Guid? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetGuid(ord); - } - - public static short? GetInt16IfNotNull(this IDataReader r, string name, short? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetInt16(ord); - } - - public static int? GetInt32IfNotNull(this IDataReader r, string name, int? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetInt32(ord); - } - - public static long? GetInt64IfNotNull(this IDataReader r, string name, long? def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetInt64(ord); - } - - public static string GetStringIfNotNull(this IDataReader r, string name, string def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetString(ord); - } - - public static object GetValueIfNotNull(this IDataReader r, string name, object def = null) - { - var ord = r.GetOrdinal(name); - if (r.IsDBNull(ord)) - return def; - - return r.GetValue(ord); - } - } - - /// Class containing useful string extensions. - internal static class StringExtensions - { - static StringExtensions() - { - InvalidMultipartMemberChars = _InvalidMultipartMemberChars.ToCharArray(); - InvalidMemberChars = _InvalidMemberChars.ToCharArray(); - } - - private static readonly string _InvalidMultipartMemberChars = " +-*/^%[]{}()!\"\\&=?¿"; - private static readonly string _InvalidMemberChars = "." + _InvalidMultipartMemberChars; - - /// - /// Gets an array with some invalid characters that cannot be used with multipart names for class members. - /// - public static char[] InvalidMultipartMemberChars { get; private set; } - - /// - /// Gets an array with some invalid characters that cannot be used with names for class members. - /// - public static char[] InvalidMemberChars { get; private set; } - - /// - /// Provides with an alternate and generic way to obtain an alternate string representation for this instance, - /// applying the following rules: - /// - Null values are returned as with the value, or a null object. - /// - Enum values are translated into their string representation. - /// - If the type has override the 'ToString' method then it is used. - /// - If it is a dictionary, then a collection of key/value pairs where the value part is also translated. - /// - If it is a collection, then a collection of value items also translated. - /// - If it has public public properties (or if not, if it has public fields), the collection of name/value - /// pairs, with the values translated. - /// - Finally it falls back to the standard 'type.FullName' mechanism. - /// - /// The object to obtain its alternate string representation from. - /// The brackets to use if needed. If not null it must be at least a 2-chars' array containing - /// the opening and closing brackets. - /// Representation of null string.. - /// The alternate string representation of this object. - public static string Sketch(this object obj, char[] brackets = null, string nullString = "(null)") - { - if (obj == null) return nullString; - if (obj is string) return (string)obj; - - Type type = obj.GetType(); - if (type.IsEnum) return obj.ToString(); - - // If the ToString() method has been overriden (by the type itself, or by its parents), let's use it... - MethodInfo method = type.GetMethod("ToString", Type.EmptyTypes); - if (method.DeclaringType != typeof(object)) return obj.ToString(); - - // For alll other cases... - StringBuilder sb = new StringBuilder(); - bool first = true; - - // Dictionaries... - if (obj is IDictionary) - { - if (brackets == null || brackets.Length < 2) - brackets = "[]".ToCharArray(); - - sb.AppendFormat("{0}", brackets[0]); first = true; foreach (DictionaryEntry kvp in (IDictionary)obj) - { - if (!first) sb.Append(", "); else first = false; - sb.AppendFormat("'{0}'='{1}'", kvp.Key.Sketch(), kvp.Value.Sketch()); - } - - sb.AppendFormat("{0}", brackets[1]); - return sb.ToString(); - } - - // IEnumerables... - IEnumerator ator = null; - if (obj is IEnumerable) - ator = ((IEnumerable)obj).GetEnumerator(); - else - { - method = type.GetMethod("GetEnumerator", Type.EmptyTypes); - if (method != null) - ator = (IEnumerator)method.Invoke(obj, null); - } - - if (ator != null) - { - if (brackets == null || brackets.Length < 2) brackets = "[]".ToCharArray(); - sb.AppendFormat("{0}", brackets[0]); first = true; while (ator.MoveNext()) - { - if (!first) sb.Append(", "); else first = false; - sb.AppendFormat("{0}", ator.Current.Sketch()); - } - - sb.AppendFormat("{0}", brackets[1]); - - if (ator is IDisposable) - ((IDisposable)ator).Dispose(); - - return sb.ToString(); - } - - // As a last resort, using the public properties (or fields if needed, or type name)... - BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy; - PropertyInfo[] props = type.GetProperties(flags); - FieldInfo[] infos = type.GetFields(flags); - - if (props.Length == 0 && infos.Length == 0) sb.Append(type.FullName); // Fallback if needed - else - { - if (brackets == null || brackets.Length < 2) brackets = "{}".ToCharArray(); - sb.AppendFormat("{0}", brackets[0]); - first = true; - - if (props.Length != 0) - { - foreach (PropertyInfo prop in props) - { - if (!first) sb.Append(", "); else first = false; - sb.AppendFormat("{0}='{1}'", prop.Name, prop.GetValue(obj, null).Sketch()); - } - } - else - { - if (infos.Length != 0) - { - foreach (FieldInfo info in infos) - { - if (!first) sb.Append(", "); else first = false; - sb.AppendFormat("{0}='{1}'", info.Name, info.GetValue(obj).Sketch()); - } - } - } - - sb.AppendFormat("{0}", brackets[1]); - } - - // And returning... - return sb.ToString(); - } - - /// - /// Returns true if the target string contains any of the characters given. - /// - /// The target string. It cannot be null. - /// An array containing the characters to test. It cannot be null. If empty false is returned. - /// True if the target string contains any of the characters given, false otherwise. - public static bool ContainsAny(this string source, char[] items) - { - if (source == null) throw new ArgumentNullException("source", "Source string cannot be null."); - if (items == null) throw new ArgumentNullException("items", "Array of characters to test cannot be null."); - - if (items.Length == 0) return false; // No characters to validate - int ix = source.IndexOfAny(items); - return ix >= 0 ? true : false; - } - - /// - /// Returns a new validated string using the rules given. - /// - /// The source string. - /// A description of the source string to build errors and exceptions if needed. - /// True if the returned string can be null. - /// True if the returned string can be empty. - /// True to trim the returned string. - /// True to left-trim the returned string. - /// True to right-trim the returned string. - /// If >= 0, the min valid length for the returned string. - /// If >= 0, the max valid length for the returned string. - /// If not '\0', the character to use to left-pad the returned string if needed. - /// If not '\0', the character to use to right-pad the returned string if needed. - /// If not null, an array containing invalid chars that must not appear in the returned - /// string. - /// If not null, an array containing the only characters that are considered valid for the - /// returned string. - /// A new validated string. - public static string Validated(this string source, string desc = null, - bool canbeNull = false, bool canbeEmpty = false, - bool trim = true, bool trimStart = false, bool trimEnd = false, - int minLen = -1, int maxLen = -1, char padLeft = '\0', char padRight = '\0', - char[] invalidChars = null, char[] validChars = null) - { - // Assuring a valid descriptor... - if (string.IsNullOrWhiteSpace(desc)) desc = "Source"; - - // Validating if null sources are accepted... - if (source == null) - { - if (!canbeNull) throw new ArgumentNullException(desc, string.Format("{0} cannot be null.", desc)); - return null; - } - - // Trimming if needed... - if (trim && !(trimStart || trimEnd)) source = source.Trim(); - else - { - if (trimStart) source = source.TrimStart(' '); - if (trimEnd) source = source.TrimEnd(' '); - } - - // Adjusting lenght... - if (minLen > 0) - { - if (padLeft != '\0') source = source.PadLeft(minLen, padLeft); - if (padRight != '\0') source = source.PadRight(minLen, padRight); - } - - if (maxLen > 0) - { - if (padLeft != '\0') source = source.PadLeft(maxLen, padLeft); - if (padRight != '\0') source = source.PadRight(maxLen, padRight); - } - - // Validating emptyness and lenghts... - if (source.Length == 0) - { - if (!canbeEmpty) throw new ArgumentException(string.Format("{0} cannot be empty.", desc)); - return string.Empty; - } - - if (minLen >= 0 && source.Length < minLen) throw new ArgumentException(string.Format("Lenght of {0} '{1}' is lower than '{2}'.", desc, source, minLen)); - if (maxLen >= 0 && source.Length > maxLen) throw new ArgumentException(string.Format("Lenght of {0} '{1}' is bigger than '{2}'.", desc, source, maxLen)); - - // Checking invalid chars... - if (invalidChars != null) - { - int n = source.IndexOfAny(invalidChars); - if (n >= 0) throw new ArgumentException(string.Format("Invalid character '{0}' found in {1} '{2}'.", source[n], desc, source)); - } - - // Checking valid chars... - if (validChars != null) - { - int n = validChars.ToString().IndexOfAny(source.ToCharArray()); - if (n >= 0) throw new ArgumentException(string.Format("Invalid character '{0}' found in {1} '{2}'.", validChars.ToString()[n], desc, source)); - } - - return source; - } - - /// - /// Splits the given string with the 'something AS alias' format, returning a tuple containing its 'something' and 'alias' parts. - /// If no alias is detected, then its component in the tuple returned is null and all the contents from the source - /// string are considered as the 'something' part. - /// - /// The source string. - /// A tuple containing the 'something' and 'alias' parts. - public static Tuple SplitSomethingAndAlias(this string source) - { - source = source.Validated("[Something AS Alias]"); - - string something = null; - string alias = null; - int n = source.LastIndexOf(" AS ", StringComparison.OrdinalIgnoreCase); - - if (n < 0) - something = source; - else - { - something = source.Substring(0, n); - alias = source.Substring(n + 4); - } - - return new Tuple(something, alias); - } - - /// Allows to replace parameters inside of string. - /// String containing parameters in format [$ParameterName]. - /// Function that should return value that will be placed in string in place of placed parameter. - /// Prefix of the parameter. This value can't be null or empty, default value [$. - /// Suffix of the parameter. This value can't be null or empty, default value ]. - /// Parsed string. - public static string FillStringWithVariables(this string stringToFill, Func getValue, string prefix = "[$", string sufix = "]") - { - int startPos = 0, endPos = 0; - prefix.Validated(); - sufix.Validated(); - - startPos = stringToFill.IndexOf(prefix, startPos); - while (startPos >= 0) - { - endPos = stringToFill.IndexOf(sufix, startPos + prefix.Length); - int nextStartPos = stringToFill.IndexOf(prefix, startPos + prefix.Length); - - if (endPos > startPos + prefix.Length + 1 && (nextStartPos > endPos || nextStartPos == -1)) - { - string paramName = stringToFill.Substring(startPos + prefix.Length, endPos - (startPos + prefix.Length)); - - stringToFill = stringToFill - .Remove(startPos, (endPos - startPos) + sufix.Length) - .Insert(startPos, getValue(paramName)); - } - - startPos = stringToFill.IndexOf(prefix, startPos + prefix.Length); - } - - return stringToFill; - } - } - - /// Defines methods to support the comparison of collections for equality. - /// The type of collection to compare. - public class CollectionComparer : IEqualityComparer> - { - /// Determines whether the specified objects are equal. - /// The first object of type T to compare. - /// The second object of type T to compare. - /// Returns true if the specified objects are equal; otherwise, false. - bool IEqualityComparer>.Equals(IEnumerable first, IEnumerable second) - { - return Equals(first, second); - } - - /// Returns a hash code for the specified object. - /// The enumerable for which a hash code is to be returned. - /// A hash code for the specified object. - int IEqualityComparer>.GetHashCode(IEnumerable enumerable) - { - return GetHashCode(enumerable); - } - - /// Returns a hash code for the specified object. - /// The enumerable for which a hash code is to be returned. - /// A hash code for the specified object. - public static int GetHashCode(IEnumerable enumerable) - { - int hash = 17; - - foreach (T val in enumerable.OrderBy(x => x)) - hash = (hash * 23) + val.GetHashCode(); - - return hash; - } - - /// Determines whether the specified objects are equal. - /// The first object of type T to compare. - /// The second object of type T to compare. - /// Returns true if the specified objects are equal; otherwise, false. - public static bool Equals(IEnumerable first, IEnumerable second) - { - if ((first == null) != (second == null)) - return false; - - if (!object.ReferenceEquals(first, second) && (first != null)) - { - if (first.Count() != second.Count()) - return false; - - if ((first.Count() != 0) && HaveMismatchedElement(first, second)) - return false; - } - - return true; - } - - private static bool HaveMismatchedElement(IEnumerable first, IEnumerable second) - { - int firstCount; - int secondCount; - - Dictionary firstElementCounts = GetElementCounts(first, out firstCount); - Dictionary secondElementCounts = GetElementCounts(second, out secondCount); - - if (firstCount != secondCount) - return true; - - foreach (KeyValuePair kvp in firstElementCounts) - if (kvp.Value != (secondElementCounts.TryGetNullable(kvp.Key) ?? 0)) - return true; - - return false; - } - - private static Dictionary GetElementCounts(IEnumerable enumerable, out int nullCount) - { - Dictionary dictionary = new Dictionary(); - nullCount = 0; - - foreach (T element in enumerable) - { - if (element == null) - nullCount++; - else - { - int count = dictionary.TryGetNullable(element) ?? 0; - dictionary[element] = ++count; - } - } - - return dictionary; - } - } - - /// Extends interface. - public interface IExtendedDisposable : IDisposable - { - /// - /// Gets a value indicating whether this instance is disposed. - /// - /// - /// true if this instance is disposed; otherwise, false. - /// - bool IsDisposed { get; } - } - - namespace Dynamics - { - - /// Class that allows to use interfaces as dynamic objects. - /// Type of class to proxy. - /// This is temporary solution. Which allows to use builders as a dynamic type. - public class DynamicProxy : DynamicObject, IDisposable - { - private T _proxy; - private Type _type; - private Dictionary _properties; - private Dictionary _methods; - - /// - /// Initializes a new instance of the class. - /// - /// The object to which proxy should be created. - /// The object to which proxy should be created is null. - public DynamicProxy(T proxiedObject) - { - if (proxiedObject == null) - throw new ArgumentNullException("proxiedObject"); - - _proxy = proxiedObject; - _type = typeof(T); - - DynamicTypeMap mapper = Mapper.DynamicMapperCache.GetMapper(); - - _properties = mapper - .ColumnsMap - .ToDictionary( - k => k.Value.Name, - v => v.Value); - - _methods = GetAllMembers(_type) - .Where(x => x is MethodInfo) - .Cast() - .Where(m => !((m.Name.StartsWith("set_") && m.ReturnType == typeof(void)) || m.Name.StartsWith("get_"))) - .Where(m => !m.IsStatic && !m.IsGenericMethod) - .ToDictionary( - k => k, - v => - { - try - { - Type type = v.ReturnType == typeof(void) ? - Expression.GetActionType(v.GetParameters().Select(t => t.ParameterType).ToArray()) : - Expression.GetDelegateType(v.GetParameters().Select(t => t.ParameterType).Concat(new[] { v.ReturnType }).ToArray()); - - return Delegate.CreateDelegate(type, _proxy, v.Name); - } - catch (ArgumentException) - { - return null; - } - }); - } - - /// Provides implementation for type conversion operations. - /// Classes derived from the - /// class can override this method to specify dynamic behavior for - /// operations that convert an object from one type to another. - /// Provides information about the conversion operation. - /// The binder.Type property provides the type to which the object must be - /// converted. For example, for the statement (String)sampleObject in C# - /// (CType(sampleObject, Type) in Visual Basic), where sampleObject is an - /// instance of the class derived from the - /// class, binder.Type returns the type. - /// The binder.Explicit property provides information about the kind of - /// conversion that occurs. It returns true for explicit conversion and - /// false for implicit conversion. - /// The result of the type conversion operation. - /// Returns true if the operation is successful; otherwise, false. - /// If this method returns false, the run-time binder of the language determines the - /// behavior. (In most cases, a language-specific run-time exception is thrown). - public override bool TryConvert(ConvertBinder binder, out object result) - { - if (binder.Type == typeof(T)) - { - result = _proxy; - return true; - } - - if (_proxy != null && - binder.Type.IsAssignableFrom(_proxy.GetType())) - { - result = _proxy; - return true; - } - - return base.TryConvert(binder, out result); - } - - /// Provides the implementation for operations that get member - /// values. Classes derived from the - /// class can override this method to specify dynamic behavior for - /// operations such as getting a value for a property. - /// Provides information about the object that - /// called the dynamic operation. The binder.Name property provides - /// the name of the member on which the dynamic operation is performed. - /// For example, for the Console.WriteLine(sampleObject.SampleProperty) - /// statement, where sampleObject is an instance of the class derived - /// from the class, - /// binder.Name returns "SampleProperty". The binder.IgnoreCase property - /// specifies whether the member name is case-sensitive. - /// The result of the get operation. For example, - /// if the method is called for a property, you can assign the property - /// value to . - /// Returns true if the operation is successful; otherwise, - /// false. If this method returns false, the run-time binder of the - /// language determines the behavior. (In most cases, a run-time exception - /// is thrown). - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - try - { - DynamicPropertyInvoker prop = _properties.TryGetValue(binder.Name); - - result = prop.NullOr(p => p.Get.NullOr(g => g(_proxy), null), null); - - return prop != null && prop.Get != null; - } - catch (Exception ex) - { - throw new InvalidOperationException(string.Format("Cannot get member {0}", binder.Name), ex); - } - } - - /// Provides the implementation for operations that set member - /// values. Classes derived from the - /// class can override this method to specify dynamic behavior for operations - /// such as setting a value for a property. - /// Provides information about the object that called - /// the dynamic operation. The binder.Name property provides the name of - /// the member to which the value is being assigned. For example, for the - /// statement sampleObject.SampleProperty = "Test", where sampleObject is - /// an instance of the class derived from the - /// class, binder.Name returns "SampleProperty". The binder.IgnoreCase - /// property specifies whether the member name is case-sensitive. - /// The value to set to the member. For example, for - /// sampleObject.SampleProperty = "Test", where sampleObject is an instance - /// of the class derived from the - /// class, the is "Test". - /// Returns true if the operation is successful; otherwise, - /// false. If this method returns false, the run-time binder of the - /// language determines the behavior. (In most cases, a language-specific - /// run-time exception is thrown). - public override bool TrySetMember(SetMemberBinder binder, object value) - { - try - { - DynamicPropertyInvoker prop = _properties.TryGetValue(binder.Name); - - if (prop != null && prop.Setter != null) - { - prop.Set(_proxy, value); - return true; - } - - return false; - } - catch (Exception ex) - { - throw new InvalidOperationException(string.Format("Cannot set member {0} to '{1}'", binder.Name, value), ex); - } - } - - /// Provides the implementation for operations that invoke a member. - /// Classes derived from the - /// class can override this method to specify dynamic behavior for - /// operations such as calling a method. - /// Provides information about the dynamic operation. - /// The binder.Name property provides the name of the member on which the - /// dynamic operation is performed. For example, for the statement - /// sampleObject.SampleMethod(100), where sampleObject is an instance of - /// the class derived from the - /// class, binder.Name returns "SampleMethod". The binder.IgnoreCase property - /// specifies whether the member name is case-sensitive. - /// The arguments that are passed to the object member - /// during the invoke operation. For example, for the statement - /// sampleObject.SampleMethod(100), where sampleObject is derived from the - /// class, - /// First element of is equal to 100. - /// The result of the member invocation. - /// Returns true if the operation is successful; otherwise, - /// false. If this method returns false, the run-time binder of the - /// language determines the behavior. (In most cases, a language-specific - /// run-time exception is thrown). - public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) - { - return TryInvokeMethod(binder.Name, out result, args) || base.TryInvokeMember(binder, args, out result); - } - - private bool TryInvokeMethod(string name, out object result, object[] args) - { - result = null; - - MethodInfo mi = _methods.Keys - .Where(m => m.Name == name) - .FirstOrDefault(m => - CompareTypes(m.GetParameters().ToArray(), - args.Select(a => a.GetType()).ToArray())); - - Delegate d = _methods.TryGetValue(mi); - - if (d != null) - { - result = d.DynamicInvoke(CompleteArguments(mi.GetParameters().ToArray(), args)); - - if (d.Method.ReturnType == _type && result is T) - result = new DynamicProxy((T)result); - - return true; - } - else if (mi != null) - { - result = mi.Invoke(_proxy, CompleteArguments(mi.GetParameters().ToArray(), args)); - - if (mi.ReturnType == _type && result is T) - result = new DynamicProxy((T)result); - - return true; - } - - return false; - } - - private bool CompareTypes(ParameterInfo[] parameters, Type[] types) - { - if (parameters.Length < types.Length || parameters.Count(p => !p.IsOptional) > types.Length) - return false; - - for (int i = 0; i < types.Length; i++) - if (types[i] != parameters[i].ParameterType && !parameters[i].ParameterType.IsAssignableFrom(types[i])) - return false; - - return true; - } - - private object[] CompleteArguments(ParameterInfo[] parameters, object[] arguments) - { - return arguments.Concat(parameters.Skip(arguments.Length).Select(p => p.DefaultValue)).ToArray(); - } - - private IEnumerable GetAllMembers(Type type) - { - if (type.IsInterface) - { - List members = new List(); - List considered = new List(); - Queue queue = new Queue(); - - considered.Add(type); - queue.Enqueue(type); - - while (queue.Count > 0) - { - Type subType = queue.Dequeue(); - foreach (Type subInterface in subType.GetInterfaces()) - { - if (considered.Contains(subInterface)) continue; - - considered.Add(subInterface); - queue.Enqueue(subInterface); - } - - MemberInfo[] typeProperties = subType.GetMembers( - BindingFlags.FlattenHierarchy - | BindingFlags.Public - | BindingFlags.Instance); - - IEnumerable newPropertyInfos = typeProperties - .Where(x => !members.Contains(x)); - - members.InsertRange(0, newPropertyInfos); - } - - return members; - } - - return type.GetMembers(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public void Dispose() - { - object res; - TryInvokeMethod("Dispose", out res, new object[] { }); - - _properties.Clear(); - - _methods = null; - _properties = null; - _type = null; - _proxy = default(T); - } - } - - /// - /// Class able to parse dynamic lambda expressions. Allows to create dynamic logic. - /// - public class DynamicParser : IExtendedDisposable - { - #region Node - - /// - /// Generic bindable operation where some of its operands is a dynamic argument, or a dynamic member or - /// a method of that argument. - /// - [Serializable] - public class Node : IDynamicMetaObjectProvider, IFinalizerDisposable, ISerializable - { - private DynamicParser _parser = null; - - #region MetaNode - - /// - /// Represents the dynamic binding and a binding logic of - /// an object participating in the dynamic binding. - /// - internal class MetaNode : DynamicMetaObject - { - /// - /// Initializes a new instance of the class. - /// - /// The parameter. - /// The restrictions. - /// The value. - public MetaNode(Expression parameter, BindingRestrictions rest, object value) - : base(parameter, rest, value) - { - } - - // Func was cool but caused memory leaks - private DynamicMetaObject GetBinder(Node node) - { - Node o = (Node)this.Value; - node.Parser = o.Parser; - o.Parser.Last = node; - - ParameterExpression p = Expression.Variable(typeof(Node), "ret"); - BlockExpression exp = Expression.Block(new ParameterExpression[] { p }, Expression.Assign(p, Expression.Constant(node))); - - return new MetaNode(exp, this.Restrictions, node); - } - - /// - /// Performs the binding of the dynamic get member operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindGetMember(GetMemberBinder binder) - { - return GetBinder(new GetMember((Node)this.Value, binder.Name)); - } - - /// - /// Performs the binding of the dynamic set member operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// The representing the value for the set member operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) - { - return GetBinder(new SetMember((Node)this.Value, binder.Name, value.Value)); - } - - /// - /// Performs the binding of the dynamic get index operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// An array of instances - indexes for the get index operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) - { - return GetBinder(new GetIndex((Node)this.Value, MetaList2List(indexes))); - } - - /// - /// Performs the binding of the dynamic set index operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// An array of instances - indexes for the set index operation. - /// The representing the value for the set index operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) - { - return GetBinder(new SetIndex((Node)this.Value, MetaList2List(indexes), value.Value)); - } - - /// - /// Performs the binding of the dynamic invoke operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// An array of instances - arguments to the invoke operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) - { - return GetBinder(new Invoke((Node)this.Value, MetaList2List(args))); - } - - /// - /// Performs the binding of the dynamic invoke member operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// An array of instances - arguments to the invoke member operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) - { - return GetBinder(new Method((Node)this.Value, binder.Name, MetaList2List(args))); - } - - /// - /// Performs the binding of the dynamic binary operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// An instance of the representing the right hand side of the binary operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) - { - return GetBinder(new Binary((Node)this.Value, binder.Operation, arg.Value)); - } - - /// - /// Performs the binding of the dynamic unary operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) - { - Node o = (Node)this.Value; - Unary node = new Unary(o, binder.Operation) { Parser = o.Parser }; - o.Parser.Last = node; - - // If operation is 'IsTrue' or 'IsFalse', we will return false to keep the engine working... - object ret = node; - if (binder.Operation == ExpressionType.IsTrue) ret = (object)false; - if (binder.Operation == ExpressionType.IsFalse) ret = (object)false; - - ParameterExpression p = Expression.Variable(ret.GetType(), "ret"); // the type is now obtained from "ret" - BlockExpression exp = Expression.Block( - new ParameterExpression[] { p }, - Expression.Assign(p, Expression.Constant(ret))); // the expression is now obtained from "ret" - - return new MetaNode(exp, this.Restrictions, node); - } - - /// - /// Performs the binding of the dynamic conversion operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// - /// The new representing the result of the binding. - /// - public override DynamicMetaObject BindConvert(ConvertBinder binder) - { - Node o = (Node)this.Value; - Convert node = new Convert(o, binder.ReturnType) { Parser = o.Parser }; - o.Parser.Last = node; - - // Reducing the object to return if this is an assignment node... - object ret = o; - bool done = false; - - while (!done) - { - if (ret is SetMember) - ret = ((SetMember)o).Value; - else if (ret is SetIndex) - ret = ((SetIndex)o).Value; - else - done = true; - } - - // Creating an instance... - if (binder.ReturnType == typeof(string)) ret = ret.ToString(); - else - { - try - { - if (binder.ReturnType.IsNullableType()) - ret = null; // to avoid cast exceptions - else - ret = Activator.CreateInstance(binder.ReturnType, true); // true to allow non-public ctor as well - } - catch - { - // as the last resort scenario - ret = new object(); - } - } - - ParameterExpression p = Expression.Variable(binder.ReturnType, "ret"); - BlockExpression exp = Expression.Block( - new ParameterExpression[] { p }, - Expression.Assign(p, Expression.Constant(ret, binder.ReturnType))); // specifying binder.ReturnType - - return new MetaNode(exp, this.Restrictions, node); - } - - private static object[] MetaList2List(DynamicMetaObject[] metaObjects) - { - if (metaObjects == null) return null; - - object[] list = new object[metaObjects.Length]; - for (int i = 0; i < metaObjects.Length; i++) - list[i] = metaObjects[i].Value; - - return list; - } - } - - #endregion MetaNode - - #region Argument - - /// - /// Describe a dynamic argument used in a dynamic lambda expression. - /// - [Serializable] - public class Argument : Node, ISerializable - { - /// - /// Initializes a new instance of the class. - /// - /// The name. - public Argument(string name) - : base(name) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Argument(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Argument::Disposed}"; - return Name; - } - } - - #endregion Argument - - #region GetMember - - /// - /// Describe a 'get member' operation, as in 'x => x.Member'. - /// - [Serializable] - public class GetMember : Node, ISerializable - { - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The name. - public GetMember(Node host, string name) - : base(host, name) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected GetMember(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::GetMember::Disposed}"; - return string.Format("{0}.{1}", Host.Sketch(), Name.Sketch()); - } - } - - #endregion GetMember - - #region SetMember - - /// - /// Describe a 'set member' operation, as in 'x => x.Member = y'. - /// - [Serializable] - public class SetMember : Node, ISerializable - { - /// - /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been - /// assigned to this instance, or if this instance is disposed. - /// - public object Value { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The name. - /// The value. - public SetMember(Node host, string name, object value) - : base(host, name) - { - Value = value; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected SetMember(SerializationInfo info, StreamingContext context) - : base(info, context) - { - string type = info.GetString("MemberType"); - Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); - if (Value != null) - info.AddValue("MemberValue", Value); - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::SetMember::Disposed}"; - return string.Format("({0}.{1} = {2})", Host.Sketch(), Name.Sketch(), Value.Sketch()); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Value != null) - { - var node = Value as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - - Value = null; - } - } - catch - { - } - } - - base.Dispose(disposing); - } - } - - #endregion SetMember - - #region GetIndex - - /// - /// Describe a 'get indexed' operation, as in 'x => x.Member[...]'. - /// - [Serializable] - public class GetIndex : Node, ISerializable - { - /// Gets the indexes. - public object[] Indexes { get; internal set; } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The indexes. - /// Indexes array cannot be null. - /// Indexes array cannot be empty. - public GetIndex(Node host, object[] indexes) - : base(host) - { - if (indexes == null) - throw new ArgumentNullException("indexes", "Indexes array cannot be null."); - if (indexes.Length == 0) - throw new ArgumentException("Indexes array cannot be empty."); - - Indexes = indexes; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected GetIndex(SerializationInfo info, StreamingContext context) - : base(info, context) - { - int count = (int)info.GetValue("IndexCount", typeof(int)); - - if (count != 0) - { - Indexes = new object[count]; for (int i = 0; i < count; i++) - { - string typeName = info.GetString("IndexType" + i); - object obj = typeName == "NULL" ? null : info.GetValue("IndexValue" + i, Type.GetType(typeName)); - Indexes[i] = obj; - } - } - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - int count = Indexes == null ? 0 : Indexes.Length; info.AddValue("IndexCount", count); - for (int i = 0; i < count; i++) - { - info.AddValue("IndexType" + i, Indexes[i] == null ? "NULL" : Indexes[i].GetType().AssemblyQualifiedName); - if (Indexes[i] != null) info.AddValue("IndexValue" + i, Indexes[i]); - } - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::GetIndex::Disposed}"; - - return string.Format("{0}{1}", Host.Sketch(), Indexes == null ? "[empty]" : Indexes.Sketch()); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Indexes != null) - { - for (int i = 0; i < Indexes.Length; i++) - { - var node = Indexes[i] as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - } - - Array.Clear(Indexes, 0, Indexes.Length); - } - } - catch - { - } - - Indexes = null; - } - - base.Dispose(disposing); - } - } - - #endregion GetIndex - - #region SetIndex - - /// - /// Describe a 'set indexed' operation, as in 'x => x.Member[...] = Value'. - /// - [Serializable] - public class SetIndex : GetIndex, ISerializable - { - /// - /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been - /// assigned to this instance, or if this instance is disposed. - /// - public object Value { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The indexes. - /// The value. - public SetIndex(Node host, object[] indexes, object value) - : base(host, indexes) - { - Value = value; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected SetIndex(SerializationInfo info, StreamingContext context) - : base(info, context) - { - string type = info.GetString("MemberType"); - Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); - if (Value != null) info.AddValue("MemberValue", Value); - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::SetIndex::Disposed}"; - - return string.Format("({0}{1} = {2})", Host.Sketch(), Indexes == null ? "[empty]" : Indexes.Sketch(), Value.Sketch()); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Value != null) - { - var node = Value as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - - Value = null; - } - } - catch - { - } - } - - base.Dispose(disposing); - } - } - - #endregion SetIndex - - #region Invoke - - /// - /// Describe a method invocation operation, as in 'x => x.Method(...)". - /// - [Serializable] - public class Invoke : Node, ISerializable - { - /// Gets the arguments. - public object[] Arguments { get; internal set; } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The arguments. - public Invoke(Node host, object[] arguments) - : base(host) - { - Arguments = arguments == null || arguments.Length == 0 ? null : arguments; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Invoke(SerializationInfo info, StreamingContext context) - : base(info, context) - { - int count = (int)info.GetValue("ArgumentCount", typeof(int)); - - if (count != 0) - { - Arguments = new object[count]; for (int i = 0; i < count; i++) - { - string typeName = info.GetString("ArgumentType" + i); - object obj = typeName == "NULL" ? null : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); - Arguments[i] = obj; - } - } - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - int count = Arguments == null ? 0 : Arguments.Length; info.AddValue("ArgumentCount", count); - for (int i = 0; i < count; i++) - { - info.AddValue("ArgumentType" + i, Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); - if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); - } - - base.GetObjectData(info, context); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Arguments != null) - { - for (int i = 0; i < Arguments.Length; i++) - { - var node = Arguments[i] as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - } - - Array.Clear(Arguments, 0, Arguments.Length); - } - } - catch - { - } - - Arguments = null; - } - - base.Dispose(disposing); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Invoke::Disposed}"; - - return string.Format("{0}{1}", Host.Sketch(), Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); - } - } - - #endregion Invoke - - #region Method - - /// - /// Describe a method invocation operation, as in 'x => x.Method(...)". - /// - [Serializable] - public class Method : Node, ISerializable - { - /// Gets the arguments. - public object[] Arguments { get; internal set; } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The name. - /// The arguments. - public Method(Node host, string name, object[] arguments) - : base(host, name) - { - Arguments = arguments == null || arguments.Length == 0 ? null : arguments; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Method(SerializationInfo info, StreamingContext context) - : base(info, context) - { - int count = (int)info.GetValue("ArgumentCount", typeof(int)); - - if (count != 0) - { - Arguments = new object[count]; for (int i = 0; i < count; i++) - { - string typeName = info.GetString("ArgumentType" + i); - object obj = typeName == "NULL" ? null : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); - Arguments[i] = obj; - } - } - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - int count = Arguments == null ? 0 : Arguments.Length; info.AddValue("ArgumentCount", count); - for (int i = 0; i < count; i++) - { - info.AddValue("ArgumentType" + i, Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); - if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); - } - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Method::Disposed}"; - - return string.Format("{0}.{1}{2}", Host.Sketch(), Name.Sketch(), Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Arguments != null) - { - for (int i = 0; i < Arguments.Length; i++) - { - var node = Arguments[i] as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - } - - Array.Clear(Arguments, 0, Arguments.Length); - } - } - catch - { - } - - Arguments = null; - } - - base.Dispose(disposing); - } - } - - #endregion Method - - #region Binary - - /// - /// Represents a binary operation between a dynamic element and an arbitrary object, including null ones, as in - /// 'x => (x && null)'. The left operand must be an instance of , whereas the right one - /// can be any object, including null values. - /// - [Serializable] - public class Binary : Node, ISerializable - { - /// Gets the operation. - public ExpressionType Operation { get; private set; } - - /// Gets host of the . - public Node Left { get { return Host; } } - - /// Gets the right side value. - public object Right { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The left. - /// The operation. - /// The right. - public Binary(Node left, ExpressionType operation, object right) - : base(left) - { - Operation = operation; - Right = right; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Binary(SerializationInfo info, StreamingContext context) - : base(info, context) - { - Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); - - string type = info.GetString("RightType"); - Right = type == "NULL" ? null : (Node)info.GetValue("RightItem", Type.GetType(type)); - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("Operation", Operation); - - info.AddValue("RightType", Right == null ? "NULL" : Right.GetType().AssemblyQualifiedName); - if (Right != null) - info.AddValue("RightItem", Right); - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Binary::Disposed}"; - - return string.Format("({0} {1} {2})", Host.Sketch(), Operation, Right.Sketch()); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - { - if (Left != null) - { - if (Left is Node) - { - Node n = (Node)Left; - - if (!n.IsDisposed) - n.Dispose(disposing); - } - } - - if (Right != null) - { - if (Right is Node) - { - Node n = (Node)Right; - - if (!n.IsDisposed) - n.Dispose(disposing); - } - - Right = null; - } - } - } - } - - #endregion Binary - - #region Unary - - /// - /// Represents an unary operation, as in 'x => !x'. The target must be a instance. There - /// is no distinction between pre- and post- version of the same operation. - /// - [Serializable] - public class Unary : Node, ISerializable - { - /// Gets the operation. - public ExpressionType Operation { get; private set; } - - /// Gets host of the . - public Node Target { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The target. - /// The operation. - public Unary(Node target, ExpressionType operation) - : base(target) - { - Operation = operation; - Target = target; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Unary(SerializationInfo info, StreamingContext context) - : base(info, context) - { - Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("Operation", Operation); - - base.GetObjectData(info, context); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Binary::Disposed}"; - - return string.Format("({0} {1})", Operation, Host.Sketch()); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public override void Dispose(bool disposing) - { - if (disposing) - { - try - { - if (Target != null) - { - var node = Target as Node; - if (node != null) - { - if (node.IsNodeAncestor(this)) - node.Host = null; - - node.Dispose(disposing); - } - - Target = null; - } - } - catch - { - } - } - - base.Dispose(disposing); - } - } - - #endregion Unary - - #region Convert - - /// - /// Represents a conversion operation, as in 'x => (string)x'. - /// - [Serializable] - public class Convert : Node, ISerializable - { - /// Gets the new type to which value will be converted. - public Type NewType { get; private set; } - - /// Gets host of the . - public Node Target { get { return Host; } } - - /// - /// Initializes a new instance of the class. - /// - /// The target. - /// The new type. - public Convert(Node target, Type newType) - : base(target) - { - NewType = newType; - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Convert(SerializationInfo info, StreamingContext context) - : base(info, context) - { - NewType = (Type)info.GetValue("NewType", typeof(Type)); - } - - /// - /// Gets the object data. - /// - /// The info. - /// The context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("NewType", NewType); - - base.GetObjectData(info, context); - } - } - - #endregion Convert - - /// - /// Gets the name of the member. It might be null if this instance is disposed. - /// - public string Name { get; internal set; } - - /// Gets host of the . - public Node Host { get; internal set; } - - /// Gets reference to the parser. - public DynamicParser Parser - { - get { return _parser; } - internal set - { - _parser = value; - if (_parser != null) - _parser._allNodes.Add(this); - } - } - - /// - /// Initializes a new instance of the class. - /// - internal Node() - { - IsDisposed = false; - } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - internal Node(Node host) - : this() - { - if (host == null) - throw new ArgumentNullException("host", "Host cannot be null."); - - Host = host; - } - - /// - /// Initializes a new instance of the class. - /// - /// The name. - internal Node(string name) - : this() - { - Name = name.Validated("Name"); - } - - /// - /// Initializes a new instance of the class. - /// - /// The host. - /// The name. - /// Host cannot be null. - internal Node(Node host, string name) - : this(host) - { - Name = name.Validated("Name"); - } - - /// - /// Initializes a new instance of the class. - /// - /// The info. - /// The context. - protected Node(SerializationInfo info, StreamingContext context) - { - Name = info.GetString("MemberName"); - - string type = info.GetString("HostType"); - Host = type == "NULL" ? null : (Node)info.GetValue("HostItem", Type.GetType(type)); - } - - /// Returns whether the given node is an ancestor of this instance. - /// The node to test. - /// True if the given node is an ancestor of this instance. - public bool IsNodeAncestor(Node node) - { - if (node != null) - { - Node parent = Host; - - while (parent != null) - { - if (object.ReferenceEquals(parent, node)) - return true; - - parent = parent.Host; - } - } - - return false; - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Node::Disposed}"; - - return "{DynamicParser::Node::Empty}"; - } - - #region Implementation of IDynamicMetaObjectProvider - - /// Returns the responsible - /// for binding operations performed on this object. - /// The expression tree representation of the runtime value. - /// The to bind this object. - /// Thrown if this instance is disposed. - public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter) - { - if (IsDisposed) - throw new ObjectDisposedException("DynamicParser.Node"); - - return new MetaNode( - parameter, - BindingRestrictions.GetInstanceRestriction(parameter, this), - this); - } - - #endregion Implementation of IDynamicMetaObjectProvider - - #region Implementation of IFinalizerDisposable - - /// Finalizes an instance of the class. - ~Node() - { - Dispose(false); - } - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public virtual void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - /// If set to true dispose object. - public virtual void Dispose(bool disposing) - { - if (disposing) - { - IsDisposed = true; - - if (Host != null && !Host.IsDisposed) - Host.Dispose(); - - Host = null; - - Parser = null; - } - } - - #endregion Implementation of IFinalizerDisposable - - #region Implementation of ISerializable - - /// - /// Populates a with the data needed to serialize the target object. - /// - /// The to populate with data. - /// The destination (see ) for this serialization. - public virtual void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (!string.IsNullOrEmpty(Name)) - info.AddValue("MemberName", Name); - - info.AddValue("HostType", Host == null ? "NULL" : Host.GetType().AssemblyQualifiedName); - if (Host != null) - info.AddValue("HostItem", Host); - } - - #endregion Implementation of ISerializable - } - - #endregion Node - - #region Data - - private List _arguments = new List(); - private List _allNodes = new List(); - private object _uncertainResult; - - #endregion Data - - #region Properties - - /// Gets the last node (root of the tree). - public Node Last { get; internal set; } - - /// - /// Gets an enumeration containing the dynamic arguments used in the dynamic lambda expression parsed. - /// - public IEnumerable Arguments - { - get - { - List list = new List(); - if (!IsDisposed && _arguments != null) - list.AddRange(_arguments); - - foreach (Node.Argument arg in list) - yield return arg; - - list.Clear(); - list = null; - } - } - - /// - /// Gets the number of dynamic arguments used in the dynamic lambda expression parsed. - /// - public int Count - { - get { return _arguments == null ? 0 : _arguments.Count; } - } - - /// - /// Gets the result of the parsing of the dynamic lambda expression. This result can be either an arbitrary object, - /// including null, if the expression resolves to it, or an instance of the class that - /// contains the last logic expression evaluated when parsing the dynamic lambda expression. - /// - public object Result - { - get { return _uncertainResult ?? Last; } - } - - #endregion Properties - - private DynamicParser(Delegate f) - { - // I know this can be almost a one liner - // but it causes memory leaks when so. - ParameterInfo[] pars = f.Method.GetParameters(); - foreach (ParameterInfo p in pars) - { - int attrs = p.GetCustomAttributes(typeof(DynamicAttribute), true).Length; - if (attrs != 0) - { - Node.Argument par = new Node.Argument(p.Name) { Parser = this }; - this._arguments.Add(par); - } - else - throw new ArgumentException(string.Format("Argument '{0}' must be dynamic.", p.Name)); - } - - try - { - _uncertainResult = f.DynamicInvoke(_arguments.ToArray()); - } - catch (TargetInvocationException e) - { - if (e.InnerException != null) throw e.InnerException; - throw; - } - } - - /// - /// Parses the dynamic lambda expression given in the form of a delegate, and returns a new instance of the - /// class that holds the dynamic arguments used in the dynamic lambda expression, and - /// the result of the parsing. - /// - /// The dynamic lambda expression to parse. - /// A new instance of . - public static DynamicParser Parse(Delegate f) - { - return new DynamicParser(f); - } - - /// Returns a that represents this instance. - /// A that represents this instance. - public override string ToString() - { - if (IsDisposed) - return "{DynamicParser::Disposed}"; - - StringBuilder sb = new StringBuilder(); - - sb.Append("("); - bool first = true; - - if (_arguments != null) - { - foreach (Node.Argument arg in _arguments) - { - if (!first) sb.Append(", "); else first = false; - sb.Append(arg); - } - } - - sb.Append(")"); - - sb.AppendFormat(" => {0}", Result.Sketch()); - - return sb.ToString(); - } - - #region Implementation of IExtendedDisposable - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - if (IsDisposed) - return; - - IsDisposed = true; - - if (_uncertainResult != null) - { - if (_uncertainResult is Node) - ((Node)_uncertainResult).Dispose(); - - _uncertainResult = null; - } - - if (Last != null) - { - if (!Last.IsDisposed) - Last.Dispose(); - - Last = null; - } - - if (_arguments != null) - { - _arguments.ForEach(x => { if (!x.IsDisposed) x.Dispose(); }); - - _arguments.Clear(); - _arguments = null; - } - - if (_allNodes != null) - { - _allNodes.ForEach(x => { if (!x.IsDisposed) x.Dispose(); }); - - _allNodes.Clear(); - _allNodes = null; - } - } - - #endregion Implementation of IExtendedDisposable - } - } - } - namespace Builders { - - /// Dynamic insert query builder interface. - /// This interface it publicly available. Implementation should be hidden. - public interface IDynamicInsertQueryBuilder : IDynamicQueryBuilder - { - /// Execute this builder. - /// Result of an execution.. - int Execute(); - - /// - /// Specifies the columns to insert using the dynamic lambda expressions given. Each expression correspond to one - /// column, and can: - /// - Resolve to a string, in this case a '=' must appear in the string. - /// - Resolve to a expression with the form: 'x => x.Column = Value'. - /// - /// The specifications. - /// The specifications. - /// This instance to permit chaining. - IDynamicInsertQueryBuilder Values(Func fn, params Func[] func); - - /// Add insert fields. - /// Insert column. - /// Insert value. - /// Builder instance. - IDynamicInsertQueryBuilder Insert(string column, object value); - - /// Add insert fields. - /// Set insert value as properties and values of an object. - /// Builder instance. - IDynamicInsertQueryBuilder Insert(object o); - } - - /// Dynamic update query builder interface. - /// This interface it publicly available. Implementation should be hidden. - public interface IDynamicUpdateQueryBuilder : IDynamicQueryBuilder - { - /// Execute this builder. - /// Result of an execution.. - int Execute(); - - #region Update - - /// Add update value or where condition using schema. - /// Update or where column name. - /// Column value. - /// Builder instance. - IDynamicUpdateQueryBuilder Update(string column, object value); - - /// Add update values and where condition columns using schema. - /// Set values or conditions as properties and values of an object. - /// Builder instance. - IDynamicUpdateQueryBuilder Update(object conditions); - - #endregion Update - - #region Values - - /// - /// Specifies the columns to update using the dynamic lambda expressions given. Each expression correspond to one - /// column, and can: - /// - Resolve to a string, in this case a '=' must appear in the string. - /// - Resolve to a expression with the form: 'x => x.Column = Value'. - /// - /// The specifications. - /// This instance to permit chaining. - IDynamicUpdateQueryBuilder Set(params Func[] func); - - /// Add insert fields. - /// Insert column. - /// Insert value. - /// Builder instance. - IDynamicUpdateQueryBuilder Values(string column, object value); - - /// Add insert fields. - /// Set insert value as properties and values of an object. - /// Builder instance. - IDynamicUpdateQueryBuilder Values(object o); - - #endregion Values - - #region Where - - /// - /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition - /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used - /// as needed. - /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator. - /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: - /// 'Where( x => x.Or( condition ) )'. - /// - /// The specification. - /// This instance to permit chaining. - IDynamicUpdateQueryBuilder Where(Func func); - - /// Add where condition. - /// Condition column with operator and value. - /// Builder instance. - IDynamicUpdateQueryBuilder Where(DynamicColumn column); - - /// Add where condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - IDynamicUpdateQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value); - - /// Add where condition. - /// Condition column. - /// Condition value. - /// Builder instance. - IDynamicUpdateQueryBuilder Where(string column, object value); - - /// Add where condition. - /// Set conditions as properties and values of an object. - /// If true use schema to determine key columns and ignore those which - /// aren't keys. - /// Builder instance. - IDynamicUpdateQueryBuilder Where(object conditions, bool schema = false); - - #endregion Where - } - - /// Interface describing parameter info. - public interface IParameter : IExtendedDisposable - { - /// Gets the parameter position in command. - /// Available after filling the command. - int Ordinal { get; } - - /// Gets the parameter temporary name. - string Name { get; } - - /// Gets or sets the parameter value. - object Value { get; set; } - - /// Gets or sets a value indicating whether name of temporary parameter is well known. - bool WellKnown { get; set; } - - /// Gets or sets a value indicating whether this is virtual. - bool Virtual { get; set; } - - /// Gets or sets the parameter schema information. - DynamicSchemaColumn? Schema { get; set; } - } - - /// Dynamic query builder base interface. - /// This interface it publicly available. Implementation should be hidden. - public interface IDynamicQueryBuilder : IExtendedDisposable - { - /// Gets instance. - DynamicDatabase Database { get; } - - /// Gets tables information. - IList Tables { get; } - - /// Gets the tables used in this builder. - IDictionary Parameters { get; } - - /// Gets or sets a value indicating whether add virtual parameters. - bool VirtualMode { get; set; } - - /// Gets a value indicating whether database supports standard schema. - bool SupportSchema { get; } - - /// Fill command with query. - /// Command to fill. - /// Filled instance of . - IDbCommand FillCommand(IDbCommand command); - - /// - /// Generates the text this command will execute against the underlying database. - /// - /// The text to execute against the underlying database. - /// This method must be override by derived classes. - string CommandText(); - - /// Gets or sets the on create temporary parameter actions. - /// This is exposed to allow setting schema of column. - List> OnCreateTemporaryParameter { get; set; } - - /// Gets or sets the on create real parameter actions. - /// This is exposed to allow modification of parameter. - List> OnCreateParameter { get; set; } - } - - /// Dynamic select query builder interface. - /// This interface it publicly available. Implementation should be hidden. - public interface IDynamicSelectQueryBuilder : IDynamicQueryBuilder ////, IEnumerable - { - /// Execute this builder. - /// Enumerator of objects expanded from query. - IEnumerable Execute(); - - /// Execute this builder and map to given type. - /// Type of object to map on. - /// Enumerator of objects expanded from query. - IEnumerable Execute() where T : class; - - /// Execute this builder as a data reader. - /// Action containing reader. - void ExecuteDataReader(Action reader); - - /// Returns a single result. - /// Result of a query. - object Scalar(); - - #if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE - - /// Returns a single result. - /// Type to parse to. - /// Default value. - /// Result of a query. - T ScalarAs(T defaultValue = default(T)); - - #endif - - #region From/Join - - /// - /// Adds to the 'From' clause the contents obtained by parsing the dynamic lambda expressions given. The supported - /// formats are: - /// - Resolve to a string: 'x => "Table AS Alias', where the alias part is optional. - /// - Resolve to an expression: 'x => x.Table.As( x.Alias )', where the alias part is optional. - /// - Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this - /// case the alias is not annotated. - /// - /// The specification. - /// The specification. - /// This instance to permit chaining. - IDynamicSelectQueryBuilder From(Func fn, params Func[] func); - - /// - /// Adds to the 'Join' clause the contents obtained by parsing the dynamic lambda expressions given. The supported - /// formats are: - /// - Resolve to a string: 'x => "Table AS Alias ON Condition', where the alias part is optional. - /// - Resolve to an expression: 'x => x.Table.As( x.Alias ).On( condition )', where the alias part is optional. - /// - Generic expression: 'x => x( expression ).As( x.Alias ).On( condition )', where the alias part is mandatory. - /// In this case the alias is not annotated. - /// The expression might be prepended by a method that, in this case, is used to specify the specific join type you - /// want to perform, as in: 'x => x.Left()...". Two considerations apply: - /// - If a 'false' argument is used when no 'Join' part appears in its name, then no 'Join' suffix is added - /// with a space in between. - /// - If a 'false' argument is used when a 'Join' part does appear, then no split is performed to separate the - /// 'Join' part. - /// - /// The specification. - /// This instance to permit chaining. - IDynamicSelectQueryBuilder Join(params Func[] func); - - #endregion From/Join - - #region Where - - /// - /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition - /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used - /// as needed. - /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator. - /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: - /// 'Where( x => x.Or( condition ) )'. - /// - /// The specification. - /// This instance to permit chaining. - IDynamicSelectQueryBuilder Where(Func func); - - /// Add where condition. - /// Condition column with operator and value. - /// Builder instance. - IDynamicSelectQueryBuilder Where(DynamicColumn column); - - /// Add where condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - IDynamicSelectQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value); - - /// Add where condition. - /// Condition column. - /// Condition value. - /// Builder instance. - IDynamicSelectQueryBuilder Where(string column, object value); - - /// Add where condition. - /// Set conditions as properties and values of an object. - /// If true use schema to determine key columns and ignore those which - /// aren't keys. - /// Builder instance. - IDynamicSelectQueryBuilder Where(object conditions, bool schema = false); - - #endregion Where - - #region Select - - /// - /// Adds to the 'Select' clause the contents obtained by parsing the dynamic lambda expressions given. The supported - /// formats are: - /// - Resolve to a string: 'x => "Table.Column AS Alias', where the alias part is optional. - /// - Resolve to an expression: 'x => x.Table.Column.As( x.Alias )', where the alias part is optional. - /// - Select all columns from a table: 'x => x.Table.All()'. - /// - Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this case - /// the alias is not annotated. - /// - /// The specification. - /// The specification. - /// This instance to permit chaining. - IDynamicSelectQueryBuilder Select(Func fn, params Func[] func); - - /// Add select columns. - /// Columns to add to object. - /// Builder instance. - IDynamicSelectQueryBuilder SelectColumn(params DynamicColumn[] columns); - - /// Add select columns. - /// Columns to add to object. - /// Column format consist of Column Name, Alias and - /// Aggregate function in this order separated by ':'. - /// Builder instance. - IDynamicSelectQueryBuilder SelectColumn(params string[] columns); - - #endregion Select - - #region GroupBy - - /// - /// Adds to the 'Group By' clause the contents obtained from from parsing the dynamic lambda expression given. - /// - /// The specification. - /// The specification. - /// This instance to permit chaining. - IDynamicSelectQueryBuilder GroupBy(Func fn, params Func[] func); - - /// Add select columns. - /// Columns to group by. - /// Builder instance. - IDynamicSelectQueryBuilder GroupByColumn(params DynamicColumn[] columns); - - /// Add select columns. - /// Columns to group by. - /// Column format consist of Column Name and - /// Alias in this order separated by ':'. - /// Builder instance. - IDynamicSelectQueryBuilder GroupByColumn(params string[] columns); - - #endregion GroupBy - - #region Having - - /// - /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition - /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used - /// as needed. - /// - If several Having() methods are chained their contents are, by default, concatenated with an 'AND' operator. - /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: - /// 'Having( x => x.Or( condition ) )'. - /// - /// The specification. - /// This instance to permit chaining. - IDynamicSelectQueryBuilder Having(Func func); - - /// Add Having condition. - /// Condition column with operator and value. - /// Builder instance. - IDynamicSelectQueryBuilder Having(DynamicColumn column); - - /// Add Having condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value); - - /// Add Having condition. - /// Condition column. - /// Condition value. - /// Builder instance. - IDynamicSelectQueryBuilder Having(string column, object value); - - /// Add Having condition. - /// Set conditions as properties and values of an object. - /// If true use schema to determine key columns and ignore those which - /// aren't keys. - /// Builder instance. - IDynamicSelectQueryBuilder Having(object conditions, bool schema = false); - - #endregion Having - - #region OrderBy - - /// - /// Adds to the 'Order By' clause the contents obtained from from parsing the dynamic lambda expression given. It - /// accepts a multipart column specification followed by an optional Ascending() or Descending() virtual methods - /// to specify the direction. If no virtual method is used, the default is ascending order. You can also use the - /// shorter versions Asc() and Desc(). - /// - /// The specification. - /// The specification. - /// This instance to permit chaining. - IDynamicSelectQueryBuilder OrderBy(Func fn, params Func[] func); - - /// Add select columns. - /// Columns to order by. - /// Builder instance. - IDynamicSelectQueryBuilder OrderByColumn(params DynamicColumn[] columns); - - /// Add select columns. - /// Columns to order by. - /// Column format consist of Column Name and - /// Alias in this order separated by ':'. - /// Builder instance. - IDynamicSelectQueryBuilder OrderByColumn(params string[] columns); - - #endregion OrderBy - - #region Top/Limit/Offset/Distinct - - /// Set top if database support it. - /// How many objects select. - /// Builder instance. - IDynamicSelectQueryBuilder Top(int? top); - - /// Set top if database support it. - /// How many objects select. - /// Builder instance. - IDynamicSelectQueryBuilder Limit(int? limit); - - /// Set top if database support it. - /// How many objects skip selecting. - /// Builder instance. - IDynamicSelectQueryBuilder Offset(int? offset); - - /// Set distinct mode. - /// Distinct mode. - /// Builder instance. - IDynamicSelectQueryBuilder Distinct(bool distinct = true); - - #endregion Top/Limit/Offset/Distinct - } - - /// Dynamic delete query builder interface. - /// This interface it publicly available. Implementation should be hidden. - public interface IDynamicDeleteQueryBuilder : IDynamicQueryBuilder - { - /// Execute this builder. - /// Result of an execution.. - int Execute(); - - /// - /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition - /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used - /// as needed. - /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator. - /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: - /// 'Where( x => x.Or( condition ) )'. - /// - /// The specification. - /// This instance to permit chaining. - IDynamicDeleteQueryBuilder Where(Func func); - - /// Add where condition. - /// Condition column with operator and value. - /// Builder instance. - IDynamicDeleteQueryBuilder Where(DynamicColumn column); - - /// Add where condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - IDynamicDeleteQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value); - - /// Add where condition. - /// Condition column. - /// Condition value. - /// Builder instance. - IDynamicDeleteQueryBuilder Where(string column, object value); - - /// Add where condition. - /// Set conditions as properties and values of an object. - /// If true use schema to determine key columns and ignore those which - /// aren't keys. - /// Builder instance. - IDynamicDeleteQueryBuilder Where(object conditions, bool schema = false); - } - - /// Interface describing table information. - public interface ITableInfo : IExtendedDisposable - { - /// Gets table owner name. - string Owner { get; } - - /// Gets table name. - string Name { get; } - - /// Gets table alias. - string Alias { get; } - - /// Gets table no lock status. - bool NoLock { get; } - - /// Gets table schema. - Dictionary Schema { get; } - } + /// 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(); - namespace Extensions - { - - internal static class DynamicHavingQueryExtensions - { - #region Where - - internal static T InternalHaving(this T builder, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - return builder.InternalHaving(false, false, func); - } - - internal static T InternalHaving(this T builder, bool addBeginBrace, bool addEndBrace, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - if (func == null) throw new ArgumentNullException("Array of functions cannot be null."); - - using (DynamicParser parser = DynamicParser.Parse(func)) - { - string condition = null; - bool and = true; - - object result = parser.Result; - if (result is string) - { - condition = (string)result; - - if (condition.ToUpper().IndexOf("OR") == 0) - { - and = false; - condition = condition.Substring(3); - } - else if (condition.ToUpper().IndexOf("AND") == 0) - condition = condition.Substring(4); - } - else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType) - return builder.InternalHaving(result); - else - { - // Intercepting the 'x => x.And()' and 'x => x.Or()' virtual methods... - if (result is DynamicParser.Node.Method && ((DynamicParser.Node.Method)result).Host is DynamicParser.Node.Argument) - { - DynamicParser.Node.Method node = (DynamicParser.Node.Method)result; - string name = node.Name.ToUpper(); - if (name == "AND" || name == "OR") - { - object[] args = ((DynamicParser.Node.Method)node).Arguments; - if (args == null) throw new ArgumentNullException("arg", string.Format("{0} is not a parameterless method.", name)); - if (args.Length != 1) throw new ArgumentException(string.Format("{0} requires one and only one parameter: {1}.", name, args.Sketch())); - - and = name == "AND" ? true : false; - result = args[0]; - } - } - - // Just parsing the contents now... - condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition"); - } - - if (addBeginBrace) builder.HavingOpenBracketsCount++; - if (addEndBrace) builder.HavingOpenBracketsCount--; - - if (builder.HavingCondition == null) - builder.HavingCondition = string.Format("{0}{1}{2}", - addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); - else - builder.HavingCondition = string.Format("{0} {1} {2}{3}{4}", builder.HavingCondition, and ? "AND" : "OR", - addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); - } - - return builder; - } - - internal static T InternalHaving(this T builder, DynamicColumn column) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - bool virt = builder.VirtualMode; - if (column.VirtualColumn.HasValue) - builder.VirtualMode = column.VirtualColumn.Value; - - Action modParam = (p) => - { - if (column.Schema.HasValue) - p.Schema = column.Schema; - - if (!p.Schema.HasValue) - p.Schema = column.Schema ?? builder.GetColumnFromSchema(column.ColumnName); - }; - - builder.CreateTemporaryParameterAction(modParam); - - // It's kind of uglu, but... well it works. - if (column.Or) - switch (column.Operator) - { - default: - case DynamicColumn.CompareOperator.Eq: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break; - case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break; - case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break; - case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); break; - case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break; - case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break; - case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break; - case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break; - case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break; - case DynamicColumn.CompareOperator.Between: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Between(column.Value))); break; - } - else - switch (column.Operator) - { - default: - case DynamicColumn.CompareOperator.Eq: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break; - case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break; - case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break; - case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break; - case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break; - case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break; - case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break; - case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break; - case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break; - case DynamicColumn.CompareOperator.Between: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break; - } - - builder.OnCreateTemporaryParameter.Remove(modParam); - builder.VirtualMode = virt; - - return builder; - } - - internal static T InternalHaving(this T builder, string column, DynamicColumn.CompareOperator op, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - if (value is DynamicColumn) - { - DynamicColumn v = (DynamicColumn)value; - - if (string.IsNullOrEmpty(v.ColumnName)) - v.ColumnName = column; - - return builder.InternalHaving(v); - } - else if (value is IEnumerable) - { - foreach (DynamicColumn v in (IEnumerable)value) - builder.InternalHaving(v); - - return builder; - } - - return builder.InternalHaving(new DynamicColumn - { - ColumnName = column, - Operator = op, - Value = value - }); - } - - internal static T InternalHaving(this T builder, string column, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - return builder.InternalHaving(column, DynamicColumn.CompareOperator.Eq, value); - } - - internal static T InternalHaving(this T builder, object conditions, bool schema = false) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving - { - if (conditions is DynamicColumn) - return builder.InternalHaving((DynamicColumn)conditions); - else if (conditions is IEnumerable) - { - foreach (DynamicColumn v in (IEnumerable)conditions) - builder.InternalHaving(v); - - return builder; - } - - IDictionary dict = conditions.ToDictionary(); - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType()); - string table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty); - - foreach (KeyValuePair condition in dict) - { - if (mapper.Ignored.Contains(condition.Key) || condition.Key == "_table") - continue; - - string colName = mapper != null ? mapper.PropertyMap.TryGetValue(condition.Key) ?? condition.Key : condition.Key; - - DynamicSchemaColumn? col = null; - - // This should be used on typed queries or update/delete steatements, which usualy operate on a single table. - if (schema) - { - col = builder.GetColumnFromSchema(colName, mapper, table); - - if ((!col.HasValue || !col.Value.IsKey) && - (mapper == null || mapper.ColumnsMap.TryGetValue(colName).NullOr(m => m.Ignore || m.Column.NullOr(c => !c.IsKey, true), true))) - continue; - - colName = col.HasValue ? col.Value.Name : colName; - } - - if (!string.IsNullOrEmpty(table)) - builder.InternalHaving(x => x(builder.FixObjectName(string.Format("{0}.{1}", table, colName))) == condition.Value); - else - builder.InternalHaving(x => x(builder.FixObjectName(colName)) == condition.Value); - } - - return builder; - } - - #endregion Where - } - - internal static class DynamicWhereQueryExtensions - { - #region Where - - internal static T InternalWhere(this T builder, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere - { - return builder.InternalWhere(false, false, func); - } - - internal static T InternalWhere(this T builder, bool addBeginBrace, bool addEndBrace, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere - { - if (func == null) throw new ArgumentNullException("Array of functions cannot be null."); - - using (DynamicParser parser = DynamicParser.Parse(func)) - { - string condition = null; - bool and = true; - - object result = parser.Result; - if (result is string) - { - condition = (string)result; - - if (condition.ToUpper().IndexOf("OR") == 0) - { - and = false; - condition = condition.Substring(3); - } - else if (condition.ToUpper().IndexOf("AND") == 0) - condition = condition.Substring(4); - } - else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType) - return builder.InternalWhere(result); - else - { - // Intercepting the 'x => x.And()' and 'x => x.Or()' virtual methods... - if (result is DynamicParser.Node.Method && ((DynamicParser.Node.Method)result).Host is DynamicParser.Node.Argument) - { - DynamicParser.Node.Method node = (DynamicParser.Node.Method)result; - string name = node.Name.ToUpper(); - if (name == "AND" || name == "OR") - { - object[] args = ((DynamicParser.Node.Method)node).Arguments; - if (args == null) throw new ArgumentNullException("arg", string.Format("{0} is not a parameterless method.", name)); - if (args.Length != 1) throw new ArgumentException(string.Format("{0} requires one and only one parameter: {1}.", name, args.Sketch())); - - and = name == "AND" ? true : false; - result = args[0]; - } - } - - // Just parsing the contents now... - condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition"); - } - - if (addBeginBrace) builder.WhereOpenBracketsCount++; - if (addEndBrace) builder.WhereOpenBracketsCount--; - - if (builder.WhereCondition == null) - builder.WhereCondition = string.Format("{0}{1}{2}", - addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); - else - builder.WhereCondition = string.Format("{0} {1} {2}{3}{4}", builder.WhereCondition, and ? "AND" : "OR", - addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); - } - - return builder; - } - - internal static T InternalWhere(this T builder, DynamicColumn column) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere - { - bool virt = builder.VirtualMode; - if (column.VirtualColumn.HasValue) - builder.VirtualMode = column.VirtualColumn.Value; - - Action modParam = (p) => - { - if (column.Schema.HasValue) - p.Schema = column.Schema; - - if (!p.Schema.HasValue) - p.Schema = column.Schema ?? builder.GetColumnFromSchema(column.ColumnName); - }; - - builder.CreateTemporaryParameterAction(modParam); - - // It's kind of uglu, but... well it works. - if (column.Or) - switch (column.Operator) - { - default: - case DynamicColumn.CompareOperator.Eq: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break; - case DynamicColumn.CompareOperator.Not: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break; - case DynamicColumn.CompareOperator.Like: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break; - case DynamicColumn.CompareOperator.NotLike: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); break; - case DynamicColumn.CompareOperator.In: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break; - case DynamicColumn.CompareOperator.Lt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break; - case DynamicColumn.CompareOperator.Lte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break; - case DynamicColumn.CompareOperator.Gt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break; - case DynamicColumn.CompareOperator.Gte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break; - case DynamicColumn.CompareOperator.Between: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Between(column.Value))); break; - } - else - switch (column.Operator) - { - default: - case DynamicColumn.CompareOperator.Eq: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break; - case DynamicColumn.CompareOperator.Not: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break; - case DynamicColumn.CompareOperator.Like: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break; - case DynamicColumn.CompareOperator.NotLike: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break; - case DynamicColumn.CompareOperator.In: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break; - case DynamicColumn.CompareOperator.Lt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break; - case DynamicColumn.CompareOperator.Lte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break; - case DynamicColumn.CompareOperator.Gt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break; - case DynamicColumn.CompareOperator.Gte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break; - case DynamicColumn.CompareOperator.Between: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break; - } - - builder.OnCreateTemporaryParameter.Remove(modParam); - builder.VirtualMode = virt; - - return builder; - } - - internal static T InternalWhere(this T builder, string column, DynamicColumn.CompareOperator op, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere - { - if (value is DynamicColumn) - { - DynamicColumn v = (DynamicColumn)value; - - if (string.IsNullOrEmpty(v.ColumnName)) - v.ColumnName = column; - - return builder.InternalWhere(v); - } - else if (value is IEnumerable) - { - foreach (DynamicColumn v in (IEnumerable)value) - builder.InternalWhere(v); - - return builder; - } - - return builder.InternalWhere(new DynamicColumn - { - ColumnName = column, - Operator = op, - Value = value - }); - } - - internal static T InternalWhere(this T builder, string column, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere - { - return builder.InternalWhere(column, DynamicColumn.CompareOperator.Eq, value); - } - - internal static T InternalWhere(this T builder, object conditions, bool schema = false) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere - { - if (conditions is DynamicColumn) - return builder.InternalWhere((DynamicColumn)conditions); - else if (conditions is IEnumerable) - { - foreach (DynamicColumn v in (IEnumerable)conditions) - builder.InternalWhere(v); - - return builder; - } - - IDictionary dict = conditions.ToDictionary(); - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType()); - string table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty); - - foreach (KeyValuePair condition in dict) - { - if (mapper.Ignored.Contains(condition.Key) || condition.Key == "_table") - continue; - - string colName = mapper != null ? mapper.PropertyMap.TryGetValue(condition.Key) ?? condition.Key : condition.Key; - - DynamicSchemaColumn? col = null; - - // This should be used on typed queries or update/delete steatements, which usualy operate on a single table. - if (schema) - { - col = builder.GetColumnFromSchema(colName, mapper, table); - - if ((!col.HasValue || !col.Value.IsKey) && - (mapper == null || mapper.ColumnsMap.TryGetValue(colName).NullOr(m => m.Ignore || m.Column.NullOr(c => !c.IsKey, true), true))) - continue; - - colName = col.HasValue ? col.Value.Name : colName; - } - - if (!string.IsNullOrEmpty(table)) - builder.InternalWhere(x => x(builder.FixObjectName(string.Format("{0}.{1}", table, colName))) == condition.Value); - else - builder.InternalWhere(x => x(builder.FixObjectName(colName)) == condition.Value); - } - - return builder; - } - - #endregion Where - } - - internal static class DynamicModifyBuilderExtensions - { - internal static T Table(this T builder, Func func) where T : DynamicModifyBuilder - { - if (func == null) - throw new ArgumentNullException("Function cannot be null."); - - using (DynamicParser parser = DynamicParser.Parse(func)) - { - object result = parser.Result; - - // If the expression result is string. - if (result is string) - return builder.Table((string)result); - else if (result is Type) - return builder.Table((Type)result); - else if (result is DynamicParser.Node) - { - // Or if it resolves to a dynamic node - DynamicParser.Node node = (DynamicParser.Node)result; - - string owner = null; - string main = null; - - while (true) - { - // Deny support for the AS() virtual method... - if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") - throw new ArgumentException(string.Format("Alias is not supported on modification builders. (Parsing: {0})", result)); - - // Support for table specifications... - if (node is DynamicParser.Node.GetMember) - { - if (owner != null) - throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); - - if (main != null) - owner = ((DynamicParser.Node.GetMember)node).Name; - else - main = ((DynamicParser.Node.GetMember)node).Name; - - node = node.Host; - continue; - } - - // Support for generic sources... - if (node is DynamicParser.Node.Invoke) - { - if (owner == null && main == null) - { - DynamicParser.Node.Invoke invoke = (DynamicParser.Node.Invoke)node; - - if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type) - return builder.Table((Type)invoke.Arguments[0]); - else if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is String) - return builder.Table((string)invoke.Arguments[0]); - else - throw new ArgumentException(string.Format("Invalid argument count or type when parsing '{2}'. Invoke supports only one argument of type Type or String", owner, main, result)); - } - else if (owner != null) - throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); - else if (main != null) - throw new ArgumentException(string.Format("Main '{0}' is already set when parsing '{1}'.", main, result)); - } - - if (!string.IsNullOrEmpty(main)) - return builder.Table(string.Format("{0}{1}", - string.IsNullOrEmpty(owner) ? string.Empty : string.Format("{0}.", owner), - main)); - } - } - - throw new ArgumentException(string.Format("Unable to set table parsing '{0}'", result)); - } - } - - internal static T Table(this T builder, string tableName, Dictionary schema = null) where T : DynamicModifyBuilder - { - Tuple tuple = tableName.Validated("Table Name").SplitSomethingAndAlias(); - - if (!string.IsNullOrEmpty(tuple.Item2)) - throw new ArgumentException(string.Format("Can not use aliases in INSERT steatement. ({0})", tableName), "tableName"); - - string[] parts = tuple.Item1.Split('.'); - - if (parts.Length > 2) - throw new ArgumentException(string.Format("Table name can consist only from name or owner and name. ({0})", tableName), "tableName"); - - builder.Tables.Clear(); - builder.Tables.Add(new DynamicQueryBuilder.TableInfo(builder.Database, - builder.Database.StripName(parts.Last()).Validated("Table"), null, - parts.Length == 2 ? builder.Database.StripName(parts.First()).Validated("Owner", canbeNull: true) : null)); - - if (schema != null) - (builder.Tables[0] as DynamicQueryBuilder.TableInfo).Schema = schema; - - return builder; - } - - internal static T Table(this T builder, Type type) where T : DynamicQueryBuilder - { - if (type.IsAnonymous()) - throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}).", type.FullName)); - - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException("Cant assign unmapable type as a table."); - - if (builder is DynamicModifyBuilder) - { - builder.Tables.Clear(); - builder.Tables.Add(new DynamicQueryBuilder.TableInfo(builder.Database, type)); - } - else if (builder is DynamicSelectQueryBuilder) - (builder as DynamicSelectQueryBuilder).From(x => x(type)); - - return builder; - } - } - } + /// + /// 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); - namespace Implementation - { - - /// Implementation of dynamic select query builder. - internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere, DynamicQueryBuilder.IQueryWithHaving + /// 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 { - 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 - + /// Execute this builder. + /// Result of an execution.. + int Execute(); + /// - /// Gets a value indicating whether this instance has select columns. + /// 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'. /// - public bool HasSelectColumns { get { return !string.IsNullOrEmpty(_select); } } - + /// 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); + /// - /// Initializes a new instance of the class. + /// Generates the text this command will execute against the underlying database. /// - /// 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 - + /// 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. - public virtual IEnumerable Execute() - { - using (IDbConnection con = Database.Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(this) - .ExecuteReader()) - using (IDataReader cache = new DynamicCachedReader(rdr)) - while (cache.Read()) - { - dynamic val = null; - - // Work around to avoid yield being in try...catchblock: - // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch - try - { - val = cache.RowToDynamic(); - } - catch (ArgumentException argex) - { - StringBuilder sb = new StringBuilder(); - cmd.Dump(sb); - - throw new ArgumentException( - string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb), - argex.InnerException.NullOr(a => a, argex)); - } - - yield return val; - } - } - } - + IEnumerable Execute(); + /// Execute this builder and map to given type. /// Type of object to map on. /// Enumerator of objects expanded from query. - public virtual IEnumerable Execute() where T : class - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); - - if (mapper == null) - throw new InvalidOperationException("Type can't be mapped for unknown reason."); - - using (IDbConnection con = Database.Open()) - using (IDbCommand cmd = con.CreateCommand()) - { - using (IDataReader rdr = cmd - .SetCommand(this) - .ExecuteReader()) - using (IDataReader cache = new DynamicCachedReader(rdr)) - while (cache.Read()) - { - dynamic val = null; - - // Work around to avoid yield being in try...catchblock: - // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch - try - { - val = cache.RowToDynamic(); - } - catch (ArgumentException argex) - { - StringBuilder sb = new StringBuilder(); - cmd.Dump(sb); - - throw new ArgumentException( - string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb), - argex.InnerException.NullOr(a => a, argex)); - } - - yield return mapper.Create(val) as T; - } - } - } - + IEnumerable Execute() where T : class; + /// Execute this builder as a data reader. /// Action containing reader. - public virtual void ExecuteDataReader(Action reader) - { - using (IDbConnection con = Database.Open()) - using (IDbCommand cmd = con.CreateCommand()) - using (IDataReader rdr = cmd - .SetCommand(this) - .ExecuteReader()) - reader(rdr); - } - - /// Execute this builder as a data reader, but - /// first makes a full reader copy in memory. - /// Action containing reader. - public virtual void ExecuteCachedDataReader(Action reader) - { - using (IDbConnection con = Database.Open()) - using (IDbCommand cmd = con.CreateCommand()) - using (IDataReader rdr = cmd - .SetCommand(this) - .ExecuteReader()) - using (IDataReader cache = new DynamicCachedReader(rdr)) - reader(cache); - } - + void ExecuteDataReader(Action reader); + /// 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(); - } - } - + object Scalar(); + #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); - } - } - + T ScalarAs(T defaultValue = default(T)); + #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: @@ -12119,200 +6952,8 @@ namespace DynamORM /// The specification. /// The specification. /// This instance to permit chaining. - public virtual IDynamicSelectQueryBuilder From(Func fn, params Func[] func) - { - if (fn == null) - throw new ArgumentNullException("Array of functions cannot be or contain null."); - - int index = FromFunc(-1, fn); - foreach (Func f in func) - index = FromFunc(index, f); - - return this; - } - - private int FromFunc(int index, Func f) - { - if (f == null) - throw new ArgumentNullException("Array of functions cannot be or contain null."); - - index++; - ITableInfo tableInfo = null; - using (DynamicParser parser = DynamicParser.Parse(f)) - { - object result = parser.Result; - - // If the expression result is string. - if (result is string) - { - string node = (string)result; - Tuple tuple = node.SplitSomethingAndAlias(); - string[] parts = tuple.Item1.Split('.'); - tableInfo = new TableInfo(Database, - Database.StripName(parts.Last()).Validated("Table"), - tuple.Item2.Validated("Alias", canbeNull: true), - parts.Length == 2 ? Database.StripName(parts.First()).Validated("Owner", canbeNull: true) : null); - } - else if (result is Type) - { - Type type = (Type)result; - if (type.IsAnonymous()) - throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}). Parsing {1}", type.FullName, result)); - - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}). Parsing {1}", type.FullName, result)); - - tableInfo = new TableInfo(Database, type); - } - else if (result is DynamicParser.Node) - { - // Or if it resolves to a dynamic node - DynamicParser.Node node = (DynamicParser.Node)result; - - string owner = null; - string main = null; - string alias = null; - bool nolock = false; - Type type = null; - - while (true) - { - // Support for the AS() virtual method... - if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") - { - if (alias != null) - throw new ArgumentException(string.Format("Alias '{0}' is already set when parsing '{1}'.", alias, result)); - - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args == null) - throw new ArgumentNullException("arg", "AS() is not a parameterless method."); - - if (args.Length != 1) - throw new ArgumentException("AS() requires one and only one parameter: " + args.Sketch()); - - alias = Parse(args[0], rawstr: true, decorate: false).Validated("Alias"); - - node = node.Host; - continue; - } - - // Support for the NoLock() virtual method... - if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") - { - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args != null && args.Length > 0) - throw new ArgumentNullException("arg", "NoLock() doesn't support arguments."); - - nolock = true; - - node = node.Host; - continue; - } - - /*if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "subquery") - { - main = Parse(this.SubQuery(((DynamicParser.Node.Method)node).Arguments.Where(p => p is Func).Cast>().ToArray()), Parameters); - continue; - }*/ - - // Support for table specifications... - if (node is DynamicParser.Node.GetMember) - { - if (owner != null) - throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); - - if (main != null) - owner = ((DynamicParser.Node.GetMember)node).Name; - else - main = ((DynamicParser.Node.GetMember)node).Name; - - node = node.Host; - continue; - } - - // Support for generic sources... - if (node is DynamicParser.Node.Invoke) - { - if (owner != null) - throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); - - if (main != null) - owner = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); - else - { - DynamicParser.Node.Invoke invoke = (DynamicParser.Node.Invoke)node; - if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type) - { - type = (Type)invoke.Arguments[0]; - if (type.IsAnonymous()) - throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}). Parsing {1}", type.FullName, result)); - - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - if (mapper == null) - throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}). Parsing {1}", type.FullName, result)); - - main = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? - mapper.Type.Name : mapper.Table.Name; - - owner = (mapper.Table != null) ? mapper.Table.Owner : owner; - } - else - main = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); - } - - node = node.Host; - continue; - } - - // Just finished the parsing... - if (node is DynamicParser.Node.Argument) break; - - // All others are assumed to be part of the main element... - if (main != null) - main = Parse(node, pars: Parameters); - else - main = Parse(node, pars: Parameters); - - break; - } - - if (!string.IsNullOrEmpty(main)) - tableInfo = type == null ? new TableInfo(Database, main, alias, owner, nolock) : new TableInfo(Database, type, alias, owner, nolock); - else - throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); - } - - // Or it is a not supported expression... - if (tableInfo == null) - throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); - - Tables.Add(tableInfo); - - // We finally add the contents... - StringBuilder sb = new StringBuilder(); - - if (!string.IsNullOrEmpty(tableInfo.Owner)) - sb.AppendFormat("{0}.", Database.DecorateName(tableInfo.Owner)); - - sb.Append(tableInfo.Name.ContainsAny(StringExtensions.InvalidMemberChars) ? tableInfo.Name : Database.DecorateName(tableInfo.Name)); - - if (!string.IsNullOrEmpty(tableInfo.Alias)) - sb.AppendFormat(" AS {0}", tableInfo.Alias); - - if (SupportNoLock && tableInfo.NoLock) - sb.AppendFormat(" WITH(NOLOCK)"); - - _from = string.IsNullOrEmpty(_from) ? sb.ToString() : string.Format("{0}, {1}", _from, sb.ToString()); - } - - return index; - } - + 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: @@ -12329,302 +6970,12 @@ namespace DynamORM /// /// The specification. /// This instance to permit chaining. - public virtual IDynamicSelectQueryBuilder Join(params Func[] func) - { - // We need to do two passes to add aliases first. - return JoinInternal(true, func).JoinInternal(false, func); - } - - /// - /// Adds to the 'Join' clause the contents obtained by parsing the dynamic lambda expressions given. The supported - /// formats are: - /// - Resolve to a string: 'x => "Table AS Alias ON Condition', where the alias part is optional. - /// - Resolve to an expression: 'x => x.Table.As( x.Alias ).On( condition )', where the alias part is optional. - /// - Generic expression: 'x => x( expression ).As( x.Alias ).On( condition )', where the alias part is mandatory. - /// In this case the alias is not annotated. - /// The expression might be prepended by a method that, in this case, is used to specify the specific join type you - /// want to perform, as in: 'x => x.Left()...". Two considerations apply: - /// - If a 'false' argument is used when no 'Join' part appears in its name, then no 'Join' suffix is added - /// with a space in between. - /// - If a 'false' argument is used when a 'Join' part does appear, then no split is performed to separate the - /// 'Join' part. - /// - /// If true just pass by to locate tables and aliases, otherwise create rules. - /// The specification. - /// This instance to permit chaining. - protected virtual DynamicSelectQueryBuilder JoinInternal(bool justAddTables, params Func[] func) - { - if (func == null) throw new ArgumentNullException("Array of functions cannot be null."); - - int index = -1; - - foreach (Func f in func) - { - index++; - ITableInfo tableInfo = null; - - if (f == null) - throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); - - using (DynamicParser parser = DynamicParser.Parse(f)) - { - object result = parser.Result; - if (result == null) throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); - - string type = null; - string main = null; - string owner = null; - string alias = null; - string condition = null; - bool nolock = false; - Type tableType = null; - - // If the expression resolves to a string... - if (result is string) - { - string node = (string)result; - - int n = node.ToUpper().IndexOf("JOIN "); - - if (n < 0) - main = node; - else - { - // For strings we only accept 'JOIN' as a suffix - type = node.Substring(0, n + 4); - main = node.Substring(n + 4); - } - - n = main.ToUpper().IndexOf("ON"); - - if (n >= 0) - { - condition = main.Substring(n + 3); - main = main.Substring(0, n).Trim(); - } - - Tuple tuple = main.SplitSomethingAndAlias(); // In this case we split on the remaining 'main' - string[] parts = tuple.Item1.Split('.'); - main = Database.StripName(parts.Last()).Validated("Table"); - owner = parts.Length == 2 ? Database.StripName(parts.First()).Validated("Owner", canbeNull: true) : null; - alias = tuple.Item2.Validated("Alias", canbeNull: true); - } - else if (result is DynamicParser.Node) - { - // Or if it resolves to a dynamic node... - DynamicParser.Node node = (DynamicParser.Node)result; - while (true) - { - // Support for the ON() virtual method... - if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "ON") - { - if (condition != null) - throw new ArgumentException(string.Format("Condition '{0}' is already set when parsing '{1}'.", alias, result)); - - object[] args = ((DynamicParser.Node.Method)node).Arguments; - if (args == null) - throw new ArgumentNullException("arg", "ON() is not a parameterless method."); - - if (args.Length != 1) - throw new ArgumentException("ON() requires one and only one parameter: " + args.Sketch()); - - condition = Parse(args[0], rawstr: true, pars: justAddTables ? null : Parameters); - - node = node.Host; - continue; - } - - // Support for the AS() virtual method... - if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") - { - if (alias != null) - throw new ArgumentException(string.Format("Alias '{0}' is already set when parsing '{1}'.", alias, result)); - - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args == null) - throw new ArgumentNullException("arg", "AS() is not a parameterless method."); - - if (args.Length != 1) - throw new ArgumentException("AS() requires one and only one parameter: " + args.Sketch()); - - alias = Parse(args[0], rawstr: true, decorate: false, isMultiPart: false).Validated("Alias"); - - node = node.Host; - continue; - } - - // Support for the NoLock() virtual method... - if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") - { - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args != null && args.Length > 0) - throw new ArgumentNullException("arg", "NoLock() doesn't support arguments."); - - nolock = true; - - node = node.Host; - continue; - } - - // Support for table specifications... - if (node is DynamicParser.Node.GetMember) - { - if (owner != null) - throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); - - if (main != null) - owner = ((DynamicParser.Node.GetMember)node).Name; - else - main = ((DynamicParser.Node.GetMember)node).Name; - - node = node.Host; - continue; - } - - // Support for Join Type specifications... - if (node is DynamicParser.Node.Method && (node.Host is DynamicParser.Node.Argument || node.Host is DynamicParser.Node.Invoke)) - { - if (type != null) throw new ArgumentException(string.Format("Join type '{0}' is already set when parsing '{1}'.", main, result)); - type = ((DynamicParser.Node.Method)node).Name; - - bool avoid = false; - object[] args = ((DynamicParser.Node.Method)node).Arguments; - - if (args != null && args.Length > 0) - { - avoid = args[0] is bool && !((bool)args[0]); - string proposedType = args.FirstOrDefault(a => a is string) as string; - if (!string.IsNullOrEmpty(proposedType)) - type = proposedType; - } - - type = type.ToUpper(); // Normalizing, and stepping out the trivial case... - if (type != "JOIN") - { - // Special cases - // x => x.LeftOuter() / x => x.RightOuter()... - type = type.Replace("OUTER", " OUTER ") - .Replace(" ", " ") - .Trim(' '); - - // x => x.Left()... - int n = type.IndexOf("JOIN"); - - if (n < 0 && !avoid) - type += " JOIN"; - - // x => x.InnerJoin() / x => x.JoinLeft() ... - else - { - if (!avoid) - { - if (n == 0) type = type.Replace("JOIN", "JOIN "); - else type = type.Replace("JOIN", " JOIN"); - } - } - } - - node = node.Host; - continue; - } - - // Support for generic sources... - if (node is DynamicParser.Node.Invoke) - { - if (owner != null) - throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); - - if (main != null) - owner = string.Format("{0}", Parse(node, rawstr: true, pars: justAddTables ? null : Parameters)); - else - { - DynamicParser.Node.Invoke invoke = (DynamicParser.Node.Invoke)node; - if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type) - { - tableType = (Type)invoke.Arguments[0]; - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(tableType); - - if (mapper == null) - throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", tableType.FullName)); - - main = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? - mapper.Type.Name : mapper.Table.Name; - - owner = (mapper.Table != null) ? mapper.Table.Owner : owner; - } - else - main = string.Format("{0}", Parse(node, rawstr: true, pars: justAddTables ? null : Parameters)); - } - - node = node.Host; - continue; - } - - // Just finished the parsing... - if (node is DynamicParser.Node.Argument) break; - throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); - } - } - else - { - // Or it is a not supported expression... - throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); - } - - // We annotate the aliases being conservative... - main = main.Validated("Main"); - - if (justAddTables) - { - if (!string.IsNullOrEmpty(main)) - tableInfo = tableType == null ? new TableInfo(Database, main, alias, owner, nolock) : new TableInfo(Database, tableType, alias, owner, nolock); - else - throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); - - Tables.Add(tableInfo); - } - else - { - // Get cached table info - tableInfo = string.IsNullOrEmpty(alias) ? - Tables.SingleOrDefault(t => t.Name == main && string.IsNullOrEmpty(t.Alias)) : - Tables.SingleOrDefault(t => t.Alias == alias); - - // We finally add the contents if we can... - StringBuilder sb = new StringBuilder(); - if (string.IsNullOrEmpty(type)) - type = "JOIN"; - - sb.AppendFormat("{0} ", type); - - if (!string.IsNullOrEmpty(tableInfo.Owner)) - sb.AppendFormat("{0}.", Database.DecorateName(tableInfo.Owner)); - - sb.Append(tableInfo.Name.ContainsAny(StringExtensions.InvalidMemberChars) ? tableInfo.Name : Database.DecorateName(tableInfo.Name)); - - if (!string.IsNullOrEmpty(tableInfo.Alias)) - sb.AppendFormat(" AS {0}", tableInfo.Alias); - - if (SupportNoLock && tableInfo.NoLock) - sb.AppendFormat(" WITH(NOLOCK)"); - - if (!string.IsNullOrEmpty(condition)) - sb.AppendFormat(" ON {0}", condition); - - _join = string.IsNullOrEmpty(_join) ? sb.ToString() : string.Format("{0} {1}", _join, sb.ToString()); // No comma in this case - } - } - } - - return this; - } - + 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 @@ -12635,52 +6986,37 @@ namespace DynamORM /// /// The specification. /// This instance to permit chaining. - public virtual IDynamicSelectQueryBuilder Where(Func func) - { - return this.InternalWhere(func); - } - + IDynamicSelectQueryBuilder Where(Func func); + /// Add where condition. /// Condition column with operator and value. /// Builder instance. - public virtual IDynamicSelectQueryBuilder Where(DynamicColumn column) - { - return this.InternalWhere(column); - } - + IDynamicSelectQueryBuilder Where(DynamicColumn 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); - } - + IDynamicSelectQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value); + /// Add where condition. /// Condition column. /// Condition value. /// Builder instance. - public virtual IDynamicSelectQueryBuilder Where(string column, object value) - { - return this.InternalWhere(column, value); - } - + 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. - public virtual IDynamicSelectQueryBuilder Where(object conditions, bool schema = false) - { - return this.InternalWhere(conditions, schema); - } - + 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: @@ -12693,203 +7029,48 @@ namespace DynamORM /// 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; - } - + IDynamicSelectQueryBuilder Select(Func fn, params Func[] func); + /// 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; - } - + 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. - 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); - } - + 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. - 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; - } - + IDynamicSelectQueryBuilder GroupBy(Func fn, params Func[] func); + /// 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; - } - + 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. - public virtual IDynamicSelectQueryBuilder GroupByColumn(params string[] columns) - { - return GroupByColumn(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray()); - } - + 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 @@ -12900,52 +7081,37 @@ namespace DynamORM /// /// The specification. /// This instance to permit chaining. - public virtual IDynamicSelectQueryBuilder Having(Func func) - { - return this.InternalHaving(func); - } - + IDynamicSelectQueryBuilder Having(Func func); + /// Add Having condition. /// Condition column with operator and value. /// Builder instance. - public virtual IDynamicSelectQueryBuilder Having(DynamicColumn column) - { - return this.InternalHaving(column); - } - + IDynamicSelectQueryBuilder Having(DynamicColumn 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); - } - + IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value); + /// Add Having condition. /// Condition column. /// Condition value. /// Builder instance. - public virtual IDynamicSelectQueryBuilder Having(string column, object value) - { - return this.InternalHaving(column, value); - } - + 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. - public virtual IDynamicSelectQueryBuilder Having(object conditions, bool schema = false) - { - return this.InternalHaving(conditions, schema); - } - + 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 @@ -12955,498 +7121,850 @@ namespace DynamORM /// 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; - } - + IDynamicSelectQueryBuilder OrderBy(Func fn, params Func[] func); + /// 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; - } - + 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. - public virtual IDynamicSelectQueryBuilder OrderByColumn(params string[] columns) - { - return OrderByColumn(columns.Select(c => DynamicColumn.ParseOrderByColumn(c)).ToArray()); - } - + IDynamicSelectQueryBuilder OrderByColumn(params string[] columns); + #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); - } - + IDynamicSelectQueryBuilder Top(int? 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; - } - + IDynamicSelectQueryBuilder Limit(int? limit); + /// 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; - } - + IDynamicSelectQueryBuilder Offset(int? offset); + /// Set distinct mode. /// Distinct mode. /// Builder instance. - public virtual IDynamicSelectQueryBuilder Distinct(bool distinct = true) - { - _distinct = distinct; - return this; - } - + IDynamicSelectQueryBuilder Distinct(bool distinct = true); + #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 + /// Dynamic update query builder interface. + /// This interface it publicly available. Implementation should be hidden. + public interface IDynamicUpdateQueryBuilder : IDynamicQueryBuilder { - 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); - } - + /// 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. - 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; - } - + 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. - 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; - } - + 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'. + /// - 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) + IDynamicUpdateQueryBuilder Set(params Func[] func); + + /// Add insert fields. + /// Insert column. + /// Insert value. + /// Builder instance. + IDynamicUpdateQueryBuilder Values(string column, object value); + + /// Add insert fields. + /// Set insert value as properties and values of an object. + /// Builder instance. + IDynamicUpdateQueryBuilder Values(object o); + + #endregion Values + + #region Where + + /// + /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator. + /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: + /// 'Where( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicUpdateQueryBuilder Where(Func func); + + /// Add where condition. + /// Condition column with operator and value. + /// Builder instance. + IDynamicUpdateQueryBuilder Where(DynamicColumn column); + + /// Add where condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + IDynamicUpdateQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value); + + /// Add where condition. + /// Condition column. + /// Condition value. + /// Builder instance. + IDynamicUpdateQueryBuilder Where(string column, object value); + + /// Add where condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + IDynamicUpdateQueryBuilder Where(object conditions, bool schema = false); + + #endregion Where + } + /// Interface describing parameter info. + public interface IParameter : IExtendedDisposable + { + /// Gets the parameter position in command. + /// Available after filling the command. + int Ordinal { get; } + + /// Gets the parameter temporary name. + string Name { get; } + + /// Gets or sets the parameter value. + object Value { get; set; } + + /// Gets or sets a value indicating whether name of temporary parameter is well known. + bool WellKnown { get; set; } + + /// Gets or sets a value indicating whether this is virtual. + bool Virtual { get; set; } + + /// Gets or sets the parameter schema information. + DynamicSchemaColumn? Schema { get; set; } + } + /// Interface describing table information. + public interface ITableInfo : IExtendedDisposable + { + /// Gets table owner name. + string Owner { get; } + + /// Gets table name. + string Name { get; } + + /// Gets table alias. + string Alias { get; } + + /// Gets table no lock status. + bool NoLock { get; } + + /// Gets table schema. + Dictionary Schema { get; } + } + namespace Extensions + { + internal static class DynamicHavingQueryExtensions { - if (func == null) - throw new ArgumentNullException("Array of specifications cannot be null."); - - int index = -1; - foreach (Func f in func) + #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)); - - object result = null; - - using (DynamicParser p = DynamicParser.Parse(f)) + + using (DynamicParser parser = DynamicParser.Parse(f)) { - result = p.Result; - + 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); - - str = string.Format("{0} = {1}", main, value); - _columns = _columns == null ? str : string.Format("{0}, {1}", _columns, str); - continue; + + _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) { - Values(result); - continue; + Insert(result); + return index; } - // Other specifications are considered invalid... string err = string.Format("Specification '{0}' is invalid.", result); str = Parse(result); @@ -13454,1885 +7972,6377 @@ namespace DynamORM 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) + /// Add insert fields. + /// Insert column. + /// Insert value. + /// Builder instance. + public virtual IDynamicInsertQueryBuilder Insert(string column, object value) { - DynamicColumn v = (DynamicColumn)value; - - if (string.IsNullOrEmpty(v.ColumnName)) - v.ColumnName = column; - - return Values(v); - } - - return Values(new DynamicColumn - { - ColumnName = column, - Value = value, - }); - } - - /// Add insert fields. - /// Set insert value as properties and values of an object. - /// Builder instance. - public virtual IDynamicUpdateQueryBuilder Values(object o) - { - if (o is DynamicColumn) - { - DynamicColumn column = (DynamicColumn)o; - DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName); - - string main = FixObjectName(column.ColumnName, onlyColumn: true); - string value = Parse(column.Value, ref col, pars: Parameters, nulls: true); - - string str = string.Format("{0} = {1}", main, value); - _columns = _columns == null ? str : string.Format("{0}, {1}", _columns, str); - - return this; - } - - IDictionary dict = o.ToDictionary(); - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(o.GetType()); - - if (mapper != null) - { - foreach (KeyValuePair con in dict) - if (!mapper.Ignored.Contains(con.Key)) - Values(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value); - } - else - foreach (KeyValuePair con in dict) - Values(con.Key, con.Value); - - return this; - } - - #endregion Values - - #region Where - - /// - /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition - /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used - /// as needed. - /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator. - /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: - /// 'Where( x => x.Or( condition ) )'. - /// - /// The specification. - /// This instance to permit chaining. - public virtual IDynamicUpdateQueryBuilder Where(Func func) - { - return this.InternalWhere(func); - } - - /// Add where condition. - /// Condition column with operator and value. - /// Builder instance. - public virtual IDynamicUpdateQueryBuilder Where(DynamicColumn column) - { - return this.InternalWhere(column); - } - - /// Add where condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - public virtual IDynamicUpdateQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value) - { - return this.InternalWhere(column, op, value); - } - - /// Add where condition. - /// Condition column. - /// Condition value. - /// Builder instance. - public virtual IDynamicUpdateQueryBuilder Where(string column, object value) - { - return this.InternalWhere(column, value); - } - - /// Add where condition. - /// Set conditions as properties and values of an object. - /// If true use schema to determine key columns and ignore those which - /// aren't keys. - /// Builder instance. - public virtual IDynamicUpdateQueryBuilder Where(object conditions, bool schema = false) - { - return this.InternalWhere(conditions, schema); - } - - #endregion Where - - #region IExtendedDisposable - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public override void Dispose() - { - base.Dispose(); - - _columns = null; - } - - #endregion IExtendedDisposable - } - - /// Implementation of dynamic query builder base interface. - internal abstract class DynamicQueryBuilder : IDynamicQueryBuilder - { - /// Empty interface to allow where query builder implementation use universal approach. - internal interface IQueryWithWhere - { - /// Gets or sets the where condition. - string WhereCondition { get; set; } - - /// Gets or sets the amount of not closed brackets in where statement. - int WhereOpenBracketsCount { get; set; } - } - - /// Empty interface to allow having query builder implementation use universal approach. - internal interface IQueryWithHaving - { - /// Gets or sets the having condition. - string HavingCondition { get; set; } - - /// Gets or sets the amount of not closed brackets in having statement. - int HavingOpenBracketsCount { get; set; } - } - - private DynamicQueryBuilder _parent = null; - - #region TableInfo - - /// Table information. - internal class TableInfo : ITableInfo - { - /// - /// Initializes a new instance of the class. - /// - internal TableInfo() - { - IsDisposed = false; - } - - /// - /// Initializes a new instance of the class. - /// - /// The database. - /// The name of table. - /// The table alias. - /// The table owner. - /// The table should be used with nolock. - public TableInfo(DynamicDatabase db, string name, string alias = null, string owner = null, bool nolock = false) - : this() - { - Name = name; - Alias = alias; - Owner = owner; - NoLock = nolock; - - if (!name.ContainsAny(StringExtensions.InvalidMemberChars)) - Schema = db.GetSchema(name, owner: owner); - } - - /// - /// Initializes a new instance of the class. - /// - /// The database. - /// The type which can be mapped to database. - /// The table alias. - /// The table owner. - /// The table should be used with nolock. - public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null, bool nolock = false) - : this() - { - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); - - Name = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? - mapper.Type.Name : mapper.Table.Name; - - Owner = (mapper.Table != null) ? mapper.Table.Owner : owner; - Alias = alias; - NoLock = nolock; - - Schema = db.GetSchema(type); - } - - /// Gets or sets table owner name. - public string Owner { get; internal set; } - - /// Gets or sets table name. - public string Name { get; internal set; } - - /// Gets or sets table alias. - public string Alias { get; internal set; } - - /// Gets or sets table alias. - public bool NoLock { get; internal set; } - - /// Gets or sets table schema. - public Dictionary Schema { get; internal set; } - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public virtual void Dispose() - { - IsDisposed = true; - - ////if (Schema != null) - //// Schema.Clear(); - - Owner = Name = Alias = null; - Schema = null; - } - } - - /// Generic based table information. - /// Type of class that is represented in database. - internal class TableInfo : TableInfo - { - /// - /// Initializes a new instance of the class. - /// - /// The database. - /// The table alias. - /// The table owner. - public TableInfo(DynamicDatabase db, string alias = null, string owner = null) - : base(db, typeof(T), alias, owner) - { - } - } - - #endregion TableInfo - - #region Parameter - - /// Interface describing parameter info. - internal class Parameter : IParameter - { - /// Initializes a new instance of the - /// class. - public Parameter() - { - IsDisposed = false; - } - - /// Gets or sets the parameter position in command. - /// Available after filling the command. - public int Ordinal { get; internal set; } - - /// Gets or sets the parameter temporary name. - public string Name { get; internal set; } - - /// Gets or sets the parameter value. - public object Value { get; set; } - - /// Gets or sets a value indicating whether name of temporary parameter is well known. - public bool WellKnown { get; set; } - - /// Gets or sets a value indicating whether this is virtual. - public bool Virtual { get; set; } - - /// Gets or sets the parameter schema information. - public DynamicSchemaColumn? Schema { get; set; } - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public virtual void Dispose() - { - IsDisposed = true; - - Name = null; - Schema = null; - } - } - - #endregion Parameter - - #region Constructor - - /// - /// Initializes a new instance of the class. - /// - /// The database. - public DynamicQueryBuilder(DynamicDatabase db) - { - IsDisposed = false; - VirtualMode = false; - Tables = new List(); - Parameters = new Dictionary(); - OnCreateTemporaryParameter = new List>(); - OnCreateParameter = new List>(); - - WhereCondition = null; - WhereOpenBracketsCount = 0; - - Database = db; - if (Database != null) - Database.AddToCache(this); - - SupportSchema = (db.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema; - SupportNoLock = (db.Options & DynamicDatabaseOptions.SupportNoLock) == DynamicDatabaseOptions.SupportNoLock; - } - - /// Initializes a new instance of the class. - /// The database. - /// The parent query. - internal DynamicQueryBuilder(DynamicDatabase db, DynamicQueryBuilder parent) - : this(db) - { - _parent = parent; - } - - #endregion Constructor - - #region IQueryWithWhere - - /// Gets or sets the where condition. - public string WhereCondition { get; set; } - - /// Gets or sets the amount of not closed brackets in where statement. - public int WhereOpenBracketsCount { get; set; } - - #endregion IQueryWithWhere - - #region IDynamicQueryBuilder - - /// Gets instance. - public DynamicDatabase Database { get; private set; } - - /// Gets the tables used in this builder. - public IList Tables { get; private set; } - - /// Gets the tables used in this builder. - public IDictionary Parameters { get; private set; } - - /// Gets or sets a value indicating whether add virtual parameters. - public bool VirtualMode { get; set; } - - /// Gets or sets the on create temporary parameter actions. - /// This is exposed to allow setting schema of column. - public List> OnCreateTemporaryParameter { get; set; } - - /// Gets or sets the on create real parameter actions. - /// This is exposed to allow modification of parameter. - public List> OnCreateParameter { get; set; } - - /// Gets a value indicating whether database supports standard schema. - public bool SupportSchema { get; private set; } - - /// Gets a value indicating whether database supports with no lock syntax. - public bool SupportNoLock { get; private set; } - - /// - /// Generates the text this command will execute against the underlying database. - /// - /// The text to execute against the underlying database. - /// This method must be override by derived classes. - public abstract string CommandText(); - - /// Fill command with query. - /// Command to fill. - /// Filled instance of . - public virtual IDbCommand FillCommand(IDbCommand command) - { - // End not ended where statement - if (this is IQueryWithWhere) - { - while (WhereOpenBracketsCount > 0) + if (value is DynamicColumn) { - WhereCondition += ")"; - WhereOpenBracketsCount--; + DynamicColumn v = (DynamicColumn)value; + + if (string.IsNullOrEmpty(v.ColumnName)) + v.ColumnName = column; + + return Insert(v); } - } - - // End not ended having statement - if (this is IQueryWithHaving) - { - IQueryWithHaving h = this as IQueryWithHaving; - - while (h.HavingOpenBracketsCount > 0) + return Insert(new DynamicColumn { - h.HavingCondition += ")"; - h.HavingOpenBracketsCount--; - } + ColumnName = column, + Value = value, + }); } - - return command.SetCommand(CommandText() - .FillStringWithVariables(s => + /// 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) { - return Parameters.TryGetValue(s).NullOr(p => - { - IDbDataParameter param = (IDbDataParameter)command - .AddParameter(this, p.Schema, p.Value) - .Parameters[command.Parameters.Count - 1]; - - (p as Parameter).Ordinal = command.Parameters.Count - 1; - - if (OnCreateParameter != null) - OnCreateParameter.ForEach(x => x(p, param)); - - return param.ParameterName; - }, s); - })); - } - - #endregion IDynamicQueryBuilder - - #region Parser - - /// Parses the arbitrary object given and translates it into a string with the appropriate - /// syntax for the database this parser is specific to. - /// The object to parse and translate. It can be any arbitrary object, including null values (if - /// permitted) and dynamic lambda expressions. - /// If not null, the parameters' list where to store the parameters extracted by the parsing. - /// If true, literal (raw) string are allowed. If false and the node is a literal then, as a - /// security measure, an exception is thrown. - /// True to accept null values and translate them into the appropriate syntax accepted by the - /// database. If false and the value is null, then an exception is thrown. - /// If set to true decorate element. - /// If set parse argument as alias. This is workaround for AS method. - /// A string containing the result of the parsing, along with the parameters extracted in the - /// instance if such is given. - /// Null nodes are not accepted. - internal virtual string Parse(object node, IDictionary pars = null, bool rawstr = false, bool nulls = false, bool decorate = true, bool isMultiPart = true) - { - DynamicSchemaColumn? c = null; - - return Parse(node, ref c, pars, rawstr, nulls, decorate, isMultiPart); - } - - /// Parses the arbitrary object given and translates it into a string with the appropriate - /// syntax for the database this parser is specific to. - /// The object to parse and translate. It can be any arbitrary object, including null values (if - /// permitted) and dynamic lambda expressions. - /// This parameter is used to determine type of parameter used in query. - /// If not null, the parameters' list where to store the parameters extracted by the parsing. - /// If true, literal (raw) string are allowed. If false and the node is a literal then, as a - /// security measure, an exception is thrown. - /// True to accept null values and translate them into the appropriate syntax accepted by the - /// database. If false and the value is null, then an exception is thrown. - /// If set to true decorate element. - /// If set parse argument as alias. This is workaround for AS method. - /// A string containing the result of the parsing, along with the parameters extracted in the - /// instance if such is given. - /// Null nodes are not accepted. - internal virtual string Parse(object node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool rawstr = false, bool nulls = false, bool decorate = true, bool isMultiPart = true) - { - // Null nodes are accepted or not depending upon the "nulls" flag... - if (node == null) - { - if (!nulls) - throw new ArgumentNullException("node", "Null nodes are not accepted."); - - return Dispatch(node, ref columnSchema, pars, decorate); - } - - // Nodes that are strings are parametrized or not depending the "rawstr" flag... - if (node is string) - { - if (rawstr) return (string)node; - else return Dispatch(node, ref columnSchema, pars, decorate); - } - - // If node is a delegate, parse it to create the logical tree... - if (node is Delegate) - { - using (DynamicParser p = DynamicParser.Parse((Delegate)node)) - { - node = p.Result; - - return Parse(node, ref columnSchema, pars, rawstr, decorate: decorate); // Intercept containers as in (x => "string") - } - } - - return Dispatch(node, ref columnSchema, pars, decorate, isMultiPart); - } - - private string Dispatch(object node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool decorate = true, bool isMultiPart = true) - { - if (node != null) - { - if (node is DynamicQueryBuilder) return ParseCommand((DynamicQueryBuilder)node, pars); - else if (node is DynamicParser.Node.Argument) return ParseArgument((DynamicParser.Node.Argument)node, isMultiPart); - else if (node is DynamicParser.Node.GetMember) return ParseGetMember((DynamicParser.Node.GetMember)node, ref columnSchema, pars, decorate, isMultiPart); - else if (node is DynamicParser.Node.SetMember) return ParseSetMember((DynamicParser.Node.SetMember)node, ref columnSchema, pars, decorate, isMultiPart); - else if (node is DynamicParser.Node.Unary) return ParseUnary((DynamicParser.Node.Unary)node, pars); - else if (node is DynamicParser.Node.Binary) return ParseBinary((DynamicParser.Node.Binary)node, pars); - else if (node is DynamicParser.Node.Method) return ParseMethod((DynamicParser.Node.Method)node, ref columnSchema, pars); - else if (node is DynamicParser.Node.Invoke) return ParseInvoke((DynamicParser.Node.Invoke)node, ref columnSchema, pars); - else if (node is DynamicParser.Node.Convert) return ParseConvert((DynamicParser.Node.Convert)node, pars); - } - - // All other cases are considered constant parameters... - return ParseConstant(node, pars, columnSchema); - } - - internal virtual string ParseCommand(DynamicQueryBuilder node, IDictionary pars = null) - { - // Getting the command's text... - string str = node.CommandText(); // Avoiding spurious "OUTPUT XXX" statements - - // If there are parameters to transform, but cannot store them, it is an error - if (node.Parameters.Count != 0 && pars == null) - return string.Format("({0})", str); - - // TODO: Make special condiion - ////throw new InvalidOperationException(string.Format("The parameters in this command '{0}' cannot be added to a null collection.", node.Parameters)); - - // Copy parameters to new comand - foreach (KeyValuePair parameter in node.Parameters) - pars.Add(parameter.Key, parameter.Value); - - return string.Format("({0})", str); - } - - protected virtual string ParseArgument(DynamicParser.Node.Argument node, bool isMultiPart = true, bool isOwner = false) - { - if (!string.IsNullOrEmpty(node.Name) && (isOwner || (isMultiPart && IsTableAlias(node.Name)))) - return node.Name; - - return null; - } - - protected virtual string ParseGetMember(DynamicParser.Node.GetMember node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool decorate = true, bool isMultiPart = true) - { - if (node.Host is DynamicParser.Node.Argument && IsTableAlias(node.Name)) - { - decorate = false; - isMultiPart = false; - } - - // This hack allows to use argument as alias, but when it is not nesesary use other column. - // Let say we hace a table Users with alias usr, and we join to table with alias ua which also has a column Users - // This allow use of usr => usr.ua.Users to result in ua."Users" instead of "Users" or usr."ua"."Users", se tests for examples. - string parent = null; - if (node.Host != null) - { - if (isMultiPart && node.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, null)) - { - if (node.Host.Host != null && node.Host.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, node.Host.Host.Name)) - parent = string.Format("{0}.{1}", Parse(node.Host.Host, pars, isMultiPart: false), Parse(node.Host, pars, isMultiPart: false)); - else - parent = Parse(node.Host, pars, isMultiPart: false); - } - else if (isMultiPart) - parent = Parse(node.Host, pars, isMultiPart: isMultiPart); - } - - ////string parent = node.Host == null || !isMultiPart ? null : Parse(node.Host, pars, isMultiPart: !IsTable(node.Name, node.Host.Name)); - string name = parent == null ? - decorate ? Database.DecorateName(node.Name) : node.Name : - string.Format("{0}.{1}", parent, decorate ? Database.DecorateName(node.Name) : node.Name); - - columnSchema = GetColumnFromSchema(name); - - return name; - } - - protected virtual string ParseSetMember(DynamicParser.Node.SetMember node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool decorate = true, bool isMultiPart = true) - { - if (node.Host is DynamicParser.Node.Argument && IsTableAlias(node.Name)) - { - decorate = false; - isMultiPart = false; - } - - string parent = null; - if (node.Host != null) - { - if (isMultiPart && node.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, null)) - { - if (node.Host.Host != null && node.Host.Host is DynamicParser.Node.GetMember && IsTable(node.Name, node.Host.Name)) - parent = string.Format("{0}.{1}", Parse(node.Host.Host, pars, isMultiPart: false), Parse(node.Host, pars, isMultiPart: false)); - else - parent = Parse(node.Host, pars, isMultiPart: false); - } - else if (isMultiPart) - parent = Parse(node.Host, pars, isMultiPart: isMultiPart); - } - - ////string parent = node.Host == null || !isMultiPart ? null : Parse(node.Host, pars, isMultiPart: !IsTable(node.Name, node.Host.Name)); - string name = parent == null ? - decorate ? Database.DecorateName(node.Name) : node.Name : - string.Format("{0}.{1}", parent, decorate ? Database.DecorateName(node.Name) : node.Name); - - columnSchema = GetColumnFromSchema(name); - - string value = Parse(node.Value, ref columnSchema, pars, nulls: true); - return string.Format("{0} = ({1})", name, value); - } - - protected virtual string ParseUnary(DynamicParser.Node.Unary node, IDictionary pars = null) - { - switch (node.Operation) - { - // Artifacts from the DynamicParser class that are not usefull here... - case ExpressionType.IsFalse: - case ExpressionType.IsTrue: return Parse(node.Target, pars); - - // Unary supported operations... - case ExpressionType.Not: return string.Format("(NOT {0})", Parse(node.Target, pars)); - case ExpressionType.Negate: return string.Format("!({0})", Parse(node.Target, pars)); - } - - throw new ArgumentException("Not supported unary operation: " + node); - } - - protected virtual string ParseBinary(DynamicParser.Node.Binary node, IDictionary pars = null) - { - string op = string.Empty; - - switch (node.Operation) - { - // Arithmetic binary operations... - case ExpressionType.Add: op = "+"; break; - case ExpressionType.Subtract: op = "-"; break; - case ExpressionType.Multiply: op = "*"; break; - case ExpressionType.Divide: op = "/"; break; - case ExpressionType.Modulo: op = "%"; break; - case ExpressionType.Power: op = "^"; break; - - case ExpressionType.And: op = "AND"; break; - case ExpressionType.Or: op = "OR"; break; - - // Logical comparisons... - case ExpressionType.GreaterThan: op = ">"; break; - case ExpressionType.GreaterThanOrEqual: op = ">="; break; - case ExpressionType.LessThan: op = "<"; break; - case ExpressionType.LessThanOrEqual: op = "<="; break; - - // Comparisons against 'NULL' require the 'IS' or 'IS NOT' operator instead the numeric ones... - case ExpressionType.Equal: op = node.Right == null && !VirtualMode ? "IS" : "="; break; - case ExpressionType.NotEqual: op = node.Right == null && !VirtualMode ? "IS NOT" : "<>"; break; - - default: throw new ArgumentException("Not supported operator: '" + node.Operation); - } - - DynamicSchemaColumn? columnSchema = null; - string left = Parse(node.Left, ref columnSchema, pars); // Not nulls: left is assumed to be an object - string right = Parse(node.Right, ref columnSchema, pars, nulls: true); - return string.Format("({0} {1} {2})", left, op, right); - } - - protected virtual string ParseMethod(DynamicParser.Node.Method node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null) - { - string method = node.Name.ToUpper(); - string parent = node.Host == null ? null : Parse(node.Host, ref columnSchema, pars: pars); - string item = null; - - // Root-level methods... - if (node.Host == null) - { - switch (method) - { - case "NOT": - if (node.Arguments == null || node.Arguments.Length != 1) throw new ArgumentNullException("NOT method expects one argument: " + node.Arguments.Sketch()); - item = Parse(node.Arguments[0], ref columnSchema, pars: pars); - return string.Format("(NOT {0})", item); - } - } - - // Column-level methods... - if (node.Host != null) - { - switch (method) - { - case "BETWEEN": - { - if (node.Arguments == null || node.Arguments.Length == 0) - throw new ArgumentException("BETWEEN method expects at least one argument: " + node.Arguments.Sketch()); - - if (node.Arguments.Length > 2) - throw new ArgumentException("BETWEEN method expects at most two arguments: " + node.Arguments.Sketch()); - - object[] arguments = node.Arguments; - - if (arguments.Length == 1 && (arguments[0] is IEnumerable || arguments[0] is Array) && !(arguments[0] is byte[])) - { - IEnumerable vals = arguments[0] as IEnumerable; - - if (vals == null && arguments[0] is Array) - vals = ((Array)arguments[0]).Cast() as IEnumerable; - - if (vals != null) - arguments = vals.ToArray(); - else - throw new ArgumentException("BETWEEN method expects single argument to be enumerable of exactly two elements: " + node.Arguments.Sketch()); - } - - return string.Format("{0} BETWEEN {1} AND {2}", parent, Parse(arguments[0], ref columnSchema, pars: pars), Parse(arguments[1], ref columnSchema, pars: pars)); - } - - case "IN": - { - if (node.Arguments == null || node.Arguments.Length == 0) - throw new ArgumentException("IN method expects at least one argument: " + node.Arguments.Sketch()); - - bool firstParam = true; - StringBuilder sbin = new StringBuilder(); - foreach (object arg in node.Arguments) - { - if (!firstParam) - sbin.Append(", "); - - if ((arg is IEnumerable || arg is Array) && !(arg is byte[])) - { - IEnumerable vals = arg as IEnumerable; - - if (vals == null && arg is Array) - vals = ((Array)arg).Cast() as IEnumerable; - - if (vals != null) - foreach (object val in vals) - { - if (!firstParam) - sbin.Append(", "); - else - firstParam = false; - - sbin.Append(Parse(val, ref columnSchema, pars: pars)); - } - else - sbin.Append(Parse(arg, ref columnSchema, pars: pars)); - } - else - sbin.Append(Parse(arg, ref columnSchema, pars: pars)); - - firstParam = false; - } - - return string.Format("{0} IN({1})", parent, sbin.ToString()); - } - - case "NOTIN": - { - if (node.Arguments == null || node.Arguments.Length == 0) - throw new ArgumentException("IN method expects at least one argument: " + node.Arguments.Sketch()); - - bool firstParam = true; - StringBuilder sbin = new StringBuilder(); - foreach (object arg in node.Arguments) - { - if (!firstParam) - sbin.Append(", "); - - if ((arg is IEnumerable || arg is Array) && !(arg is byte[])) - { - IEnumerable vals = arg as IEnumerable; - - if (vals == null && arg is Array) - vals = ((Array)arg).Cast() as IEnumerable; - - if (vals != null) - foreach (object val in vals) - { - if (!firstParam) - sbin.Append(", "); - else - firstParam = false; - - sbin.Append(Parse(val, ref columnSchema, pars: pars)); - } - else - sbin.Append(Parse(arg, ref columnSchema, pars: pars)); - } - else - sbin.Append(Parse(arg, ref columnSchema, pars: pars)); - - firstParam = false; - } - - return string.Format("{0} NOT IN({1})", parent, sbin.ToString()); - } - - case "LIKE": - if (node.Arguments == null || node.Arguments.Length != 1) - throw new ArgumentException("LIKE method expects one argument: " + node.Arguments.Sketch()); - - return string.Format("{0} LIKE {1}", parent, Parse(node.Arguments[0], ref columnSchema, pars: pars)); - - case "NOTLIKE": - if (node.Arguments == null || node.Arguments.Length != 1) - throw new ArgumentException("NOT LIKE method expects one argument: " + node.Arguments.Sketch()); - - return string.Format("{0} NOT LIKE {1}", parent, Parse(node.Arguments[0], ref columnSchema, pars: pars)); - - case "AS": - if (node.Arguments == null || node.Arguments.Length != 1) - throw new ArgumentException("AS method expects one argument: " + node.Arguments.Sketch()); - - item = Parse(node.Arguments[0], pars: null, rawstr: true, isMultiPart: false); // pars=null to avoid to parameterize aliases - item = item.Validated("Alias"); // Intercepting null and empty aliases - return string.Format("{0} AS {1}", parent, item); - - case "NOLOCK": - if (!SupportNoLock) - return parent; - - if (node.Arguments != null && node.Arguments.Length > 1) - throw new ArgumentException("NOLOCK method expects no arguments."); - - return string.Format("{0} {1}", parent, "WITH(NOLOCK)"); - - case "COUNT": - if (node.Arguments != null && node.Arguments.Length > 1) - throw new ArgumentException("COUNT method expects one or none argument: " + node.Arguments.Sketch()); - - if (node.Arguments == null || node.Arguments.Length == 0) - return "COUNT(*)"; - - return string.Format("COUNT({0})", Parse(node.Arguments[0], ref columnSchema, pars: Parameters, nulls: true)); - - case "COUNT0": - if (node.Arguments != null && node.Arguments.Length > 0) - throw new ArgumentException("COUNT0 method doesn't expect arguments"); - - return "COUNT(0)"; - } - } - - // Default case: parsing the method's name along with its arguments... - method = parent == null ? node.Name : string.Format("{0}.{1}", parent, node.Name); - StringBuilder sb = new StringBuilder(); - sb.AppendFormat("{0}(", method); - - if (node.Arguments != null && node.Arguments.Length != 0) - { - bool first = true; - - foreach (object argument in node.Arguments) - { - if (!first) - sb.Append(", "); - else - first = false; - - sb.Append(Parse(argument, ref columnSchema, pars, nulls: true)); // We don't accept raw strings here!!! - } - } - - sb.Append(")"); - return sb.ToString(); - } - - protected virtual string ParseInvoke(DynamicParser.Node.Invoke node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null) - { - // This is used as an especial syntax to merely concatenate its arguments. It is used as a way to extend the supported syntax without the need of treating all the possible cases... - if (node.Arguments == null || node.Arguments.Length == 0) - return string.Empty; - - StringBuilder sb = new StringBuilder(); - foreach (object arg in node.Arguments) - { - if (arg is string) - { - sb.Append((string)arg); - - if (node.Arguments.Length == 1 && !columnSchema.HasValue) - columnSchema = GetColumnFromSchema((string)arg); - } - else - sb.Append(Parse(arg, ref columnSchema, pars, rawstr: true, nulls: true)); - } - - return sb.ToString(); - } - - protected virtual string ParseConvert(DynamicParser.Node.Convert node, IDictionary pars = null) - { - // The cast mechanism is left for the specific database implementation, that should override this method - // as needed... - string r = Parse(node.Target, pars); - return r; - } - - protected virtual string ParseConstant(object node, IDictionary pars = null, DynamicSchemaColumn? columnSchema = null) - { - if (node == null && !VirtualMode) - return ParseNull(); - - if (pars != null) - { - bool wellKnownName = VirtualMode && node is String && ((String)node).StartsWith("[$") && ((String)node).EndsWith("]") && ((String)node).Length > 4; - - // If we have a list of parameters to store it, let's parametrize it - Parameter par = new Parameter() - { - Name = wellKnownName ? ((String)node).Substring(2, ((String)node).Length - 3) : Guid.NewGuid().ToString(), - Value = wellKnownName ? null : node, - WellKnown = wellKnownName, - Virtual = VirtualMode, - Schema = columnSchema, - }; - - // If we are adding parameter we inform external sources about this. - if (OnCreateTemporaryParameter != null) - OnCreateTemporaryParameter.ForEach(x => x(par)); - - pars.Add(par.Name, par); - - return string.Format("[${0}]", par.Name); - } - - return node.ToString(); // Last resort case - } - - protected virtual string ParseNull() - { - return "NULL"; // Override if needed - } - - #endregion Parser - - #region Helpers - - internal bool IsTableAlias(string name) - { - DynamicQueryBuilder builder = this; - - while (builder != null) - { - if (builder.Tables.Any(t => t.Alias == name)) - return true; - - builder = builder._parent; - } - - return false; - } - - internal bool IsTable(string name, string owner) - { - DynamicQueryBuilder builder = this; - - while (builder != null) - { - if ((string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower())) || - (!string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower() && - !string.IsNullOrEmpty(t.Owner) && t.Owner.ToLower() == owner.ToLower()))) - return true; - - builder = builder._parent; - } - - return false; - } - - internal string FixObjectName(string main, bool onlyColumn = false) - { - if (main.IndexOf("(") > 0 && main.IndexOf(")") > 0) - return main.FillStringWithVariables(f => string.Format("({0})", FixObjectNamePrivate(f, onlyColumn)), "(", ")"); - else - return FixObjectNamePrivate(main, onlyColumn); - } - - private string FixObjectNamePrivate(string f, bool onlyColumn = false) - { - IEnumerable objects = f.Split('.') - .Select(x => Database.StripName(x)); - - if (onlyColumn || objects.Count() == 1) - f = Database.DecorateName(objects.Last()); - else if (!IsTableAlias(objects.First())) - f = string.Join(".", objects.Select(o => Database.DecorateName(o))); - else - f = string.Format("{0}.{1}", objects.First(), string.Join(".", objects.Skip(1).Select(o => Database.DecorateName(o)))); - - return f; - } - - internal DynamicSchemaColumn? GetColumnFromSchema(string colName, DynamicTypeMap mapper = null, string table = null) - { - // This is tricky and will not always work unfortunetly. - ////if (colName.ContainsAny(StringExtensions.InvalidMultipartMemberChars)) - //// return null; - - // First we need to get real column name and it's owner if exist. - string[] parts = colName.Split('.'); - for (int i = 0; i < parts.Length; i++) - parts[i] = Database.StripName(parts[i]); - - string columnName = parts.Last(); - - // Get table name from mapper - string tableName = table; - - if (string.IsNullOrEmpty(tableName)) - { - tableName = (mapper != null && mapper.Table != null) ? mapper.Table.Name : string.Empty; - - if (parts.Length > 1 && string.IsNullOrEmpty(tableName)) - { - // OK, we have a multi part identifier, that's good, we can get table name - tableName = string.Join(".", parts.Take(parts.Length - 1)); - } - } - - // Try to get table info from cache - ITableInfo tableInfo = !string.IsNullOrEmpty(tableName) ? - Tables.FirstOrDefault(x => !string.IsNullOrEmpty(x.Alias) && x.Alias.ToLower() == tableName) ?? - Tables.FirstOrDefault(x => x.Name.ToLower() == tableName.ToLower()) ?? Tables.FirstOrDefault() : - this is DynamicModifyBuilder || Tables.Count == 1 ? Tables.FirstOrDefault() : null; - - // Try to get column from schema - if (tableInfo != null && tableInfo.Schema != null) - return tableInfo.Schema.TryGetNullable(columnName.ToLower()); - - // Well, we failed to find a column - return null; - } - - #endregion Helpers - - #region IExtendedDisposable - - /// Gets a value indicating whether this instance is disposed. - public bool IsDisposed { get; private set; } - - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public virtual void Dispose() - { - if (IsDisposed) - return; - - IsDisposed = true; - - if (Database != null) - Database.RemoveFromCache(this); - - if (Parameters != null) - { - foreach (KeyValuePair p in Parameters) - p.Value.Dispose(); - - Parameters.Clear(); - Parameters = null; - } - - if (Tables != null) - { - foreach (ITableInfo t in Tables) - if (t != null) - t.Dispose(); - - Tables.Clear(); - Tables = null; - } - - WhereCondition = null; - Database = null; - } - - #endregion IExtendedDisposable - } - - /// Implementation of dynamic insert query builder. - internal class DynamicInsertQueryBuilder : DynamicModifyBuilder, IDynamicInsertQueryBuilder - { - private string _columns; - private string _values; - - /// - /// Initializes a new instance of the class. - /// - /// The database. - internal DynamicInsertQueryBuilder(DynamicDatabase db) - : base(db) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The database. - /// Name of the table. - public DynamicInsertQueryBuilder(DynamicDatabase db, string tableName) - : base(db, tableName) - { - } - - /// Generates the text this command will execute against the underlying database. - /// The text to execute against the underlying database. - /// This method must be override by derived classes. - public override string CommandText() - { - ITableInfo info = Tables.Single(); - return string.Format("INSERT INTO {0}{1} ({2}) VALUES ({3})", - string.IsNullOrEmpty(info.Owner) ? string.Empty : string.Format("{0}.", Database.DecorateName(info.Owner)), - Database.DecorateName(info.Name), _columns, _values); - } - - #region Insert - - /// - /// Specifies the columns to insert using the dynamic lambda expressions given. Each expression correspond to one - /// column, and can: - /// - Resolve to a string, in this case a '=' must appear in the string. - /// - Resolve to a expression with the form: 'x => x.Column = Value'. - /// - /// The specifications. - /// The specifications. - /// This instance to permit chaining. - public virtual IDynamicInsertQueryBuilder Values(Func fn, params Func[] func) - { - if (fn == null) - throw new ArgumentNullException("Array of specifications cannot be null."); - - int index = InsertFunc(-1, fn); - - if (func != null) - foreach (Func f in func) - index = InsertFunc(index, f); - - return this; - } - - private int InsertFunc(int index, Func f) - { - index++; - - if (f == null) - throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); - - using (DynamicParser parser = DynamicParser.Parse(f)) - { - object result = parser.Result; - if (result == null) - throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); - - string main = null; - string value = null; - string str = null; - - // When 'x => x.Table.Column = value' or 'x => x.Column = value'... - if (result is DynamicParser.Node.SetMember) - { - DynamicParser.Node.SetMember node = (DynamicParser.Node.SetMember)result; - - DynamicSchemaColumn? col = GetColumnFromSchema(node.Name); - main = Database.DecorateName(node.Name); - value = Parse(node.Value, ref col, pars: Parameters, nulls: true); - + 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 index; + + return this; } - else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType) + IDictionary dict = o.ToDictionary(); + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(o.GetType()); + + if (mapper != null) { - Insert(result); - return index; + foreach (KeyValuePair con in dict) + if (!mapper.Ignored.Contains(con.Key)) + { + string colName = mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key; + DynamicPropertyInvoker propMap = mapper.ColumnsMap.TryGetValue(colName.ToLower()); + + if (propMap == null || propMap.Column == null || !propMap.Column.IsNoInsert) + Insert(colName, con.Value); // TODO: This probably should get value from mapper + } } - - // 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); - + else + foreach (KeyValuePair con in dict) + Insert(con.Key, con.Value); + return this; } - - IDictionary dict = o.ToDictionary(); - DynamicTypeMap mapper = DynamicMapperCache.GetMapper(o.GetType()); - - if (mapper != null) + #endregion Insert + + #region IExtendedDisposable + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public override void Dispose() { - foreach (KeyValuePair con in dict) - if (!mapper.Ignored.Contains(con.Key)) + base.Dispose(); + + _columns = _values = null; + } + #endregion IExtendedDisposable + } + /// Base query builder for insert/update/delete statements. + internal abstract class DynamicModifyBuilder : DynamicQueryBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// The database. + public DynamicModifyBuilder(DynamicDatabase db) + : base(db) + { + VirtualMode = false; + } + /// + /// Initializes a new instance of the class. + /// + /// The database. + /// Name of the table. + public DynamicModifyBuilder(DynamicDatabase db, string tableName) + : this(db) + { + VirtualMode = false; + this.Table(tableName); + } + /// Execute this builder. + /// Result of an execution.. + public virtual int Execute() + { + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(this) + .ExecuteNonQuery(); + } + } + } + /// Implementation of dynamic query builder base interface. + internal abstract class DynamicQueryBuilder : IDynamicQueryBuilder + { + /// Empty interface to allow where query builder implementation use universal approach. + internal interface IQueryWithWhere + { + /// Gets or sets the where condition. + string WhereCondition { get; set; } + + /// Gets or sets the amount of not closed brackets in where statement. + int WhereOpenBracketsCount { get; set; } + } + /// Empty interface to allow having query builder implementation use universal approach. + internal interface IQueryWithHaving + { + /// Gets or sets the having condition. + string HavingCondition { get; set; } + + /// Gets or sets the amount of not closed brackets in having statement. + int HavingOpenBracketsCount { get; set; } + } + private DynamicQueryBuilder _parent = null; + + #region TableInfo + + /// Table information. + internal class TableInfo : ITableInfo + { + /// + /// Initializes a new instance of the class. + /// + internal TableInfo() + { + IsDisposed = false; + } + /// + /// Initializes a new instance of the class. + /// + /// The database. + /// The name of table. + /// The table alias. + /// The table owner. + /// The table should be used with nolock. + public TableInfo(DynamicDatabase db, string name, string alias = null, string owner = null, bool nolock = false) + : this() + { + Name = name; + Alias = alias; + Owner = owner; + NoLock = nolock; + + if (!name.ContainsAny(StringExtensions.InvalidMemberChars)) + Schema = db.GetSchema(name, owner: owner); + } + /// + /// Initializes a new instance of the class. + /// + /// The database. + /// The type which can be mapped to database. + /// The table alias. + /// The table owner. + /// The table should be used with nolock. + public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null, bool nolock = false) + : this() + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + Name = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + + Owner = (mapper.Table != null) ? mapper.Table.Owner : owner; + Alias = alias; + NoLock = nolock; + + Schema = db.GetSchema(type); + } + /// Gets or sets table owner name. + public string Owner { get; internal set; } + + /// Gets or sets table name. + public string Name { get; internal set; } + + /// Gets or sets table alias. + public string Alias { get; internal set; } + + /// Gets or sets table alias. + public bool NoLock { get; internal set; } + + /// Gets or sets table schema. + public Dictionary Schema { get; internal set; } + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public virtual void Dispose() + { + IsDisposed = true; + + ////if (Schema != null) + //// Schema.Clear(); + + Owner = Name = Alias = null; + Schema = null; + } + } + /// Generic based table information. + /// Type of class that is represented in database. + internal class TableInfo : TableInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The database. + /// The table alias. + /// The table owner. + public TableInfo(DynamicDatabase db, string alias = null, string owner = null) + : base(db, typeof(T), alias, owner) + { + } + } + #endregion TableInfo + + #region Parameter + + /// Interface describing parameter info. + internal class Parameter : IParameter + { + /// Initializes a new instance of the + /// class. + public Parameter() + { + IsDisposed = false; + } + /// Gets or sets the parameter position in command. + /// Available after filling the command. + public int Ordinal { get; internal set; } + + /// Gets or sets the parameter temporary name. + public string Name { get; internal set; } + + /// Gets or sets the parameter value. + public object Value { get; set; } + + /// Gets or sets a value indicating whether name of temporary parameter is well known. + public bool WellKnown { get; set; } + + /// Gets or sets a value indicating whether this is virtual. + public bool Virtual { get; set; } + + /// Gets or sets the parameter schema information. + public DynamicSchemaColumn? Schema { get; set; } + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public virtual void Dispose() + { + IsDisposed = true; + + Name = null; + Schema = null; + } + } + #endregion Parameter + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The database. + public DynamicQueryBuilder(DynamicDatabase db) + { + IsDisposed = false; + VirtualMode = false; + Tables = new List(); + Parameters = new Dictionary(); + OnCreateTemporaryParameter = new List>(); + OnCreateParameter = new List>(); + + WhereCondition = null; + WhereOpenBracketsCount = 0; + + Database = db; + if (Database != null) + Database.AddToCache(this); + + SupportSchema = (db.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema; + SupportNoLock = (db.Options & DynamicDatabaseOptions.SupportNoLock) == DynamicDatabaseOptions.SupportNoLock; + } + /// Initializes a new instance of the class. + /// The database. + /// The parent query. + internal DynamicQueryBuilder(DynamicDatabase db, DynamicQueryBuilder parent) + : this(db) + { + _parent = parent; + } + #endregion Constructor + + #region IQueryWithWhere + + /// Gets or sets the where condition. + public string WhereCondition { get; set; } + + /// Gets or sets the amount of not closed brackets in where statement. + public int WhereOpenBracketsCount { get; set; } + + #endregion IQueryWithWhere + + #region IDynamicQueryBuilder + + /// Gets instance. + public DynamicDatabase Database { get; private set; } + + /// Gets the tables used in this builder. + public IList Tables { get; private set; } + + /// Gets the tables used in this builder. + public IDictionary Parameters { get; private set; } + + /// Gets or sets a value indicating whether add virtual parameters. + public bool VirtualMode { get; set; } + + /// Gets or sets the on create temporary parameter actions. + /// This is exposed to allow setting schema of column. + public List> OnCreateTemporaryParameter { get; set; } + + /// Gets or sets the on create real parameter actions. + /// This is exposed to allow modification of parameter. + public List> OnCreateParameter { get; set; } + + /// Gets a value indicating whether database supports standard schema. + public bool SupportSchema { get; private set; } + + /// Gets a value indicating whether database supports with no lock syntax. + public bool SupportNoLock { get; private set; } + + /// + /// Generates the text this command will execute against the underlying database. + /// + /// The text to execute against the underlying database. + /// This method must be override by derived classes. + public abstract string CommandText(); + + /// Fill command with query. + /// Command to fill. + /// Filled instance of . + public virtual IDbCommand FillCommand(IDbCommand command) + { + // End not ended where statement + if (this is IQueryWithWhere) + { + while (WhereOpenBracketsCount > 0) { - string colName = mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key; - DynamicPropertyInvoker propMap = mapper.ColumnsMap.TryGetValue(colName.ToLower()); - - if (propMap == null || propMap.Column == null || !propMap.Column.IsNoInsert) - Insert(colName, con.Value); // TODO: This probably should get value from mapper + 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); + })); } - 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()) + #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) { - return cmd - .SetCommand(this) - .ExecuteNonQuery(); + DynamicSchemaColumn? c = null; + + return Parse(node, ref c, pars, rawstr, nulls, decorate, isMultiPart); } + /// Parses the arbitrary object given and translates it into a string with the appropriate + /// syntax for the database this parser is specific to. + /// The object to parse and translate. It can be any arbitrary object, including null values (if + /// permitted) and dynamic lambda expressions. + /// This parameter is used to determine type of parameter used in query. + /// If not null, the parameters' list where to store the parameters extracted by the parsing. + /// If true, literal (raw) string are allowed. If false and the node is a literal then, as a + /// security measure, an exception is thrown. + /// True to accept null values and translate them into the appropriate syntax accepted by the + /// database. If false and the value is null, then an exception is thrown. + /// If set to true decorate element. + /// If set parse argument as alias. This is workaround for AS method. + /// A string containing the result of the parsing, along with the parameters extracted in the + /// instance if such is given. + /// Null nodes are not accepted. + internal virtual string Parse(object node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool rawstr = false, bool nulls = false, bool decorate = true, bool isMultiPart = true) + { + // Null nodes are accepted or not depending upon the "nulls" flag... + if (node == null) + { + if (!nulls) + throw new ArgumentNullException("node", "Null nodes are not accepted."); + + return Dispatch(node, ref columnSchema, pars, decorate); + } + // Nodes that are strings are parametrized or not depending the "rawstr" flag... + if (node is string) + { + if (rawstr) return (string)node; + else return Dispatch(node, ref columnSchema, pars, decorate); + } + // If node is a delegate, parse it to create the logical tree... + if (node is Delegate) + { + using (DynamicParser p = DynamicParser.Parse((Delegate)node)) + { + node = p.Result; + + return Parse(node, ref columnSchema, pars, rawstr, decorate: decorate); // Intercept containers as in (x => "string") + } + } + return Dispatch(node, ref columnSchema, pars, decorate, isMultiPart); + } + private string Dispatch(object node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool decorate = true, bool isMultiPart = true) + { + if (node != null) + { + if (node is DynamicQueryBuilder) return ParseCommand((DynamicQueryBuilder)node, pars); + else if (node is DynamicParser.Node.Argument) return ParseArgument((DynamicParser.Node.Argument)node, isMultiPart); + else if (node is DynamicParser.Node.GetMember) return ParseGetMember((DynamicParser.Node.GetMember)node, ref columnSchema, pars, decorate, isMultiPart); + else if (node is DynamicParser.Node.SetMember) return ParseSetMember((DynamicParser.Node.SetMember)node, ref columnSchema, pars, decorate, isMultiPart); + else if (node is DynamicParser.Node.Unary) return ParseUnary((DynamicParser.Node.Unary)node, pars); + else if (node is DynamicParser.Node.Binary) return ParseBinary((DynamicParser.Node.Binary)node, pars); + else if (node is DynamicParser.Node.Method) return ParseMethod((DynamicParser.Node.Method)node, ref columnSchema, pars); + else if (node is DynamicParser.Node.Invoke) return ParseInvoke((DynamicParser.Node.Invoke)node, ref columnSchema, pars); + else if (node is DynamicParser.Node.Convert) return ParseConvert((DynamicParser.Node.Convert)node, pars); + } + // All other cases are considered constant parameters... + return ParseConstant(node, pars, columnSchema); + } + internal virtual string ParseCommand(DynamicQueryBuilder node, IDictionary pars = null) + { + // Getting the command's text... + string str = node.CommandText(); // Avoiding spurious "OUTPUT XXX" statements + + // If there are parameters to transform, but cannot store them, it is an error + if (node.Parameters.Count != 0 && pars == null) + return string.Format("({0})", str); + + // TODO: Make special condiion + ////throw new InvalidOperationException(string.Format("The parameters in this command '{0}' cannot be added to a null collection.", node.Parameters)); + + // Copy parameters to new comand + foreach (KeyValuePair parameter in node.Parameters) + pars.Add(parameter.Key, parameter.Value); + + return string.Format("({0})", str); + } + protected virtual string ParseArgument(DynamicParser.Node.Argument node, bool isMultiPart = true, bool isOwner = false) + { + if (!string.IsNullOrEmpty(node.Name) && (isOwner || (isMultiPart && IsTableAlias(node.Name)))) + return node.Name; + + return null; + } + protected virtual string ParseGetMember(DynamicParser.Node.GetMember node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool decorate = true, bool isMultiPart = true) + { + if (node.Host is DynamicParser.Node.Argument && IsTableAlias(node.Name)) + { + decorate = false; + isMultiPart = false; + } + // This hack allows to use argument as alias, but when it is not nesesary use other column. + // Let say we hace a table Users with alias usr, and we join to table with alias ua which also has a column Users + // This allow use of usr => usr.ua.Users to result in ua."Users" instead of "Users" or usr."ua"."Users", se tests for examples. + string parent = null; + if (node.Host != null) + { + if (isMultiPart && node.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, null)) + { + if (node.Host.Host != null && node.Host.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, node.Host.Host.Name)) + parent = string.Format("{0}.{1}", Parse(node.Host.Host, pars, isMultiPart: false), Parse(node.Host, pars, isMultiPart: false)); + else + parent = Parse(node.Host, pars, isMultiPart: false); + } + else if (isMultiPart) + parent = Parse(node.Host, pars, isMultiPart: isMultiPart); + } + ////string parent = node.Host == null || !isMultiPart ? null : Parse(node.Host, pars, isMultiPart: !IsTable(node.Name, node.Host.Name)); + string name = parent == null ? + decorate ? Database.DecorateName(node.Name) : node.Name : + string.Format("{0}.{1}", parent, decorate ? Database.DecorateName(node.Name) : node.Name); + + columnSchema = GetColumnFromSchema(name); + + return name; + } + protected virtual string ParseSetMember(DynamicParser.Node.SetMember node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null, bool decorate = true, bool isMultiPart = true) + { + if (node.Host is DynamicParser.Node.Argument && IsTableAlias(node.Name)) + { + decorate = false; + isMultiPart = false; + } + string parent = null; + if (node.Host != null) + { + if (isMultiPart && node.Host is DynamicParser.Node.GetMember && IsTable(node.Host.Name, null)) + { + if (node.Host.Host != null && node.Host.Host is DynamicParser.Node.GetMember && IsTable(node.Name, node.Host.Name)) + parent = string.Format("{0}.{1}", Parse(node.Host.Host, pars, isMultiPart: false), Parse(node.Host, pars, isMultiPart: false)); + else + parent = Parse(node.Host, pars, isMultiPart: false); + } + else if (isMultiPart) + parent = Parse(node.Host, pars, isMultiPart: isMultiPart); + } + ////string parent = node.Host == null || !isMultiPart ? null : Parse(node.Host, pars, isMultiPart: !IsTable(node.Name, node.Host.Name)); + string name = parent == null ? + decorate ? Database.DecorateName(node.Name) : node.Name : + string.Format("{0}.{1}", parent, decorate ? Database.DecorateName(node.Name) : node.Name); + + columnSchema = GetColumnFromSchema(name); + + string value = Parse(node.Value, ref columnSchema, pars, nulls: true); + return string.Format("{0} = ({1})", name, value); + } + protected virtual string ParseUnary(DynamicParser.Node.Unary node, IDictionary pars = null) + { + switch (node.Operation) + { + // Artifacts from the DynamicParser class that are not usefull here... + case ExpressionType.IsFalse: + case ExpressionType.IsTrue: return Parse(node.Target, pars); + + // Unary supported operations... + case ExpressionType.Not: return string.Format("(NOT {0})", Parse(node.Target, pars)); + case ExpressionType.Negate: return string.Format("!({0})", Parse(node.Target, pars)); + } + throw new ArgumentException("Not supported unary operation: " + node); + } + protected virtual string ParseBinary(DynamicParser.Node.Binary node, IDictionary pars = null) + { + string op = string.Empty; + + switch (node.Operation) + { + // Arithmetic binary operations... + case ExpressionType.Add: op = "+"; break; + case ExpressionType.Subtract: op = "-"; break; + case ExpressionType.Multiply: op = "*"; break; + case ExpressionType.Divide: op = "/"; break; + case ExpressionType.Modulo: op = "%"; break; + case ExpressionType.Power: op = "^"; break; + + case ExpressionType.And: op = "AND"; break; + case ExpressionType.Or: op = "OR"; break; + + // Logical comparisons... + case ExpressionType.GreaterThan: op = ">"; break; + case ExpressionType.GreaterThanOrEqual: op = ">="; break; + case ExpressionType.LessThan: op = "<"; break; + case ExpressionType.LessThanOrEqual: op = "<="; break; + + // Comparisons against 'NULL' require the 'IS' or 'IS NOT' operator instead the numeric ones... + case ExpressionType.Equal: op = node.Right == null && !VirtualMode ? "IS" : "="; break; + case ExpressionType.NotEqual: op = node.Right == null && !VirtualMode ? "IS NOT" : "<>"; break; + + default: throw new ArgumentException("Not supported operator: '" + node.Operation); + } + DynamicSchemaColumn? columnSchema = null; + string left = Parse(node.Left, ref columnSchema, pars); // Not nulls: left is assumed to be an object + string right = Parse(node.Right, ref columnSchema, pars, nulls: true); + return string.Format("({0} {1} {2})", left, op, right); + } + protected virtual string ParseMethod(DynamicParser.Node.Method node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null) + { + string method = node.Name.ToUpper(); + string parent = node.Host == null ? null : Parse(node.Host, ref columnSchema, pars: pars); + string item = null; + + // Root-level methods... + if (node.Host == null) + { + switch (method) + { + case "NOT": + if (node.Arguments == null || node.Arguments.Length != 1) throw new ArgumentNullException("NOT method expects one argument: " + node.Arguments.Sketch()); + item = Parse(node.Arguments[0], ref columnSchema, pars: pars); + return string.Format("(NOT {0})", item); + } + } + // Column-level methods... + if (node.Host != null) + { + switch (method) + { + case "BETWEEN": + { + if (node.Arguments == null || node.Arguments.Length == 0) + throw new ArgumentException("BETWEEN method expects at least one argument: " + node.Arguments.Sketch()); + + if (node.Arguments.Length > 2) + throw new ArgumentException("BETWEEN method expects at most two arguments: " + node.Arguments.Sketch()); + + object[] arguments = node.Arguments; + + if (arguments.Length == 1 && (arguments[0] is IEnumerable || arguments[0] is Array) && !(arguments[0] is byte[])) + { + IEnumerable vals = arguments[0] as IEnumerable; + + if (vals == null && arguments[0] is Array) + vals = ((Array)arguments[0]).Cast() as IEnumerable; + + if (vals != null) + arguments = vals.ToArray(); + else + throw new ArgumentException("BETWEEN method expects single argument to be enumerable of exactly two elements: " + node.Arguments.Sketch()); + } + return string.Format("{0} BETWEEN {1} AND {2}", parent, Parse(arguments[0], ref columnSchema, pars: pars), Parse(arguments[1], ref columnSchema, pars: pars)); + } + case "IN": + { + if (node.Arguments == null || node.Arguments.Length == 0) + throw new ArgumentException("IN method expects at least one argument: " + node.Arguments.Sketch()); + + bool firstParam = true; + StringBuilder sbin = new StringBuilder(); + foreach (object arg in node.Arguments) + { + if (!firstParam) + sbin.Append(", "); + + if ((arg is IEnumerable || arg is Array) && !(arg is byte[])) + { + IEnumerable vals = arg as IEnumerable; + + if (vals == null && arg is Array) + vals = ((Array)arg).Cast() as IEnumerable; + + if (vals != null) + foreach (object val in vals) + { + if (!firstParam) + sbin.Append(", "); + else + firstParam = false; + + sbin.Append(Parse(val, ref columnSchema, pars: pars)); + } + else + sbin.Append(Parse(arg, ref columnSchema, pars: pars)); + } + else + sbin.Append(Parse(arg, ref columnSchema, pars: pars)); + + firstParam = false; + } + return string.Format("{0} IN({1})", parent, sbin.ToString()); + } + case "NOTIN": + { + if (node.Arguments == null || node.Arguments.Length == 0) + throw new ArgumentException("IN method expects at least one argument: " + node.Arguments.Sketch()); + + bool firstParam = true; + StringBuilder sbin = new StringBuilder(); + foreach (object arg in node.Arguments) + { + if (!firstParam) + sbin.Append(", "); + + if ((arg is IEnumerable || arg is Array) && !(arg is byte[])) + { + IEnumerable vals = arg as IEnumerable; + + if (vals == null && arg is Array) + vals = ((Array)arg).Cast() as IEnumerable; + + if (vals != null) + foreach (object val in vals) + { + if (!firstParam) + sbin.Append(", "); + else + firstParam = false; + + sbin.Append(Parse(val, ref columnSchema, pars: pars)); + } + else + sbin.Append(Parse(arg, ref columnSchema, pars: pars)); + } + else + sbin.Append(Parse(arg, ref columnSchema, pars: pars)); + + firstParam = false; + } + return string.Format("{0} NOT IN({1})", parent, sbin.ToString()); + } + case "LIKE": + if (node.Arguments == null || node.Arguments.Length != 1) + throw new ArgumentException("LIKE method expects one argument: " + node.Arguments.Sketch()); + + return string.Format("{0} LIKE {1}", parent, Parse(node.Arguments[0], ref columnSchema, pars: pars)); + + case "NOTLIKE": + if (node.Arguments == null || node.Arguments.Length != 1) + throw new ArgumentException("NOT LIKE method expects one argument: " + node.Arguments.Sketch()); + + return string.Format("{0} NOT LIKE {1}", parent, Parse(node.Arguments[0], ref columnSchema, pars: pars)); + + case "AS": + if (node.Arguments == null || node.Arguments.Length != 1) + throw new ArgumentException("AS method expects one argument: " + node.Arguments.Sketch()); + + item = Parse(node.Arguments[0], pars: null, rawstr: true, isMultiPart: false); // pars=null to avoid to parameterize aliases + item = item.Validated("Alias"); // Intercepting null and empty aliases + return string.Format("{0} AS {1}", parent, item); + + case "NOLOCK": + if (!SupportNoLock) + return parent; + + if (node.Arguments != null && node.Arguments.Length > 1) + throw new ArgumentException("NOLOCK method expects no arguments."); + + return string.Format("{0} {1}", parent, "WITH(NOLOCK)"); + + case "COUNT": + if (node.Arguments != null && node.Arguments.Length > 1) + throw new ArgumentException("COUNT method expects one or none argument: " + node.Arguments.Sketch()); + + if (node.Arguments == null || node.Arguments.Length == 0) + return "COUNT(*)"; + + return string.Format("COUNT({0})", Parse(node.Arguments[0], ref columnSchema, pars: Parameters, nulls: true)); + + case "COUNT0": + if (node.Arguments != null && node.Arguments.Length > 0) + throw new ArgumentException("COUNT0 method doesn't expect arguments"); + + return "COUNT(0)"; + } + } + // Default case: parsing the method's name along with its arguments... + method = parent == null ? node.Name : string.Format("{0}.{1}", parent, node.Name); + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("{0}(", method); + + if (node.Arguments != null && node.Arguments.Length != 0) + { + bool first = true; + + foreach (object argument in node.Arguments) + { + if (!first) + sb.Append(", "); + else + first = false; + + sb.Append(Parse(argument, ref columnSchema, pars, nulls: true)); // We don't accept raw strings here!!! + } + } + sb.Append(")"); + return sb.ToString(); + } + protected virtual string ParseInvoke(DynamicParser.Node.Invoke node, ref DynamicSchemaColumn? columnSchema, IDictionary pars = null) + { + // This is used as an especial syntax to merely concatenate its arguments. It is used as a way to extend the supported syntax without the need of treating all the possible cases... + if (node.Arguments == null || node.Arguments.Length == 0) + return string.Empty; + + StringBuilder sb = new StringBuilder(); + foreach (object arg in node.Arguments) + { + if (arg is string) + { + sb.Append((string)arg); + + if (node.Arguments.Length == 1 && !columnSchema.HasValue) + columnSchema = GetColumnFromSchema((string)arg); + } + else + sb.Append(Parse(arg, ref columnSchema, pars, rawstr: true, nulls: true)); + } + return sb.ToString(); + } + protected virtual string ParseConvert(DynamicParser.Node.Convert node, IDictionary pars = null) + { + // The cast mechanism is left for the specific database implementation, that should override this method + // as needed... + string r = Parse(node.Target, pars); + return r; + } + protected virtual string ParseConstant(object node, IDictionary pars = null, DynamicSchemaColumn? columnSchema = null) + { + if (node == null && !VirtualMode) + return ParseNull(); + + if (pars != null) + { + bool wellKnownName = VirtualMode && node is String && ((String)node).StartsWith("[$") && ((String)node).EndsWith("]") && ((String)node).Length > 4; + + // If we have a list of parameters to store it, let's parametrize it + Parameter par = new Parameter() + { + Name = wellKnownName ? ((String)node).Substring(2, ((String)node).Length - 3) : Guid.NewGuid().ToString(), + Value = wellKnownName ? null : node, + WellKnown = wellKnownName, + Virtual = VirtualMode, + Schema = columnSchema, + }; + + // If we are adding parameter we inform external sources about this. + if (OnCreateTemporaryParameter != null) + OnCreateTemporaryParameter.ForEach(x => x(par)); + + pars.Add(par.Name, par); + + return string.Format("[${0}]", par.Name); + } + return node.ToString(); // Last resort case + } + protected virtual string ParseNull() + { + return "NULL"; // Override if needed + } + #endregion Parser + + #region Helpers + + internal bool IsTableAlias(string name) + { + DynamicQueryBuilder builder = this; + + while (builder != null) + { + if (builder.Tables.Any(t => t.Alias == name)) + return true; + + builder = builder._parent; + } + return false; + } + internal bool IsTable(string name, string owner) + { + DynamicQueryBuilder builder = this; + + while (builder != null) + { + if ((string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower())) || + (!string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower() && + !string.IsNullOrEmpty(t.Owner) && t.Owner.ToLower() == owner.ToLower()))) + return true; + + builder = builder._parent; + } + return false; + } + internal string FixObjectName(string main, bool onlyColumn = false) + { + if (main.IndexOf("(") > 0 && main.IndexOf(")") > 0) + return main.FillStringWithVariables(f => string.Format("({0})", FixObjectNamePrivate(f, onlyColumn)), "(", ")"); + else + return FixObjectNamePrivate(main, onlyColumn); + } + private string FixObjectNamePrivate(string f, bool onlyColumn = false) + { + IEnumerable objects = f.Split('.') + .Select(x => Database.StripName(x)); + + if (onlyColumn || objects.Count() == 1) + f = Database.DecorateName(objects.Last()); + else if (!IsTableAlias(objects.First())) + f = string.Join(".", objects.Select(o => Database.DecorateName(o))); + else + f = string.Format("{0}.{1}", objects.First(), string.Join(".", objects.Skip(1).Select(o => Database.DecorateName(o)))); + + return f; + } + internal DynamicSchemaColumn? GetColumnFromSchema(string colName, DynamicTypeMap mapper = null, string table = null) + { + // This is tricky and will not always work unfortunetly. + ////if (colName.ContainsAny(StringExtensions.InvalidMultipartMemberChars)) + //// return null; + + // First we need to get real column name and it's owner if exist. + string[] parts = colName.Split('.'); + for (int i = 0; i < parts.Length; i++) + parts[i] = Database.StripName(parts[i]); + + string columnName = parts.Last(); + + // Get table name from mapper + string tableName = table; + + if (string.IsNullOrEmpty(tableName)) + { + tableName = (mapper != null && mapper.Table != null) ? mapper.Table.Name : string.Empty; + + if (parts.Length > 1 && string.IsNullOrEmpty(tableName)) + { + // OK, we have a multi part identifier, that's good, we can get table name + tableName = string.Join(".", parts.Take(parts.Length - 1)); + } + } + // Try to get table info from cache + ITableInfo tableInfo = !string.IsNullOrEmpty(tableName) ? + Tables.FirstOrDefault(x => !string.IsNullOrEmpty(x.Alias) && x.Alias.ToLower() == tableName) ?? + Tables.FirstOrDefault(x => x.Name.ToLower() == tableName.ToLower()) ?? Tables.FirstOrDefault() : + this is DynamicModifyBuilder || Tables.Count == 1 ? Tables.FirstOrDefault() : null; + + // Try to get column from schema + if (tableInfo != null && tableInfo.Schema != null) + return tableInfo.Schema.TryGetNullable(columnName.ToLower()); + + // Well, we failed to find a column + return null; + } + #endregion Helpers + + #region IExtendedDisposable + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public virtual void Dispose() + { + if (IsDisposed) + return; + + IsDisposed = true; + + if (Database != null) + Database.RemoveFromCache(this); + + if (Parameters != null) + { + foreach (KeyValuePair p in Parameters) + p.Value.Dispose(); + + Parameters.Clear(); + Parameters = null; + } + if (Tables != null) + { + foreach (ITableInfo t in Tables) + if (t != null) + t.Dispose(); + + Tables.Clear(); + Tables = null; + } + WhereCondition = null; + Database = null; + } + #endregion IExtendedDisposable } - } - - /// Implementation of dynamic 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) + /// Implementation of dynamic select query builder. + internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere, DynamicQueryBuilder.IQueryWithHaving { + private int? _limit = null; + private int? _offset = null; + private bool _distinct = false; + + private string _select; + private string _from; + private string _join; + private string _groupby; + private string _orderby; + + #region IQueryWithHaving + + /// Gets or sets the having condition. + public string HavingCondition { get; set; } + + /// Gets or sets the amount of not closed brackets in having statement. + public int HavingOpenBracketsCount { get; set; } + + #endregion IQueryWithHaving + + /// + /// Gets a value indicating whether this instance has select columns. + /// + public bool HasSelectColumns { get { return !string.IsNullOrEmpty(_select); } } + + /// + /// Initializes a new instance of the class. + /// + /// The database. + public DynamicSelectQueryBuilder(DynamicDatabase db) + : base(db) + { + } + /// + /// Initializes a new instance of the class. + /// + /// The database. + /// The parent query. + internal DynamicSelectQueryBuilder(DynamicDatabase db, DynamicQueryBuilder parent) + : base(db, parent) + { + } + /// Generates the text this command will execute against the underlying database. + /// The text to execute against the underlying database. + public override string CommandText() + { + bool lused = false; + bool oused = false; + + StringBuilder sb = new StringBuilder("SELECT"); + if (_distinct) sb.AppendFormat(" DISTINCT"); + + if (_limit.HasValue) + { + if ((Database.Options & DynamicDatabaseOptions.SupportTop) == DynamicDatabaseOptions.SupportTop) + { + sb.AppendFormat(" TOP {0}", _limit); + lused = true; + } + else if ((Database.Options & DynamicDatabaseOptions.SupportFirstSkip) == DynamicDatabaseOptions.SupportFirstSkip) + { + sb.AppendFormat(" FIRST {0}", _limit); + lused = true; + } + } + if (_offset.HasValue && (Database.Options & DynamicDatabaseOptions.SupportFirstSkip) == DynamicDatabaseOptions.SupportFirstSkip) + { + sb.AppendFormat(" SKIP {0}", _offset); + oused = true; + } + if (_select != null) sb.AppendFormat(" {0}", _select); else sb.Append(" *"); + if (_from != null) sb.AppendFormat(" FROM {0}", _from); + if (_join != null) sb.AppendFormat(" {0}", _join); + if (WhereCondition != null) sb.AppendFormat(" WHERE {0}", WhereCondition); + if (_groupby != null) sb.AppendFormat(" GROUP BY {0}", _groupby); + if (HavingCondition != null) sb.AppendFormat(" HAVING {0}", HavingCondition); + if (_orderby != null) sb.AppendFormat(" ORDER BY {0}", _orderby); + if (_limit.HasValue && !lused && (Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset) + sb.AppendFormat(" LIMIT {0}", _limit); + if (_offset.HasValue && !oused && (Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset) + sb.AppendFormat(" OFFSET {0}", _offset); + + return sb.ToString(); + } + #region Execution + + /// Execute this builder. + /// Enumerator of objects expanded from query. + public virtual IEnumerable Execute() + { + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(this) + .ExecuteReader()) + using (IDataReader cache = new DynamicCachedReader(rdr)) + while (cache.Read()) + { + dynamic val = null; + + // Work around to avoid yield being in try...catchblock: + // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch + try + { + val = cache.RowToDynamic(); + } + catch (ArgumentException argex) + { + StringBuilder sb = new StringBuilder(); + cmd.Dump(sb); + + throw new ArgumentException( + string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb), + argex.InnerException.NullOr(a => a, argex)); + } + yield return val; + } + } + } + /// Execute this builder and map to given type. + /// Type of object to map on. + /// Enumerator of objects expanded from query. + public virtual IEnumerable Execute() where T : class + { + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidOperationException("Type can't be mapped for unknown reason."); + + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + using (IDataReader rdr = cmd + .SetCommand(this) + .ExecuteReader()) + using (IDataReader cache = new DynamicCachedReader(rdr)) + while (cache.Read()) + { + dynamic val = null; + + // Work around to avoid yield being in try...catchblock: + // http://stackoverflow.com/questions/346365/why-cant-yield-return-appear-inside-a-try-block-with-a-catch + try + { + val = cache.RowToDynamic(); + } + catch (ArgumentException argex) + { + StringBuilder sb = new StringBuilder(); + cmd.Dump(sb); + + throw new ArgumentException( + string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb), + argex.InnerException.NullOr(a => a, argex)); + } + yield return mapper.Create(val) as T; + } + } + } + /// Execute this builder as a data reader. + /// Action containing reader. + public virtual void ExecuteDataReader(Action reader) + { + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + using (IDataReader rdr = cmd + .SetCommand(this) + .ExecuteReader()) + reader(rdr); + } + /// Execute this builder as a data reader, but + /// first makes a full reader copy in memory. + /// Action containing reader. + public virtual void ExecuteCachedDataReader(Action reader) + { + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + using (IDataReader rdr = cmd + .SetCommand(this) + .ExecuteReader()) + using (IDataReader cache = new DynamicCachedReader(rdr)) + reader(cache); + } + /// Returns a single result. + /// Result of a query. + public virtual object Scalar() + { + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(this) + .ExecuteScalar(); + } + } + #if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE + + /// Returns a single result. + /// Type to parse to. + /// Default value. + /// Result of a query. + public virtual T ScalarAs(T defaultValue = default(T)) + { + using (IDbConnection con = Database.Open()) + using (IDbCommand cmd = con.CreateCommand()) + { + return cmd + .SetCommand(this) + .ExecuteScalarAs(defaultValue); + } + } + #endif + + #endregion Execution + + #region From/Join + + /// + /// Adds to the 'From' clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: 'x => "Table AS Alias', where the alias part is optional. + /// - Resolve to an expression: 'x => x.Table.As( x.Alias )', where the alias part is optional. + /// - Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this + /// case the alias is not annotated. + /// + /// The specification. + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder From(Func fn, params Func[] func) + { + if (fn == null) + throw new ArgumentNullException("Array of functions cannot be or contain null."); + + int index = FromFunc(-1, fn); + foreach (Func f in func) + index = FromFunc(index, f); + + return this; + } + private int FromFunc(int index, Func f) + { + if (f == null) + throw new ArgumentNullException("Array of functions cannot be or contain null."); + + index++; + ITableInfo tableInfo = null; + using (DynamicParser parser = DynamicParser.Parse(f)) + { + object result = parser.Result; + + // If the expression result is string. + if (result is string) + { + string node = (string)result; + Tuple tuple = node.SplitSomethingAndAlias(); + string[] parts = tuple.Item1.Split('.'); + tableInfo = new TableInfo(Database, + Database.StripName(parts.Last()).Validated("Table"), + tuple.Item2.Validated("Alias", canbeNull: true), + parts.Length == 2 ? Database.StripName(parts.First()).Validated("Owner", canbeNull: true) : null); + } + else if (result is Type) + { + Type type = (Type)result; + if (type.IsAnonymous()) + throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}). Parsing {1}", type.FullName, result)); + + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}). Parsing {1}", type.FullName, result)); + + tableInfo = new TableInfo(Database, type); + } + else if (result is DynamicParser.Node) + { + // Or if it resolves to a dynamic node + DynamicParser.Node node = (DynamicParser.Node)result; + + string owner = null; + string main = null; + string alias = null; + bool nolock = false; + Type type = null; + + while (true) + { + // Support for the AS() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") + { + if (alias != null) + throw new ArgumentException(string.Format("Alias '{0}' is already set when parsing '{1}'.", alias, result)); + + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args == null) + throw new ArgumentNullException("arg", "AS() is not a parameterless method."); + + if (args.Length != 1) + throw new ArgumentException("AS() requires one and only one parameter: " + args.Sketch()); + + alias = Parse(args[0], rawstr: true, decorate: false).Validated("Alias"); + + node = node.Host; + continue; + } + // Support for the NoLock() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") + { + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null && args.Length > 0) + throw new ArgumentNullException("arg", "NoLock() doesn't support arguments."); + + nolock = true; + + node = node.Host; + continue; + } + /*if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "subquery") + { + main = Parse(this.SubQuery(((DynamicParser.Node.Method)node).Arguments.Where(p => p is Func).Cast>().ToArray()), Parameters); + continue; + }*/ + + // Support for table specifications... + if (node is DynamicParser.Node.GetMember) + { + if (owner != null) + throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); + + if (main != null) + owner = ((DynamicParser.Node.GetMember)node).Name; + else + main = ((DynamicParser.Node.GetMember)node).Name; + + node = node.Host; + continue; + } + // Support for generic sources... + if (node is DynamicParser.Node.Invoke) + { + if (owner != null) + throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); + + if (main != null) + owner = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); + else + { + DynamicParser.Node.Invoke invoke = (DynamicParser.Node.Invoke)node; + if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type) + { + type = (Type)invoke.Arguments[0]; + if (type.IsAnonymous()) + throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}). Parsing {1}", type.FullName, result)); + + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); + + if (mapper == null) + throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}). Parsing {1}", type.FullName, result)); + + main = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + + owner = (mapper.Table != null) ? mapper.Table.Owner : owner; + } + else + main = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); + } + node = node.Host; + continue; + } + // Just finished the parsing... + if (node is DynamicParser.Node.Argument) break; + + // All others are assumed to be part of the main element... + if (main != null) + main = Parse(node, pars: Parameters); + else + main = Parse(node, pars: Parameters); + + break; + } + if (!string.IsNullOrEmpty(main)) + tableInfo = type == null ? new TableInfo(Database, main, alias, owner, nolock) : new TableInfo(Database, type, alias, owner, nolock); + else + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + } + // Or it is a not supported expression... + if (tableInfo == null) + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + + Tables.Add(tableInfo); + + // We finally add the contents... + StringBuilder sb = new StringBuilder(); + + if (!string.IsNullOrEmpty(tableInfo.Owner)) + sb.AppendFormat("{0}.", Database.DecorateName(tableInfo.Owner)); + + sb.Append(tableInfo.Name.ContainsAny(StringExtensions.InvalidMemberChars) ? tableInfo.Name : Database.DecorateName(tableInfo.Name)); + + if (!string.IsNullOrEmpty(tableInfo.Alias)) + sb.AppendFormat(" AS {0}", tableInfo.Alias); + + if (SupportNoLock && tableInfo.NoLock) + sb.AppendFormat(" WITH(NOLOCK)"); + + _from = string.IsNullOrEmpty(_from) ? sb.ToString() : string.Format("{0}, {1}", _from, sb.ToString()); + } + return index; + } + /// + /// Adds to the 'Join' clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: 'x => "Table AS Alias ON Condition', where the alias part is optional. + /// - Resolve to an expression: 'x => x.Table.As( x.Alias ).On( condition )', where the alias part is optional. + /// - Generic expression: 'x => x( expression ).As( x.Alias ).On( condition )', where the alias part is mandatory. + /// In this case the alias is not annotated. + /// The expression might be prepended by a method that, in this case, is used to specify the specific join type you + /// want to perform, as in: 'x => x.Left()...". Two considerations apply: + /// - If a 'false' argument is used when no 'Join' part appears in its name, then no 'Join' suffix is added + /// with a space in between. + /// - If a 'false' argument is used when a 'Join' part does appear, then no split is performed to separate the + /// 'Join' part. + /// + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder Join(params Func[] func) + { + // We need to do two passes to add aliases first. + return JoinInternal(true, func).JoinInternal(false, func); + } + /// + /// Adds to the 'Join' clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: 'x => "Table AS Alias ON Condition', where the alias part is optional. + /// - Resolve to an expression: 'x => x.Table.As( x.Alias ).On( condition )', where the alias part is optional. + /// - Generic expression: 'x => x( expression ).As( x.Alias ).On( condition )', where the alias part is mandatory. + /// In this case the alias is not annotated. + /// The expression might be prepended by a method that, in this case, is used to specify the specific join type you + /// want to perform, as in: 'x => x.Left()...". Two considerations apply: + /// - If a 'false' argument is used when no 'Join' part appears in its name, then no 'Join' suffix is added + /// with a space in between. + /// - If a 'false' argument is used when a 'Join' part does appear, then no split is performed to separate the + /// 'Join' part. + /// + /// If true just pass by to locate tables and aliases, otherwise create rules. + /// The specification. + /// This instance to permit chaining. + protected virtual DynamicSelectQueryBuilder JoinInternal(bool justAddTables, params Func[] func) + { + if (func == null) throw new ArgumentNullException("Array of functions cannot be null."); + + int index = -1; + + foreach (Func f in func) + { + index++; + ITableInfo tableInfo = null; + + if (f == null) + throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); + + using (DynamicParser parser = DynamicParser.Parse(f)) + { + object result = parser.Result; + if (result == null) throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); + + string type = null; + string main = null; + string owner = null; + string alias = null; + string condition = null; + bool nolock = false; + Type tableType = null; + + // If the expression resolves to a string... + if (result is string) + { + string node = (string)result; + + int n = node.ToUpper().IndexOf("JOIN "); + + if (n < 0) + main = node; + else + { + // For strings we only accept 'JOIN' as a suffix + type = node.Substring(0, n + 4); + main = node.Substring(n + 4); + } + n = main.ToUpper().IndexOf("ON"); + + if (n >= 0) + { + condition = main.Substring(n + 3); + main = main.Substring(0, n).Trim(); + } + Tuple tuple = main.SplitSomethingAndAlias(); // In this case we split on the remaining 'main' + string[] parts = tuple.Item1.Split('.'); + main = Database.StripName(parts.Last()).Validated("Table"); + owner = parts.Length == 2 ? Database.StripName(parts.First()).Validated("Owner", canbeNull: true) : null; + alias = tuple.Item2.Validated("Alias", canbeNull: true); + } + else if (result is DynamicParser.Node) + { + // Or if it resolves to a dynamic node... + DynamicParser.Node node = (DynamicParser.Node)result; + while (true) + { + // Support for the ON() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "ON") + { + if (condition != null) + throw new ArgumentException(string.Format("Condition '{0}' is already set when parsing '{1}'.", alias, result)); + + object[] args = ((DynamicParser.Node.Method)node).Arguments; + if (args == null) + throw new ArgumentNullException("arg", "ON() is not a parameterless method."); + + if (args.Length != 1) + throw new ArgumentException("ON() requires one and only one parameter: " + args.Sketch()); + + condition = Parse(args[0], rawstr: true, pars: justAddTables ? null : Parameters); + + node = node.Host; + continue; + } + // Support for the AS() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") + { + if (alias != null) + throw new ArgumentException(string.Format("Alias '{0}' is already set when parsing '{1}'.", alias, result)); + + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args == null) + throw new ArgumentNullException("arg", "AS() is not a parameterless method."); + + if (args.Length != 1) + throw new ArgumentException("AS() requires one and only one parameter: " + args.Sketch()); + + alias = Parse(args[0], rawstr: true, decorate: false, isMultiPart: false).Validated("Alias"); + + node = node.Host; + continue; + } + // Support for the NoLock() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "NOLOCK") + { + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null && args.Length > 0) + throw new ArgumentNullException("arg", "NoLock() doesn't support arguments."); + + nolock = true; + + node = node.Host; + continue; + } + // Support for table specifications... + if (node is DynamicParser.Node.GetMember) + { + if (owner != null) + throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); + + if (main != null) + owner = ((DynamicParser.Node.GetMember)node).Name; + else + main = ((DynamicParser.Node.GetMember)node).Name; + + node = node.Host; + continue; + } + // Support for Join Type specifications... + if (node is DynamicParser.Node.Method && (node.Host is DynamicParser.Node.Argument || node.Host is DynamicParser.Node.Invoke)) + { + if (type != null) throw new ArgumentException(string.Format("Join type '{0}' is already set when parsing '{1}'.", main, result)); + type = ((DynamicParser.Node.Method)node).Name; + + bool avoid = false; + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null && args.Length > 0) + { + avoid = args[0] is bool && !((bool)args[0]); + string proposedType = args.FirstOrDefault(a => a is string) as string; + if (!string.IsNullOrEmpty(proposedType)) + type = proposedType; + } + type = type.ToUpper(); // Normalizing, and stepping out the trivial case... + if (type != "JOIN") + { + // Special cases + // x => x.LeftOuter() / x => x.RightOuter()... + type = type.Replace("OUTER", " OUTER ") + .Replace(" ", " ") + .Trim(' '); + + // x => x.Left()... + int n = type.IndexOf("JOIN"); + + if (n < 0 && !avoid) + type += " JOIN"; + + // x => x.InnerJoin() / x => x.JoinLeft() ... + else + { + if (!avoid) + { + if (n == 0) type = type.Replace("JOIN", "JOIN "); + else type = type.Replace("JOIN", " JOIN"); + } + } + } + node = node.Host; + continue; + } + // Support for generic sources... + if (node is DynamicParser.Node.Invoke) + { + if (owner != null) + throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); + + if (main != null) + owner = string.Format("{0}", Parse(node, rawstr: true, pars: justAddTables ? null : Parameters)); + else + { + DynamicParser.Node.Invoke invoke = (DynamicParser.Node.Invoke)node; + if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type) + { + tableType = (Type)invoke.Arguments[0]; + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(tableType); + + if (mapper == null) + throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", tableType.FullName)); + + main = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + + owner = (mapper.Table != null) ? mapper.Table.Owner : owner; + } + else + main = string.Format("{0}", Parse(node, rawstr: true, pars: justAddTables ? null : Parameters)); + } + node = node.Host; + continue; + } + // Just finished the parsing... + if (node is DynamicParser.Node.Argument) break; + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + } + } + else + { + // Or it is a not supported expression... + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + } + // We annotate the aliases being conservative... + main = main.Validated("Main"); + + if (justAddTables) + { + if (!string.IsNullOrEmpty(main)) + tableInfo = tableType == null ? new TableInfo(Database, main, alias, owner, nolock) : new TableInfo(Database, tableType, alias, owner, nolock); + else + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + + Tables.Add(tableInfo); + } + else + { + // Get cached table info + tableInfo = string.IsNullOrEmpty(alias) ? + Tables.SingleOrDefault(t => t.Name == main && string.IsNullOrEmpty(t.Alias)) : + Tables.SingleOrDefault(t => t.Alias == alias); + + // We finally add the contents if we can... + StringBuilder sb = new StringBuilder(); + if (string.IsNullOrEmpty(type)) + type = "JOIN"; + + sb.AppendFormat("{0} ", type); + + if (!string.IsNullOrEmpty(tableInfo.Owner)) + sb.AppendFormat("{0}.", Database.DecorateName(tableInfo.Owner)); + + sb.Append(tableInfo.Name.ContainsAny(StringExtensions.InvalidMemberChars) ? tableInfo.Name : Database.DecorateName(tableInfo.Name)); + + if (!string.IsNullOrEmpty(tableInfo.Alias)) + sb.AppendFormat(" AS {0}", tableInfo.Alias); + + if (SupportNoLock && tableInfo.NoLock) + sb.AppendFormat(" WITH(NOLOCK)"); + + if (!string.IsNullOrEmpty(condition)) + sb.AppendFormat(" ON {0}", condition); + + _join = string.IsNullOrEmpty(_join) ? sb.ToString() : string.Format("{0} {1}", _join, sb.ToString()); // No comma in this case + } + } + } + return this; + } + #endregion From/Join + + #region Where + + /// + /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator. + /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: + /// 'Where( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder Where(Func func) + { + return this.InternalWhere(func); + } + /// Add where condition. + /// Condition column with operator and value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Where(DynamicColumn column) + { + return this.InternalWhere(column); + } + /// Add where condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value) + { + return this.InternalWhere(column, op, value); + } + /// Add where condition. + /// Condition column. + /// Condition value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Where(string column, object value) + { + return this.InternalWhere(column, value); + } + /// Add where condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Where(object conditions, bool schema = false) + { + return this.InternalWhere(conditions, schema); + } + #endregion Where + + #region Select + + /// + /// Adds to the 'Select' clause the contents obtained by parsing the dynamic lambda expressions given. The supported + /// formats are: + /// - Resolve to a string: 'x => "Table.Column AS Alias', where the alias part is optional. + /// - Resolve to an expression: 'x => x.Table.Column.As( x.Alias )', where the alias part is optional. + /// - Select all columns from a table: 'x => x.Table.All()'. + /// - Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this case + /// the alias is not annotated. + /// + /// The specification. + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder Select(Func fn, params Func[] func) + { + if (fn == null) + throw new ArgumentNullException("Array of specifications cannot be null."); + + int index = SelectFunc(-1, fn); + if (func != null) + foreach (Func f in func) + index = SelectFunc(index, f); + + return this; + } + private int SelectFunc(int index, Func f) + { + index++; + if (f == null) + throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); + + using (DynamicParser parser = DynamicParser.Parse(f)) + { + object result = parser.Result; + if (result == null) + throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); + + string main = null; + string alias = null; + bool all = false; + bool anon = false; + + // If the expression resolves to a string... + if (result is string) + { + string node = (string)result; + Tuple tuple = node.SplitSomethingAndAlias(); + main = tuple.Item1.Validated("Table and/or Column"); + + main = FixObjectName(main); + + alias = tuple.Item2.Validated("Alias", canbeNull: true); + } + else if (result is DynamicParser.Node) + { + // Or if it resolves to a dynamic node... + ParseSelectNode(result, ref main, ref alias, ref all); + } + else if (result.GetType().IsAnonymous()) + { + anon = true; + + foreach (KeyValuePair prop in result.ToDictionary()) + { + if (prop.Value is string) + { + string node = (string)prop.Value; + Tuple tuple = node.SplitSomethingAndAlias(); + main = FixObjectName(tuple.Item1.Validated("Table and/or Column")); + + ////alias = tuple.Item2.Validated("Alias", canbeNull: true); + } + else if (prop.Value is DynamicParser.Node) + { + // Or if it resolves to a dynamic node... + ParseSelectNode(prop.Value, ref main, ref alias, ref all); + } + else + { + // Or it is a not supported expression... + throw new ArgumentException(string.Format("Specification #{0} in anonymous type is invalid: {1}", index, prop.Value)); + } + alias = Database.DecorateName(prop.Key); + ParseSelectAddColumn(main, alias, all); + } + } + else + { + // Or it is a not supported expression... + throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); + } + if (!anon) + ParseSelectAddColumn(main, alias, all); + } + return index; + } + /// Add select columns. + /// Columns to add to object. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder SelectColumn(params DynamicColumn[] columns) + { + foreach (DynamicColumn col in columns) + Select(x => col.ToSQLSelectColumn(Database)); + + return this; + } + /// Add select columns. + /// Columns to add to object. + /// Column format consist of Column Name, Alias and + /// Aggregate function in this order separated by ':'. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder SelectColumn(params string[] columns) + { + DynamicColumn[] cols = new DynamicColumn[columns.Length]; + for (int i = 0; i < columns.Length; i++) + cols[i] = DynamicColumn.ParseSelectColumn(columns[i]); + + return SelectColumn(cols); + } + #endregion Select + + #region GroupBy + + /// + /// Adds to the 'Group By' clause the contents obtained from from parsing the dynamic lambda expression given. + /// + /// The specification. + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder GroupBy(Func fn, params Func[] func) + { + if (fn == null) + throw new ArgumentNullException("Array of specifications cannot be null."); + + int index = GroupByFunc(-1, fn); + + if (func != null) + for (int i = 0; i < func.Length; i++) + { + Func f = func[i]; + index = GroupByFunc(index, f); + } + return this; + } + private int GroupByFunc(int index, Func f) + { + index++; + if (f == null) + throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); + + using (DynamicParser parser = DynamicParser.Parse(f)) + { + object result = parser.Result; + if (result == null) + throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); + + string main = null; + + if (result is string) + main = FixObjectName(result as string); + else + main = Parse(result, pars: Parameters); + + main = main.Validated("Group By"); + if (_groupby == null) + _groupby = main; + else + _groupby = string.Format("{0}, {1}", _groupby, main); + } + return index; + } + /// Add select columns. + /// Columns to group by. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder GroupByColumn(params DynamicColumn[] columns) + { + for (int i = 0; i < columns.Length; i++) + { + DynamicColumn col = columns[i]; + GroupBy(x => col.ToSQLGroupByColumn(Database)); + } + return this; + } + /// Add select columns. + /// Columns to group by. + /// Column format consist of Column Name and + /// Alias in this order separated by ':'. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder GroupByColumn(params string[] columns) + { + return GroupByColumn(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray()); + } + #endregion GroupBy + + #region Having + + /// + /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Having() methods are chained their contents are, by default, concatenated with an 'AND' operator. + /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: + /// 'Having( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder Having(Func func) + { + return this.InternalHaving(func); + } + /// Add Having condition. + /// Condition column with operator and value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(DynamicColumn column) + { + return this.InternalHaving(column); + } + /// Add Having condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value) + { + return this.InternalHaving(column, op, value); + } + /// Add Having condition. + /// Condition column. + /// Condition value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(string column, object value) + { + return this.InternalHaving(column, value); + } + /// Add Having condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(object conditions, bool schema = false) + { + return this.InternalHaving(conditions, schema); + } + #endregion Having + + #region OrderBy + + /// + /// Adds to the 'Order By' clause the contents obtained from from parsing the dynamic lambda expression given. It + /// accepts a multipart column specification followed by an optional Ascending() or Descending() virtual methods + /// to specify the direction. If no virtual method is used, the default is ascending order. You can also use the + /// shorter versions Asc() and Desc(). + /// + /// The specification. + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder OrderBy(Func fn, params Func[] func) + { + if (fn == null) + throw new ArgumentNullException("Array of specifications cannot be null."); + + int index = OrderByFunc(-1, fn); + + if (func != null) + for (int i = 0; i < func.Length; i++) + { + Func f = func[i]; + index = OrderByFunc(index, f); + } + return this; + } + private int OrderByFunc(int index, Func f) + { + index++; + if (f == null) + throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); + + using (DynamicParser parser = DynamicParser.Parse(f)) + { + object result = parser.Result; + if (result == null) throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); + + string main = null; + bool ascending = true; + + if (result is int) + main = result.ToString(); + else if (result is string) + { + string[] parts = ((string)result).Split(' '); + main = Database.StripName(parts.First()); + + int colNo; + if (!Int32.TryParse(main, out colNo)) + main = FixObjectName(main); + + ascending = parts.Length != 2 || parts.Last().ToUpper() == "ASCENDING" || parts.Last().ToUpper() == "ASC"; + } + else + { + // Intercepting trailing 'Ascending' or 'Descending' virtual methods... + if (result is DynamicParser.Node.Method) + { + DynamicParser.Node.Method node = (DynamicParser.Node.Method)result; + string name = node.Name.ToUpper(); + if (name == "ASCENDING" || name == "ASC" || name == "DESCENDING" || name == "DESC") + { + object[] args = node.Arguments; + if (args != null && !(node.Host is DynamicParser.Node.Argument)) + throw new ArgumentException(string.Format("{0} must be a parameterless method, but found: {1}.", name, args.Sketch())); + else if ((args == null || args.Length != 1) && node.Host is DynamicParser.Node.Argument) + throw new ArgumentException(string.Format("{0} requires one numeric parameter, but found: {1}.", name, args.Sketch())); + + ascending = (name == "ASCENDING" || name == "ASC") ? true : false; + + if (args != null && args.Length == 1) + { + int col = -1; + if (args[0] is int) + main = args[0].ToString(); + else if (args[0] is string) + { + if (Int32.TryParse(args[0].ToString(), out col)) + main = col.ToString(); + else + main = FixObjectName(args[0].ToString()); + } + else + main = Parse(args[0], pars: Parameters); + } + result = node.Host; + } + } + // Just parsing the contents... + if (!(result is DynamicParser.Node.Argument)) + main = Parse(result, pars: Parameters); + } + main = main.Validated("Order By"); + main = string.Format("{0} {1}", main, ascending ? "ASC" : "DESC"); + + if (_orderby == null) + _orderby = main; + else + _orderby = string.Format("{0}, {1}", _orderby, main); + } + return index; + } + /// Add select columns. + /// Columns to order by. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder OrderByColumn(params DynamicColumn[] columns) + { + for (int i = 0; i < columns.Length; i++) + { + DynamicColumn col = columns[i]; + OrderBy(x => col.ToSQLOrderByColumn(Database)); + } + return this; + } + /// Add select columns. + /// Columns to order by. + /// Column format consist of Column Name and + /// Alias in this order separated by ':'. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder OrderByColumn(params string[] columns) + { + return OrderByColumn(columns.Select(c => DynamicColumn.ParseOrderByColumn(c)).ToArray()); + } + #endregion OrderBy + + #region Top/Limit/Offset/Distinct + + /// Set top if database support it. + /// How many objects select. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Top(int? top) + { + return Limit(top); + } + /// Set top if database support it. + /// How many objects select. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Limit(int? limit) + { + if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset && + (Database.Options & DynamicDatabaseOptions.SupportFirstSkip) != DynamicDatabaseOptions.SupportFirstSkip && + (Database.Options & DynamicDatabaseOptions.SupportTop) != DynamicDatabaseOptions.SupportTop) + throw new NotSupportedException("Database doesn't support LIMIT clause."); + + _limit = limit; + return this; + } + /// Set top if database support it. + /// How many objects skip selecting. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Offset(int? offset) + { + if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset && + (Database.Options & DynamicDatabaseOptions.SupportFirstSkip) != DynamicDatabaseOptions.SupportFirstSkip) + throw new NotSupportedException("Database doesn't support OFFSET clause."); + + _offset = offset; + return this; + } + /// Set distinct mode. + /// Distinct mode. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Distinct(bool distinct = true) + { + _distinct = distinct; + return this; + } + #endregion Top/Limit/Offset/Distinct + + #region Helpers + + private void ParseSelectAddColumn(string main, string alias, bool all) + { + // We annotate the aliases being conservative... + main = main.Validated("Main"); + + ////if (alias != null && !main.ContainsAny(StringExtensions.InvalidMemberChars)) TableAliasList.Add(new KTableAlias(main, alias)); + + // If all columns are requested... + if (all) + main += ".*"; + + // We finally add the contents... + string str = (alias == null || all) ? main : string.Format("{0} AS {1}", main, alias); + _select = _select == null ? str : string.Format("{0}, {1}", _select, str); + } + private void ParseSelectNode(object result, ref string column, ref string alias, ref bool all) + { + string main = null; + + DynamicParser.Node node = (DynamicParser.Node)result; + while (true) + { + // Support for the AS() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") + { + if (alias != null) + throw new ArgumentException(string.Format("Alias '{0}' is already set when parsing '{1}'.", alias, result)); + + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args == null) + throw new ArgumentNullException("arg", "AS() is not a parameterless method."); + + if (args.Length != 1) + throw new ArgumentException("AS() requires one and only one parameter: " + args.Sketch()); + + // Yes, we decorate columns + alias = Parse(args[0], rawstr: true, decorate: true, isMultiPart: false).Validated("Alias"); + + node = node.Host; + continue; + } + // Support for the ALL() virtual method... + if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "ALL") + { + if (all) + throw new ArgumentException(string.Format("Flag to select all columns is already set when parsing '{0}'.", result)); + + object[] args = ((DynamicParser.Node.Method)node).Arguments; + + if (args != null) + throw new ArgumentException("ALL() must be a parameterless virtual method, but found: " + args.Sketch()); + + all = true; + + node = node.Host; + continue; + } + // Support for table and/or column specifications... + if (node is DynamicParser.Node.GetMember) + { + if (main != null) + throw new ArgumentException(string.Format("Main '{0}' is already set when parsing '{1}'.", main, result)); + + main = ((DynamicParser.Node.GetMember)node).Name; + + if (node.Host is DynamicParser.Node.GetMember) + { + // If leaf then decorate + main = Database.DecorateName(main); + + // Supporting multipart specifications... + node = node.Host; + + // Get table/alias name + string table = ((DynamicParser.Node.GetMember)node).Name; + bool isAlias = node.Host is DynamicParser.Node.Argument && IsTableAlias(table); + + if (isAlias) + main = string.Format("{0}.{1}", table, main); + else if (node.Host is DynamicParser.Node.GetMember) + { + node = node.Host; + main = string.Format("{0}.{1}.{2}", + Database.DecorateName(((DynamicParser.Node.GetMember)node).Name), + Database.DecorateName(table), main); + } + else + main = string.Format("{0}.{1}", Database.DecorateName(table), main); + } + else if (node.Host is DynamicParser.Node.Argument) + { + string table = ((DynamicParser.Node.Argument)node.Host).Name; + + if (IsTableAlias(table)) + main = string.Format("{0}.{1}", table, Database.DecorateName(main)); + else if (!IsTableAlias(main)) + main = Database.DecorateName(main); + } + else if (!(node.Host is DynamicParser.Node.Argument && IsTableAlias(main))) + main = Database.DecorateName(main); + + node = node.Host; + + continue; + } + // Support for generic sources... + if (node is DynamicParser.Node.Invoke) + { + if (main != null) + throw new ArgumentException(string.Format("Main '{0}' is already set when parsing '{1}'.", main, result)); + + main = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); + + node = node.Host; + continue; + } + // Just finished the parsing... + if (node is DynamicParser.Node.Argument) + { + if (string.IsNullOrEmpty(main) && IsTableAlias(node.Name)) + main = node.Name; + + break; + } + // All others are assumed to be part of the main element... + if (main != null) throw new ArgumentException(string.Format("Main '{0}' is already set when parsing '{1}'.", main, result)); + main = Parse(node, pars: Parameters); + + break; + } + column = main; + } + #endregion Helpers + + #region IExtendedDisposable + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public override void Dispose() + { + base.Dispose(); + + _select = _from = _join = _groupby = _orderby = null; + } + #endregion IExtendedDisposable } - - /// - /// Initializes a new instance of the class. - /// - /// The database. - /// Name of the table. - public DynamicDeleteQueryBuilder(DynamicDatabase db, string tableName) - : base(db, tableName) + /// 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 } - - /// Generates the text this command will execute against the underlying database. - /// The text to execute against the underlying database. - /// This method must be override by derived classes. - public override string CommandText() - { - ITableInfo info = Tables.Single(); - return string.Format("DELETE FROM {0}{1}{2}{3}", - string.IsNullOrEmpty(info.Owner) ? string.Empty : string.Format("{0}.", Database.DecorateName(info.Owner)), - Database.DecorateName(info.Name), - string.IsNullOrEmpty(WhereCondition) ? string.Empty : " WHERE ", - WhereCondition); - } - - #region Where - - /// - /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition - /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used - /// as needed. - /// - If several Where() methods are chained their contents are, by default, concatenated with an 'AND' operator. - /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: - /// 'Where( x => x.Or( condition ) )'. - /// - /// The specification. - /// This instance to permit chaining. - public virtual IDynamicDeleteQueryBuilder Where(Func func) - { - return this.InternalWhere(func); - } - - /// Add where condition. - /// Condition column with operator and value. - /// Builder instance. - public virtual IDynamicDeleteQueryBuilder Where(DynamicColumn column) - { - return this.InternalWhere(column); - } - - /// Add where condition. - /// Condition column. - /// Condition operator. - /// Condition value. - /// Builder instance. - public virtual IDynamicDeleteQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value) - { - return this.InternalWhere(column, op, value); - } - - /// Add where condition. - /// Condition column. - /// Condition value. - /// Builder instance. - public virtual IDynamicDeleteQueryBuilder Where(string column, object value) - { - return this.InternalWhere(column, value); - } - - /// Add where condition. - /// Set conditions as properties and values of an object. - /// If true use schema to determine key columns and ignore those which - /// aren't keys. - /// Builder instance. - public virtual IDynamicDeleteQueryBuilder Where(object conditions, bool schema = false) - { - return this.InternalWhere(conditions, schema); - } - - #endregion Where - } } } - - namespace Objects + namespace Helpers { - - /// 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) + /// Defines methods to support the comparison of collections for equality. + /// The type of collection to compare. + public class CollectionComparer : IEqualityComparer> { - _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()) + /// 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) { - 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."); + return Equals(first, second); } - } - - /// 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())) + /// 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) { - if (query - .Values(x => this) - .Execute() > 0) - { - _changedFields.Clear(); - SetDynamicEntityState(DynamicEntityState.Existing); - return true; - } + return GetHashCode(enumerable); } - - 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)) + /// 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) { - MakeQueryWhere(mapper, query); - - bool any = false; - - if (_changedFields.Any()) - { - foreach (var cf in _changedFields) - { - var cn = mapper.PropertyMap[cf.Key]; - var pm = mapper.ColumnsMap[cn.ToLower()]; - if (pm.Ignore) - continue; - - if (pm.Column != null) - { - if (pm.Column.IsKey || pm.Column.IsNoUpdate) - continue; - - if (!pm.Column.AllowNull && cf.Value == null) - continue; - } - - query.Values(cn, cf.Value); - any = true; - } - } - - if (!any) - foreach (var pmk in mapper.ColumnsMap) - { - var pm = pmk.Value; - var val = pm.Get(this); - var cn = pm.Name; - - if (pm.Ignore) - continue; - - if (pm.Column != null) - { - if (!string.IsNullOrEmpty(pm.Column.Name)) - cn = pm.Column.Name; - - if (pm.Column.IsKey) - continue; - - if (!pm.Column.AllowNull && val == null) - continue; - } - - query.Values(cn, val); - } - - if (query.Execute() == 0) + 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; - - SetDynamicEntityState(DynamicEntityState.Existing); - _changedFields.Clear(); - + + 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; } - } - - /// 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)) + private static bool HaveMismatchedElement(IEnumerable first, IEnumerable second) { - MakeQueryWhere(mapper, query); - - if (query.Execute() == 0) + int firstCount; + int secondCount; + + Dictionary firstElementCounts = GetElementCounts(first, out firstCount); + Dictionary secondElementCounts = GetElementCounts(second, out secondCount); + + if (firstCount != secondCount) + return true; + + foreach (KeyValuePair kvp in firstElementCounts) + if (kvp.Value != (secondElementCounts.TryGetNullable(kvp.Key) ?? 0)) + return true; + + return false; + } + private static Dictionary GetElementCounts(IEnumerable enumerable, out int nullCount) + { + Dictionary dictionary = new Dictionary(); + nullCount = 0; + + foreach (T element in enumerable) + { + if (element == null) + nullCount++; + else + { + int count = dictionary.TryGetNullable(element) ?? 0; + dictionary[element] = ++count; + } + } + return dictionary; + } + } + /// Extensions for data reader handling. + public static class DataReaderExtensions + { + /// Gets the data table from data reader. + /// The data reader. + /// The name to give the table. If tableName is null or an empty string, a default name is given when added to the System.Data.DataTableCollection. + /// The namespace for the XML representation of the data stored in the DataTable. + /// + public static DataTable ToDataTable(this IDataReader r, string name = null, string nameSpace = null) + { + DataTable schemaTable = r.GetSchemaTable(); + DataTable resultTable = new DataTable(name, nameSpace); + + foreach (DataRow col in schemaTable.Rows) + { + dynamic c = col.RowToDynamicUpper(); + + DataColumn dataColumn = new DataColumn(); + dataColumn.ColumnName = c.COLUMNNAME; + dataColumn.DataType = (Type)c.DATATYPE; + dataColumn.ReadOnly = true; + dataColumn.Unique = c.ISUNIQUE; + + resultTable.Columns.Add(dataColumn); + } + while (r.Read()) + { + DataRow row = resultTable.NewRow(); + for (int i = 0; i < resultTable.Columns.Count; i++) + row[i] = r[i]; + + resultTable.Rows.Add(row); + } + return resultTable; + } + } + /// Framework detection and specific implementations. + public static class FrameworkTools + { + #region Mono or .NET Framework detection + + /// This is pretty simple trick. + private static bool _isMono = Type.GetType("Mono.Runtime") != null; + + /// Gets a value indicating whether application is running under mono runtime. + public static bool IsMono { get { return _isMono; } } + + #endregion Mono or .NET Framework detection + + static FrameworkTools() + { + _frameworkTypeArgumentsGetter = CreateTypeArgumentsGetter(); + } + #region GetGenericTypeArguments + + private static Func> _frameworkTypeArgumentsGetter = null; + + private static Func> CreateTypeArgumentsGetter() + { + // HACK: Creating binders assuming types are correct... this may fail. + if (IsMono) + { + Type binderType = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder"); + + if (binderType != null) + { + ParameterExpression param = Expression.Parameter(typeof(InvokeMemberBinder), "o"); + + try + { + return Expression.Lambda>>( + Expression.TypeAs( + Expression.Field( + Expression.TypeAs(param, binderType), "typeArguments"), + typeof(IList)), param).Compile(); + } + catch + { + } + PropertyInfo prop = binderType.GetProperty("TypeArguments"); + + if (!prop.CanRead) + return null; + + return Expression.Lambda>>( + Expression.TypeAs( + Expression.Property( + Expression.TypeAs(param, binderType), prop.Name), + typeof(IList)), param).Compile(); + } + } + else + { + Type inter = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); + + if (inter != null) + { + PropertyInfo prop = inter.GetProperty("TypeArguments"); + + if (!prop.CanRead) + return null; + + ParameterExpression objParm = Expression.Parameter(typeof(InvokeMemberBinder), "o"); + + return Expression.Lambda>>( + Expression.TypeAs( + Expression.Property( + Expression.TypeAs(objParm, inter), prop.Name), + typeof(IList)), objParm).Compile(); + } + } + return null; + } + /// Extension method allowing to easily extract generic type + /// arguments from assuming that it + /// inherits from + /// Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder + /// in .NET Framework or + /// Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder + /// under Mono. + /// Binder from which get type arguments. + /// This is generally a bad solution, but there is no other + /// currently so we have to go with it. + /// List of types passed as generic parameters. + public static IList GetGenericTypeArguments(this InvokeMemberBinder binder) + { + // First try to use delegate if exist + if (_frameworkTypeArgumentsGetter != null) + return _frameworkTypeArgumentsGetter(binder); + + if (_isMono) + { + // HACK: Using Reflection + // In mono this is trivial. + + // First we get field info. + FieldInfo field = binder.GetType().GetField("typeArguments", BindingFlags.Instance | + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + + // If this was a success get and return it's value + if (field != null) + return field.GetValue(binder) as IList; + else + { + PropertyInfo prop = binder.GetType().GetProperty("TypeArguments"); + + // If we have a property, return it's value + if (prop != null) + return prop.GetValue(binder, null) as IList; + } + } + else + { + // HACK: Using Reflection + // In this case, we need more aerobic :D + + // First, get the interface + Type inter = binder.GetType().GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); + + if (inter != null) + { + // Now get property. + PropertyInfo prop = inter.GetProperty("TypeArguments"); + + // If we have a property, return it's value + if (prop != null) + return prop.GetValue(binder, null) as IList; + } + } + // Sadly return null if failed. + return null; + } + #endregion GetGenericTypeArguments + } + /// Extends interface. + public interface IExtendedDisposable : IDisposable + { + /// + /// Gets a value indicating whether this instance is disposed. + /// + /// + /// true if this instance is disposed; otherwise, false. + /// + bool IsDisposed { get; } + } + /// Extends interface. + public interface IFinalizerDisposable : IExtendedDisposable + { + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + void Dispose(bool disposing); + } + public static class ReaderExtensions + { + public static bool? GetBooleanIfNotNull(this IDataReader r, string name, bool? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetBoolean(ord); + } + public static byte? GetByteIfNotNull(this IDataReader r, string name, byte? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetByte(ord); + } + public static char? GetCharIfNotNull(this IDataReader r, string name, char? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetChar(ord); + } + public static DateTime? GetDateTimeIfNotNull(this IDataReader r, string name, DateTime? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetDateTime(ord); + } + public static decimal? GetDecimalIfNotNull(this IDataReader r, string name, decimal? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetDecimal(ord); + } + public static double? GetDoubleIfNotNull(this IDataReader r, string name, double? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetDouble(ord); + } + public static float? GetFloatIfNotNull(this IDataReader r, string name, float? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetFloat(ord); + } + public static Guid? GetGuidIfNotNull(this IDataReader r, string name, Guid? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetGuid(ord); + } + public static short? GetInt16IfNotNull(this IDataReader r, string name, short? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetInt16(ord); + } + public static int? GetInt32IfNotNull(this IDataReader r, string name, int? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetInt32(ord); + } + public static long? GetInt64IfNotNull(this IDataReader r, string name, long? def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetInt64(ord); + } + public static string GetStringIfNotNull(this IDataReader r, string name, string def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetString(ord); + } + public static object GetValueIfNotNull(this IDataReader r, string name, object def = null) + { + var ord = r.GetOrdinal(name); + if (r.IsDBNull(ord)) + return def; + + return r.GetValue(ord); + } + } + /// Class containing useful string extensions. + internal static class StringExtensions + { + static StringExtensions() + { + InvalidMultipartMemberChars = _InvalidMultipartMemberChars.ToCharArray(); + InvalidMemberChars = _InvalidMemberChars.ToCharArray(); + } + private static readonly string _InvalidMultipartMemberChars = " +-*/^%[]{}()!\"\\&=?¿"; + private static readonly string _InvalidMemberChars = "." + _InvalidMultipartMemberChars; + + /// + /// Gets an array with some invalid characters that cannot be used with multipart names for class members. + /// + public static char[] InvalidMultipartMemberChars { get; private set; } + + /// + /// Gets an array with some invalid characters that cannot be used with names for class members. + /// + public static char[] InvalidMemberChars { get; private set; } + + /// + /// Provides with an alternate and generic way to obtain an alternate string representation for this instance, + /// applying the following rules: + /// - Null values are returned as with the value, or a null object. + /// - Enum values are translated into their string representation. + /// - If the type has override the 'ToString' method then it is used. + /// - If it is a dictionary, then a collection of key/value pairs where the value part is also translated. + /// - If it is a collection, then a collection of value items also translated. + /// - If it has public public properties (or if not, if it has public fields), the collection of name/value + /// pairs, with the values translated. + /// - Finally it falls back to the standard 'type.FullName' mechanism. + /// + /// The object to obtain its alternate string representation from. + /// The brackets to use if needed. If not null it must be at least a 2-chars' array containing + /// the opening and closing brackets. + /// Representation of null string.. + /// The alternate string representation of this object. + public static string Sketch(this object obj, char[] brackets = null, string nullString = "(null)") + { + if (obj == null) return nullString; + if (obj is string) return (string)obj; + + Type type = obj.GetType(); + if (type.IsEnum) return obj.ToString(); + + // If the ToString() method has been overriden (by the type itself, or by its parents), let's use it... + MethodInfo method = type.GetMethod("ToString", Type.EmptyTypes); + if (method.DeclaringType != typeof(object)) return obj.ToString(); + + // For alll other cases... + StringBuilder sb = new StringBuilder(); + bool first = true; + + // Dictionaries... + if (obj is IDictionary) + { + if (brackets == null || brackets.Length < 2) + brackets = "[]".ToCharArray(); + + sb.AppendFormat("{0}", brackets[0]); first = true; foreach (DictionaryEntry kvp in (IDictionary)obj) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("'{0}'='{1}'", kvp.Key.Sketch(), kvp.Value.Sketch()); + } + sb.AppendFormat("{0}", brackets[1]); + return sb.ToString(); + } + // IEnumerables... + IEnumerator ator = null; + if (obj is IEnumerable) + ator = ((IEnumerable)obj).GetEnumerator(); + else + { + method = type.GetMethod("GetEnumerator", Type.EmptyTypes); + if (method != null) + ator = (IEnumerator)method.Invoke(obj, null); + } + if (ator != null) + { + if (brackets == null || brackets.Length < 2) brackets = "[]".ToCharArray(); + sb.AppendFormat("{0}", brackets[0]); first = true; while (ator.MoveNext()) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("{0}", ator.Current.Sketch()); + } + sb.AppendFormat("{0}", brackets[1]); + + if (ator is IDisposable) + ((IDisposable)ator).Dispose(); + + return sb.ToString(); + } + // As a last resort, using the public properties (or fields if needed, or type name)... + BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy; + PropertyInfo[] props = type.GetProperties(flags); + FieldInfo[] infos = type.GetFields(flags); + + if (props.Length == 0 && infos.Length == 0) sb.Append(type.FullName); // Fallback if needed + else + { + if (brackets == null || brackets.Length < 2) brackets = "{}".ToCharArray(); + sb.AppendFormat("{0}", brackets[0]); + first = true; + + if (props.Length != 0) + { + foreach (PropertyInfo prop in props) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("{0}='{1}'", prop.Name, prop.GetValue(obj, null).Sketch()); + } + } + else + { + if (infos.Length != 0) + { + foreach (FieldInfo info in infos) + { + if (!first) sb.Append(", "); else first = false; + sb.AppendFormat("{0}='{1}'", info.Name, info.GetValue(obj).Sketch()); + } + } + } + sb.AppendFormat("{0}", brackets[1]); + } + // And returning... + return sb.ToString(); + } + /// + /// Returns true if the target string contains any of the characters given. + /// + /// The target string. It cannot be null. + /// An array containing the characters to test. It cannot be null. If empty false is returned. + /// True if the target string contains any of the characters given, false otherwise. + public static bool ContainsAny(this string source, char[] items) + { + if (source == null) throw new ArgumentNullException("source", "Source string cannot be null."); + if (items == null) throw new ArgumentNullException("items", "Array of characters to test cannot be null."); + + if (items.Length == 0) return false; // No characters to validate + int ix = source.IndexOfAny(items); + return ix >= 0 ? true : false; + } + /// + /// Returns a new validated string using the rules given. + /// + /// The source string. + /// A description of the source string to build errors and exceptions if needed. + /// True if the returned string can be null. + /// True if the returned string can be empty. + /// True to trim the returned string. + /// True to left-trim the returned string. + /// True to right-trim the returned string. + /// If >= 0, the min valid length for the returned string. + /// If >= 0, the max valid length for the returned string. + /// If not '\0', the character to use to left-pad the returned string if needed. + /// If not '\0', the character to use to right-pad the returned string if needed. + /// If not null, an array containing invalid chars that must not appear in the returned + /// string. + /// If not null, an array containing the only characters that are considered valid for the + /// returned string. + /// A new validated string. + public static string Validated(this string source, string desc = null, + bool canbeNull = false, bool canbeEmpty = false, + bool trim = true, bool trimStart = false, bool trimEnd = false, + int minLen = -1, int maxLen = -1, char padLeft = '\0', char padRight = '\0', + char[] invalidChars = null, char[] validChars = null) + { + // Assuring a valid descriptor... + if (string.IsNullOrWhiteSpace(desc)) desc = "Source"; + + // Validating if null sources are accepted... + if (source == null) + { + if (!canbeNull) throw new ArgumentNullException(desc, string.Format("{0} cannot be null.", desc)); + return null; + } + // Trimming if needed... + if (trim && !(trimStart || trimEnd)) source = source.Trim(); + else + { + if (trimStart) source = source.TrimStart(' '); + if (trimEnd) source = source.TrimEnd(' '); + } + // Adjusting lenght... + if (minLen > 0) + { + if (padLeft != '\0') source = source.PadLeft(minLen, padLeft); + if (padRight != '\0') source = source.PadRight(minLen, padRight); + } + if (maxLen > 0) + { + if (padLeft != '\0') source = source.PadLeft(maxLen, padLeft); + if (padRight != '\0') source = source.PadRight(maxLen, padRight); + } + // Validating emptyness and lenghts... + if (source.Length == 0) + { + if (!canbeEmpty) throw new ArgumentException(string.Format("{0} cannot be empty.", desc)); + return string.Empty; + } + if (minLen >= 0 && source.Length < minLen) throw new ArgumentException(string.Format("Lenght of {0} '{1}' is lower than '{2}'.", desc, source, minLen)); + if (maxLen >= 0 && source.Length > maxLen) throw new ArgumentException(string.Format("Lenght of {0} '{1}' is bigger than '{2}'.", desc, source, maxLen)); + + // Checking invalid chars... + if (invalidChars != null) + { + int n = source.IndexOfAny(invalidChars); + if (n >= 0) throw new ArgumentException(string.Format("Invalid character '{0}' found in {1} '{2}'.", source[n], desc, source)); + } + // Checking valid chars... + if (validChars != null) + { + int n = validChars.ToString().IndexOfAny(source.ToCharArray()); + if (n >= 0) throw new ArgumentException(string.Format("Invalid character '{0}' found in {1} '{2}'.", validChars.ToString()[n], desc, source)); + } + return source; + } + /// + /// Splits the given string with the 'something AS alias' format, returning a tuple containing its 'something' and 'alias' parts. + /// If no alias is detected, then its component in the tuple returned is null and all the contents from the source + /// string are considered as the 'something' part. + /// + /// The source string. + /// A tuple containing the 'something' and 'alias' parts. + public static Tuple SplitSomethingAndAlias(this string source) + { + source = source.Validated("[Something AS Alias]"); + + string something = null; + string alias = null; + int n = source.LastIndexOf(" AS ", StringComparison.OrdinalIgnoreCase); + + if (n < 0) + something = source; + else + { + something = source.Substring(0, n); + alias = source.Substring(n + 4); + } + return new Tuple(something, alias); + } + /// Allows to replace parameters inside of string. + /// String containing parameters in format [$ParameterName]. + /// Function that should return value that will be placed in string in place of placed parameter. + /// Prefix of the parameter. This value can't be null or empty, default value [$. + /// Suffix of the parameter. This value can't be null or empty, default value ]. + /// Parsed string. + public static string FillStringWithVariables(this string stringToFill, Func getValue, string prefix = "[$", string sufix = "]") + { + int startPos = 0, endPos = 0; + prefix.Validated(); + sufix.Validated(); + + startPos = stringToFill.IndexOf(prefix, startPos); + while (startPos >= 0) + { + endPos = stringToFill.IndexOf(sufix, startPos + prefix.Length); + int nextStartPos = stringToFill.IndexOf(prefix, startPos + prefix.Length); + + if (endPos > startPos + prefix.Length + 1 && (nextStartPos > endPos || nextStartPos == -1)) + { + string paramName = stringToFill.Substring(startPos + prefix.Length, endPos - (startPos + prefix.Length)); + + stringToFill = stringToFill + .Remove(startPos, (endPos - startPos) + sufix.Length) + .Insert(startPos, getValue(paramName)); + } + startPos = stringToFill.IndexOf(prefix, startPos + prefix.Length); + } + return stringToFill; + } + } + /// Class contains unclassified extensions. + internal static class UnclassifiedExtensions + { + /// Easy way to use conditional value. + /// Includes . + /// Input object type to check. + /// Result type. + /// The object to check. + /// The select function. + /// The else value. + /// Selected value or default value. + /// It lets you do this: + /// var lname = thingy.NullOr(t => t.Name).NullOr(n => n.ToLower()); + /// which is more fluent and (IMO) easier to read than this: + /// var lname = (thingy != null ? thingy.Name : null) != null ? thingy.Name.ToLower() : null; + /// + public static R NullOr(this T obj, Func func, R elseValue = default(R)) where T : class + { + return obj != null && obj != DBNull.Value ? + func(obj) : elseValue; + } + /// Easy way to use conditional value. + /// Includes . + /// Input object type to check. + /// Result type. + /// The object to check. + /// The select function. + /// The else value function. + /// Selected value or default value. + /// It lets you do this: + /// var lname = thingy.NullOr(t => t.Name).NullOr(n => n.ToLower()); + /// which is more fluent and (IMO) easier to read than this: + /// var lname = (thingy != null ? thingy.Name : null) != null ? thingy.Name.ToLower() : null; + /// + public static R NullOrFn(this T obj, Func func, Func elseFunc = null) where T : class + { + // Old if to avoid recurency. + return obj != null && obj != DBNull.Value ? + func(obj) : elseFunc != null ? elseFunc() : default(R); + } + #if !NET6_0_OR_GREATER + /// Simple distinct by selector extension. + /// The enumerator of elements distinct by specified selector. + /// Source collection. + /// Distinct key selector. + /// The enumerable element type parameter. + /// The selector type parameter. + public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) + { + HashSet seenKeys = new HashSet(); + foreach (R element in source) + if (seenKeys.Add(keySelector(element))) + yield return element; + } + #endif + } + namespace Dynamics + { + /// + /// Class able to parse dynamic lambda expressions. Allows to create dynamic logic. + /// + public class DynamicParser : IExtendedDisposable + { + #region Node + + /// + /// Generic bindable operation where some of its operands is a dynamic argument, or a dynamic member or + /// a method of that argument. + /// + [Serializable] + public class Node : IDynamicMetaObjectProvider, IFinalizerDisposable, ISerializable + { + private DynamicParser _parser = null; + + #region MetaNode + + /// + /// Represents the dynamic binding and a binding logic of + /// an object participating in the dynamic binding. + /// + internal class MetaNode : DynamicMetaObject + { + /// + /// Initializes a new instance of the class. + /// + /// The parameter. + /// The restrictions. + /// The value. + public MetaNode(Expression parameter, BindingRestrictions rest, object value) + : base(parameter, rest, value) + { + } + // Func was cool but caused memory leaks + private DynamicMetaObject GetBinder(Node node) + { + Node o = (Node)this.Value; + node.Parser = o.Parser; + o.Parser.Last = node; + + ParameterExpression p = Expression.Variable(typeof(Node), "ret"); + BlockExpression exp = Expression.Block(new ParameterExpression[] { p }, Expression.Assign(p, Expression.Constant(node))); + + return new MetaNode(exp, this.Restrictions, node); + } + /// + /// Performs the binding of the dynamic get member operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindGetMember(GetMemberBinder binder) + { + return GetBinder(new GetMember((Node)this.Value, binder.Name)); + } + /// + /// Performs the binding of the dynamic set member operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// The representing the value for the set member operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) + { + return GetBinder(new SetMember((Node)this.Value, binder.Name, value.Value)); + } + /// + /// Performs the binding of the dynamic get index operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - indexes for the get index operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) + { + return GetBinder(new GetIndex((Node)this.Value, MetaList2List(indexes))); + } + /// + /// Performs the binding of the dynamic set index operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - indexes for the set index operation. + /// The representing the value for the set index operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) + { + return GetBinder(new SetIndex((Node)this.Value, MetaList2List(indexes), value.Value)); + } + /// + /// Performs the binding of the dynamic invoke operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - arguments to the invoke operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) + { + return GetBinder(new Invoke((Node)this.Value, MetaList2List(args))); + } + /// + /// Performs the binding of the dynamic invoke member operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An array of instances - arguments to the invoke member operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) + { + return GetBinder(new Method((Node)this.Value, binder.Name, MetaList2List(args))); + } + /// + /// Performs the binding of the dynamic binary operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// An instance of the representing the right hand side of the binary operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) + { + return GetBinder(new Binary((Node)this.Value, binder.Operation, arg.Value)); + } + /// + /// Performs the binding of the dynamic unary operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) + { + Node o = (Node)this.Value; + Unary node = new Unary(o, binder.Operation) { Parser = o.Parser }; + o.Parser.Last = node; + + // If operation is 'IsTrue' or 'IsFalse', we will return false to keep the engine working... + object ret = node; + if (binder.Operation == ExpressionType.IsTrue) ret = (object)false; + if (binder.Operation == ExpressionType.IsFalse) ret = (object)false; + + ParameterExpression p = Expression.Variable(ret.GetType(), "ret"); // the type is now obtained from "ret" + BlockExpression exp = Expression.Block( + new ParameterExpression[] { p }, + Expression.Assign(p, Expression.Constant(ret))); // the expression is now obtained from "ret" + + return new MetaNode(exp, this.Restrictions, node); + } + /// + /// Performs the binding of the dynamic conversion operation. + /// + /// An instance of the that represents the details of the dynamic operation. + /// + /// The new representing the result of the binding. + /// + public override DynamicMetaObject BindConvert(ConvertBinder binder) + { + Node o = (Node)this.Value; + Convert node = new Convert(o, binder.ReturnType) { Parser = o.Parser }; + o.Parser.Last = node; + + // Reducing the object to return if this is an assignment node... + object ret = o; + bool done = false; + + while (!done) + { + if (ret is SetMember) + ret = ((SetMember)o).Value; + else if (ret is SetIndex) + ret = ((SetIndex)o).Value; + else + done = true; + } + // Creating an instance... + if (binder.ReturnType == typeof(string)) ret = ret.ToString(); + else + { + try + { + if (binder.ReturnType.IsNullableType()) + ret = null; // to avoid cast exceptions + else + ret = Activator.CreateInstance(binder.ReturnType, true); // true to allow non-public ctor as well + } + catch + { + // as the last resort scenario + ret = new object(); + } + } + ParameterExpression p = Expression.Variable(binder.ReturnType, "ret"); + BlockExpression exp = Expression.Block( + new ParameterExpression[] { p }, + Expression.Assign(p, Expression.Constant(ret, binder.ReturnType))); // specifying binder.ReturnType + + return new MetaNode(exp, this.Restrictions, node); + } + private static object[] MetaList2List(DynamicMetaObject[] metaObjects) + { + if (metaObjects == null) return null; + + object[] list = new object[metaObjects.Length]; + for (int i = 0; i < metaObjects.Length; i++) + list[i] = metaObjects[i].Value; + + return list; + } + } + #endregion MetaNode + + #region Argument + + /// + /// Describe a dynamic argument used in a dynamic lambda expression. + /// + [Serializable] + public class Argument : Node, ISerializable + { + /// + /// Initializes a new instance of the class. + /// + /// The name. + public Argument(string name) + : base(name) + { + } + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Argument(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Argument::Disposed}"; + return Name; + } + } + #endregion Argument + + #region GetMember + + /// + /// Describe a 'get member' operation, as in 'x => x.Member'. + /// + [Serializable] + public class GetMember : Node, ISerializable + { + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + public GetMember(Node host, string name) + : base(host, name) + { + } + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected GetMember(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::GetMember::Disposed}"; + return string.Format("{0}.{1}", Host.Sketch(), Name.Sketch()); + } + } + #endregion GetMember + + #region SetMember + + /// + /// Describe a 'set member' operation, as in 'x => x.Member = y'. + /// + [Serializable] + public class SetMember : Node, ISerializable + { + /// + /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been + /// assigned to this instance, or if this instance is disposed. + /// + public object Value { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + /// The value. + public SetMember(Node host, string name, object value) + : base(host, name) + { + Value = value; + } + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected SetMember(SerializationInfo info, StreamingContext context) + : base(info, context) + { + string type = info.GetString("MemberType"); + Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); + } + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); + if (Value != null) + info.AddValue("MemberValue", Value); + + base.GetObjectData(info, context); + } + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::SetMember::Disposed}"; + return string.Format("({0}.{1} = {2})", Host.Sketch(), Name.Sketch(), Value.Sketch()); + } + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Value != null) + { + var node = Value as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + Value = null; + } + } + catch + { + } + } + base.Dispose(disposing); + } + } + #endregion SetMember + + #region GetIndex + + /// + /// Describe a 'get indexed' operation, as in 'x => x.Member[...]'. + /// + [Serializable] + public class GetIndex : Node, ISerializable + { + /// Gets the indexes. + public object[] Indexes { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The indexes. + /// Indexes array cannot be null. + /// Indexes array cannot be empty. + public GetIndex(Node host, object[] indexes) + : base(host) + { + if (indexes == null) + throw new ArgumentNullException("indexes", "Indexes array cannot be null."); + if (indexes.Length == 0) + throw new ArgumentException("Indexes array cannot be empty."); + + Indexes = indexes; + } + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected GetIndex(SerializationInfo info, StreamingContext context) + : base(info, context) + { + int count = (int)info.GetValue("IndexCount", typeof(int)); + + if (count != 0) + { + Indexes = new object[count]; for (int i = 0; i < count; i++) + { + string typeName = info.GetString("IndexType" + i); + object obj = typeName == "NULL" ? null : info.GetValue("IndexValue" + i, Type.GetType(typeName)); + Indexes[i] = obj; + } + } + } + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + int count = Indexes == null ? 0 : Indexes.Length; info.AddValue("IndexCount", count); + for (int i = 0; i < count; i++) + { + info.AddValue("IndexType" + i, Indexes[i] == null ? "NULL" : Indexes[i].GetType().AssemblyQualifiedName); + if (Indexes[i] != null) info.AddValue("IndexValue" + i, Indexes[i]); + } + base.GetObjectData(info, context); + } + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::GetIndex::Disposed}"; + + return string.Format("{0}{1}", Host.Sketch(), Indexes == null ? "[empty]" : Indexes.Sketch()); + } + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Indexes != null) + { + for (int i = 0; i < Indexes.Length; i++) + { + var node = Indexes[i] as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + } + Array.Clear(Indexes, 0, Indexes.Length); + } + } + catch + { + } + Indexes = null; + } + base.Dispose(disposing); + } + } + #endregion GetIndex + + #region SetIndex + + /// + /// Describe a 'set indexed' operation, as in 'x => x.Member[...] = Value'. + /// + [Serializable] + public class SetIndex : GetIndex, ISerializable + { + /// + /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been + /// assigned to this instance, or if this instance is disposed. + /// + public object Value { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The indexes. + /// The value. + public SetIndex(Node host, object[] indexes, object value) + : base(host, indexes) + { + Value = value; + } + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected SetIndex(SerializationInfo info, StreamingContext context) + : base(info, context) + { + string type = info.GetString("MemberType"); + Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); + } + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); + if (Value != null) info.AddValue("MemberValue", Value); + + base.GetObjectData(info, context); + } + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::SetIndex::Disposed}"; + + return string.Format("({0}{1} = {2})", Host.Sketch(), Indexes == null ? "[empty]" : Indexes.Sketch(), Value.Sketch()); + } + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Value != null) + { + var node = Value as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + Value = null; + } + } + catch + { + } + } + base.Dispose(disposing); + } + } + #endregion SetIndex + + #region Invoke + + /// + /// Describe a method invocation operation, as in 'x => x.Method(...)". + /// + [Serializable] + public class Invoke : Node, ISerializable + { + /// Gets the arguments. + public object[] Arguments { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The arguments. + public Invoke(Node host, object[] arguments) + : base(host) + { + Arguments = arguments == null || arguments.Length == 0 ? null : arguments; + } + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Invoke(SerializationInfo info, StreamingContext context) + : base(info, context) + { + int count = (int)info.GetValue("ArgumentCount", typeof(int)); + + if (count != 0) + { + Arguments = new object[count]; for (int i = 0; i < count; i++) + { + string typeName = info.GetString("ArgumentType" + i); + object obj = typeName == "NULL" ? null : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); + Arguments[i] = obj; + } + } + } + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + int count = Arguments == null ? 0 : Arguments.Length; info.AddValue("ArgumentCount", count); + for (int i = 0; i < count; i++) + { + info.AddValue("ArgumentType" + i, Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); + if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); + } + base.GetObjectData(info, context); + } + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Arguments != null) + { + for (int i = 0; i < Arguments.Length; i++) + { + var node = Arguments[i] as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + } + Array.Clear(Arguments, 0, Arguments.Length); + } + } + catch + { + } + Arguments = null; + } + base.Dispose(disposing); + } + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Invoke::Disposed}"; + + return string.Format("{0}{1}", Host.Sketch(), Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); + } + } + #endregion Invoke + + #region Method + + /// + /// Describe a method invocation operation, as in 'x => x.Method(...)". + /// + [Serializable] + public class Method : Node, ISerializable + { + /// Gets the arguments. + public object[] Arguments { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + /// The arguments. + public Method(Node host, string name, object[] arguments) + : base(host, name) + { + Arguments = arguments == null || arguments.Length == 0 ? null : arguments; + } + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Method(SerializationInfo info, StreamingContext context) + : base(info, context) + { + int count = (int)info.GetValue("ArgumentCount", typeof(int)); + + if (count != 0) + { + Arguments = new object[count]; for (int i = 0; i < count; i++) + { + string typeName = info.GetString("ArgumentType" + i); + object obj = typeName == "NULL" ? null : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); + Arguments[i] = obj; + } + } + } + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + int count = Arguments == null ? 0 : Arguments.Length; info.AddValue("ArgumentCount", count); + for (int i = 0; i < count; i++) + { + info.AddValue("ArgumentType" + i, Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); + if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); + } + base.GetObjectData(info, context); + } + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Method::Disposed}"; + + return string.Format("{0}.{1}{2}", Host.Sketch(), Name.Sketch(), Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); + } + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Arguments != null) + { + for (int i = 0; i < Arguments.Length; i++) + { + var node = Arguments[i] as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + } + Array.Clear(Arguments, 0, Arguments.Length); + } + } + catch + { + } + Arguments = null; + } + base.Dispose(disposing); + } + } + #endregion Method + + #region Binary + + /// + /// Represents a binary operation between a dynamic element and an arbitrary object, including null ones, as in + /// 'x => (x && null)'. The left operand must be an instance of , whereas the right one + /// can be any object, including null values. + /// + [Serializable] + public class Binary : Node, ISerializable + { + /// Gets the operation. + public ExpressionType Operation { get; private set; } + + /// Gets host of the . + public Node Left { get { return Host; } } + + /// Gets the right side value. + public object Right { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The left. + /// The operation. + /// The right. + public Binary(Node left, ExpressionType operation, object right) + : base(left) + { + Operation = operation; + Right = right; + } + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Binary(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); + + string type = info.GetString("RightType"); + Right = type == "NULL" ? null : (Node)info.GetValue("RightItem", Type.GetType(type)); + } + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Operation", Operation); + + info.AddValue("RightType", Right == null ? "NULL" : Right.GetType().AssemblyQualifiedName); + if (Right != null) + info.AddValue("RightItem", Right); + + base.GetObjectData(info, context); + } + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Binary::Disposed}"; + + return string.Format("({0} {1} {2})", Host.Sketch(), Operation, Right.Sketch()); + } + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + if (Left != null) + { + if (Left is Node) + { + Node n = (Node)Left; + + if (!n.IsDisposed) + n.Dispose(disposing); + } + } + if (Right != null) + { + if (Right is Node) + { + Node n = (Node)Right; + + if (!n.IsDisposed) + n.Dispose(disposing); + } + Right = null; + } + } + } + } + #endregion Binary + + #region Unary + + /// + /// Represents an unary operation, as in 'x => !x'. The target must be a instance. There + /// is no distinction between pre- and post- version of the same operation. + /// + [Serializable] + public class Unary : Node, ISerializable + { + /// Gets the operation. + public ExpressionType Operation { get; private set; } + + /// Gets host of the . + public Node Target { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The target. + /// The operation. + public Unary(Node target, ExpressionType operation) + : base(target) + { + Operation = operation; + Target = target; + } + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Unary(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); + } + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Operation", Operation); + + base.GetObjectData(info, context); + } + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Binary::Disposed}"; + + return string.Format("({0} {1})", Operation, Host.Sketch()); + } + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (Target != null) + { + var node = Target as Node; + if (node != null) + { + if (node.IsNodeAncestor(this)) + node.Host = null; + + node.Dispose(disposing); + } + Target = null; + } + } + catch + { + } + } + base.Dispose(disposing); + } + } + #endregion Unary + + #region Convert + + /// + /// Represents a conversion operation, as in 'x => (string)x'. + /// + [Serializable] + public class Convert : Node, ISerializable + { + /// Gets the new type to which value will be converted. + public Type NewType { get; private set; } + + /// Gets host of the . + public Node Target { get { return Host; } } + + /// + /// Initializes a new instance of the class. + /// + /// The target. + /// The new type. + public Convert(Node target, Type newType) + : base(target) + { + NewType = newType; + } + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Convert(SerializationInfo info, StreamingContext context) + : base(info, context) + { + NewType = (Type)info.GetValue("NewType", typeof(Type)); + } + /// + /// Gets the object data. + /// + /// The info. + /// The context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("NewType", NewType); + + base.GetObjectData(info, context); + } + } + #endregion Convert + + /// + /// Gets the name of the member. It might be null if this instance is disposed. + /// + public string Name { get; internal set; } + + /// Gets host of the . + public Node Host { get; internal set; } + + /// Gets reference to the parser. + public DynamicParser Parser + { + get { return _parser; } + internal set + { + _parser = value; + if (_parser != null) + _parser._allNodes.Add(this); + } + } + /// + /// Initializes a new instance of the class. + /// + internal Node() + { + IsDisposed = false; + } + /// + /// Initializes a new instance of the class. + /// + /// The host. + internal Node(Node host) + : this() + { + if (host == null) + throw new ArgumentNullException("host", "Host cannot be null."); + + Host = host; + } + /// + /// Initializes a new instance of the class. + /// + /// The name. + internal Node(string name) + : this() + { + Name = name.Validated("Name"); + } + /// + /// Initializes a new instance of the class. + /// + /// The host. + /// The name. + /// Host cannot be null. + internal Node(Node host, string name) + : this(host) + { + Name = name.Validated("Name"); + } + /// + /// Initializes a new instance of the class. + /// + /// The info. + /// The context. + protected Node(SerializationInfo info, StreamingContext context) + { + Name = info.GetString("MemberName"); + + string type = info.GetString("HostType"); + Host = type == "NULL" ? null : (Node)info.GetValue("HostItem", Type.GetType(type)); + } + /// Returns whether the given node is an ancestor of this instance. + /// The node to test. + /// True if the given node is an ancestor of this instance. + public bool IsNodeAncestor(Node node) + { + if (node != null) + { + Node parent = Host; + + while (parent != null) + { + if (object.ReferenceEquals(parent, node)) + return true; + + parent = parent.Host; + } + } + return false; + } + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Node::Disposed}"; + + return "{DynamicParser::Node::Empty}"; + } + #region Implementation of IDynamicMetaObjectProvider + + /// Returns the responsible + /// for binding operations performed on this object. + /// The expression tree representation of the runtime value. + /// The to bind this object. + /// Thrown if this instance is disposed. + public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter) + { + if (IsDisposed) + throw new ObjectDisposedException("DynamicParser.Node"); + + return new MetaNode( + parameter, + BindingRestrictions.GetInstanceRestriction(parameter, this), + this); + } + #endregion Implementation of IDynamicMetaObjectProvider + + #region Implementation of IFinalizerDisposable + + /// Finalizes an instance of the class. + ~Node() + { + Dispose(false); + } + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public virtual void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public virtual void Dispose(bool disposing) + { + if (disposing) + { + IsDisposed = true; + + if (Host != null && !Host.IsDisposed) + Host.Dispose(); + + Host = null; + + Parser = null; + } + } + #endregion Implementation of IFinalizerDisposable + + #region Implementation of ISerializable + + /// + /// Populates a with the data needed to serialize the target object. + /// + /// The to populate with data. + /// The destination (see ) for this serialization. + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (!string.IsNullOrEmpty(Name)) + info.AddValue("MemberName", Name); + + info.AddValue("HostType", Host == null ? "NULL" : Host.GetType().AssemblyQualifiedName); + if (Host != null) + info.AddValue("HostItem", Host); + } + #endregion Implementation of ISerializable + } + #endregion Node + + #region Data + + private List _arguments = new List(); + private List _allNodes = new List(); + private object _uncertainResult; + + #endregion Data + + #region Properties + + /// Gets the last node (root of the tree). + public Node Last { get; internal set; } + + /// + /// Gets an enumeration containing the dynamic arguments used in the dynamic lambda expression parsed. + /// + public IEnumerable Arguments + { + get + { + List list = new List(); + if (!IsDisposed && _arguments != null) + list.AddRange(_arguments); + + foreach (Node.Argument arg in list) + yield return arg; + + list.Clear(); + list = null; + } + } + /// + /// Gets the number of dynamic arguments used in the dynamic lambda expression parsed. + /// + public int Count + { + get { return _arguments == null ? 0 : _arguments.Count; } + } + /// + /// Gets the result of the parsing of the dynamic lambda expression. This result can be either an arbitrary object, + /// including null, if the expression resolves to it, or an instance of the class that + /// contains the last logic expression evaluated when parsing the dynamic lambda expression. + /// + public object Result + { + get { return _uncertainResult ?? Last; } + } + #endregion Properties + + private DynamicParser(Delegate f) + { + // I know this can be almost a one liner + // but it causes memory leaks when so. + ParameterInfo[] pars = f.Method.GetParameters(); + foreach (ParameterInfo p in pars) + { + int attrs = p.GetCustomAttributes(typeof(DynamicAttribute), true).Length; + if (attrs != 0) + { + Node.Argument par = new Node.Argument(p.Name) { Parser = this }; + this._arguments.Add(par); + } + else + throw new ArgumentException(string.Format("Argument '{0}' must be dynamic.", p.Name)); + } + try + { + _uncertainResult = f.DynamicInvoke(_arguments.ToArray()); + } + catch (TargetInvocationException e) + { + if (e.InnerException != null) throw e.InnerException; + throw; + } + } + /// + /// Parses the dynamic lambda expression given in the form of a delegate, and returns a new instance of the + /// class that holds the dynamic arguments used in the dynamic lambda expression, and + /// the result of the parsing. + /// + /// The dynamic lambda expression to parse. + /// A new instance of . + public static DynamicParser Parse(Delegate f) + { + return new DynamicParser(f); + } + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() + { + if (IsDisposed) + return "{DynamicParser::Disposed}"; + + StringBuilder sb = new StringBuilder(); + + sb.Append("("); + bool first = true; + + if (_arguments != null) + { + foreach (Node.Argument arg in _arguments) + { + if (!first) sb.Append(", "); else first = false; + sb.Append(arg); + } + } + sb.Append(")"); + + sb.AppendFormat(" => {0}", Result.Sketch()); + + return sb.ToString(); + } + #region Implementation of IExtendedDisposable + + /// Gets a value indicating whether this instance is disposed. + public bool IsDisposed { get; private set; } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (IsDisposed) + return; + + IsDisposed = true; + + if (_uncertainResult != null) + { + if (_uncertainResult is Node) + ((Node)_uncertainResult).Dispose(); + + _uncertainResult = null; + } + if (Last != null) + { + if (!Last.IsDisposed) + Last.Dispose(); + + Last = null; + } + if (_arguments != null) + { + _arguments.ForEach(x => { if (!x.IsDisposed) x.Dispose(); }); + + _arguments.Clear(); + _arguments = null; + } + if (_allNodes != null) + { + _allNodes.ForEach(x => { if (!x.IsDisposed) x.Dispose(); }); + + _allNodes.Clear(); + _allNodes = null; + } + } + #endregion Implementation of IExtendedDisposable + } + /// 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; - - 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) + } + private bool CompareTypes(ParameterInfo[] parameters, Type[] types) { - query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); - keyNotDefined = false; + 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); } } - - if (keyNotDefined) - throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", - this.GetType().FullName)); - } - - private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicDeleteQueryBuilder query) - { - bool keyNotDefined = true; - - foreach (var cm in mapper.ColumnsMap) - { - if (cm.Value.Column != null && cm.Value.Column.IsKey) - { - query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); - keyNotDefined = false; - } - } - - if (keyNotDefined) - throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", - this.GetType().FullName)); - } - - private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicSelectQueryBuilder query) - { - bool keyNotDefined = true; - - foreach (var cm in mapper.ColumnsMap) - { - if (cm.Value.Column != null && cm.Value.Column.IsKey) - { - var v = cm.Value.Get(this); - - if (v == null) - throw new InvalidOperationException(String.Format("Class '{0}' have key columns {1} not filled with data.", - this.GetType().FullName, cm.Value.Name)); - - query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); - keyNotDefined = false; - } - } - - if (keyNotDefined) - throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", - this.GetType().FullName)); - } - - #endregion Query Helpers - } - - /// Possible states of dynamic database objects. - public enum DynamicEntityState - { - /// Default state. This state will only permit to refresh data from database. - /// In this state repository will be unable to tell if object with this state should be added - /// or updated in database, but you can still manually perform update or insert on such object. - Unknown, - - /// This state should be set to new objects in database. - New, - - /// This state is ser when data is refreshed from database or object was loaded from repository. - Existing, - - /// You can set this state to an object if you want repository to perform delete from database. - ToBeDeleted, - - /// This state is set for objects that were deleted from database. - Deleted, - } - - /// Base repository class for specified object type. - /// Type of stored object. - public class DynamicRepositoryBase : IDisposable where T : DynamicEntityBase - { - private DynamicDatabase _database; - - /// Initializes a new instance of the class. - /// The database. - public DynamicRepositoryBase(DynamicDatabase database) - { - _database = database; - } - - /// Get all rows from database. - /// Objects enumerator. - public virtual IEnumerable GetAll() - { - using (var q = _database.From()) - return EnumerateQuery(q); - } - - /// Get rows from database by custom query. - /// The query. - /// Query must be based on object type. - /// Objects enumerator. - public virtual IEnumerable GetByQuery(IDynamicSelectQueryBuilder query) - { - return EnumerateQuery(query); - } - - private IEnumerable EnumerateQuery(IDynamicSelectQueryBuilder query, bool forceType = true) - { - if (forceType) - { - var mapper = DynamicMapperCache.GetMapper(typeof(T)); - - var tn = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? - mapper.Type.Name : mapper.Table.Name; - - if (!query.Tables.Any(t => t.Name == tn)) - throw new InvalidOperationException(string.Format("Query is not related to '{0}' class.", typeof(T).FullName)); - } - - foreach (var o in query.Execute()) - { - o.SetDynamicEntityState(DynamicEntityState.Existing); - yield return o; - } - } - - /// Saves single object to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Save(T element) - { - return element.Save(_database); - } - - /// Saves collection of objects to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Save(IEnumerable element) - { - return element.All(x => x.Save(_database)); - } - - /// Insert single object to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Insert(T element) - { - return element.Insert(_database); - } - - /// Insert collection of objects to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Insert(IEnumerable element) - { - return element.All(x => x.Insert(_database)); - } - - /// Update single object to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Update(T element) - { - return element.Update(_database); - } - - /// Update collection of objects to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Update(IEnumerable element) - { - return element.All(x => x.Update(_database)); - } - - /// Delete single object to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Delete(T element) - { - return element.Delete(_database); - } - - /// Delete collection of objects to database. - /// The element. - /// Returns true if operation was successful. - public virtual bool Delete(IEnumerable element) - { - return element.All(x => x.Delete(_database)); - } - - /// Releases unmanaged and - optionally - managed resources. - public virtual void Dispose() - { - _database = null; - } - } - - /// Class containing changed property data. - /// - public class DynamicPropertyChangingEventArgs : EventArgs - { - /// Gets the name of the property. - /// The name of the property. - public string PropertyName { get; private set; } - - /// Gets the old property value. - /// The old value. - public object OldValue { get; private set; } - - /// Gets the new property value. - /// The new value. - public object NewValue { get; private set; } - - /// Initializes a new instance of the class. - /// Name of the property. - /// The old property value. - /// The new property value. - public DynamicPropertyChangingEventArgs(string propertyName, object oldValue, object newValue) - { - PropertyName = propertyName; - OldValue = oldValue; - NewValue = newValue; - } } } -} + namespace Mapper + { + /// Allows to add table name to class. + [AttributeUsage(AttributeTargets.Property)] + public class ColumnAttribute : Attribute + { + /// Gets or sets name. + public string Name { get; set; } + /// Gets or sets column type. + /// Used when overriding schema. + public DbType? Type { get; set; } + + /// Gets or sets a value indicating whether column is a key. + public bool IsKey { get; set; } + + /// Gets or sets a value indicating whether column allows null or not. + /// Information only. + public bool AllowNull { get; set; } + + /// Gets or sets a value indicating whether column should have unique value. + /// Used when overriding schema. + public bool? IsUnique { get; set; } + + /// Gets or sets column size. + /// Used when overriding schema. + public int? Size { get; set; } + + /// Gets or sets column precision. + /// Used when overriding schema. + public byte? Precision { get; set; } + + /// Gets or sets column scale. + /// Used when overriding schema. + public byte? Scale { get; set; } + + /// Gets or sets a value indicating whether this column is no allowed to be inserted. + /// This is only a suggestion to automated mapping. + public bool IsNoInsert { get; set; } + + /// Gets or sets a value indicating whether this column is no allowed to be updated. + /// This is only a suggestion to automated mapping. + public bool IsNoUpdate { get; set; } + + #region Constructors + + /// Initializes a new instance of the class. + public ColumnAttribute() + { + AllowNull = true; + } + /// Initializes a new instance of the class. + /// Name of column. + public ColumnAttribute(string name) + : this() + { + Name = name; + } + /// Initializes a new instance of the class. + /// Set column as a key column. + public ColumnAttribute(bool isKey) + : this() + { + IsKey = isKey; + } + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + public ColumnAttribute(string name, bool isKey) + : this(name) + { + IsKey = isKey; + } + /// Initializes a new instance of the class. + /// Set column as a key column. + /// Set column type. + public ColumnAttribute(bool isKey, DbType type) + : this(isKey) + { + Type = type; + } + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + public ColumnAttribute(string name, bool isKey, DbType type) + : this(name, isKey) + { + Type = type; + } + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + /// Set column value size. + public ColumnAttribute(string name, bool isKey, DbType type, int size) + : this(name, isKey, type) + { + Size = size; + } + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + /// Set column value precision. + /// Set column value scale. + public ColumnAttribute(string name, bool isKey, DbType type, byte precision, byte scale) + : this(name, isKey, type) + { + Precision = precision; + Scale = scale; + } + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column type. + /// Set column value size. + /// Set column value precision. + /// Set column value scale. + public ColumnAttribute(string name, bool isKey, DbType type, int size, byte precision, byte scale) + : this(name, isKey, type, precision, scale) + { + Size = size; + } + /// Initializes a new instance of the class. + /// Name of column. + /// Set column as a key column. + /// Set column has unique value. + /// Set column type. + /// Set column value size. + /// Set column value precision. + /// Set column value scale. + public ColumnAttribute(string name, bool isKey, bool isUnique, DbType type, int size, byte precision, byte scale) + : this(name, isKey, type, size, precision, scale) + { + IsUnique = isUnique; + } + #endregion Constructors + } + /// Type cast helper. + public static class DynamicCast + { + /// Gets the default value. + /// The type. + /// Default instance. + public static object GetDefaultValue(this Type type) + { + return type.IsValueType ? TypeDefaults.GetOrAdd(type, t => Activator.CreateInstance(t)) : null; + } + /// Casts the object to this type. + /// The type to which cast value. + /// The value to cast. + /// Value casted to new type. + public static object CastObject(this Type type, object val) + { + return GetConverter(type, val)(val); + } + private static readonly ConcurrentDictionary TypeDefaults = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary> TypeAsCasts = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> TypeConvert = new ConcurrentDictionary>(); + private static readonly ParameterExpression ConvParameter = Expression.Parameter(typeof(object), "val"); + + [MethodImpl(MethodImplOptions.Synchronized)] + private static Func GetConverter(Type targetType, object val) + { + Func fn; + + if (!targetType.IsValueType && !val.GetType().IsValueType) + { + if (!TypeAsCasts.TryGetValue(targetType, out fn)) + { + UnaryExpression instanceCast = Expression.TypeAs(ConvParameter, targetType); + + fn = Expression.Lambda>(Expression.TypeAs(instanceCast, typeof(object)), ConvParameter).Compile(); + TypeAsCasts.AddOrUpdate(targetType, fn, (t, f) => fn); + } + } + else + { + var fromType = val != null ? val.GetType() : typeof(object); + var key = new PairOfTypes(fromType, targetType); + if (TypeConvert.TryGetValue(key, out fn)) + return fn; + + fn = (Func)Expression.Lambda(Expression.Convert(Expression.Convert(Expression.Convert(ConvParameter, fromType), targetType), typeof(object)), ConvParameter).Compile(); + TypeConvert.AddOrUpdate(key, fn, (t, f) => fn); + } + return fn; + } + private class PairOfTypes + { + private readonly Type _first; + private readonly Type _second; + + public PairOfTypes(Type first, Type second) + { + this._first = first; + this._second = second; + } + public override int GetHashCode() + { + return (31 * _first.GetHashCode()) + _second.GetHashCode(); + } + public override bool Equals(object obj) + { + if (obj == this) + return true; + + var other = obj as PairOfTypes; + if (other == null) + return false; + + return _first.Equals(other._first) + && _second.Equals(other._second); + } + } + } + /// Class with mapper cache. + public static class DynamicMapperCache + { + private static readonly object SyncLock = new object(); + private static Dictionary _cache = new Dictionary(); + + /// Get type mapper. + /// Type of mapper. + /// Type mapper. + public static DynamicTypeMap GetMapper() + { + return GetMapper(typeof(T)); + } + /// Get type mapper. + /// Type of mapper. + /// Type mapper. + public static DynamicTypeMap GetMapper(Type type) + { + if (type == null) + return null; + /*if (type.IsAnonymous()) + return null;*/ + + DynamicTypeMap mapper = null; + + lock (SyncLock) + { + if (!_cache.TryGetValue(type, out mapper)) + { + mapper = new DynamicTypeMap(type); + + if (mapper != null) + _cache.Add(type, mapper); + } + } + return mapper; + } + } + /// Exception thrown when mapper fails to set or get a property. + /// + public class DynamicMapperException : Exception + { + /// Initializes a new instance of the class. + public DynamicMapperException() + { + } + /// Initializes a new instance of the class. + /// The message that describes the error. + public DynamicMapperException(string message) : base(message) + { + } + /// Initializes a new instance of the class. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public DynamicMapperException(string message, Exception innerException) : base(message, innerException) + { + } + } + /// Dynamic property invoker. + public class DynamicPropertyInvoker + { + internal class ParameterSpec + { + public string Name { get; set; } + + public DbType Type { get; set; } + + public int Ordinal { get; set; } + } + /// Gets the array type of property if main type is a form of collection. + public Type ArrayType { get; private set; } + + /// Gets a value indicating whether this property is in fact a generic list. + public bool IsGnericEnumerable { get; private set; } + + /// Gets the type of property. + public Type Type { get; private set; } + + /// Gets value getter. + public Func Get { get; private set; } + + /// Gets value setter. + public Action Setter { get; private set; } + + /// Gets the property information. + public PropertyInfo PropertyInfo { get; private set; } + + /// Gets name of property. + public string Name { get; private set; } + + /// Gets type column description. + public ColumnAttribute Column { get; private set; } + + /// Gets type list of property requirements. + public List Requirements { get; private set; } + + /// Gets a value indicating whether this is ignored in some cases. + public bool Ignore { get; private set; } + + /// Gets a value indicating whether this instance hold data contract type. + public bool IsDataContract { get; private set; } + + /// Initializes a new instance of the class. + /// Property info to be invoked in the future. + /// Column attribute if exist. + public DynamicPropertyInvoker(PropertyInfo property, ColumnAttribute attr) + { + PropertyInfo = property; + Name = property.Name; + Type = property.PropertyType; + + object[] ignore = property.GetCustomAttributes(typeof(IgnoreAttribute), false); + Requirements = property.GetCustomAttributes(typeof(RequiredAttribute), false).Cast().ToList(); + + Ignore = ignore != null && ignore.Length > 0; + + IsGnericEnumerable = Type.IsGenericEnumerable(); + + ArrayType = Type.IsArray ? Type.GetElementType() : + IsGnericEnumerable ? Type.GetGenericArguments().First() : + Type; + + IsDataContract = ArrayType.GetCustomAttributes(false).Any(x => x.GetType().Name == "DataContractAttribute"); + + if (ArrayType.IsArray) + throw new InvalidOperationException("Jagged arrays are not supported"); + + if (ArrayType.IsGenericEnumerable()) + throw new InvalidOperationException("Enumerables of enumerables are not supported"); + + Column = attr; + + if (attr != null && attr.AllowNull && !Type.IsNullableType() && Type != typeof(string)) + attr.AllowNull = false; + + if (property.CanRead) + Get = CreateGetter(property); + + if (property.CanWrite) + Setter = CreateSetter(property); + } + private Func CreateGetter(PropertyInfo property) + { + if (property == null || !property.CanRead) + return null; + + var objParm = Expression.Parameter(typeof(object), "o"); + var target = property.DeclaringType.IsValueType + ? (Expression)Expression.Convert(objParm, property.DeclaringType) + : Expression.TypeAs(objParm, property.DeclaringType); + + return Expression.Lambda>( + Expression.Convert( + Expression.Property( + target, + property.Name), + typeof(object)), objParm).Compile(); + } + private Action CreateSetter(PropertyInfo property) + { + if (!property.CanWrite) + return null; + + var objParm = Expression.Parameter(typeof(object), "o"); + var valueParm = Expression.Parameter(typeof(object), "value"); + + return Expression.Lambda>( + Expression.Assign( + Expression.Property( + Expression.Convert(objParm, property.DeclaringType), + property.Name), + Expression.Convert(valueParm, property.PropertyType)), + objParm, valueParm).Compile(); + } + /// Sets the specified value to destination object. + /// The destination object. + /// The value. + public void Set(object dest, object val, bool byProperty = false) + { + object value = null; + + try + { + if (!Type.IsAssignableFrom(val.GetType())) + { + if (Type.IsArray || IsGnericEnumerable) + { + if (val != null) + { + if (val is IEnumerable) + { + var lst = (val as IEnumerable).Select(x => GetElementVal(ArrayType, x, byProperty)).ToList(); + + value = Array.CreateInstance(ArrayType, lst.Count); + + int i = 0; + foreach (var e in lst) + ((Array)value).SetValue(e, i++); + } + else + { + value = Array.CreateInstance(ArrayType, 1); + ((Array)value).SetValue(GetElementVal(ArrayType, val, byProperty), 0); + } + } + else + value = Array.CreateInstance(ArrayType, 0); + } + else + value = GetElementVal(Type, val, byProperty); + } + else + value = val; + + Setter(dest, value); + } + catch (Exception ex) + { + throw new DynamicMapperException( + string.Format("Error trying to convert and set value '{0}' of type '{1}' to type '{2}' in object of type '{3}'", + val == null ? string.Empty : val.ToString(), val.GetType(), Type.FullName, dest.GetType().FullName), + ex); + } + } + private object GetElementVal(System.Type etype, object val, bool byProperty) + { + bool nullable = etype.IsGenericType && etype.GetGenericTypeDefinition() == typeof(Nullable<>); + Type type = Nullable.GetUnderlyingType(etype) ?? etype; + + if (val == null && type.IsValueType) + { + if (nullable) + return null; + else + return Activator.CreateInstance(Type); + } + else if ((val == null && !type.IsValueType) || (val != null && type == val.GetType())) + return val; + else if (type.IsEnum && val.GetType().IsValueType) + return Enum.ToObject(type, val); + else if (type.IsEnum) + try + { + return Enum.Parse(type, val.ToString()); + } + catch (ArgumentException) + { + if (nullable) + return null; + + throw; + } + else if (Type == typeof(string) && val.GetType() == typeof(Guid)) + return val.ToString(); + else if (Type == typeof(Guid)) + { + if (val.GetType() == typeof(byte[])) + return new Guid((byte[])val); + else if (val.GetType() == typeof(string)) + { + Guid g; + return Guid.TryParse((string)val, out g) ? g : Guid.Empty; + } + else return (nullable) ? null : (object)Guid.Empty; + } + else if (!typeof(IConvertible).IsAssignableFrom(type) && (IsDataContract || (!type.IsValueType && val is IDictionary))) + { + if (byProperty) + return val.MapByProperty(type); + + return val.Map(type); + } + else + try + { + return Convert.ChangeType(val, type); + } + catch + { + if (nullable) + return null; + + throw; + } + } + #region Type command cache + + internal ParameterSpec InsertCommandParameter { get; set; } + + internal ParameterSpec UpdateCommandParameter { get; set; } + + internal ParameterSpec DeleteCommandParameter { get; set; } + + #endregion Type command cache + } + /// Represents type columnMap. + public class DynamicTypeMap + { + /// Gets mapper destination type creator. + public Type Type { get; private set; } + + /// Gets type table description. + public TableAttribute Table { get; private set; } + + /// Gets object creator. + public Func Creator { get; private set; } + + /// Gets map of columns to properties. + /// Key: Column name (lower), Value: . + public Dictionary ColumnsMap { get; private set; } + + /// Gets map of properties to column. + /// Key: Property name, Value: Column name. + public Dictionary PropertyMap { get; private set; } + + /// Gets list of ignored properties. + public List Ignored { get; private set; } + + /// Initializes a new instance of the class. + /// Type to which columnMap objects. + public DynamicTypeMap(Type type) + { + Type = type; + + object[] attr = type.GetCustomAttributes(typeof(TableAttribute), false); + + if (attr != null && attr.Length > 0) + Table = (TableAttribute)attr[0]; + + Creator = CreateCreator(); + CreateColumnAndPropertyMap(); + } + private void CreateColumnAndPropertyMap() + { + Dictionary columnMap = new Dictionary(); + Dictionary propertyMap = new Dictionary(); + List ignored = new List(); + + foreach (PropertyInfo pi in GetAllMembers(Type).Where(x => x is PropertyInfo).Cast()) + { + // Skip indexers + if (pi.GetIndexParameters().Any()) + continue; + + ColumnAttribute attr = null; + + object[] attrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true); + + if (attrs != null && attrs.Length > 0) + attr = (ColumnAttribute)attrs[0]; + + string col = attr == null || string.IsNullOrEmpty(attr.Name) ? pi.Name : attr.Name; + + DynamicPropertyInvoker val = new DynamicPropertyInvoker(pi, attr); + columnMap.Add(col.ToLower(), val); + + propertyMap.Add(pi.Name, col); + + if (val.Ignore) + ignored.Add(pi.Name); + } + ColumnsMap = columnMap; + PropertyMap = propertyMap; + + Ignored = ignored; ////columnMap.Where(i => i.Value.Ignore).Select(i => i.Value.Name).ToList(); + } + private Func CreateCreator() + { + var c = Type.GetConstructor(Type.EmptyTypes); + if (c == null) + c = Type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null); + + if (c != null) + return Expression.Lambda>(Expression.New(Type)).Compile(); + + return null; + } + /// Create object of type and fill values from source. + /// Object containing values that will be mapped to newly created object. + /// New object of type with matching values from source. + public object Create(object source) + { + return Map(source, Creator()); + } + /// Create object of type and fill values from source using property names. + /// Object containing values that will be mapped to newly created object. + /// New object of type with matching values from source. + public object CreateByProperty(object source) + { + return MapByProperty(source, Creator()); + } + /// Fill values from source to object in destination. + /// Object containing values that will be mapped to newly created object. + /// Object of type to which copy values from source. + /// Object of type with matching values from source. + public object Map(object source, object destination) + { + DynamicPropertyInvoker dpi = null; + + foreach (KeyValuePair item in source.ToDictionary()) + { + if (ColumnsMap.TryGetValue(item.Key.ToLower(), out dpi) && item.Value != null) + if (dpi.Setter != null) + dpi.Set(destination, item.Value); + } + return destination; + } + /// Fill values from source to object in destination using property names. + /// Object containing values that will be mapped to newly created object. + /// Object of type to which copy values from source. + /// Object of type with matching values from source. + public object MapByProperty(object source, object destination) + { + string cn = null; + DynamicPropertyInvoker dpi = null; + + foreach (KeyValuePair item in source.ToDictionary()) + { + if (PropertyMap.TryGetValue(item.Key, out cn) && item.Value != null) + if (ColumnsMap.TryGetValue(cn.ToLower(), out dpi) && item.Value != null) + if (dpi.Setter != null) + dpi.Set(destination, item.Value, true); + } + return destination; + } + /// Validates the object. + /// The value. + /// List of not valid results. + public IList ValidateObject(object val) + { + var result = new List(); + + if (val == null || val.GetType() != Type) + return null; + + foreach (var prop in ColumnsMap.Values) + { + if (prop.Requirements == null || !prop.Requirements.Any()) + continue; + + var v = prop.Get(val); + + foreach (var r in prop.Requirements.Where(x => !x.ElementRequirement)) + { + var valid = r.ValidateSimpleValue(prop, v); + + if (valid == ValidateResult.Valid) + { + if (prop.Type.IsArray || prop.IsGnericEnumerable) + { + var map = DynamicMapperCache.GetMapper(prop.ArrayType); + + var list = v as IEnumerable; + + if (list == null) + { + var enumerable = v as IEnumerable; + if (enumerable != null) + list = enumerable.Cast(); + } + if (list != null) + foreach (var item in list) + { + if (prop.Requirements.Any(x => x.ElementRequirement)) + { + foreach (var re in prop.Requirements.Where(x => x.ElementRequirement)) + { + var validelem = re.ValidateSimpleValue(prop.ArrayType, prop.ArrayType.IsGenericEnumerable(), item); + + if (validelem == ValidateResult.NotSupported) + { + result.AddRange(map.ValidateObject(item)); + break; + } + else if (validelem != ValidateResult.Valid) + result.Add(new ValidationResult() + { + Property = prop, + Requirement = r, + Value = item, + Result = validelem, + }); + } + } + else + result.AddRange(map.ValidateObject(item)); + } + } + continue; + } + if (valid == ValidateResult.NotSupported) + { + result.AddRange(DynamicMapperCache.GetMapper(prop.Type).ValidateObject(v)); + continue; + } + result.Add(new ValidationResult() + { + Property = prop, + Requirement = r, + Value = v, + Result = valid, + }); + } + } + return result; + } + private IEnumerable GetAllMembers(Type type) + { + if (type.IsInterface) + { + List members = new List(); + List considered = new List(); + Queue queue = new Queue(); + + considered.Add(type); + queue.Enqueue(type); + + while (queue.Count > 0) + { + Type subType = queue.Dequeue(); + foreach (Type subInterface in subType.GetInterfaces()) + { + if (considered.Contains(subInterface)) continue; + + considered.Add(subInterface); + queue.Enqueue(subInterface); + } + MemberInfo[] typeProperties = subType.GetMembers( + BindingFlags.FlattenHierarchy + | BindingFlags.Public + | BindingFlags.Instance); + + IEnumerable newPropertyInfos = typeProperties + .Where(x => !members.Contains(x)); + + members.InsertRange(0, newPropertyInfos); + } + return members; + } + return type.GetMembers(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance); + } + #region Type command cache + + internal string InsertCommandText { get; set; } + + internal string UpdateCommandText { get; set; } + + internal string DeleteCommandText { get; set; } + + #endregion Type command cache + } + /// Allows to add ignore action to property. + /// Property still get's mapped from output. + [AttributeUsage(AttributeTargets.Property)] + public class IgnoreAttribute : Attribute + { + } + /// Allows to add table name to class. + [AttributeUsage(AttributeTargets.Class)] + public class TableAttribute : Attribute + { + /// Gets or sets table owner name. + public string Owner { get; set; } + + /// Gets or sets name. + public string Name { get; set; } + + /// Gets or sets a value indicating whether override database + /// schema values. + /// If database doesn't support schema, you still have to + /// set this to true to get schema from type. + public bool Override { get; set; } + } + } + namespace Objects + { + /// Base class for strong typed objects. + public class DynamicEntityBase + { + private Dictionary _changedFields = new Dictionary(); + private DynamicEntityState _dynamicEntityState = DynamicEntityState.Unknown; + + /// Occurs when object property is changing. + public event EventHandler PropertyChanging; + + /// Gets the state of the dynamic entity. + /// Current state of entity. + public virtual DynamicEntityState GetDynamicEntityState() { return _dynamicEntityState; } + + /// Sets the state of the dynamic entity. + /// Using this method will reset modified fields list. + /// The state. + public virtual void SetDynamicEntityState(DynamicEntityState state) + { + _dynamicEntityState = state; + + if (_changedFields != null) + _changedFields.Clear(); + } + /// Called when object property is changing. + /// Name of the property. + /// The old property value. + /// The new property value. + protected virtual void OnPropertyChanging(string propertyName, object oldValue, object newValue) + { + OnPropertyChanging(new DynamicPropertyChangingEventArgs(propertyName, oldValue, newValue)); + } + /// Raises the event. + /// The instance containing the event data. + protected virtual void OnPropertyChanging(DynamicPropertyChangingEventArgs e) + { + _changedFields[e.PropertyName] = e.NewValue; + if (PropertyChanging != null) + PropertyChanging(this, e); + } + /// Validates this object instance. + /// Returns list of containing results of validation. + public virtual IList Validate() + { + return DynamicMapperCache.GetMapper(this.GetType()).ValidateObject(this); + } + /// Saves this object to database. + /// The database. + /// Returns true if operation was successful. + /// Can be thrown when object is in invalid state. + public virtual bool Save(DynamicDatabase database) + { + switch (GetDynamicEntityState()) + { + default: + case DynamicEntityState.Unknown: + throw new InvalidOperationException("Unknown object state. Unable to decide whish action should be performed."); + + case DynamicEntityState.New: + return Insert(database); + + case DynamicEntityState.Existing: + if (IsModified()) + return Update(database); + + return true; + + case DynamicEntityState.ToBeDeleted: + return Delete(database); + + case DynamicEntityState.Deleted: + throw new InvalidOperationException("Unable to do any database action on deleted object."); + } + } + /// Determines whether this instance is in existing state and fields was modified since this state was set modified. + /// Returns true if this instance is modified; otherwise, false. + public virtual bool IsModified() + { + if (GetDynamicEntityState() != DynamicEntityState.Existing) + return false; + + return _changedFields != null && _changedFields.Any(); + } + #region Insert/Update/Delete + + /// Inserts this object to database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Insert(DynamicDatabase db) + { + using (var query = db.Insert(this.GetType())) + { + if (query + .Values(x => this) + .Execute() > 0) + { + _changedFields.Clear(); + SetDynamicEntityState(DynamicEntityState.Existing); + return true; + } + } + return false; + } + /// Updates this object in database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Update(DynamicDatabase db) + { + var t = GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + using (var query = db.Update(t)) + { + MakeQueryWhere(mapper, query); + + bool any = false; + + if (_changedFields.Any()) + { + foreach (var cf in _changedFields) + { + var cn = mapper.PropertyMap[cf.Key]; + var pm = mapper.ColumnsMap[cn.ToLower()]; + if (pm.Ignore) + continue; + + if (pm.Column != null) + { + if (pm.Column.IsKey || pm.Column.IsNoUpdate) + continue; + + if (!pm.Column.AllowNull && cf.Value == null) + continue; + } + query.Values(cn, cf.Value); + any = true; + } + } + if (!any) + foreach (var pmk in mapper.ColumnsMap) + { + var pm = pmk.Value; + var val = pm.Get(this); + var cn = pm.Name; + + if (pm.Ignore) + continue; + + if (pm.Column != null) + { + if (!string.IsNullOrEmpty(pm.Column.Name)) + cn = pm.Column.Name; + + if (pm.Column.IsKey) + continue; + + if (!pm.Column.AllowNull && val == null) + continue; + } + query.Values(cn, val); + } + if (query.Execute() == 0) + return false; + + SetDynamicEntityState(DynamicEntityState.Existing); + _changedFields.Clear(); + + return true; + } + } + /// Deletes this object from database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Delete(DynamicDatabase db) + { + var t = this.GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + + using (var query = db.Delete(t)) + { + MakeQueryWhere(mapper, query); + + if (query.Execute() == 0) + return false; + + SetDynamicEntityState(DynamicEntityState.Deleted); + } + return true; + } + #endregion Insert/Update/Delete + + #region Select + + /// Refresh non key data from database. + /// The database. + /// All properties that are primary key values must be filled. + /// Returns true if operation was successful. + public virtual bool Refresh(DynamicDatabase db) + { + var t = this.GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + using (var query = db.From(t)) + { + MakeQueryWhere(mapper, query); + var o = (query.Execute() as IEnumerable).FirstOrDefault(); + + if (o == null) + return false; + + mapper.Map(o, this); + + SetDynamicEntityState(DynamicEntityState.Existing); + _changedFields.Clear(); + } + return true; + } + #endregion Select + + #region Query Helpers + + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicUpdateQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicDeleteQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicSelectQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + var v = cm.Value.Get(this); + + if (v == null) + throw new InvalidOperationException(String.Format("Class '{0}' have key columns {1} not filled with data.", + this.GetType().FullName, cm.Value.Name)); + + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + #endregion Query Helpers + } + /// Possible states of dynamic database objects. + public enum DynamicEntityState + { + /// Default state. This state will only permit to refresh data from database. + /// In this state repository will be unable to tell if object with this state should be added + /// or updated in database, but you can still manually perform update or insert on such object. + Unknown, + + /// This state should be set to new objects in database. + New, + + /// This state is ser when data is refreshed from database or object was loaded from repository. + Existing, + + /// You can set this state to an object if you want repository to perform delete from database. + ToBeDeleted, + + /// This state is set for objects that were deleted from database. + Deleted, + } + /// Class containing changed property data. + /// + public class DynamicPropertyChangingEventArgs : EventArgs + { + /// Gets the name of the property. + /// The name of the property. + public string PropertyName { get; private set; } + + /// Gets the old property value. + /// The old value. + public object OldValue { get; private set; } + + /// Gets the new property value. + /// The new value. + public object NewValue { get; private set; } + + /// Initializes a new instance of the class. + /// Name of the property. + /// The old property value. + /// The new property value. + public DynamicPropertyChangingEventArgs(string propertyName, object oldValue, object newValue) + { + PropertyName = propertyName; + OldValue = oldValue; + NewValue = newValue; + } + } + /// Base repository class for specified object type. + /// Type of stored object. + public class DynamicRepositoryBase : IDisposable where T : DynamicEntityBase + { + private DynamicDatabase _database; + + /// Initializes a new instance of the class. + /// The database. + public DynamicRepositoryBase(DynamicDatabase database) + { + _database = database; + } + /// Get all rows from database. + /// Objects enumerator. + public virtual IEnumerable GetAll() + { + using (var q = _database.From()) + return EnumerateQuery(q); + } + /// Get rows from database by custom query. + /// The query. + /// Query must be based on object type. + /// Objects enumerator. + public virtual IEnumerable GetByQuery(IDynamicSelectQueryBuilder query) + { + return EnumerateQuery(query); + } + private IEnumerable EnumerateQuery(IDynamicSelectQueryBuilder query, bool forceType = true) + { + if (forceType) + { + var mapper = DynamicMapperCache.GetMapper(typeof(T)); + + var tn = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + + if (!query.Tables.Any(t => t.Name == tn)) + throw new InvalidOperationException(string.Format("Query is not related to '{0}' class.", typeof(T).FullName)); + } + foreach (var o in query.Execute()) + { + o.SetDynamicEntityState(DynamicEntityState.Existing); + yield return o; + } + } + /// Saves single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Save(T element) + { + return element.Save(_database); + } + /// Saves collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Save(IEnumerable element) + { + return element.All(x => x.Save(_database)); + } + /// Insert single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Insert(T element) + { + return element.Insert(_database); + } + /// Insert collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Insert(IEnumerable element) + { + return element.All(x => x.Insert(_database)); + } + /// Update single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Update(T element) + { + return element.Update(_database); + } + /// Update collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Update(IEnumerable element) + { + return element.All(x => x.Update(_database)); + } + /// Delete single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Delete(T element) + { + return element.Delete(_database); + } + /// Delete collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Delete(IEnumerable element) + { + return element.All(x => x.Delete(_database)); + } + /// Releases unmanaged and - optionally - managed resources. + public virtual void Dispose() + { + _database = null; + } + } + } + namespace Validation + { + /// Required attribute can be used to validate fields in objects using mapper class. + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class RequiredAttribute : Attribute + { + /// Gets or sets minimum value or length of field. + public decimal? Min { get; set; } + + /// Gets or sets maximum value or length of field. + public decimal? Max { get; set; } + + /// Gets or sets pattern to verify. + public Regex Pattern { get; set; } + + /// Gets or sets a value indicating whether property value is required or not. + public bool Required { get; set; } + + /// Gets or sets a value indicating whether this is an element requirement. + public bool ElementRequirement { get; set; } + + /// Initializes a new instance of the class. + /// This field will be required. + public RequiredAttribute(bool required = true) + { + Required = required; + } + /// Initializes a new instance of the class. + /// Limiting value to set. + /// Whether set maximum parameter (true) or minimum parameter (false). + /// This field will be required. + public RequiredAttribute(float val, bool max, bool required = true) + { + if (max) + Max = (decimal)val; + else + Min = (decimal)val; + Required = required; + } + /// Initializes a new instance of the class. + /// Minimum value to set. + /// Maximum value to set. + /// This field will be required. + public RequiredAttribute(float min, float max, bool required = true) + { + Min = (decimal)min; + Max = (decimal)max; + Required = required; + } + /// Initializes a new instance of the class. + /// Minimum value to set. + /// Maximum value to set. + /// Pattern to check. + /// This field will be required. + public RequiredAttribute(float min, float max, string pattern, bool required = true) + { + Min = (decimal)min; + Max = (decimal)max; + Pattern = new Regex(pattern, RegexOptions.Compiled); + Required = required; + } + internal ValidateResult ValidateSimpleValue(DynamicPropertyInvoker dpi, object val) + { + return ValidateSimpleValue(dpi.Type, dpi.IsGnericEnumerable, val); + } + internal ValidateResult ValidateSimpleValue(Type type, bool isGnericEnumerable, object val) + { + if (val == null) + { + if (Required) + return ValidateResult.ValueIsMissing; + else + return ValidateResult.Valid; + } + if (type.IsValueType) + { + if (val is decimal || val is long || val is int || val is float || val is double || val is short || val is byte || + val is decimal? || val is long? || val is int? || val is float? || val is double? || val is short? || val is byte?) + { + decimal dec = Convert.ToDecimal(val); + + if (Min.HasValue && Min.Value > dec) + return ValidateResult.ValueTooSmall; + + if (Max.HasValue && Max.Value < dec) + return ValidateResult.ValueTooLarge; + + return ValidateResult.Valid; + } + else + { + var str = val.ToString(); + + if (Min.HasValue && Min.Value > str.Length) + return ValidateResult.ValueTooShort; + + if (Max.HasValue && Max.Value < str.Length) + return ValidateResult.ValueTooLong; + + if (Pattern != null && !Pattern.IsMatch(str)) + return ValidateResult.ValueDontMatchPattern; + + return ValidateResult.Valid; + } + } + else if (type.IsArray || isGnericEnumerable) + { + int? cnt = null; + + var list = val as IEnumerable; + if (list != null) + cnt = list.Count(); + else + { + var enumerable = val as IEnumerable; + if (enumerable != null) + cnt = enumerable.Cast().Count(); + } + if (Min.HasValue && Min.Value > cnt) + return ValidateResult.TooFewElementsInCollection; + + if (Max.HasValue && Max.Value < cnt) + return ValidateResult.TooManyElementsInCollection; + + return ValidateResult.Valid; + } + else if (type == typeof(string)) + { + var str = (string)val; + + if (Min.HasValue && Min.Value > str.Length) + return ValidateResult.ValueTooShort; + + if (Max.HasValue && Max.Value < str.Length) + return ValidateResult.ValueTooLong; + + if (Pattern != null && !Pattern.IsMatch(str)) + return ValidateResult.ValueDontMatchPattern; + + return ValidateResult.Valid; + } + return ValidateResult.NotSupported; + } + } + /// Validation result enum. + public enum ValidateResult + { + /// The valid value. + Valid, + + /// The value is missing. + ValueIsMissing, + + /// The value too small. + ValueTooSmall, + + /// The value too large. + ValueTooLarge, + + /// The too few elements in collection. + TooFewElementsInCollection, + + /// The too many elements in collection. + TooManyElementsInCollection, + + /// The value too short. + ValueTooShort, + + /// The value too long. + ValueTooLong, + + /// The value don't match pattern. + ValueDontMatchPattern, + + /// The not supported. + NotSupported, + } + /// Validation result. + public class ValidationResult + { + /// Gets the property invoker. + public DynamicPropertyInvoker Property { get; internal set; } + + /// Gets the requirement definition. + public RequiredAttribute Requirement { get; internal set; } + + /// Gets the value that is broken. + public object Value { get; internal set; } + + /// Gets the result. + public ValidateResult Result { get;internal set;} + } + } +} diff --git a/AmalgamationTool/Program.cs b/AmalgamationTool/Program.cs index 99bd1ad..17514ba 100644 --- a/AmalgamationTool/Program.cs +++ b/AmalgamationTool/Program.cs @@ -1,192 +1,261 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -namespace AmalgamationTool -{ - internal class Program - { - private static void Main(string[] args) - { - List usings = new List(); - Dictionary> classes = new Dictionary>(); - - // Build a file using string builder. - StringBuilder sb = new StringBuilder(); - - foreach (var f in new DirectoryInfo(Path.GetFullPath(args[0].Trim('"', '\''))).GetFiles("*.cs", SearchOption.AllDirectories)) - { - string content = File.ReadAllText(f.FullName); - - string namespaceName = string.Empty; - - // Deal with usings - foreach (var u in content.Split(new string[] { Environment.NewLine }, StringSplitOptions.None) - .Where(l => l.Trim().StartsWith("using ") && !l.Trim().StartsWith("using (")) - .Select(l => l.Trim())) - if (!usings.Contains(u)) - usings.Add(u); - - // Extract namespace - - //if (args.Length > 2) - //{ - // var tcontent = Regex.Replace(content, @"^\s*using\s+.*\s*;$", string.Empty); - // tcontent = Regex.Replace(content, @"^\s*namespace\s+.*\s*", string.Empty).Trim(); - - // var ns = Regex.Match(content, @"^\s*namespace\s+(?.*)\s*"); - - // if (ns.Success) - // { - // if (!classes.ContainsKey(ns.Groups["ns"].Value)) - // classes.Add(ns.Groups["ns"].Value, new List()); - - // classes[ns.Groups["ns"].Value].Add(tcontent); - // } - //} - //else - { - if (content.Trim().Length == 0) - continue; - - var nstart = content.IndexOf("namespace ") + "namespace ".Length; - var bbrace = content.IndexOf("{", nstart); - var nlen = bbrace - nstart; - - if (nstart < "namespace ".Length) - { - if (f.Name.ToLower() == "assemblyinfo.cs") - { - var hs = content.IndexOf("/*"); - var es = content.IndexOf("*/", hs) + 2; - if (es > hs) - { - sb.AppendLine(content.Substring(hs, es - hs)); - sb.AppendLine(); - } - } - - continue; - } - - string ns = content.Substring(nstart, nlen).Trim(); - - // Add namespace if not exist - if (!classes.ContainsKey(ns)) - classes.Add(ns, new List()); - - var ebrace = content.LastIndexOf('}'); - - // Cut content as class/enum - classes[ns].Add(content.Substring(bbrace + 1, ebrace - bbrace - 1)); - } - } - - usings.Sort(); - - foreach (var u in usings) - sb.AppendLine(u); - - sb.AppendLine(@" -[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."")]"); - - FillClassesAndNamespacesIddented(classes, sb); - - string amalgamation = sb.ToString(); - - sb = new StringBuilder(); - - string prevTrimmed = null; - - string[] array = amalgamation.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); - - for (int i = 0; i < array.Length; i++) - { - string l = array[i]; - var currentTrimmed = l.Trim(); - var nextTrimmed = (i + 1 == array.Length) ? null : array[i + 1].Trim(); - - if (prevTrimmed != null) - { - switch (prevTrimmed) - { - case "": - if (currentTrimmed == string.Empty) - continue; - break; - - case "{": - case "}": - if (currentTrimmed == string.Empty && (nextTrimmed == prevTrimmed || nextTrimmed == string.Empty)) - continue; - break; - } - } - - sb.AppendLine(l); - prevTrimmed = currentTrimmed; - } - - File.WriteAllText(Path.GetFullPath(args[1].Trim('"', '\'')), sb.ToString()); - } - - private static void FillClassesAndNamespaces(Dictionary> classes, StringBuilder sb) - { - foreach (var n in classes) - { - sb.AppendFormat("namespace {0}{1}{{", n.Key, Environment.NewLine); - n.Value.ForEach(c => sb.Append(c)); - sb.AppendLine("}"); - sb.AppendLine(string.Empty); - } - } - - private static void FillClassesAndNamespacesIddented(Dictionary> classes, StringBuilder sb) - { - var min = classes.Min(k => k.Key.Split('.').Count()); - - foreach (var n in classes.Where(nc => nc.Key.Split('.').Count() == min)) - { - sb.AppendFormat("namespace {0}{1}{{", n.Key, Environment.NewLine); - n.Value.ForEach(c => sb.Append(c)); - - SubNamespaces(classes, n.Key, sb, min); - - sb.AppendLine("}"); - sb.AppendLine(string.Empty); - } - } - - private static void SubNamespaces(Dictionary> classes, string p, StringBuilder sb, int ident) - { - sb.AppendLine(string.Empty); - - foreach (var n in classes.Where(nc => nc.Key.Split('.').Count() == ident + 1 && nc.Key.StartsWith(p))) - { - for (int i = 0; i < ident; i++) sb.Append(" "); - sb.AppendFormat("namespace {0}{1}", n.Key.Substring(p.Length + 1), Environment.NewLine); - - for (int i = 0; i < ident; i++) sb.Append(" "); - sb.Append("{"); - n.Value.ForEach(c => - { - foreach (var l in c.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)) - { - for (int i = 0; i < ident; i++) sb.Append(" "); - sb.AppendLine(l); - } - }); - - SubNamespaces(classes, n.Key, sb, ident + 1); - - for (int i = 0; i < ident; i++) sb.Append(" "); - sb.AppendLine("}"); - sb.AppendLine(string.Empty); - } - } - } -} \ No newline at end of file +using System.Text; +using System.Text.RegularExpressions; + +namespace AmalgamationTool; + +internal static class Program +{ + private const string NamespaceToken = "namespace "; + + private static int Main(string[] args) + { + if (args.Length < 2) + { + Console.Error.WriteLine("Usage: AmalgamationTool "); + return 1; + } + + var sourceDir = Path.GetFullPath(args[0].Trim('"', '\'')); + var outputFile = Path.GetFullPath(args[1].Trim('"', '\'')); + + if (!Directory.Exists(sourceDir)) + { + Console.Error.WriteLine($"Source directory not found: {sourceDir}"); + return 2; + } + + var allUsings = new SortedSet(StringComparer.Ordinal); + var namespaces = new SortedDictionary>(StringComparer.Ordinal); + var headerComment = string.Empty; + + foreach (var file in EnumerateInputFiles(sourceDir)) + { + var content = File.ReadAllText(file); + if (string.IsNullOrWhiteSpace(content)) + { + continue; + } + + if (string.IsNullOrEmpty(headerComment)) + { + headerComment = TryGetFileHeaderComment(content); + } + + foreach (var @using in ExtractUsingLines(content)) + { + allUsings.Add(@using); + } + + var nsData = TryExtractNamespaceBody(content); + if (nsData == null) + { + continue; + } + + if (!namespaces.TryGetValue(nsData.Value.Namespace, out var nodes)) + { + nodes = new List(); + namespaces[nsData.Value.Namespace] = nodes; + } + + nodes.Add(nsData.Value.Body); + } + + var output = BuildAmalgamation(headerComment, allUsings, namespaces); + Directory.CreateDirectory(Path.GetDirectoryName(outputFile) ?? "."); + File.WriteAllText(outputFile, output, new UTF8Encoding(false)); + + Console.WriteLine($"Amalgamation generated: {outputFile}"); + return 0; + } + + private static IEnumerable EnumerateInputFiles(string sourceDir) + { + var root = new DirectoryInfo(sourceDir); + return root.EnumerateFiles("*.cs", SearchOption.AllDirectories) + .Where(f => !f.FullName.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase)) + .Where(f => !f.FullName.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase)) + .Where(f => !f.Name.Equals("AssemblyInfo.cs", StringComparison.OrdinalIgnoreCase)) + .Where(f => !f.Name.Equals("DynamORM.Amalgamation.cs", StringComparison.OrdinalIgnoreCase)) + .Select(f => f.FullName) + .OrderBy(f => f, StringComparer.Ordinal); + } + + private static IEnumerable ExtractUsingLines(string content) + { + var matches = Regex.Matches(content, @"^\s*using\s+[^;]+;", RegexOptions.Multiline); + foreach (Match match in matches) + { + var line = match.Value.Trim(); + if (!line.StartsWith("using (", StringComparison.Ordinal)) + { + yield return line; + } + } + } + + private static (string Namespace, string Body)? TryExtractNamespaceBody(string content) + { + var nsIndex = content.IndexOf(NamespaceToken, StringComparison.Ordinal); + if (nsIndex < 0) + { + return null; + } + + var nsStart = nsIndex + NamespaceToken.Length; + var braceStart = content.IndexOf('{', nsStart); + if (braceStart < 0) + { + return null; + } + + var namespaceName = content.Substring(nsStart, braceStart - nsStart).Trim(); + if (string.IsNullOrWhiteSpace(namespaceName)) + { + return null; + } + + var braceEnd = FindMatchingBrace(content, braceStart); + if (braceEnd <= braceStart) + { + return null; + } + + var body = content.Substring(braceStart + 1, braceEnd - braceStart - 1).Trim('\r', '\n'); + return (namespaceName, body); + } + + private static int FindMatchingBrace(string content, int openBrace) + { + var depth = 0; + for (var i = openBrace; i < content.Length; i++) + { + switch (content[i]) + { + case '{': + depth++; + break; + case '}': + depth--; + if (depth == 0) + { + return i; + } + break; + } + } + + return -1; + } + + private static string TryGetFileHeaderComment(string content) + { + var match = Regex.Match(content, @"^\s*/\*.*?\*/", RegexOptions.Singleline); + return match.Success ? match.Value.Trim() : string.Empty; + } + + private static string BuildAmalgamation( + string headerComment, + IEnumerable allUsings, + SortedDictionary> namespaces) + { + var sb = new StringBuilder(); + if (!string.IsNullOrWhiteSpace(headerComment)) + { + sb.AppendLine(headerComment); + sb.AppendLine(); + } + + foreach (var @using in allUsings) + { + sb.AppendLine(@using); + } + + sb.AppendLine(); + sb.AppendLine("[module: System.Diagnostics.CodeAnalysis.SuppressMessage(\"StyleCop.CSharp.MaintainabilityRules\", \"SA1402:FileMayOnlyContainASingleClass\", Justification = \"This is a generated file which generates all the necessary support classes.\")]"); + sb.AppendLine("[module: System.Diagnostics.CodeAnalysis.SuppressMessage(\"StyleCop.CSharp.MaintainabilityRules\", \"SA1403:FileMayOnlyContainASingleNamespace\", Justification = \"This is a generated file which generates all the necessary support classes.\")]"); + sb.AppendLine(); + + FillNamespaceTree(namespaces, sb); + return CleanupWhitespace(sb.ToString()); + } + + private static void FillNamespaceTree(SortedDictionary> classes, StringBuilder sb) + { + var minDepth = classes.Keys.Min(k => k.Split('.').Length); + + foreach (var root in classes.Where(c => c.Key.Split('.').Length == minDepth)) + { + sb.AppendLine($"namespace {root.Key}"); + sb.AppendLine("{"); + + foreach (var code in root.Value) + { + sb.AppendLine(code); + } + + FillSubNamespaces(classes, root.Key, minDepth, sb); + sb.AppendLine("}"); + sb.AppendLine(); + } + } + + private static void FillSubNamespaces( + SortedDictionary> classes, + string parent, + int depth, + StringBuilder sb) + { + foreach (var child in classes.Where(c => c.Key.StartsWith(parent + ".", StringComparison.Ordinal) && c.Key.Split('.').Length == depth + 1)) + { + var indent = new string(' ', depth * 4); + var shortNamespace = child.Key.Substring(parent.Length + 1); + sb.Append(indent).AppendLine($"namespace {shortNamespace}"); + sb.Append(indent).AppendLine("{"); + + foreach (var block in child.Value) + { + foreach (var line in block.Replace("\r\n", "\n").Split('\n')) + { + sb.Append(indent).Append(" ").AppendLine(line); + } + } + + FillSubNamespaces(classes, child.Key, depth + 1, sb); + sb.Append(indent).AppendLine("}"); + sb.AppendLine(); + } + } + + private static string CleanupWhitespace(string content) + { + var lines = content.Replace("\r\n", "\n").Split('\n'); + var output = new StringBuilder(); + string? previous = null; + + for (var i = 0; i < lines.Length; i++) + { + var current = lines[i]; + var trimmed = current.Trim(); + var nextTrimmed = i + 1 < lines.Length ? lines[i + 1].Trim() : null; + + if (string.IsNullOrEmpty(trimmed)) + { + if (string.IsNullOrEmpty(previous)) + { + continue; + } + + if (previous == "{" || previous == "}" || nextTrimmed == "}" || string.IsNullOrEmpty(nextTrimmed)) + { + continue; + } + } + + output.AppendLine(current.TrimEnd()); + previous = trimmed; + } + + return output.ToString(); + } +} diff --git a/AmalgamationTool/Properties/AssemblyInfo.cs b/AmalgamationTool/Properties/AssemblyInfo.cs deleted file mode 100644 index 6aa9b29..0000000 --- a/AmalgamationTool/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("AmalgamationTool")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("AmalgamationTool")] -[assembly: AssemblyCopyright("Copyright © Microsoft 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("de540809-dd27-47b1-bb01-7dce3a880cde")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DynamORM.Tests/DynamORM.Tests.csproj b/DynamORM.Tests/DynamORM.Tests.csproj index b197d61..428d6ba 100644 --- a/DynamORM.Tests/DynamORM.Tests.csproj +++ b/DynamORM.Tests/DynamORM.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 Dynamic Object-Relational Mapping tests library. Copyright © RUSSEK Software 2012-2026 RUSSEK Software @@ -25,13 +25,13 @@ - - - - - - - + + + + + + + diff --git a/DynamORM.Tests/Helpers/AttachToDebugger.cs b/DynamORM.Tests/Helpers/AttachToDebugger.cs index f4e0702..f9a7b32 100644 --- a/DynamORM.Tests/Helpers/AttachToDebugger.cs +++ b/DynamORM.Tests/Helpers/AttachToDebugger.cs @@ -1,64 +1,64 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System.Collections.Generic; -using System.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Helpers -{ - /// Class responsible for users operations testing. - [TestClass] - public class AttachToDebugger - { - /// Test anonymous type compatibility. - [TestMethod] - public void TestAnonType() - { - var a = new { x = 1, y = 2 }; - var b = new { x = 3, y = 4 }; - - Assert.AreEqual(a.GetType(), b.GetType()); - } - - /// Test anonymous type value. - [TestMethod] - public void TestAnonTypeValue() - { - var a = new { x = 1, y = "bla bla" }; - var b = new { x = 1, y = "bla bla" }; - - Assert.AreEqual(a, b); - Assert.IsTrue(a.Equals(b)); - - Dictionary dict = new Dictionary() { { a, 999 } }; - - Assert.IsTrue(dict.ContainsKey(b)); - } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Collections.Generic; +using System.Diagnostics; +using NUnit.Framework; + +namespace DynamORM.Tests.Helpers +{ + /// Class responsible for sample_users operations testing. + [TestFixture] + public class AttachToDebugger + { + /// Test anonymous type compatibility. + [Test] + public void TestAnonType() + { + var a = new { x = 1, y = 2 }; + var b = new { x = 3, y = 4 }; + + Assert.AreEqual(a.GetType(), b.GetType()); + } + + /// Test anonymous type value. + [Test] + public void TestAnonTypeValue() + { + var a = new { x = 1, y = "bla bla" }; + var b = new { x = 1, y = "bla bla" }; + + Assert.AreEqual(a, b); + Assert.IsTrue(a.Equals(b)); + + Dictionary dict = new Dictionary() { { a, 999 } }; + + Assert.IsTrue(dict.ContainsKey(b)); + } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Helpers/Dynamic/DynamicParserTests.cs b/DynamORM.Tests/Helpers/Dynamic/DynamicParserTests.cs index 25961b9..7e6a805 100644 --- a/DynamORM.Tests/Helpers/Dynamic/DynamicParserTests.cs +++ b/DynamORM.Tests/Helpers/Dynamic/DynamicParserTests.cs @@ -1,121 +1,121 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System; -using DynamORM.Helpers.Dynamics; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Helpers.Dynamic -{ - /// tests. - [TestClass] - public class DynamicParserTests - { - /// - /// Tests the get member. - /// - [TestMethod] - public void TestGetMember() - { - Func f = x => x.SomePropery; - - var val = DynamicParser.Parse(f).Result as DynamicParser.Node.GetMember; - - Assert.IsNotNull(val); - Assert.AreEqual("SomePropery", val.Name); - } - - /// - /// Tests the set member. - /// - [TestMethod] - public void TestSetMember() - { - Func f = x => x.SomePropery = "value"; - - var val = DynamicParser.Parse(f).Result as DynamicParser.Node.SetMember; - - Assert.IsNotNull(val); - Assert.AreEqual("SomePropery", val.Name); - Assert.AreEqual("value", val.Value); - } - - /// - /// Tests the index of the get. - /// - [TestMethod] - public void TestGetIndex() - { - Func f = x => x.SomePropery[0]; - - var val = DynamicParser.Parse(f).Result as DynamicParser.Node.GetIndex; - - Assert.IsNotNull(val); - } - - /// - /// Tests the index of the set. - /// - [TestMethod] - public void TestSetIndex() - { - Func f = x => x.SomePropery[0] = "value"; - - var val = DynamicParser.Parse(f).Result as DynamicParser.Node.SetIndex; - - Assert.IsNotNull(val); - Assert.AreEqual("value", val.Value); - } - - /// - /// Tests something. - /// - [TestMethod] - public void TestSomething() - { - Func f = x => x.SomePropery == "value" || x.OtherProperty == -1; - - var p = DynamicParser.Parse(f); - var val = p.Result as DynamicParser.Node.Binary; - - Assert.IsNotNull(val); - - var left = val.Host as DynamicParser.Node.Binary; - var right = val.Right as DynamicParser.Node.Binary; - - Assert.IsNotNull(left); - Assert.IsNotNull(right); - - Assert.IsInstanceOfType(left.Host, typeof(DynamicParser.Node.GetMember)); - Assert.IsInstanceOfType(right.Host, typeof(DynamicParser.Node.GetMember)); - - Assert.AreEqual("value", left.Right); - Assert.AreEqual(-1, right.Right); - } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using DynamORM.Helpers.Dynamics; +using NUnit.Framework; + +namespace DynamORM.Tests.Helpers.Dynamic +{ + /// tests. + [TestFixture] + public class DynamicParserTests + { + /// + /// Tests the get member. + /// + [Test] + public void TestGetMember() + { + Func f = x => x.SomePropery; + + var val = DynamicParser.Parse(f).Result as DynamicParser.Node.GetMember; + + Assert.IsNotNull(val); + Assert.AreEqual("SomePropery", val.Name); + } + + /// + /// Tests the set member. + /// + [Test] + public void TestSetMember() + { + Func f = x => x.SomePropery = "value"; + + var val = DynamicParser.Parse(f).Result as DynamicParser.Node.SetMember; + + Assert.IsNotNull(val); + Assert.AreEqual("SomePropery", val.Name); + Assert.AreEqual("value", val.Value); + } + + /// + /// Tests the index of the get. + /// + [Test] + public void TestGetIndex() + { + Func f = x => x.SomePropery[0]; + + var val = DynamicParser.Parse(f).Result as DynamicParser.Node.GetIndex; + + Assert.IsNotNull(val); + } + + /// + /// Tests the index of the set. + /// + [Test] + public void TestSetIndex() + { + Func f = x => x.SomePropery[0] = "value"; + + var val = DynamicParser.Parse(f).Result as DynamicParser.Node.SetIndex; + + Assert.IsNotNull(val); + Assert.AreEqual("value", val.Value); + } + + /// + /// Tests something. + /// + [Test] + public void TestSomething() + { + Func f = x => x.SomePropery == "value" || x.OtherProperty == -1; + + var p = DynamicParser.Parse(f); + var val = p.Result as DynamicParser.Node.Binary; + + Assert.IsNotNull(val); + + var left = val.Host as DynamicParser.Node.Binary; + var right = val.Right as DynamicParser.Node.Binary; + + Assert.IsNotNull(left); + Assert.IsNotNull(right); + + Assert.IsInstanceOf(left.Host); + Assert.IsInstanceOf(right.Host); + + Assert.AreEqual("value", left.Right); + Assert.AreEqual(-1, right.Right); + } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Helpers/PoolingTests.cs b/DynamORM.Tests/Helpers/PoolingTests.cs index 55bb032..a5e28c9 100644 --- a/DynamORM.Tests/Helpers/PoolingTests.cs +++ b/DynamORM.Tests/Helpers/PoolingTests.cs @@ -1,100 +1,100 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Helpers -{ - /// Pooling tests. - [TestClass] - public class PoolingTests : TestsBase - { - /// Setup test parameters. - [TestInitialize] - public virtual void SetUp() - { - CreateTestDatabase(); - } - - /// Tear down test objects. - [TestCleanup] - public virtual void TearDown() - { - DestroyDynamicDatabase(); - DestroyTestDatabase(); - } - - /// Test single mode command disposing. - [TestMethod] - public void TestSingleModeCommand() - { - CreateDynamicDatabase(); - - var cmd = Database.Open().CreateCommand(); - - cmd.SetCommand("SELECT COUNT(0) FROM sqlite_master;"); - - Database.Dispose(); - Database = null; - - Assert.ThrowsException(() => cmd.ExecuteScalar()); - } - - /// Test single mode transaction disposing. - [TestMethod] - public void TestSingleModeTransaction() - { - try - { - CreateDynamicDatabase(); - - using (var conn = Database.Open()) - using (var trans = conn.BeginTransaction()) - using (var cmd = conn.CreateCommand()) - { - Assert.AreEqual(1, cmd.SetCommand("INSERT INTO \"users\" (\"code\") VALUES ('999');").ExecuteNonQuery()); - - Database.Dispose(); - Database = null; - - trans.Commit(); - } - - // Verify (rollback) - CreateDynamicDatabase(); - Assert.AreEqual(0, Database.Table("users").Count(columns: "id", code: "999")); - } - finally - { - // Remove for next tests - Database.Dispose(); - Database = null; - } - } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using NUnit.Framework; + +namespace DynamORM.Tests.Helpers +{ + /// Pooling tests. + [TestFixture] + public class PoolingTests : TestsBase + { + /// Setup test parameters. + [SetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + } + + /// Tear down test objects. + [TearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// Test single mode command disposing. + [Test] + public void TestSingleModeCommand() + { + CreateDynamicDatabase(); + + var cmd = Database.Open().CreateCommand(); + + cmd.SetCommand("SELECT COUNT(0) FROM sqlite_master;"); + + Database.Dispose(); + Database = null; + + Assert.Throws(() => cmd.ExecuteScalar()); + } + + /// Test single mode transaction disposing. + [Test] + public void TestSingleModeTransaction() + { + try + { + CreateDynamicDatabase(); + + using (var conn = Database.Open()) + using (var trans = conn.BeginTransaction()) + using (var cmd = conn.CreateCommand()) + { + Assert.AreEqual(1, cmd.SetCommand("INSERT INTO \"sample_users\" (\"code\") VALUES ('999');").ExecuteNonQuery()); + + Database.Dispose(); + Database = null; + + trans.Commit(); + } + + // Verify (rollback) + CreateDynamicDatabase(); + Assert.AreEqual(0, Database.Table("sample_users").Count(columns: "id", code: "999")); + } + finally + { + // Remove for next tests + Database.Dispose(); + Database = null; + } + } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Helpers/Users.cs b/DynamORM.Tests/Helpers/Users.cs index e402075..51ccb39 100644 --- a/DynamORM.Tests/Helpers/Users.cs +++ b/DynamORM.Tests/Helpers/Users.cs @@ -1,73 +1,73 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using DynamORM.Mapper; - -namespace DynamORM.Tests.Helpers -{ - /// Users table representation. - [Table(Name = "users", Override = true)] - public class Users - { - /// Gets or sets id column value. - [Column("id", true)] - public long Id { get; set; } - - /// Gets or sets code column value. - [Column("code")] - public string Code { get; set; } - - /// Gets or sets login column value. - [Column("login")] - public string Login { get; set; } - - /// Gets or sets first column value. - [Column("first")] - public string First { get; set; } - - /// Gets or sets last column value. - [Column("last")] - public string Last { get; set; } - - /// Gets or sets password column value. - [Column("password")] - public string Password { get; set; } - - /// Gets or sets email column value. - [Column("email")] - public string Email { get; set; } - - /// Gets or sets quote column value. - [Column("quote")] - public string Quote { get; set; } - - /// Gets or sets value of aggregate fields. - [Ignore] - public object AggregateField { get; set; } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using DynamORM.Mapper; + +namespace DynamORM.Tests.Helpers +{ + /// Users table representation. + [Table(Name = "sample_users", Override = true)] + public class Users + { + /// Gets or sets id column value. + [Column("id", true)] + public long Id { get; set; } + + /// Gets or sets code column value. + [Column("code")] + public string Code { get; set; } + + /// Gets or sets login column value. + [Column("login")] + public string Login { get; set; } + + /// Gets or sets first column value. + [Column("first")] + public string First { get; set; } + + /// Gets or sets last column value. + [Column("last")] + public string Last { get; set; } + + /// Gets or sets password column value. + [Column("password")] + public string Password { get; set; } + + /// Gets or sets email column value. + [Column("email")] + public string Email { get; set; } + + /// Gets or sets quote column value. + [Column("quote")] + public string Quote { get; set; } + + /// Gets or sets value of aggregate fields. + [Ignore] + public object AggregateField { get; set; } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Helpers/UsersBareBoneClass.cs b/DynamORM.Tests/Helpers/UsersBareBoneClass.cs index 5e1178f..dcbad2e 100644 --- a/DynamORM.Tests/Helpers/UsersBareBoneClass.cs +++ b/DynamORM.Tests/Helpers/UsersBareBoneClass.cs @@ -1,67 +1,67 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System.Diagnostics.CodeAnalysis; -using DynamORM.Mapper; - -namespace DynamORM.Tests.Helpers -{ - /// Users table representation. - [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Bare bone table mapping.")] - public class users - { - /// Gets or sets id column value. - [Column("id", true)] - public long id { get; set; } - - /// Gets or sets code column value. - public string code { get; set; } - - /// Gets or sets login column value. - public string login { get; set; } - - /// Gets or sets first column value. - public string first { get; set; } - - /// Gets or sets last column value. - public string last { get; set; } - - /// Gets or sets password column value. - public string password { get; set; } - - /// Gets or sets email column value. - public string email { get; set; } - - /// Gets or sets quote column value. - public string quote { get; set; } - - /// Gets or sets value of aggregate fields. - [Ignore] - public object aggregatefield { get; set; } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Diagnostics.CodeAnalysis; +using DynamORM.Mapper; + +namespace DynamORM.Tests.Helpers +{ + /// Users table representation. + [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Bare bone table mapping.")] + public class sample_users + { + /// Gets or sets id column value. + [Column("id", true)] + public long id { get; set; } + + /// Gets or sets code column value. + public string code { get; set; } + + /// Gets or sets login column value. + public string login { get; set; } + + /// Gets or sets first column value. + public string first { get; set; } + + /// Gets or sets last column value. + public string last { get; set; } + + /// Gets or sets password column value. + public string password { get; set; } + + /// Gets or sets email column value. + public string email { get; set; } + + /// Gets or sets quote column value. + public string quote { get; set; } + + /// Gets or sets value of aggregate fields. + [Ignore] + public object aggregatefield { get; set; } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Helpers/Validation/ObjectValidationTest.cs b/DynamORM.Tests/Helpers/Validation/ObjectValidationTest.cs index d9cd99b..6c6b007 100644 --- a/DynamORM.Tests/Helpers/Validation/ObjectValidationTest.cs +++ b/DynamORM.Tests/Helpers/Validation/ObjectValidationTest.cs @@ -1,80 +1,80 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using DynamORM.Mapper; -using DynamORM.Validation; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Helpers.Validation -{ - [TestClass] - public class ObjectValidationTest - { - public class TestObject - { - [Required(1f, 10f)] - public int TestInt { get; set; } - - [Required(7, false, false)] - public string CanBeNull { get; set; } - - [Required(2, true)] - [Required(7, 18, ElementRequirement = true)] - public decimal[] ArrayTest { get; set; } - } - - [TestMethod] - public void ValidateCorrectObject() - { - var result = DynamicMapperCache.GetMapper().ValidateObject( - new TestObject - { - TestInt = 2, - ArrayTest = new decimal[] { 7, 18 }, - }); - - Assert.IsNotNull(result); - Assert.AreEqual(0, result.Count); - } - - [TestMethod] - public void ValidateIncorrectObject() - { - var result = DynamicMapperCache.GetMapper().ValidateObject( - new TestObject - { - TestInt = 0, - CanBeNull = string.Empty, - ArrayTest = new decimal[] { 0, 0 }, - }); - - Assert.IsNotNull(result); - Assert.AreEqual(4, result.Count); - } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using DynamORM.Mapper; +using DynamORM.Validation; +using NUnit.Framework; + +namespace DynamORM.Tests.Helpers.Validation +{ + [TestFixture] + public class ObjectValidationTest + { + public class TestObject + { + [Required(1f, 10f)] + public int TestInt { get; set; } + + [Required(7, false, false)] + public string CanBeNull { get; set; } + + [Required(2, true)] + [Required(7, 18, ElementRequirement = true)] + public decimal[] ArrayTest { get; set; } + } + + [Test] + public void ValidateCorrectObject() + { + var result = DynamicMapperCache.GetMapper().ValidateObject( + new TestObject + { + TestInt = 2, + ArrayTest = new decimal[] { 7, 18 }, + }); + + Assert.IsNotNull(result); + Assert.AreEqual(0, result.Count); + } + + [Test] + public void ValidateIncorrectObject() + { + var result = DynamicMapperCache.GetMapper().ValidateObject( + new TestObject + { + TestInt = 0, + CanBeNull = string.Empty, + ArrayTest = new decimal[] { 0, 0 }, + }); + + Assert.IsNotNull(result); + Assert.AreEqual(4, result.Count); + } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Modify/DynamicModificationTests.cs b/DynamORM.Tests/Modify/DynamicModificationTests.cs index a1e6746..f2ac11d 100644 --- a/DynamORM.Tests/Modify/DynamicModificationTests.cs +++ b/DynamORM.Tests/Modify/DynamicModificationTests.cs @@ -1,391 +1,391 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System; -using DynamORM.Tests.Helpers; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Modify -{ - /// Test standard dynamic access ORM. - [TestClass] - public class DynamicModificationTests : TestsBase - { - /// Setup test parameters. - [TestInitialize] - public virtual void SetUp() - { - CreateTestDatabase(); - CreateDynamicDatabase(); - } - - /// Tear down test objects. - [TestCleanup] - public virtual void TearDown() - { - DestroyDynamicDatabase(); - DestroyTestDatabase(); - } - - /// Create table using specified method. - /// Dynamic table. - public virtual dynamic GetTestTable() - { - return Database.Table("users"); - } - - #region Insert - - /// Test row insertion by dynamic arguments. - [TestMethod] - public void TestInsertByArguments() - { - Assert.AreEqual(1, GetTestTable().Insert(code: "201", first: null, last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla")); - - // Verify - var o = GetTestTable().Single(code: "201"); - Assert.AreNotEqual(200, o.id); - Assert.AreEqual("201", o.code.ToString()); - Assert.IsNull(o.first); - Assert.AreEqual("Gagarin", o.last); - Assert.AreEqual("juri.gagarin@megacorp.com", o.email); - Assert.AreEqual("bla, bla, bla", o.quote); - Assert.IsNull(o.password); - } - - /// Test row insertion by dynamic object. - [TestMethod] - public void TestInsertByDynamicObjects() - { - Assert.AreEqual(1, GetTestTable().Insert(values: new { code = "202", first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); - - // Verify - var o = GetTestTable().Single(code: "202"); - Assert.AreNotEqual(200, o.id); - Assert.AreEqual("202", o.code.ToString()); - Assert.IsNull(o.first); - Assert.AreEqual("Gagarin", o.last); - Assert.AreEqual("juri.gagarin@megacorp.com", o.email); - Assert.AreEqual("bla, bla, bla", o.quote); - Assert.IsNull(o.password); - } - - /// Test row insertion by mapped object. - [TestMethod] - public void TestInsertByMappedObject() - { - var u = GetTestTable(); - - Assert.AreEqual(1, u.Insert(values: new Users - { - Id = u.Max(columns: "id") + 1, - Code = "203", - First = null, - Last = "Gagarin", - Email = "juri.gagarin@megacorp.com", - Quote = "bla, bla, bla" - })); - - // Verify - var o = u.Single(code: "203"); - Assert.AreNotEqual(200, o.id); - Assert.AreEqual("203", o.code.ToString()); - Assert.IsNull(o.first); - Assert.AreEqual("Gagarin", o.last); - Assert.AreEqual("juri.gagarin@megacorp.com", o.email); - Assert.AreEqual("bla, bla, bla", o.quote); - Assert.IsNull(o.password); - } - - /// Test row insertion by basic object. - [TestMethod] - public void TestInsertByBasicObject() - { - var u = GetTestTable(); - - Assert.AreEqual(1, u.Insert(values: new users - { - id = u.Max(columns: "id") + 1, - code = "204", - first = null, - last = "Gagarin", - email = "juri.gagarin@megacorp.com", - quote = "bla, bla, bla" - })); - - // Verify - var o = u.Single(code: "204"); - Assert.AreNotEqual(200, o.id); - Assert.AreEqual("204", o.code.ToString()); - Assert.IsNull(o.first); - Assert.AreEqual("Gagarin", o.last); - Assert.AreEqual("juri.gagarin@megacorp.com", o.email); - Assert.AreEqual("bla, bla, bla", o.quote); - Assert.IsNull(o.password); - } - - #endregion Insert - - #region Update - - /// Test row updating by dynamic arguments. - [TestMethod] - public void TestUpdateByArguments() - { - Assert.AreEqual(1, GetTestTable().Update(id: 1, code: "201", first: null, last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla")); - - // Verify - var o = GetTestTable().Single(code: "201"); - Assert.AreEqual(1, o.id); - Assert.AreEqual("201", o.code.ToString()); - Assert.IsNull(o.first); - Assert.AreEqual("Gagarin", o.last); - Assert.AreEqual("juri.gagarin@megacorp.com", o.email); - Assert.AreEqual("bla, bla, bla", o.quote); - Assert.IsNull(o.password); - } - - /// Test row updating by dynamic objects. - [TestMethod] - public void TestUpdateByDynamicObject() - { - Assert.AreEqual(1, GetTestTable().Update(update: new { id = 2, code = "202", first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); - - // Verify - var o = GetTestTable().Single(code: "202"); - Assert.AreEqual(2, o.id); - Assert.AreEqual("202", o.code.ToString()); - Assert.IsNull(o.first); - Assert.AreEqual("Gagarin", o.last); - Assert.AreEqual("juri.gagarin@megacorp.com", o.email); - Assert.AreEqual("bla, bla, bla", o.quote); - Assert.IsNull(o.password); - } - - /// Test row updating by mapped object. - [TestMethod] - public void TestUpdateByMappedObject() - { - var u = GetTestTable(); - - Assert.AreEqual(1, u.Update(update: new Users - { - Id = 3, - Code = "203", - First = null, - Last = "Gagarin", - Email = "juri.gagarin@megacorp.com", - Quote = "bla, bla, bla" - })); - - // Verify - var o = u.Single(code: "203"); - Assert.AreEqual(3, o.id); - Assert.AreEqual("203", o.code.ToString()); - Assert.IsNull(o.first); - Assert.AreEqual("Gagarin", o.last); - Assert.AreEqual("juri.gagarin@megacorp.com", o.email); - Assert.AreEqual("bla, bla, bla", o.quote); - Assert.IsNull(o.password); - } - - /// Test row updating by basic object. - [TestMethod] - public void TestUpdateByBasicObject() - { - var u = GetTestTable(); - - Assert.AreEqual(1, u.Update(update: new users - { - id = 4, - code = "204", - first = null, - last = "Gagarin", - email = "juri.gagarin@megacorp.com", - quote = "bla, bla, bla" - })); - - // Verify - var o = u.Single(code: "204"); - Assert.AreEqual(4, o.id); - Assert.AreEqual("204", o.code.ToString()); - Assert.IsNull(o.first); - Assert.AreEqual("Gagarin", o.last); - Assert.AreEqual("juri.gagarin@megacorp.com", o.email); - Assert.AreEqual("bla, bla, bla", o.quote); - Assert.IsNull(o.password); - } - - /// Test row updating by dynamic objects. - [TestMethod] - public void TestUpdateByDynamicObjects() - { - Assert.AreEqual(1, GetTestTable().Update(values: new { code = "205", first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" }, where: new { id = 5 })); - - // Verify - var o = GetTestTable().Single(code: "205"); - Assert.AreEqual(5, o.id); - Assert.AreEqual("205", o.code.ToString()); - Assert.IsNull(o.first); - Assert.AreEqual("Gagarin", o.last); - Assert.AreEqual("juri.gagarin@megacorp.com", o.email); - Assert.AreEqual("bla, bla, bla", o.quote); - Assert.IsNull(o.password); - } - - /// Test row updating by mapped objects. - [TestMethod] - public void TestUpdateByMappedObjects() - { - var u = GetTestTable(); - - Assert.AreEqual(1, u.Update(values: new Users - { - Id = 6, - Code = "206", - First = null, - Last = "Gagarin", - Email = "juri.gagarin@megacorp.com", - Quote = "bla, bla, bla" - }, id: 6)); - - // Verify - var o = u.Single(code: "206"); - Assert.AreEqual(6, o.id); - Assert.AreEqual("206", o.code.ToString()); - Assert.IsNull(o.first); - Assert.AreEqual("Gagarin", o.last); - Assert.AreEqual("juri.gagarin@megacorp.com", o.email); - Assert.AreEqual("bla, bla, bla", o.quote); - Assert.IsNull(o.password); - } - - /// Test row updating by basic objects. - [TestMethod] - public void TestUpdateByBasicObjects() - { - var u = GetTestTable(); - - Assert.AreEqual(1, u.Update(values: new users - { - id = 7, - code = "207", - first = null, - last = "Gagarin", - email = "juri.gagarin@megacorp.com", - quote = "bla, bla, bla" - }, id: 7)); - - // Verify - var o = u.Single(code: "207"); - Assert.AreEqual(7, o.id); - Assert.AreEqual("207", o.code.ToString()); - Assert.IsNull(o.first); - Assert.AreEqual("Gagarin", o.last); - Assert.AreEqual("juri.gagarin@megacorp.com", o.email); - Assert.AreEqual("bla, bla, bla", o.quote); - Assert.IsNull(o.password); - } - - #endregion Update - - #region Delete - - /// Test row deleting by dynamic arguments. - [TestMethod] - public void TestDeleteByArguments() - { - Assert.AreEqual(1, GetTestTable().Delete(code: "10")); - - // Verify - Assert.AreEqual(0, GetTestTable().Count(code: "10")); - } - - /// Test row deleting by dynamic objects (all except ID should be ignored). - [TestMethod] - public void TestDeleteyDynamicObject() - { - Assert.AreEqual(1, GetTestTable().Delete(delete: new { id = 11, code = 11, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); - - // Verify - Assert.AreEqual(0, GetTestTable().Count(id: 11)); - } - - /// Test row deleting by mapped object. - [TestMethod] - public void TestDeleteByMappedObject() - { - var u = GetTestTable(); - - Assert.AreEqual(1, u.Delete(delete: new Users - { - Id = 12, - Code = "12", - First = "Juri", - Last = "Gagarin", - Email = "juri.gagarin@megacorp.com", - Quote = "bla, bla, bla" - })); - - // Verify - Assert.AreEqual(0, GetTestTable().Count(id: 12)); - } - - /// Test row deleting by basic object. - [TestMethod] - public void TestDeleteByBasicObject() - { - var u = GetTestTable(); - - Assert.AreEqual(1, u.Delete(delete: new users - { - id = 13, - code = "13", - first = "Juri", - last = "Gagarin", - email = "juri.gagarin@megacorp.com", - quote = "bla, bla, bla" - })); - - // Verify - Assert.AreEqual(0, GetTestTable().Count(id: 13)); - } - - /// Test row deleting by dynamic objects (all except ID should be ignored). - [TestMethod] - public void TestDeleteyDynamicObjectWhere() - { - Assert.AreEqual(1, GetTestTable().Delete(where: new { id = 14, code = "14" })); - - // Verify - Assert.AreEqual(0, GetTestTable().Count(id: 14)); - } - - #endregion Delete - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using DynamORM.Tests.Helpers; +using NUnit.Framework; + +namespace DynamORM.Tests.Modify +{ + /// Test standard dynamic access ORM. + [TestFixture] + public class DynamicModificationTests : TestsBase + { + /// Setup test parameters. + [SetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase(); + } + + /// Tear down test objects. + [TearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// Create table using specified method. + /// Dynamic table. + public virtual dynamic GetTestTable() + { + return Database.Table("sample_users"); + } + + #region Insert + + /// Test row insertion by dynamic arguments. + [Test] + public void TestInsertByArguments() + { + Assert.AreEqual(1, GetTestTable().Insert(code: "201", first: null, last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla")); + + // Verify + var o = GetTestTable().Single(code: "201"); + Assert.AreNotEqual(200, o.id); + Assert.AreEqual("201", o.code.ToString()); + Assert.IsNull(o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.IsNull(o.password); + } + + /// Test row insertion by dynamic object. + [Test] + public void TestInsertByDynamicObjects() + { + Assert.AreEqual(1, GetTestTable().Insert(values: new { code = "202", first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); + + // Verify + var o = GetTestTable().Single(code: "202"); + Assert.AreNotEqual(200, o.id); + Assert.AreEqual("202", o.code.ToString()); + Assert.IsNull(o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.IsNull(o.password); + } + + /// Test row insertion by mapped object. + [Test] + public void TestInsertByMappedObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Insert(values: new Users + { + Id = u.Max(columns: "id") + 1, + Code = "203", + First = null, + Last = "Gagarin", + Email = "juri.gagarin@megacorp.com", + Quote = "bla, bla, bla" + })); + + // Verify + var o = u.Single(code: "203"); + Assert.AreNotEqual(200, o.id); + Assert.AreEqual("203", o.code.ToString()); + Assert.IsNull(o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.IsNull(o.password); + } + + /// Test row insertion by basic object. + [Test] + public void TestInsertByBasicObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Insert(values: new sample_users + { + id = u.Max(columns: "id") + 1, + code = "204", + first = null, + last = "Gagarin", + email = "juri.gagarin@megacorp.com", + quote = "bla, bla, bla" + })); + + // Verify + var o = u.Single(code: "204"); + Assert.AreNotEqual(200, o.id); + Assert.AreEqual("204", o.code.ToString()); + Assert.IsNull(o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.IsNull(o.password); + } + + #endregion Insert + + #region Update + + /// Test row updating by dynamic arguments. + [Test] + public void TestUpdateByArguments() + { + Assert.AreEqual(1, GetTestTable().Update(id: 1, code: "201", first: null, last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla")); + + // Verify + var o = GetTestTable().Single(code: "201"); + Assert.AreEqual(1, o.id); + Assert.AreEqual("201", o.code.ToString()); + Assert.IsNull(o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.IsNull(o.password); + } + + /// Test row updating by dynamic objects. + [Test] + public void TestUpdateByDynamicObject() + { + Assert.AreEqual(1, GetTestTable().Update(update: new { id = 2, code = "202", first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); + + // Verify + var o = GetTestTable().Single(code: "202"); + Assert.AreEqual(2, o.id); + Assert.AreEqual("202", o.code.ToString()); + Assert.IsNull(o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.IsNull(o.password); + } + + /// Test row updating by mapped object. + [Test] + public void TestUpdateByMappedObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Update(update: new Users + { + Id = 3, + Code = "203", + First = null, + Last = "Gagarin", + Email = "juri.gagarin@megacorp.com", + Quote = "bla, bla, bla" + })); + + // Verify + var o = u.Single(code: "203"); + Assert.AreEqual(3, o.id); + Assert.AreEqual("203", o.code.ToString()); + Assert.IsNull(o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.IsNull(o.password); + } + + /// Test row updating by basic object. + [Test] + public void TestUpdateByBasicObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Update(update: new sample_users + { + id = 4, + code = "204", + first = null, + last = "Gagarin", + email = "juri.gagarin@megacorp.com", + quote = "bla, bla, bla" + })); + + // Verify + var o = u.Single(code: "204"); + Assert.AreEqual(4, o.id); + Assert.AreEqual("204", o.code.ToString()); + Assert.IsNull(o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.IsNull(o.password); + } + + /// Test row updating by dynamic objects. + [Test] + public void TestUpdateByDynamicObjects() + { + Assert.AreEqual(1, GetTestTable().Update(values: new { code = "205", first = DBNull.Value, last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" }, where: new { id = 5 })); + + // Verify + var o = GetTestTable().Single(code: "205"); + Assert.AreEqual(5, o.id); + Assert.AreEqual("205", o.code.ToString()); + Assert.IsNull(o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.IsNull(o.password); + } + + /// Test row updating by mapped objects. + [Test] + public void TestUpdateByMappedObjects() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Update(values: new Users + { + Id = 6, + Code = "206", + First = null, + Last = "Gagarin", + Email = "juri.gagarin@megacorp.com", + Quote = "bla, bla, bla" + }, id: 6)); + + // Verify + var o = u.Single(code: "206"); + Assert.AreEqual(6, o.id); + Assert.AreEqual("206", o.code.ToString()); + Assert.IsNull(o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.IsNull(o.password); + } + + /// Test row updating by basic objects. + [Test] + public void TestUpdateByBasicObjects() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Update(values: new sample_users + { + id = 7, + code = "207", + first = null, + last = "Gagarin", + email = "juri.gagarin@megacorp.com", + quote = "bla, bla, bla" + }, id: 7)); + + // Verify + var o = u.Single(code: "207"); + Assert.AreEqual(7, o.id); + Assert.AreEqual("207", o.code.ToString()); + Assert.IsNull(o.first); + Assert.AreEqual("Gagarin", o.last); + Assert.AreEqual("juri.gagarin@megacorp.com", o.email); + Assert.AreEqual("bla, bla, bla", o.quote); + Assert.IsNull(o.password); + } + + #endregion Update + + #region Delete + + /// Test row deleting by dynamic arguments. + [Test] + public void TestDeleteByArguments() + { + Assert.AreEqual(1, GetTestTable().Delete(code: "10")); + + // Verify + Assert.AreEqual(0, GetTestTable().Count(code: "10")); + } + + /// Test row deleting by dynamic objects (all except ID should be ignored). + [Test] + public void TestDeleteyDynamicObject() + { + Assert.AreEqual(1, GetTestTable().Delete(delete: new { id = 11, code = 11, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" })); + + // Verify + Assert.AreEqual(0, GetTestTable().Count(id: 11)); + } + + /// Test row deleting by mapped object. + [Test] + public void TestDeleteByMappedObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Delete(delete: new Users + { + Id = 12, + Code = "12", + First = "Juri", + Last = "Gagarin", + Email = "juri.gagarin@megacorp.com", + Quote = "bla, bla, bla" + })); + + // Verify + Assert.AreEqual(0, GetTestTable().Count(id: 12)); + } + + /// Test row deleting by basic object. + [Test] + public void TestDeleteByBasicObject() + { + var u = GetTestTable(); + + Assert.AreEqual(1, u.Delete(delete: new sample_users + { + id = 13, + code = "13", + first = "Juri", + last = "Gagarin", + email = "juri.gagarin@megacorp.com", + quote = "bla, bla, bla" + })); + + // Verify + Assert.AreEqual(0, GetTestTable().Count(id: 13)); + } + + /// Test row deleting by dynamic objects (all except ID should be ignored). + [Test] + public void TestDeleteyDynamicObjectWhere() + { + Assert.AreEqual(1, GetTestTable().Delete(where: new { id = 14, code = "14" })); + + // Verify + Assert.AreEqual(0, GetTestTable().Count(id: 14)); + } + + #endregion Delete + } } \ No newline at end of file diff --git a/DynamORM.Tests/Modify/DynamicNoSchemaModificationTests.cs b/DynamORM.Tests/Modify/DynamicNoSchemaModificationTests.cs index a57ac92..1fa57a2 100644 --- a/DynamORM.Tests/Modify/DynamicNoSchemaModificationTests.cs +++ b/DynamORM.Tests/Modify/DynamicNoSchemaModificationTests.cs @@ -1,55 +1,55 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Modify -{ - /// Test standard dynamic access ORM. With out schema information from database. - [TestClass] - public class DynamicNoSchemaModificationTests : DynamicModificationTests - { - /// Setup test parameters. - [TestInitialize] - public virtual void SetUp() - { - CreateTestDatabase(); - CreateDynamicDatabase( - DynamicDatabaseOptions.SingleConnection | - DynamicDatabaseOptions.SingleTransaction | - DynamicDatabaseOptions.SupportLimitOffset); - } - - /// Create table using specified method. - /// Dynamic table. - public override dynamic GetTestTable() - { - return Database.Table("users", new string[] { "id" }); - } - } -} \ No newline at end of file +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using NUnit.Framework; + +namespace DynamORM.Tests.Modify +{ + /// Test standard dynamic access ORM. With out schema information from database. + [TestFixture] + public class DynamicNoSchemaModificationTests : DynamicModificationTests + { + /// Setup test parameters. + [SetUp] + public override void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase( + DynamicDatabaseOptions.SingleConnection | + DynamicDatabaseOptions.SingleTransaction | + DynamicDatabaseOptions.SupportLimitOffset); + } + + /// Create table using specified method. + /// Dynamic table. + public override dynamic GetTestTable() + { + return Database.Table("sample_users", new string[] { "id" }); + } + } +} diff --git a/DynamORM.Tests/Modify/DynamicTypeSchemaModificationTests.cs b/DynamORM.Tests/Modify/DynamicTypeSchemaModificationTests.cs index a8fd296..0a01692 100644 --- a/DynamORM.Tests/Modify/DynamicTypeSchemaModificationTests.cs +++ b/DynamORM.Tests/Modify/DynamicTypeSchemaModificationTests.cs @@ -1,69 +1,69 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System.Collections.Generic; -using DynamORM.Tests.Helpers; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Modify -{ - /// Test standard dynamic access ORM. With out schema information from database. - [TestClass] - public class DynamicTypeSchemaModificationTests : DynamicModificationTests - { - /// Create table using specified method. - /// Dynamic table. - public override dynamic GetTestTable() - { - return Database.Table(); - } - - /// - /// Tests the bulk insert. - /// - [TestMethod] - public void TestBulkInsert() - { - Assert.AreEqual(2, Database.Insert(new List - { - new users - { - id = 1001, - login = "a", - }, - new users - { - id = 1002, - login = "b", - } - })); - - Assert.AreEqual(2, Database.Delete().Where(u => u.users.id.In(1001, 1002)).Execute()); - } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Collections.Generic; +using DynamORM.Tests.Helpers; +using NUnit.Framework; + +namespace DynamORM.Tests.Modify +{ + /// Test standard dynamic access ORM. With out schema information from database. + [TestFixture] + public class DynamicTypeSchemaModificationTests : DynamicModificationTests + { + /// Create table using specified method. + /// Dynamic table. + public override dynamic GetTestTable() + { + return Database.Table(); + } + + /// + /// Tests the bulk insert. + /// + [Test] + public void TestBulkInsert() + { + Assert.AreEqual(2, Database.Insert(new List + { + new sample_users + { + id = 1001, + login = "a", + }, + new sample_users + { + id = 1002, + login = "b", + } + })); + + Assert.AreEqual(2, Database.Delete().Where(u => u.sample_users.id.In(1001, 1002)).Execute()); + } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Modify/ParserTests.cs b/DynamORM.Tests/Modify/ParserTests.cs index 99154e7..9cdc1c6 100644 --- a/DynamORM.Tests/Modify/ParserTests.cs +++ b/DynamORM.Tests/Modify/ParserTests.cs @@ -1,225 +1,225 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System.Linq; -using DynamORM.Builders; -using DynamORM.Builders.Implementation; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using DynamORM.Tests.Helpers; -using System.Collections.Generic; -using static System.Data.Entity.Infrastructure.Design.Executor; -using System.Runtime.InteropServices; - -namespace DynamORM.Tests.Modify -{ - /// New parser tests. - [TestClass] - public class ParserTests : TestsBase - { - /// Setup test parameters. - [TestInitialize] - public virtual void SetUp() - { - CreateTestDatabase(); - CreateDynamicDatabase( - DynamicDatabaseOptions.SingleConnection | - DynamicDatabaseOptions.SingleTransaction | - DynamicDatabaseOptions.SupportLimitOffset); - } - - /// Tear down test objects. - [TestCleanup] - public virtual void TearDown() - { - DestroyDynamicDatabase(); - DestroyTestDatabase(); - } - - #region Insert - - /// - /// Tests the basic insert. - /// - [TestMethod] - public void TestInsertBasic() - { - IDynamicInsertQueryBuilder cmd = new DynamicInsertQueryBuilder(Database, "Users"); - - cmd.Values(x => x.Users.Code = "001", x => x.Users.Name = "Admin", x => x.Users.IsAdmin = 1); - - Assert.AreEqual(string.Format(@"INSERT INTO ""Users"" (""Code"", ""Name"", ""IsAdmin"") VALUES ({0})", - string.Join(", ", cmd.Parameters.Keys.Select(p => string.Format("[${0}]", p)))), cmd.CommandText()); - } - - /// - /// Tests the insert with sub query. - /// - [TestMethod] - public void TestInsertSubQuery() - { - IDynamicInsertQueryBuilder cmd = new DynamicInsertQueryBuilder(Database, "Users"); - - cmd.Values(x => x.Code = "001", x => x.Name = "Admin", x => x.IsAdmin = x(cmd - .SubQuery(a => a.AccessRights.As(a.a)) - .Select(a => a.IsAdmin) - .Where(a => a.User_Id == "001"))); - - Assert.AreEqual(string.Format(@"INSERT INTO ""Users"" (""Code"", ""Name"", ""IsAdmin"") VALUES ({0}, (SELECT a.""IsAdmin"" FROM ""AccessRights"" AS a WHERE (a.""User_Id"" = [${1}])))", - string.Join(", ", cmd.Parameters.Keys.Take(2).Select(p => string.Format("[${0}]", p))), cmd.Parameters.Keys.Last()), cmd.CommandText()); - } - - /// - /// Tests the basic insert using object. - /// - [TestMethod] - public void TestInsertBasicObject() - { - IDynamicInsertQueryBuilder cmd = new DynamicInsertQueryBuilder(Database, "Users"); - - cmd.Values(x => new { Code = "001", Name = "Admin", IsAdmin = 1 }); - - Assert.AreEqual(string.Format(@"INSERT INTO ""Users"" (""Code"", ""Name"", ""IsAdmin"") VALUES ({0})", - string.Join(", ", cmd.Parameters.Keys.Select(p => string.Format("[${0}]", p)))), cmd.CommandText()); - } - - /// - /// Tests the insert using object with sub query. - /// - [TestMethod] - public void TestInsertSubQueryObject() - { - IDynamicInsertQueryBuilder cmd = new DynamicInsertQueryBuilder(Database, "Users"); - - cmd.Values(x => new - { - Code = "001", - Name = "Admin", - IsAdmin = x(cmd - .SubQuery(a => a.AccessRights.As(a.a)) - .Select(a => a.IsAdmin) - .Where(a => a.User_Id == "001")) - }); - - Assert.AreEqual(string.Format(@"INSERT INTO ""Users"" (""Code"", ""Name"", ""IsAdmin"") VALUES ({0}, (SELECT a.""IsAdmin"" FROM ""AccessRights"" AS a WHERE (a.""User_Id"" = [${1}])))", - string.Join(", ", cmd.Parameters.Keys.Take(2).Select(p => string.Format("[${0}]", p))), cmd.Parameters.Keys.Last()), cmd.CommandText()); - } - - #endregion Insert - - #region Update - - /// - /// Tests the basic update. - /// - [TestMethod] - public void TestUpdateBasicSet() - { - IDynamicUpdateQueryBuilder cmd = new DynamicUpdateQueryBuilder(Database, "Users"); - - cmd.Set(x => x.Users.Code = "001", x => x.Users.Name = "Admin", x => x.Users.IsAdmin = 1) - .Where(x => x.Users.Id_User == 1); - - Assert.AreEqual(string.Format(@"UPDATE ""Users"" SET ""Code"" = [${0}], ""Name"" = [${1}], ""IsAdmin"" = [${2}] WHERE (""Users"".""Id_User"" = [${3}])", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], cmd.Parameters.Keys.ToArray()[3]), cmd.CommandText()); - } - - /// - /// Tests the basic update. - /// - [TestMethod] - public void TestUpdateBasicValues() - { - IDynamicUpdateQueryBuilder cmd = new DynamicUpdateQueryBuilder(Database, "Users"); - - cmd - .Values("Code", "001") - .Values("Name", "Admin") - .Values("IsAdmin", "1") - .Where(x => x.Users.Id_User == 1); - - Assert.AreEqual(string.Format(@"UPDATE ""Users"" SET ""Code"" = [${0}], ""Name"" = [${1}], ""IsAdmin"" = [${2}] WHERE (""Users"".""Id_User"" = [${3}])", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], cmd.Parameters.Keys.ToArray()[3]), cmd.CommandText()); - } - - /// - /// Tests the insert with sub query. - /// - [TestMethod] - public void TestUpdateSubQuery() - { - IDynamicUpdateQueryBuilder cmd = new DynamicUpdateQueryBuilder(Database, "Users"); - cmd.Set(x => x.Users.Code = "001", x => x.Users.Name = "Admin", x => x.Users.IsAdmin = x(cmd - .SubQuery(a => a.AccessRights.As(a.a)) - .Select(a => a.IsAdmin) - .Where(a => a.User_Id == a.Users.Id_User))) - .Where(x => x.Users.Id_User == 1); - - Assert.AreEqual(string.Format(@"UPDATE ""Users"" SET ""Code"" = [${0}], ""Name"" = [${1}], ""IsAdmin"" = (SELECT a.""IsAdmin"" FROM ""AccessRights"" AS a WHERE (a.""User_Id"" = ""Users"".""Id_User"")) WHERE (""Users"".""Id_User"" = [${2}])", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); - } - - /// - /// Tests the basic insert using object. - /// - [TestMethod] - public void TestUpdateBasicObject() - { - IDynamicUpdateQueryBuilder cmd = new DynamicUpdateQueryBuilder(Database, "Users"); - - cmd.Set(x => new { Code = "001", Name = "Admin", IsAdmin = 1 }) - .Where(x => new { Id_User = 1 }); - - Assert.AreEqual(string.Format(@"UPDATE ""Users"" SET ""Code"" = [${0}], ""Name"" = [${1}], ""IsAdmin"" = [${2}] WHERE (""Id_User"" = [${3}])", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], cmd.Parameters.Keys.ToArray()[3]), cmd.CommandText()); - } - - /// - /// Tests the basic insert using object. - /// - [TestMethod] - public void TestUpdateSubQueryObject() - { - IDynamicUpdateQueryBuilder cmd = new DynamicUpdateQueryBuilder(Database, "Users"); - - cmd.Set(x => new - { - Code = "001", - Name = "Admin", - IsAdmin = x(cmd - .SubQuery(a => a.AccessRights.As(a.a)) - .Select(a => a.IsAdmin) - .Where(a => a.User_Id == a.Users.Id_User)) - }).Where(x => new { Id_User = 1 }); - - Assert.AreEqual(string.Format(@"UPDATE ""Users"" SET ""Code"" = [${0}], ""Name"" = [${1}], ""IsAdmin"" = (SELECT a.""IsAdmin"" FROM ""AccessRights"" AS a WHERE (a.""User_Id"" = ""Users"".""Id_User"")) WHERE (""Id_User"" = [${2}])", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); - } - - #endregion Update - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Linq; +using DynamORM.Builders; +using DynamORM.Builders.Implementation; +using NUnit.Framework; +using DynamORM.Tests.Helpers; +using System.Collections.Generic; +using static System.Data.Entity.Infrastructure.Design.Executor; +using System.Runtime.InteropServices; + +namespace DynamORM.Tests.Modify +{ + /// New parser tests. + [TestFixture] + public class ParserTests : TestsBase + { + /// Setup test parameters. + [SetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase( + DynamicDatabaseOptions.SingleConnection | + DynamicDatabaseOptions.SingleTransaction | + DynamicDatabaseOptions.SupportLimitOffset); + } + + /// Tear down test objects. + [TearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + #region Insert + + /// + /// Tests the basic insert. + /// + [Test] + public void TestInsertBasic() + { + IDynamicInsertQueryBuilder cmd = new DynamicInsertQueryBuilder(Database, "Users"); + + cmd.Values(x => x.Users.Code = "001", x => x.Users.Name = "Admin", x => x.Users.IsAdmin = 1); + + Assert.AreEqual(string.Format(@"INSERT INTO ""Users"" (""Code"", ""Name"", ""IsAdmin"") VALUES ({0})", + string.Join(", ", cmd.Parameters.Keys.Select(p => string.Format("[${0}]", p)))), cmd.CommandText()); + } + + /// + /// Tests the insert with sub query. + /// + [Test] + public void TestInsertSubQuery() + { + IDynamicInsertQueryBuilder cmd = new DynamicInsertQueryBuilder(Database, "Users"); + + cmd.Values(x => x.Code = "001", x => x.Name = "Admin", x => x.IsAdmin = x(cmd + .SubQuery(a => a.AccessRights.As(a.a)) + .Select(a => a.IsAdmin) + .Where(a => a.User_Id == "001"))); + + Assert.AreEqual(string.Format(@"INSERT INTO ""Users"" (""Code"", ""Name"", ""IsAdmin"") VALUES ({0}, (SELECT a.""IsAdmin"" FROM ""AccessRights"" AS a WHERE (a.""User_Id"" = [${1}])))", + string.Join(", ", cmd.Parameters.Keys.Take(2).Select(p => string.Format("[${0}]", p))), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests the basic insert using object. + /// + [Test] + public void TestInsertBasicObject() + { + IDynamicInsertQueryBuilder cmd = new DynamicInsertQueryBuilder(Database, "Users"); + + cmd.Values(x => new { Code = "001", Name = "Admin", IsAdmin = 1 }); + + Assert.AreEqual(string.Format(@"INSERT INTO ""Users"" (""Code"", ""Name"", ""IsAdmin"") VALUES ({0})", + string.Join(", ", cmd.Parameters.Keys.Select(p => string.Format("[${0}]", p)))), cmd.CommandText()); + } + + /// + /// Tests the insert using object with sub query. + /// + [Test] + public void TestInsertSubQueryObject() + { + IDynamicInsertQueryBuilder cmd = new DynamicInsertQueryBuilder(Database, "Users"); + + cmd.Values(x => new + { + Code = "001", + Name = "Admin", + IsAdmin = x(cmd + .SubQuery(a => a.AccessRights.As(a.a)) + .Select(a => a.IsAdmin) + .Where(a => a.User_Id == "001")) + }); + + Assert.AreEqual(string.Format(@"INSERT INTO ""Users"" (""Code"", ""Name"", ""IsAdmin"") VALUES ({0}, (SELECT a.""IsAdmin"" FROM ""AccessRights"" AS a WHERE (a.""User_Id"" = [${1}])))", + string.Join(", ", cmd.Parameters.Keys.Take(2).Select(p => string.Format("[${0}]", p))), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + #endregion Insert + + #region Update + + /// + /// Tests the basic update. + /// + [Test] + public void TestUpdateBasicSet() + { + IDynamicUpdateQueryBuilder cmd = new DynamicUpdateQueryBuilder(Database, "Users"); + + cmd.Set(x => x.Users.Code = "001", x => x.Users.Name = "Admin", x => x.Users.IsAdmin = 1) + .Where(x => x.Users.Id_User == 1); + + Assert.AreEqual(string.Format(@"UPDATE ""Users"" SET ""Code"" = [${0}], ""Name"" = [${1}], ""IsAdmin"" = [${2}] WHERE (""Users"".""Id_User"" = [${3}])", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], cmd.Parameters.Keys.ToArray()[3]), cmd.CommandText()); + } + + /// + /// Tests the basic update. + /// + [Test] + public void TestUpdateBasicValues() + { + IDynamicUpdateQueryBuilder cmd = new DynamicUpdateQueryBuilder(Database, "Users"); + + cmd + .Values("Code", "001") + .Values("Name", "Admin") + .Values("IsAdmin", "1") + .Where(x => x.Users.Id_User == 1); + + Assert.AreEqual(string.Format(@"UPDATE ""Users"" SET ""Code"" = [${0}], ""Name"" = [${1}], ""IsAdmin"" = [${2}] WHERE (""Users"".""Id_User"" = [${3}])", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], cmd.Parameters.Keys.ToArray()[3]), cmd.CommandText()); + } + + /// + /// Tests the insert with sub query. + /// + [Test] + public void TestUpdateSubQuery() + { + IDynamicUpdateQueryBuilder cmd = new DynamicUpdateQueryBuilder(Database, "Users"); + cmd.Set(x => x.Users.Code = "001", x => x.Users.Name = "Admin", x => x.Users.IsAdmin = x(cmd + .SubQuery(a => a.AccessRights.As(a.a)) + .Select(a => a.IsAdmin) + .Where(a => a.User_Id == a.Users.Id_User))) + .Where(x => x.Users.Id_User == 1); + + Assert.AreEqual(string.Format(@"UPDATE ""Users"" SET ""Code"" = [${0}], ""Name"" = [${1}], ""IsAdmin"" = (SELECT a.""IsAdmin"" FROM ""AccessRights"" AS a WHERE (a.""User_Id"" = ""Users"".""Id_User"")) WHERE (""Users"".""Id_User"" = [${2}])", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + } + + /// + /// Tests the basic insert using object. + /// + [Test] + public void TestUpdateBasicObject() + { + IDynamicUpdateQueryBuilder cmd = new DynamicUpdateQueryBuilder(Database, "Users"); + + cmd.Set(x => new { Code = "001", Name = "Admin", IsAdmin = 1 }) + .Where(x => new { Id_User = 1 }); + + Assert.AreEqual(string.Format(@"UPDATE ""Users"" SET ""Code"" = [${0}], ""Name"" = [${1}], ""IsAdmin"" = [${2}] WHERE (""Id_User"" = [${3}])", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], cmd.Parameters.Keys.ToArray()[3]), cmd.CommandText()); + } + + /// + /// Tests the basic insert using object. + /// + [Test] + public void TestUpdateSubQueryObject() + { + IDynamicUpdateQueryBuilder cmd = new DynamicUpdateQueryBuilder(Database, "Users"); + + cmd.Set(x => new + { + Code = "001", + Name = "Admin", + IsAdmin = x(cmd + .SubQuery(a => a.AccessRights.As(a.a)) + .Select(a => a.IsAdmin) + .Where(a => a.User_Id == a.Users.Id_User)) + }).Where(x => new { Id_User = 1 }); + + Assert.AreEqual(string.Format(@"UPDATE ""Users"" SET ""Code"" = [${0}], ""Name"" = [${1}], ""IsAdmin"" = (SELECT a.""IsAdmin"" FROM ""AccessRights"" AS a WHERE (a.""User_Id"" = ""Users"".""Id_User"")) WHERE (""Id_User"" = [${2}])", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + } + + #endregion Update + } } \ No newline at end of file diff --git a/DynamORM.Tests/Properties/Resources.Designer.cs b/DynamORM.Tests/Properties/Resources.Designer.cs index 950aeca..a2d525e 100644 --- a/DynamORM.Tests/Properties/Resources.Designer.cs +++ b/DynamORM.Tests/Properties/Resources.Designer.cs @@ -1,75 +1,75 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace DynamORM.Tests.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DynamORM.Tests.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to CREATE TABLE users (id INTEGER NOT NULL PRIMARY KEY, code, login text, first text, last text, password text, email text, quote text); - ///INSERT INTO users (code,first,last,email,quote) VALUES ('1','Clarke','Jarvis','eu.accumsan@nonarcuVivamus.org','non leo. Vivamus'); - ///INSERT INTO users (code,first,last,email,quote) VALUES ('2','Marny','Fry','Cras.convallis.convallis@nisiCumsociis.ca','aliquam eu, accumsan sed, facilisis vitae, orci. Phasellus'); - ///INSERT INTO users (code,first,last,email,quote) VALUES ('3',' [rest of string was truncated]";. - /// - internal static string UsersTable { - get { - return ResourceManager.GetString("UsersTable", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DynamORM.Tests.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DynamORM.Tests.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to CREATE TABLE sample_users (id INTEGER NOT NULL PRIMARY KEY, code, login text, first text, last text, password text, email text, quote text); + ///INSERT INTO sample_users (code,first,last,email,quote) VALUES ('1','Clarke','Jarvis','eu.accumsan@nonarcuVivamus.org','non leo. Vivamus'); + ///INSERT INTO sample_users (code,first,last,email,quote) VALUES ('2','Marny','Fry','Cras.convallis.convallis@nisiCumsociis.ca','aliquam eu, accumsan sed, facilisis vitae, orci. Phasellus'); + ///INSERT INTO sample_users (code,first,last,email,quote) VALUES ('3',' [rest of string was truncated]";. + /// + internal static string UsersTable { + get { + return ResourceManager.GetString("UsersTable", resourceCulture); + } + } + } +} diff --git a/DynamORM.Tests/Properties/Resources.resx b/DynamORM.Tests/Properties/Resources.resx index f0356bb..2ee7ee8 100644 --- a/DynamORM.Tests/Properties/Resources.resx +++ b/DynamORM.Tests/Properties/Resources.resx @@ -1,324 +1,324 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - CREATE TABLE users (id INTEGER NOT NULL PRIMARY KEY, code, login text, first text, last text, password text, email text, quote text); -INSERT INTO users (code,first,last,email,quote) VALUES ('1','Clarke','Jarvis','eu.accumsan@nonarcuVivamus.org','non leo. Vivamus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('2','Marny','Fry','Cras.convallis.convallis@nisiCumsociis.ca','aliquam eu, accumsan sed, facilisis vitae, orci. Phasellus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('3','Dai','Marks','dictum.augue@venenatis.ca','nulla ante, iaculis nec, eleifend non, dapibus rutrum, justo. Praesent'); -INSERT INTO users (code,first,last,email,quote) VALUES ('4','Forrest','Hendricks','justo.sit.amet@odioa.edu','auctor. Mauris vel turpis. Aliquam adipiscing lobortis'); -INSERT INTO users (code,first,last,email,quote) VALUES ('5','Blossom','Dunlap','libero.mauris@Phasellusfermentumconvallis.ca','luctus sit amet, faucibus ut, nulla. Cras eu tellus eu'); -INSERT INTO users (code,first,last,email,quote) VALUES ('6','George','Rios','vitae@sodales.ca','elit. Aliquam auctor, velit eget laoreet'); -INSERT INTO users (code,first,last,email,quote) VALUES ('7','Ivory','Henderson','elit.Aliquam.auctor@Nullamvelitdui.ca','Fusce diam nunc, ullamcorper'); -INSERT INTO users (code,first,last,email,quote) VALUES ('8','Inez','Goodwin','consectetuer.mauris@nibhPhasellus.edu','eu, placerat eget, venenatis a, magna.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('9','Sigourney','Gonzales','lectus.pede.ultrices@sagittislobortismauris.org','egestas hendrerit'); -INSERT INTO users (code,first,last,email,quote) VALUES ('10','Fulton','Terrell','penatibus@euaugue.com','Nulla interdum. Curabitur dictum.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('11','Logan','Freeman','malesuada.malesuada@nullaIntegervulputate.edu','dui, semper et, lacinia'); -INSERT INTO users (code,first,last,email,quote) VALUES ('12','Anne','Irwin','lorem.ut.aliquam@ligula.org','erat, in consectetuer ipsum nunc id enim.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('13','Alexandra','Church','sit@sempererat.org','lorem, luctus ut, pellentesque eget, dictum placerat, augue. Sed'); -INSERT INTO users (code,first,last,email,quote) VALUES ('14','Adena','Branch','sit.amet@accumsanlaoreetipsum.org','natoque penatibus et'); -INSERT INTO users (code,first,last,email,quote) VALUES ('15','Lionel','Hoover','ac@Donectempor.ca','at pede. Cras vulputate'); -INSERT INTO users (code,first,last,email,quote) VALUES ('16','Aimee','Strickland','ornare.lectus@tinciduntduiaugue.ca','vitae odio sagittis semper. Nam'); -INSERT INTO users (code,first,last,email,quote) VALUES ('17','Selma','Williamson','metus.In.nec@quamquisdiam.org','id nunc'); -INSERT INTO users (code,first,last,email,quote) VALUES ('18','Lara','Trujillo','lacus@convallisest.edu','Integer sem elit, pharetra ut,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('19','Ori','Ellis','egestas@at.ca','odio. Nam interdum'); -INSERT INTO users (code,first,last,email,quote) VALUES ('20','Macey','Carey','sed.consequat@ametorciUt.org','magna'); -INSERT INTO users (code,first,last,email,quote) VALUES ('21','Quynn','Randall','Cras.dictum@malesuada.org','egestas. Fusce aliquet magna a neque.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('22','Alec','Robles','Fusce.feugiat@mollisdui.com','a'); -INSERT INTO users (code,first,last,email,quote) VALUES ('23','Jakeem','Bell','ante@laoreetlectusquis.edu','eget massa. Suspendisse eleifend. Cras'); -INSERT INTO users (code,first,last,email,quote) VALUES ('24','Katelyn','Cannon','sit.amet@PhasellusornareFusce.org','nisi dictum augue malesuada malesuada.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('25','Christian','Alford','per@vulputate.ca','turpis.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('26','Leila','Forbes','nec.ante@idblanditat.ca','ac ipsum. Phasellus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('27','Hadley','Gillespie','Lorem.ipsum@Nullamvitaediam.com','vestibulum lorem, sit amet ultricies sem magna nec quam.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('28','Julian','Keith','facilisis@loremvitaeodio.edu','nulla at sem molestie sodales. Mauris blandit enim consequat purus.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('29','Allen','Ramos','neque@lobortis.ca','amet diam eu dolor egestas rhoncus. Proin nisl sem, consequat'); -INSERT INTO users (code,first,last,email,quote) VALUES ('30','Hermione','Walsh','dictum.magna@tincidunt.edu','scelerisque'); -INSERT INTO users (code,first,last,email,quote) VALUES ('31','Xena','Graves','eu.dui@tinciduntaliquamarcu.ca','nunc interdum feugiat. Sed nec metus facilisis lorem tristique aliquet.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('32','Tanisha','Blackburn','aliquam.eu.accumsan@dui.edu','fringilla mi lacinia mattis. Integer eu'); -INSERT INTO users (code,first,last,email,quote) VALUES ('33','Norman','Hobbs','eu.euismod.ac@tinciduntDonecvitae.com','pulvinar arcu et pede. Nunc'); -INSERT INTO users (code,first,last,email,quote) VALUES ('34','Guy','Molina','non@rutrumurna.org','Vivamus euismod urna.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('35','Rama','Albert','nunc@laciniavitaesodales.com','eu augue porttitor interdum. Sed auctor odio a purus. Duis'); -INSERT INTO users (code,first,last,email,quote) VALUES ('36','Owen','Combs','Nullam.enim.Sed@magnaPhasellusdolor.com','mollis vitae, posuere at, velit. Cras lorem lorem, luctus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('37','Ashely','Graham','eget.odio@enimsitamet.com','commodo at, libero. Morbi accumsan laoreet ipsum. Curabitur consequat,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('38','Paul','Levy','nec@leo.com','augue scelerisque mollis. Phasellus libero mauris, aliquam eu, accumsan'); -INSERT INTO users (code,first,last,email,quote) VALUES ('39','Octavia','Calderon','eu@maurissitamet.edu','est, mollis non, cursus non,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('40','Lenore','Pugh','eu.metus.In@Cumsociisnatoque.com','scelerisque mollis. Phasellus libero mauris, aliquam eu, accumsan sed, facilisis'); -INSERT INTO users (code,first,last,email,quote) VALUES ('41','Regan','Clemons','eu.neque.pellentesque@Integerinmagna.ca','Curae;'); -INSERT INTO users (code,first,last,email,quote) VALUES ('42','Zachary','Haynes','eu.erat@ipsum.edu','quis lectus. Nullam'); -INSERT INTO users (code,first,last,email,quote) VALUES ('43','Dane','Hyde','rhoncus@auctor.com','magna et ipsum cursus vestibulum. Mauris magna. Duis dignissim'); -INSERT INTO users (code,first,last,email,quote) VALUES ('44','Mara','Hansen','Nunc.commodo@ultricies.org','magna. Ut tincidunt orci quis lectus. Nullam suscipit, est'); -INSERT INTO users (code,first,last,email,quote) VALUES ('45','Zelda','Parrish','turpis.In@rutrummagnaCras.com','Suspendisse aliquet, sem ut cursus luctus, ipsum leo'); -INSERT INTO users (code,first,last,email,quote) VALUES ('46','Amaya','Richmond','lacinia.at@in.org','ac, fermentum vel, mauris.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('47','Hedda','Murray','ac.fermentum.vel@ipsum.com','tincidunt, neque vitae semper egestas, urna justo faucibus lectus, a'); -INSERT INTO users (code,first,last,email,quote) VALUES ('48','Brendan','Macdonald','dignissim@leoMorbi.com','vestibulum lorem, sit'); -INSERT INTO users (code,first,last,email,quote) VALUES ('49','Gray','Mccall','risus.Donec@atsem.ca','libero est, congue'); -INSERT INTO users (code,first,last,email,quote) VALUES ('50','Shoshana','Tran','nunc.In@aliquet.org','magna sed dui.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('51','Sylvester','Parks','Mauris.molestie@ipsumdolorsit.org','nec, eleifend non, dapibus rutrum, justo.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('52','Kennan','Stokes','nonummy.ut.molestie@varius.org','Ut nec urna et arcu imperdiet ullamcorper. Duis'); -INSERT INTO users (code,first,last,email,quote) VALUES ('53','Quin','Park','porttitor@vulputateeu.com','scelerisque, lorem ipsum sodales'); -INSERT INTO users (code,first,last,email,quote) VALUES ('54','Madison','Bailey','nec.orci.Donec@vestibulumnequesed.org','augue. Sed'); -INSERT INTO users (code,first,last,email,quote) VALUES ('55','Kenyon','Reilly','ultrices.iaculis.odio@sit.ca','enim. Curabitur massa. Vestibulum accumsan neque'); -INSERT INTO users (code,first,last,email,quote) VALUES ('56','Drew','Brooks','molestie.orci@aliquetdiamSed.org','at risus. Nunc ac sem ut dolor dapibus gravida.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('57','Omar','Dunn','vel@Donecsollicitudinadipiscing.ca','ultrices posuere cubilia Curae; Phasellus ornare. Fusce mollis. Duis sit'); -INSERT INTO users (code,first,last,email,quote) VALUES ('58','Glenna','Lambert','Proin.non@Phasellus.com','neque. In'); -INSERT INTO users (code,first,last,email,quote) VALUES ('59','Aspen','Bailey','in.dolor.Fusce@ipsum.org','a, arcu. Sed et libero. Proin'); -INSERT INTO users (code,first,last,email,quote) VALUES ('60','Adele','Carlson','velit.justo.nec@vehicularisusNulla.edu','risus.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('61','Kerry','Zimmerman','luctus.aliquet.odio@urnajusto.edu','purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis'); -INSERT INTO users (code,first,last,email,quote) VALUES ('62','Hedda','Guthrie','vulputate.risus@Phaselluselit.ca','ac mattis velit justo nec'); -INSERT INTO users (code,first,last,email,quote) VALUES ('63','Wyoming','Blackburn','nec.ante@lorem.org','ultricies'); -INSERT INTO users (code,first,last,email,quote) VALUES ('64','Palmer','Dennis','venenatis.lacus@Donecnonjusto.org','a, arcu. Sed et libero. Proin'); -INSERT INTO users (code,first,last,email,quote) VALUES ('65','Wesley','Reeves','massa.Suspendisse.eleifend@nec.edu','consequat'); -INSERT INTO users (code,first,last,email,quote) VALUES ('66','Mallory','Todd','Duis@Crasloremlorem.edu','nascetur ridiculus mus. Proin vel arcu eu odio'); -INSERT INTO users (code,first,last,email,quote) VALUES ('67','Perry','Kirk','tincidunt.adipiscing@mauris.ca','ullamcorper viverra. Maecenas iaculis aliquet diam. Sed diam lorem,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('68','Justina','Horne','enim.nec.tempus@magnanec.com','ipsum ac mi eleifend egestas. Sed pharetra, felis eget'); -INSERT INTO users (code,first,last,email,quote) VALUES ('69','Noel','Santana','Duis.elementum.dui@Nullamvitae.org','Phasellus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('70','Sylvester','Bridges','Sed.neque@ornarelectusante.org','Maecenas libero'); -INSERT INTO users (code,first,last,email,quote) VALUES ('71','Cailin','Baldwin','eu@nibhenim.com','consectetuer'); -INSERT INTO users (code,first,last,email,quote) VALUES ('72','Beatrice','Henson','risus.Nunc.ac@Etiam.ca','nec, imperdiet nec, leo. Morbi neque'); -INSERT INTO users (code,first,last,email,quote) VALUES ('73','Kellie','Curry','quis.turpis.vitae@Quisque.org','ipsum. Suspendisse non leo.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('74','Justine','Stewart','Curabitur@dui.org','natoque penatibus et magnis dis parturient'); -INSERT INTO users (code,first,last,email,quote) VALUES ('75','Dean','Waters','quis.turpis@morbi.ca','Nulla interdum. Curabitur dictum. Phasellus in felis. Nulla tempor augue'); -INSERT INTO users (code,first,last,email,quote) VALUES ('76','Helen','Porter','molestie.dapibus.ligula@ipsum.com','enim non nisi. Aenean eget'); -INSERT INTO users (code,first,last,email,quote) VALUES ('77','Janna','Acosta','lectus.a@tempusrisus.com','lectus justo'); -INSERT INTO users (code,first,last,email,quote) VALUES ('78','Libby','Vaughn','nisl.sem.consequat@convallis.ca','purus mauris a nunc.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('79','Winifred','Cooke','aliquet.sem.ut@etrutrum.ca','metus facilisis lorem tristique aliquet. Phasellus fermentum convallis ligula. Donec'); -INSERT INTO users (code,first,last,email,quote) VALUES ('80','Taylor','Glass','sit@maurissitamet.com','orci, consectetuer euismod'); -INSERT INTO users (code,first,last,email,quote) VALUES ('81','Melyssa','Palmer','pharetra.sed.hendrerit@fermentum.ca','neque tellus, imperdiet non, vestibulum nec,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('82','Kuame','Holman','Vivamus@Donecnibh.edu','cursus non, egestas a, dui. Cras'); -INSERT INTO users (code,first,last,email,quote) VALUES ('83','Martin','Hughes','magna@nonbibendum.org','et ultrices posuere cubilia'); -INSERT INTO users (code,first,last,email,quote) VALUES ('84','Roanna','Potter','amet.dapibus@aenim.com','est tempor bibendum. Donec'); -INSERT INTO users (code,first,last,email,quote) VALUES ('85','Cleo','Carson','Curabitur.dictum.Phasellus@liberoIntegerin.com','mauris'); -INSERT INTO users (code,first,last,email,quote) VALUES ('86','Adele','Daugherty','vulputate.ullamcorper@elitfermentumrisus.edu','consequat purus. Maecenas'); -INSERT INTO users (code,first,last,email,quote) VALUES ('87','Macon','Todd','et.malesuada@Crasloremlorem.com','montes, nascetur ridiculus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('88','Ira','Merritt','risus@eunullaat.ca','metus.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('89','Dustin','Landry','nec.orci.Donec@vulputateeu.com','Nunc pulvinar arcu et pede. Nunc sed'); -INSERT INTO users (code,first,last,email,quote) VALUES ('90','Debra','Shepherd','dolor.elit@ornareFusce.ca','Suspendisse non leo. Vivamus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('91','Channing','Mcknight','tellus@laoreet.ca','non ante bibendum ullamcorper. Duis cursus, diam at pretium aliquet,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('92','Abraham','Rodgers','tincidunt.nibh@ipsum.ca','Cum sociis'); -INSERT INTO users (code,first,last,email,quote) VALUES ('93','Abel','Mcintyre','et@turpisvitaepurus.com','at pretium aliquet,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('94','Lysandra','Cotton','malesuada.malesuada.Integer@gravidamolestie.org','commodo hendrerit. Donec'); -INSERT INTO users (code,first,last,email,quote) VALUES ('95','Kane','Bennett','ante.Vivamus@Donec.ca','consectetuer rhoncus. Nullam velit dui, semper et, lacinia vitae, sodales'); -INSERT INTO users (code,first,last,email,quote) VALUES ('96','Timothy','Rivas','hendrerit.consectetuer@nonarcuVivamus.ca','Suspendisse non leo. Vivamus nibh dolor, nonummy ac, feugiat'); -INSERT INTO users (code,first,last,email,quote) VALUES ('97','Arden','Cote','magnis@sedestNunc.org','purus mauris a nunc.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('98','Brynn','Britt','ac@nibhQuisque.edu','Duis gravida. Praesent eu nulla at sem molestie'); -INSERT INTO users (code,first,last,email,quote) VALUES ('99','Jerome','Kirkland','ac@nibhPhasellus.com','neque'); -INSERT INTO users (code,first,last,email,quote) VALUES ('100','Hoyt','Tran','ullamcorper.viverra@parturientmontesnascetur.org','ac tellus. Suspendisse sed dolor. Fusce mi'); -INSERT INTO users (code,first,last,email,quote) VALUES ('101','Abraham','Downs','velit.eu.sem@neceuismod.ca','suscipit,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('102','Vivien','Fletcher','Nunc.ut@quamPellentesquehabitant.edu','lobortis. Class aptent taciti sociosqu ad'); -INSERT INTO users (code,first,last,email,quote) VALUES ('103','Azalia','Turner','scelerisque@at.edu','odio sagittis semper. Nam tempor diam dictum'); -INSERT INTO users (code,first,last,email,quote) VALUES ('104','Tate','Ellis','velit.Sed.malesuada@Cras.com','sed'); -INSERT INTO users (code,first,last,email,quote) VALUES ('105','Dennis','Walls','Nullam.scelerisque@amifringilla.edu','arcu. Vestibulum ut eros non enim commodo hendrerit. Donec porttitor'); -INSERT INTO users (code,first,last,email,quote) VALUES ('106','Amela','Collins','Quisque@magna.org','Proin'); -INSERT INTO users (code,first,last,email,quote) VALUES ('107','Olivia','Dejesus','scelerisque@vitae.org','Duis cursus, diam at pretium aliquet,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('108','Mechelle','Russo','Fusce.aliquet.magna@Praesent.org','penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin'); -INSERT INTO users (code,first,last,email,quote) VALUES ('109','Hall','Burke','Vivamus.nibh@pellentesque.edu','et libero. Proin mi. Aliquam'); -INSERT INTO users (code,first,last,email,quote) VALUES ('110','Chanda','Ayers','magna.Nam.ligula@NullafacilisiSed.org','dui, semper et, lacinia vitae, sodales at,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('111','Abdul','Dominguez','pretium.aliquet.metus@vellectusCum.com','vitae erat vel pede blandit congue. In scelerisque scelerisque dui.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('112','Wynter','Lynn','tellus.Aenean@ligulatortordictum.ca','adipiscing fringilla, porttitor vulputate, posuere vulputate, lacus. Cras interdum.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('113','Molly','Willis','montes.nascetur@sed.org','sem. Nulla interdum. Curabitur dictum. Phasellus in felis.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('114','Shafira','Harper','Phasellus.in.felis@ategestas.edu','Vivamus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('115','Otto','Gentry','in.molestie.tortor@Fusce.edu','est. Mauris eu turpis. Nulla aliquet. Proin velit.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('116','Todd','Riddle','et@nislsem.com','Nulla'); -INSERT INTO users (code,first,last,email,quote) VALUES ('117','Mufutau','Pollard','sit@ridiculusmus.org','accumsan neque et nunc. Quisque ornare tortor at'); -INSERT INTO users (code,first,last,email,quote) VALUES ('118','Noah','Sears','tristique.neque.venenatis@luctuslobortis.org','dictum'); -INSERT INTO users (code,first,last,email,quote) VALUES ('119','Amanda','Clarke','in.consequat@non.org','nisi dictum augue malesuada malesuada. Integer id magna et ipsum'); -INSERT INTO users (code,first,last,email,quote) VALUES ('120','Vladimir','Colon','velit.dui@scelerisquenequeNullam.com','pharetra sed, hendrerit a, arcu. Sed et libero.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('121','Aaron','Hernandez','Quisque.ornare.tortor@magna.ca','sit amet ante. Vivamus non lorem vitae odio'); -INSERT INTO users (code,first,last,email,quote) VALUES ('122','Bert','Gonzales','fermentum@Nullamvelit.ca','quis massa. Mauris vestibulum, neque sed dictum eleifend, nunc'); -INSERT INTO users (code,first,last,email,quote) VALUES ('123','Bevis','Leblanc','viverra.Donec@dictumaugue.com','neque sed dictum eleifend, nunc risus varius orci, in'); -INSERT INTO users (code,first,last,email,quote) VALUES ('124','Noble','Fisher','Cum@eu.com','Nunc quis arcu'); -INSERT INTO users (code,first,last,email,quote) VALUES ('125','Xaviera','Barton','non.arcu@sagittis.com','ultrices posuere cubilia Curae; Phasellus ornare. Fusce mollis. Duis'); -INSERT INTO users (code,first,last,email,quote) VALUES ('126','Logan','Roy','Nulla@hendreritconsectetuercursus.com','sed, facilisis vitae, orci. Phasellus dapibus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('127','Wilma','Sweet','Nullam.nisl.Maecenas@gravidasagittis.org','ut, nulla. Cras'); -INSERT INTO users (code,first,last,email,quote) VALUES ('128','Candace','Olsen','sit.amet@lobortisnisi.com','habitant morbi tristique senectus et netus et malesuada'); -INSERT INTO users (code,first,last,email,quote) VALUES ('129','Claire','Alvarado','risus.varius@interdumSed.com','a felis ullamcorper viverra. Maecenas iaculis aliquet'); -INSERT INTO users (code,first,last,email,quote) VALUES ('130','Aurelia','Bean','diam.dictum.sapien@eu.edu','libero. Integer in magna. Phasellus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('131','Carly','Wilcox','massa.non.ante@Aliquam.com','vehicula risus. Nulla eget metus eu erat semper rutrum. Fusce'); -INSERT INTO users (code,first,last,email,quote) VALUES ('132','Xanthus','Graves','vulputate.velit.eu@vitaerisus.com','Cras sed leo. Cras vehicula aliquet libero. Integer in magna.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('133','Uta','Justice','adipiscing@consequatdolor.edu','Nam tempor diam dictum sapien. Aenean massa.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('134','Alika','Parker','faucibus.ut@Fuscemilorem.edu','vel, venenatis vel, faucibus id,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('135','Kasimir','Pugh','sed.turpis@FuscefeugiatLorem.com','justo. Praesent luctus. Curabitur egestas nunc'); -INSERT INTO users (code,first,last,email,quote) VALUES ('136','Brock','Acevedo','dapibus.rutrum@convallisconvallisdolor.org','eu enim. Etiam'); -INSERT INTO users (code,first,last,email,quote) VALUES ('137','Orla','Hogan','in@Aliquamauctor.com','ac turpis egestas. Aliquam fringilla cursus purus. Nullam scelerisque neque'); -INSERT INTO users (code,first,last,email,quote) VALUES ('138','Tatyana','Bean','molestie.arcu@sempercursus.edu','pellentesque, tellus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('139','Cadman','Humphrey','auctor@Aeneangravida.edu','eget, dictum'); -INSERT INTO users (code,first,last,email,quote) VALUES ('140','Delilah','Quinn','Pellentesque.habitant@liberoMorbi.edu','lobortis quam a felis'); -INSERT INTO users (code,first,last,email,quote) VALUES ('141','Briar','Prince','euismod@Aeneangravida.com','at, libero.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('142','Hedda','Garrett','eleifend@sitamet.org','ac turpis'); -INSERT INTO users (code,first,last,email,quote) VALUES ('143','Sage','Hardy','semper@acfeugiat.org','Nunc laoreet lectus quis massa.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('144','Iris','Meyers','Duis.a.mi@Donecnibhenim.edu','vitae aliquam eros turpis non enim. Mauris quis'); -INSERT INTO users (code,first,last,email,quote) VALUES ('145','Hayes','Bates','molestie@magnaSuspendisse.com','fringilla. Donec feugiat metus sit amet ante.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('146','Rana','Cain','malesuada@loremsitamet.edu','mi'); -INSERT INTO users (code,first,last,email,quote) VALUES ('147','acqueline','Mays','est.Nunc.laoreet@est.edu','odio. Phasellus at augue id ante dictum cursus. Nunc'); -INSERT INTO users (code,first,last,email,quote) VALUES ('148','Nichole','Suarez','cursus.purus.Nullam@ac.edu','Sed malesuada augue ut lacus. Nulla'); -INSERT INTO users (code,first,last,email,quote) VALUES ('149','Sasha','Sparks','fermentum@nonenim.edu','Sed dictum. Proin eget odio.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('150','Timon','Lowery','suscipit@Donecat.edu','pede blandit congue. In scelerisque scelerisque dui. Suspendisse ac metus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('151','Ivor','Charles','augue.eu@odiovelest.org','quis urna. Nunc quis arcu vel quam dignissim'); -INSERT INTO users (code,first,last,email,quote) VALUES ('152','Joelle','Trevino','eget@diamdictumsapien.ca','tortor, dictum eu, placerat eget, venenatis a,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('153','Aristotle','Wall','at@Pellentesqueutipsum.com','quis,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('154','Amena','Boyd','elit.Etiam@vehiculaet.com','molestie dapibus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('155','Rashad','Osborn','ac@aliquam.ca','sed'); -INSERT INTO users (code,first,last,email,quote) VALUES ('156','Theodore','Williamson','dignissim.magna@volutpat.ca','Fusce fermentum fermentum'); -INSERT INTO users (code,first,last,email,quote) VALUES ('157','Rajah','Logan','enim@Aliquamerat.com','nunc. Quisque ornare tortor'); -INSERT INTO users (code,first,last,email,quote) VALUES ('158','Zane','Perez','aliquet@aliquetPhasellus.com','pede.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('159','Jena','Rios','urna@velitegetlaoreet.org','eu erat semper rutrum.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('160','Amber','Gallagher','conubia@elit.org','Maecenas ornare egestas'); -INSERT INTO users (code,first,last,email,quote) VALUES ('161','Veda','Pittman','habitant.morbi.tristique@aliquamenimnec.ca','risus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('162','Nathan','Lowe','dui.Cum.sociis@Sed.org','sed, est. Nunc'); -INSERT INTO users (code,first,last,email,quote) VALUES ('163','Matthew','Townsend','commodo.auctor.velit@egestasadui.ca','ligula. Aenean euismod'); -INSERT INTO users (code,first,last,email,quote) VALUES ('164','Oscar','Richards','vulputate.ullamcorper@eueleifendnec.ca','laoreet ipsum. Curabitur consequat, lectus sit'); -INSERT INTO users (code,first,last,email,quote) VALUES ('165','Gareth','Jackson','nec.diam.Duis@nisl.com','iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('166','Alice','Hyde','Sed.auctor.odio@tortor.edu','id, libero. Donec consectetuer mauris id sapien. Cras dolor'); -INSERT INTO users (code,first,last,email,quote) VALUES ('167','Giacomo','Ramos','et.ipsum.cursus@massanon.edu','auctor non, feugiat nec, diam. Duis'); -INSERT INTO users (code,first,last,email,quote) VALUES ('168','Ciara','Jacobson','Donec.egestas.Aliquam@Aeneangravidanunc.edu','molestie tellus.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('169','Logan','Hendricks','luctus@vulputatedui.ca','in, cursus et, eros. Proin ultrices. Duis volutpat nunc'); -INSERT INTO users (code,first,last,email,quote) VALUES ('170','MacKenzie','Campos','luctus.ut.pellentesque@pellentesquea.edu','erat,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('171','Nina','Best','purus.sapien@aliquet.ca','dolor sit amet, consectetuer'); -INSERT INTO users (code,first,last,email,quote) VALUES ('172','Chester','Howe','Cum.sociis.natoque@Duis.ca','dui, nec tempus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('173','Nora','Callahan','in.hendrerit@ametconsectetueradipiscing.com','tempus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('174','Molly','Bray','consectetuer@velitSedmalesuada.edu','lorem fringilla ornare placerat, orci lacus vestibulum lorem, sit'); -INSERT INTO users (code,first,last,email,quote) VALUES ('175','Ariel','Osborn','et@Aeneanegestashendrerit.ca','enim. Sed nulla ante, iaculis nec,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('176','Arsenio','Leblanc','Pellentesque.ut.ipsum@nibh.com','elit pede, malesuada vel, venenatis vel, faucibus id, libero.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('177','Deborah','Bowman','enim.Etiam@primisinfaucibus.ca','eu nibh vulputate mauris'); -INSERT INTO users (code,first,last,email,quote) VALUES ('178','Delilah','Horton','tincidunt.nunc@arcuet.ca','nec ante. Maecenas mi'); -INSERT INTO users (code,first,last,email,quote) VALUES ('179','Isaiah','Buckley','Fusce.feugiat@massa.org','ut'); -INSERT INTO users (code,first,last,email,quote) VALUES ('180','Logan','Jacobs','Phasellus@utsemNulla.ca','ullamcorper eu, euismod'); -INSERT INTO users (code,first,last,email,quote) VALUES ('181','Lesley','Brown','fringilla@erateget.com','nostra, per inceptos'); -INSERT INTO users (code,first,last,email,quote) VALUES ('182','Kay','Dodson','a.purus@velpedeblandit.ca','tortor at risus. Nunc ac sem ut'); -INSERT INTO users (code,first,last,email,quote) VALUES ('183','Yeo','Hayes','vitae.posuere.at@scelerisque.com','a, dui. Cras'); -INSERT INTO users (code,first,last,email,quote) VALUES ('184','Keegan','Brock','molestie.tellus@convallisestvitae.ca','mi eleifend egestas. Sed pharetra, felis eget varius ultrices,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('185','Kim','Foley','at@acrisusMorbi.com','scelerisque sed, sapien.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('186','Celeste','Delacruz','ipsum.non.arcu@vulputate.edu','Donec fringilla. Donec feugiat metus'); -INSERT INTO users (code,first,last,email,quote) VALUES ('187','Hilda','Rowe','gravida.sit@nisi.ca','eu nulla at sem molestie sodales. Mauris blandit enim'); -INSERT INTO users (code,first,last,email,quote) VALUES ('188','Fuller','Mclaughlin','purus.gravida.sagittis@arcu.com','erat. Sed nunc est, mollis non, cursus non, egestas a,'); -INSERT INTO users (code,first,last,email,quote) VALUES ('189','Madeline','Henderson','lorem.fringilla@cursusdiam.com','Sed pharetra, felis eget varius ultrices, mauris'); -INSERT INTO users (code,first,last,email,quote) VALUES ('190','Josephine','Osborn','metus@tincidunt.ca','a sollicitudin orci sem eget massa. Suspendisse'); -INSERT INTO users (code,first,last,email,quote) VALUES ('191','Ivana','Jimenez','justo@egestasSedpharetra.org','Duis risus odio, auctor vitae, aliquet nec, imperdiet nec, leo.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('192','Stephanie','Dickerson','aliquet.nec.imperdiet@Aliquamrutrumlorem.com','nec ante. Maecenas mi felis, adipiscing'); -INSERT INTO users (code,first,last,email,quote) VALUES ('193','Yardley','Trevino','lacinia.mattis@porttitoreros.edu','lacus.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('194','Carol','Acosta','Donec@aliquetmolestie.ca','arcu'); -INSERT INTO users (code,first,last,email,quote) VALUES ('195','Lysandra','Mosley','imperdiet@Suspendissesed.org','viverra. Maecenas iaculis aliquet diam.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('196','Tamara','Solis','eleifend.egestas.Sed@duiinsodales.com','sit amet lorem semper auctor. Mauris vel'); -INSERT INTO users (code,first,last,email,quote) VALUES ('197','Palmer','Perez','nibh@nonduinec.edu','lacus vestibulum lorem, sit amet'); -INSERT INTO users (code,first,last,email,quote) VALUES ('198','Maia','Donaldson','gravida.Aliquam.tincidunt@volutpatNulladignissim.edu','sit amet luctus vulputate, nisi'); -INSERT INTO users (code,first,last,email,quote) VALUES ('199','Murphy','Wright','et.pede@aptenttacitisociosqu.ca','non arcu. Vivamus sit amet risus. Donec egestas.'); -INSERT INTO users (code,first,last,email,quote) VALUES ('200','Omar','Campos','nunc.ac.mattis@luctussitamet.edu','parturient'); -UPDATE users SET login = lower(first || '.' || last); - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + CREATE TABLE sample_users (id INTEGER NOT NULL PRIMARY KEY, code, login text, first text, last text, password text, email text, quote text); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('1','Clarke','Jarvis','eu.accumsan@nonarcuVivamus.org','non leo. Vivamus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('2','Marny','Fry','Cras.convallis.convallis@nisiCumsociis.ca','aliquam eu, accumsan sed, facilisis vitae, orci. Phasellus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('3','Dai','Marks','dictum.augue@venenatis.ca','nulla ante, iaculis nec, eleifend non, dapibus rutrum, justo. Praesent'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('4','Forrest','Hendricks','justo.sit.amet@odioa.edu','auctor. Mauris vel turpis. Aliquam adipiscing lobortis'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('5','Blossom','Dunlap','libero.mauris@Phasellusfermentumconvallis.ca','luctus sit amet, faucibus ut, nulla. Cras eu tellus eu'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('6','George','Rios','vitae@sodales.ca','elit. Aliquam auctor, velit eget laoreet'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('7','Ivory','Henderson','elit.Aliquam.auctor@Nullamvelitdui.ca','Fusce diam nunc, ullamcorper'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('8','Inez','Goodwin','consectetuer.mauris@nibhPhasellus.edu','eu, placerat eget, venenatis a, magna.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('9','Sigourney','Gonzales','lectus.pede.ultrices@sagittislobortismauris.org','egestas hendrerit'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('10','Fulton','Terrell','penatibus@euaugue.com','Nulla interdum. Curabitur dictum.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('11','Logan','Freeman','malesuada.malesuada@nullaIntegervulputate.edu','dui, semper et, lacinia'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('12','Anne','Irwin','lorem.ut.aliquam@ligula.org','erat, in consectetuer ipsum nunc id enim.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('13','Alexandra','Church','sit@sempererat.org','lorem, luctus ut, pellentesque eget, dictum placerat, augue. Sed'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('14','Adena','Branch','sit.amet@accumsanlaoreetipsum.org','natoque penatibus et'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('15','Lionel','Hoover','ac@Donectempor.ca','at pede. Cras vulputate'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('16','Aimee','Strickland','ornare.lectus@tinciduntduiaugue.ca','vitae odio sagittis semper. Nam'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('17','Selma','Williamson','metus.In.nec@quamquisdiam.org','id nunc'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('18','Lara','Trujillo','lacus@convallisest.edu','Integer sem elit, pharetra ut,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('19','Ori','Ellis','egestas@at.ca','odio. Nam interdum'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('20','Macey','Carey','sed.consequat@ametorciUt.org','magna'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('21','Quynn','Randall','Cras.dictum@malesuada.org','egestas. Fusce aliquet magna a neque.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('22','Alec','Robles','Fusce.feugiat@mollisdui.com','a'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('23','Jakeem','Bell','ante@laoreetlectusquis.edu','eget massa. Suspendisse eleifend. Cras'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('24','Katelyn','Cannon','sit.amet@PhasellusornareFusce.org','nisi dictum augue malesuada malesuada.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('25','Christian','Alford','per@vulputate.ca','turpis.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('26','Leila','Forbes','nec.ante@idblanditat.ca','ac ipsum. Phasellus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('27','Hadley','Gillespie','Lorem.ipsum@Nullamvitaediam.com','vestibulum lorem, sit amet ultricies sem magna nec quam.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('28','Julian','Keith','facilisis@loremvitaeodio.edu','nulla at sem molestie sodales. Mauris blandit enim consequat purus.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('29','Allen','Ramos','neque@lobortis.ca','amet diam eu dolor egestas rhoncus. Proin nisl sem, consequat'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('30','Hermione','Walsh','dictum.magna@tincidunt.edu','scelerisque'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('31','Xena','Graves','eu.dui@tinciduntaliquamarcu.ca','nunc interdum feugiat. Sed nec metus facilisis lorem tristique aliquet.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('32','Tanisha','Blackburn','aliquam.eu.accumsan@dui.edu','fringilla mi lacinia mattis. Integer eu'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('33','Norman','Hobbs','eu.euismod.ac@tinciduntDonecvitae.com','pulvinar arcu et pede. Nunc'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('34','Guy','Molina','non@rutrumurna.org','Vivamus euismod urna.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('35','Rama','Albert','nunc@laciniavitaesodales.com','eu augue porttitor interdum. Sed auctor odio a purus. Duis'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('36','Owen','Combs','Nullam.enim.Sed@magnaPhasellusdolor.com','mollis vitae, posuere at, velit. Cras lorem lorem, luctus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('37','Ashely','Graham','eget.odio@enimsitamet.com','commodo at, libero. Morbi accumsan laoreet ipsum. Curabitur consequat,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('38','Paul','Levy','nec@leo.com','augue scelerisque mollis. Phasellus libero mauris, aliquam eu, accumsan'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('39','Octavia','Calderon','eu@maurissitamet.edu','est, mollis non, cursus non,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('40','Lenore','Pugh','eu.metus.In@Cumsociisnatoque.com','scelerisque mollis. Phasellus libero mauris, aliquam eu, accumsan sed, facilisis'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('41','Regan','Clemons','eu.neque.pellentesque@Integerinmagna.ca','Curae;'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('42','Zachary','Haynes','eu.erat@ipsum.edu','quis lectus. Nullam'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('43','Dane','Hyde','rhoncus@auctor.com','magna et ipsum cursus vestibulum. Mauris magna. Duis dignissim'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('44','Mara','Hansen','Nunc.commodo@ultricies.org','magna. Ut tincidunt orci quis lectus. Nullam suscipit, est'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('45','Zelda','Parrish','turpis.In@rutrummagnaCras.com','Suspendisse aliquet, sem ut cursus luctus, ipsum leo'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('46','Amaya','Richmond','lacinia.at@in.org','ac, fermentum vel, mauris.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('47','Hedda','Murray','ac.fermentum.vel@ipsum.com','tincidunt, neque vitae semper egestas, urna justo faucibus lectus, a'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('48','Brendan','Macdonald','dignissim@leoMorbi.com','vestibulum lorem, sit'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('49','Gray','Mccall','risus.Donec@atsem.ca','libero est, congue'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('50','Shoshana','Tran','nunc.In@aliquet.org','magna sed dui.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('51','Sylvester','Parks','Mauris.molestie@ipsumdolorsit.org','nec, eleifend non, dapibus rutrum, justo.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('52','Kennan','Stokes','nonummy.ut.molestie@varius.org','Ut nec urna et arcu imperdiet ullamcorper. Duis'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('53','Quin','Park','porttitor@vulputateeu.com','scelerisque, lorem ipsum sodales'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('54','Madison','Bailey','nec.orci.Donec@vestibulumnequesed.org','augue. Sed'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('55','Kenyon','Reilly','ultrices.iaculis.odio@sit.ca','enim. Curabitur massa. Vestibulum accumsan neque'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('56','Drew','Brooks','molestie.orci@aliquetdiamSed.org','at risus. Nunc ac sem ut dolor dapibus gravida.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('57','Omar','Dunn','vel@Donecsollicitudinadipiscing.ca','ultrices posuere cubilia Curae; Phasellus ornare. Fusce mollis. Duis sit'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('58','Glenna','Lambert','Proin.non@Phasellus.com','neque. In'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('59','Aspen','Bailey','in.dolor.Fusce@ipsum.org','a, arcu. Sed et libero. Proin'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('60','Adele','Carlson','velit.justo.nec@vehicularisusNulla.edu','risus.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('61','Kerry','Zimmerman','luctus.aliquet.odio@urnajusto.edu','purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('62','Hedda','Guthrie','vulputate.risus@Phaselluselit.ca','ac mattis velit justo nec'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('63','Wyoming','Blackburn','nec.ante@lorem.org','ultricies'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('64','Palmer','Dennis','venenatis.lacus@Donecnonjusto.org','a, arcu. Sed et libero. Proin'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('65','Wesley','Reeves','massa.Suspendisse.eleifend@nec.edu','consequat'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('66','Mallory','Todd','Duis@Crasloremlorem.edu','nascetur ridiculus mus. Proin vel arcu eu odio'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('67','Perry','Kirk','tincidunt.adipiscing@mauris.ca','ullamcorper viverra. Maecenas iaculis aliquet diam. Sed diam lorem,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('68','Justina','Horne','enim.nec.tempus@magnanec.com','ipsum ac mi eleifend egestas. Sed pharetra, felis eget'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('69','Noel','Santana','Duis.elementum.dui@Nullamvitae.org','Phasellus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('70','Sylvester','Bridges','Sed.neque@ornarelectusante.org','Maecenas libero'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('71','Cailin','Baldwin','eu@nibhenim.com','consectetuer'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('72','Beatrice','Henson','risus.Nunc.ac@Etiam.ca','nec, imperdiet nec, leo. Morbi neque'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('73','Kellie','Curry','quis.turpis.vitae@Quisque.org','ipsum. Suspendisse non leo.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('74','Justine','Stewart','Curabitur@dui.org','natoque penatibus et magnis dis parturient'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('75','Dean','Waters','quis.turpis@morbi.ca','Nulla interdum. Curabitur dictum. Phasellus in felis. Nulla tempor augue'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('76','Helen','Porter','molestie.dapibus.ligula@ipsum.com','enim non nisi. Aenean eget'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('77','Janna','Acosta','lectus.a@tempusrisus.com','lectus justo'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('78','Libby','Vaughn','nisl.sem.consequat@convallis.ca','purus mauris a nunc.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('79','Winifred','Cooke','aliquet.sem.ut@etrutrum.ca','metus facilisis lorem tristique aliquet. Phasellus fermentum convallis ligula. Donec'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('80','Taylor','Glass','sit@maurissitamet.com','orci, consectetuer euismod'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('81','Melyssa','Palmer','pharetra.sed.hendrerit@fermentum.ca','neque tellus, imperdiet non, vestibulum nec,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('82','Kuame','Holman','Vivamus@Donecnibh.edu','cursus non, egestas a, dui. Cras'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('83','Martin','Hughes','magna@nonbibendum.org','et ultrices posuere cubilia'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('84','Roanna','Potter','amet.dapibus@aenim.com','est tempor bibendum. Donec'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('85','Cleo','Carson','Curabitur.dictum.Phasellus@liberoIntegerin.com','mauris'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('86','Adele','Daugherty','vulputate.ullamcorper@elitfermentumrisus.edu','consequat purus. Maecenas'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('87','Macon','Todd','et.malesuada@Crasloremlorem.com','montes, nascetur ridiculus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('88','Ira','Merritt','risus@eunullaat.ca','metus.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('89','Dustin','Landry','nec.orci.Donec@vulputateeu.com','Nunc pulvinar arcu et pede. Nunc sed'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('90','Debra','Shepherd','dolor.elit@ornareFusce.ca','Suspendisse non leo. Vivamus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('91','Channing','Mcknight','tellus@laoreet.ca','non ante bibendum ullamcorper. Duis cursus, diam at pretium aliquet,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('92','Abraham','Rodgers','tincidunt.nibh@ipsum.ca','Cum sociis'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('93','Abel','Mcintyre','et@turpisvitaepurus.com','at pretium aliquet,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('94','Lysandra','Cotton','malesuada.malesuada.Integer@gravidamolestie.org','commodo hendrerit. Donec'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('95','Kane','Bennett','ante.Vivamus@Donec.ca','consectetuer rhoncus. Nullam velit dui, semper et, lacinia vitae, sodales'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('96','Timothy','Rivas','hendrerit.consectetuer@nonarcuVivamus.ca','Suspendisse non leo. Vivamus nibh dolor, nonummy ac, feugiat'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('97','Arden','Cote','magnis@sedestNunc.org','purus mauris a nunc.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('98','Brynn','Britt','ac@nibhQuisque.edu','Duis gravida. Praesent eu nulla at sem molestie'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('99','Jerome','Kirkland','ac@nibhPhasellus.com','neque'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('100','Hoyt','Tran','ullamcorper.viverra@parturientmontesnascetur.org','ac tellus. Suspendisse sed dolor. Fusce mi'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('101','Abraham','Downs','velit.eu.sem@neceuismod.ca','suscipit,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('102','Vivien','Fletcher','Nunc.ut@quamPellentesquehabitant.edu','lobortis. Class aptent taciti sociosqu ad'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('103','Azalia','Turner','scelerisque@at.edu','odio sagittis semper. Nam tempor diam dictum'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('104','Tate','Ellis','velit.Sed.malesuada@Cras.com','sed'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('105','Dennis','Walls','Nullam.scelerisque@amifringilla.edu','arcu. Vestibulum ut eros non enim commodo hendrerit. Donec porttitor'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('106','Amela','Collins','Quisque@magna.org','Proin'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('107','Olivia','Dejesus','scelerisque@vitae.org','Duis cursus, diam at pretium aliquet,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('108','Mechelle','Russo','Fusce.aliquet.magna@Praesent.org','penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('109','Hall','Burke','Vivamus.nibh@pellentesque.edu','et libero. Proin mi. Aliquam'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('110','Chanda','Ayers','magna.Nam.ligula@NullafacilisiSed.org','dui, semper et, lacinia vitae, sodales at,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('111','Abdul','Dominguez','pretium.aliquet.metus@vellectusCum.com','vitae erat vel pede blandit congue. In scelerisque scelerisque dui.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('112','Wynter','Lynn','tellus.Aenean@ligulatortordictum.ca','adipiscing fringilla, porttitor vulputate, posuere vulputate, lacus. Cras interdum.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('113','Molly','Willis','montes.nascetur@sed.org','sem. Nulla interdum. Curabitur dictum. Phasellus in felis.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('114','Shafira','Harper','Phasellus.in.felis@ategestas.edu','Vivamus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('115','Otto','Gentry','in.molestie.tortor@Fusce.edu','est. Mauris eu turpis. Nulla aliquet. Proin velit.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('116','Todd','Riddle','et@nislsem.com','Nulla'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('117','Mufutau','Pollard','sit@ridiculusmus.org','accumsan neque et nunc. Quisque ornare tortor at'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('118','Noah','Sears','tristique.neque.venenatis@luctuslobortis.org','dictum'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('119','Amanda','Clarke','in.consequat@non.org','nisi dictum augue malesuada malesuada. Integer id magna et ipsum'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('120','Vladimir','Colon','velit.dui@scelerisquenequeNullam.com','pharetra sed, hendrerit a, arcu. Sed et libero.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('121','Aaron','Hernandez','Quisque.ornare.tortor@magna.ca','sit amet ante. Vivamus non lorem vitae odio'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('122','Bert','Gonzales','fermentum@Nullamvelit.ca','quis massa. Mauris vestibulum, neque sed dictum eleifend, nunc'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('123','Bevis','Leblanc','viverra.Donec@dictumaugue.com','neque sed dictum eleifend, nunc risus varius orci, in'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('124','Noble','Fisher','Cum@eu.com','Nunc quis arcu'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('125','Xaviera','Barton','non.arcu@sagittis.com','ultrices posuere cubilia Curae; Phasellus ornare. Fusce mollis. Duis'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('126','Logan','Roy','Nulla@hendreritconsectetuercursus.com','sed, facilisis vitae, orci. Phasellus dapibus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('127','Wilma','Sweet','Nullam.nisl.Maecenas@gravidasagittis.org','ut, nulla. Cras'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('128','Candace','Olsen','sit.amet@lobortisnisi.com','habitant morbi tristique senectus et netus et malesuada'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('129','Claire','Alvarado','risus.varius@interdumSed.com','a felis ullamcorper viverra. Maecenas iaculis aliquet'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('130','Aurelia','Bean','diam.dictum.sapien@eu.edu','libero. Integer in magna. Phasellus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('131','Carly','Wilcox','massa.non.ante@Aliquam.com','vehicula risus. Nulla eget metus eu erat semper rutrum. Fusce'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('132','Xanthus','Graves','vulputate.velit.eu@vitaerisus.com','Cras sed leo. Cras vehicula aliquet libero. Integer in magna.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('133','Uta','Justice','adipiscing@consequatdolor.edu','Nam tempor diam dictum sapien. Aenean massa.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('134','Alika','Parker','faucibus.ut@Fuscemilorem.edu','vel, venenatis vel, faucibus id,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('135','Kasimir','Pugh','sed.turpis@FuscefeugiatLorem.com','justo. Praesent luctus. Curabitur egestas nunc'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('136','Brock','Acevedo','dapibus.rutrum@convallisconvallisdolor.org','eu enim. Etiam'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('137','Orla','Hogan','in@Aliquamauctor.com','ac turpis egestas. Aliquam fringilla cursus purus. Nullam scelerisque neque'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('138','Tatyana','Bean','molestie.arcu@sempercursus.edu','pellentesque, tellus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('139','Cadman','Humphrey','auctor@Aeneangravida.edu','eget, dictum'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('140','Delilah','Quinn','Pellentesque.habitant@liberoMorbi.edu','lobortis quam a felis'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('141','Briar','Prince','euismod@Aeneangravida.com','at, libero.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('142','Hedda','Garrett','eleifend@sitamet.org','ac turpis'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('143','Sage','Hardy','semper@acfeugiat.org','Nunc laoreet lectus quis massa.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('144','Iris','Meyers','Duis.a.mi@Donecnibhenim.edu','vitae aliquam eros turpis non enim. Mauris quis'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('145','Hayes','Bates','molestie@magnaSuspendisse.com','fringilla. Donec feugiat metus sit amet ante.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('146','Rana','Cain','malesuada@loremsitamet.edu','mi'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('147','acqueline','Mays','est.Nunc.laoreet@est.edu','odio. Phasellus at augue id ante dictum cursus. Nunc'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('148','Nichole','Suarez','cursus.purus.Nullam@ac.edu','Sed malesuada augue ut lacus. Nulla'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('149','Sasha','Sparks','fermentum@nonenim.edu','Sed dictum. Proin eget odio.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('150','Timon','Lowery','suscipit@Donecat.edu','pede blandit congue. In scelerisque scelerisque dui. Suspendisse ac metus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('151','Ivor','Charles','augue.eu@odiovelest.org','quis urna. Nunc quis arcu vel quam dignissim'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('152','Joelle','Trevino','eget@diamdictumsapien.ca','tortor, dictum eu, placerat eget, venenatis a,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('153','Aristotle','Wall','at@Pellentesqueutipsum.com','quis,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('154','Amena','Boyd','elit.Etiam@vehiculaet.com','molestie dapibus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('155','Rashad','Osborn','ac@aliquam.ca','sed'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('156','Theodore','Williamson','dignissim.magna@volutpat.ca','Fusce fermentum fermentum'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('157','Rajah','Logan','enim@Aliquamerat.com','nunc. Quisque ornare tortor'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('158','Zane','Perez','aliquet@aliquetPhasellus.com','pede.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('159','Jena','Rios','urna@velitegetlaoreet.org','eu erat semper rutrum.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('160','Amber','Gallagher','conubia@elit.org','Maecenas ornare egestas'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('161','Veda','Pittman','habitant.morbi.tristique@aliquamenimnec.ca','risus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('162','Nathan','Lowe','dui.Cum.sociis@Sed.org','sed, est. Nunc'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('163','Matthew','Townsend','commodo.auctor.velit@egestasadui.ca','ligula. Aenean euismod'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('164','Oscar','Richards','vulputate.ullamcorper@eueleifendnec.ca','laoreet ipsum. Curabitur consequat, lectus sit'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('165','Gareth','Jackson','nec.diam.Duis@nisl.com','iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('166','Alice','Hyde','Sed.auctor.odio@tortor.edu','id, libero. Donec consectetuer mauris id sapien. Cras dolor'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('167','Giacomo','Ramos','et.ipsum.cursus@massanon.edu','auctor non, feugiat nec, diam. Duis'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('168','Ciara','Jacobson','Donec.egestas.Aliquam@Aeneangravidanunc.edu','molestie tellus.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('169','Logan','Hendricks','luctus@vulputatedui.ca','in, cursus et, eros. Proin ultrices. Duis volutpat nunc'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('170','MacKenzie','Campos','luctus.ut.pellentesque@pellentesquea.edu','erat,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('171','Nina','Best','purus.sapien@aliquet.ca','dolor sit amet, consectetuer'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('172','Chester','Howe','Cum.sociis.natoque@Duis.ca','dui, nec tempus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('173','Nora','Callahan','in.hendrerit@ametconsectetueradipiscing.com','tempus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('174','Molly','Bray','consectetuer@velitSedmalesuada.edu','lorem fringilla ornare placerat, orci lacus vestibulum lorem, sit'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('175','Ariel','Osborn','et@Aeneanegestashendrerit.ca','enim. Sed nulla ante, iaculis nec,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('176','Arsenio','Leblanc','Pellentesque.ut.ipsum@nibh.com','elit pede, malesuada vel, venenatis vel, faucibus id, libero.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('177','Deborah','Bowman','enim.Etiam@primisinfaucibus.ca','eu nibh vulputate mauris'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('178','Delilah','Horton','tincidunt.nunc@arcuet.ca','nec ante. Maecenas mi'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('179','Isaiah','Buckley','Fusce.feugiat@massa.org','ut'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('180','Logan','Jacobs','Phasellus@utsemNulla.ca','ullamcorper eu, euismod'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('181','Lesley','Brown','fringilla@erateget.com','nostra, per inceptos'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('182','Kay','Dodson','a.purus@velpedeblandit.ca','tortor at risus. Nunc ac sem ut'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('183','Yeo','Hayes','vitae.posuere.at@scelerisque.com','a, dui. Cras'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('184','Keegan','Brock','molestie.tellus@convallisestvitae.ca','mi eleifend egestas. Sed pharetra, felis eget varius ultrices,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('185','Kim','Foley','at@acrisusMorbi.com','scelerisque sed, sapien.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('186','Celeste','Delacruz','ipsum.non.arcu@vulputate.edu','Donec fringilla. Donec feugiat metus'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('187','Hilda','Rowe','gravida.sit@nisi.ca','eu nulla at sem molestie sodales. Mauris blandit enim'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('188','Fuller','Mclaughlin','purus.gravida.sagittis@arcu.com','erat. Sed nunc est, mollis non, cursus non, egestas a,'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('189','Madeline','Henderson','lorem.fringilla@cursusdiam.com','Sed pharetra, felis eget varius ultrices, mauris'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('190','Josephine','Osborn','metus@tincidunt.ca','a sollicitudin orci sem eget massa. Suspendisse'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('191','Ivana','Jimenez','justo@egestasSedpharetra.org','Duis risus odio, auctor vitae, aliquet nec, imperdiet nec, leo.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('192','Stephanie','Dickerson','aliquet.nec.imperdiet@Aliquamrutrumlorem.com','nec ante. Maecenas mi felis, adipiscing'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('193','Yardley','Trevino','lacinia.mattis@porttitoreros.edu','lacus.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('194','Carol','Acosta','Donec@aliquetmolestie.ca','arcu'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('195','Lysandra','Mosley','imperdiet@Suspendissesed.org','viverra. Maecenas iaculis aliquet diam.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('196','Tamara','Solis','eleifend.egestas.Sed@duiinsodales.com','sit amet lorem semper auctor. Mauris vel'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('197','Palmer','Perez','nibh@nonduinec.edu','lacus vestibulum lorem, sit amet'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('198','Maia','Donaldson','gravida.Aliquam.tincidunt@volutpatNulladignissim.edu','sit amet luctus vulputate, nisi'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('199','Murphy','Wright','et.pede@aptenttacitisociosqu.ca','non arcu. Vivamus sit amet risus. Donec egestas.'); +INSERT INTO sample_users (code,first,last,email,quote) VALUES ('200','Omar','Campos','nunc.ac.mattis@luctussitamet.edu','parturient'); +UPDATE sample_users SET login = lower(first || '.' || last); + \ No newline at end of file diff --git a/DynamORM.Tests/Select/DynamicAccessTests.cs b/DynamORM.Tests/Select/DynamicAccessTests.cs index 3664750..d64d2ac 100644 --- a/DynamORM.Tests/Select/DynamicAccessTests.cs +++ b/DynamORM.Tests/Select/DynamicAccessTests.cs @@ -1,655 +1,655 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using DynamORM.Builders; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Select -{ - /// Test standard dynamic access ORM. - [TestClass] - public class DynamicAccessTests : TestsBase - { - /// Setup test parameters. - [TestInitialize] - public virtual void SetUp() - { - CreateTestDatabase(); - CreateDynamicDatabase(); - } - - /// Tear down test objects. - [TestCleanup] - public virtual void TearDown() - { - DestroyDynamicDatabase(); - DestroyTestDatabase(); - } - - /// Create table using specified method. - /// Dynamic table. - public virtual dynamic GetTestTable() - { - return Database.Table("users"); - } - - /// Create table using specified method. - /// Dynamic table. - public virtual IDynamicSelectQueryBuilder GetTestBuilder() - { - return Database.Table("users").Query() as IDynamicSelectQueryBuilder; - } - - #region Select - - /// Test unknown op. - [TestMethod] - public void TestUnknownOperation() - { - Assert.ThrowsException(() => GetTestTable().MakeMeASandwitch(with: "cheese")); - } - - /// Test dynamic Count method. - [TestMethod] - public void TestCount() - { - Assert.AreEqual(200, GetTestTable().Count(columns: "id")); - } - - /// Test dynamic Count method. - [TestMethod] - public void TestCount2() - { - Assert.AreEqual(200L, GetTestBuilder().Select(x => x.Count(x.id)).Scalar()); - } - - /// Test count with in statement. - [TestMethod] - public void TestSelectInEnumerableCount() - { - Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn - { - Operator = DynamicColumn.CompareOperator.In, - Value = new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3) - })); - } - - /// Test count with in statement. - [TestMethod] - public void TestSelectInEnumerableCount2() - { - Assert.AreEqual(4L, GetTestBuilder() - .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3))) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test count with in statement. - [TestMethod] - public void TestSelectInArrayCount() - { - Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn - { - Operator = DynamicColumn.CompareOperator.In, - Value = new object[] { "Hendricks", "Goodwin", "Freeman" } - })); - } - - /// Test count with in statement. - [TestMethod] - public void TestSelectInArrayCount2() - { - Assert.AreEqual(4L, GetTestBuilder() - .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" })) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic First method. - [TestMethod] - public void TestFirst() - { - Assert.AreEqual(1, GetTestTable().First(columns: "id").id); - } - - /// Test dynamic First method. - [TestMethod] - public void TestFirst2() - { - Assert.AreEqual(1, GetTestBuilder() - .Select(x => x.id) - .Execute() - .First().id); - } - - /// Test dynamic Last method. - [TestMethod] - public void TestLast() - { - Assert.AreEqual(200, GetTestTable().Last(columns: "id").id); - } - - /// Test dynamic Last method. - [TestMethod] - public void TestLast2() - { - Assert.AreEqual(200, GetTestBuilder() - .Select(x => x.id) - .Execute() - .Last().id); - } - - /// Test dynamic Count method. - [TestMethod] - public void TestCountSpecificRecord() - { - Assert.AreEqual(1, GetTestTable().Count(first: "Ori")); - } - - /// Test dynamic Count method. - [TestMethod] - public void TestCountSpecificRecord2() - { - Assert.AreEqual(1L, GetTestBuilder() - .Where(x => x.first == "Ori") - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic Min method. - [TestMethod] - public void TestMin() - { - Assert.AreEqual(1, GetTestTable().Min(columns: "id")); - } - - /// Test dynamic Min method. - [TestMethod] - public void TestMin2() - { - Assert.AreEqual(1L, GetTestBuilder() - .Select(x => x.Min(x.id)) - .Scalar()); - } - - /// Test dynamic Min method. - [TestMethod] - public void TestMax() - { - Assert.AreEqual(200, GetTestTable().Max(columns: "id")); - } - - /// Test dynamic Min method. - [TestMethod] - public void TestMax2() - { - Assert.AreEqual(200L, GetTestBuilder() - .Select(x => x.Max(x.id)) - .Scalar()); - } - - /// Test dynamic Min method. - [TestMethod] - public void TesttAvg() - { - Assert.AreEqual(100.5, GetTestTable().Avg(columns: "id")); - } - - /// Test dynamic Min method. - [TestMethod] - public void TesttAvg2() - { - Assert.AreEqual(100.5, GetTestBuilder() - .Select(x => x.Avg(x.id)) - .Scalar()); - } - - /// Test dynamic Sum method. - [TestMethod] - public void TestSum() - { - Assert.AreEqual(20100, GetTestTable().Sum(columns: "id")); - } - - /// Test dynamic Sum method. - [TestMethod] - public void TestSum2() - { - Assert.AreEqual(20100L, GetTestBuilder() - .Select(x => x.Sum(x.id)) - .Scalar()); - } - - /// Test dynamic Scalar method for invalid operation exception. - [TestMethod] - public void TestScalarException() - { - Assert.ThrowsException(() => GetTestTable().Scalar(id: 19)); - } - - /// Test dynamic Scalar method. - [TestMethod] - public void TestScalar() - { - Assert.AreEqual("Ori", GetTestTable().Scalar(columns: "first", id: 19)); - } - - /// Test dynamic Scalar method. - [TestMethod] - public void TestScalar2() - { - Assert.AreEqual("Ori", GetTestBuilder() - .Where(x => x.id == 19) - .Select(x => x.first) - .Scalar()); - } - - /// Test dynamic Scalar method with SQLite specific aggregate. - [TestMethod] - public void TestScalarGroupConcat() - { - // This test should produce something like this: - // select group_concat("first") AS first from "users" where "id" < 20; - Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", - GetTestTable().Scalar(columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); - } - - /// Test dynamic Scalar method with SQLite specific aggregate. - [TestMethod] - public void TestScalarGroupConcat2() - { - // This test should produce something like this: - // select group_concat("first") AS first from "users" where "id" < 20; - Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", - GetTestBuilder() - .Where(x => x.id < 20) - .Select(x => x.group_concat(x.first).As(x.first)) - .Scalar()); - } - - /// Test dynamic Scalar method with SQLite specific aggregate not using aggregate field. - [TestMethod] - public void TestScalarGroupConcatNoAggregateField() - { - // This test should produce something like this: - // select group_concat(first) AS first from "users" where "id" < 20; - Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", - GetTestTable().Scalar(columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); - } - - /// Test dynamic Scalar method with SQLite specific aggregate not using aggregate field. - [TestMethod] - public void TestScalarGroupConcatNoAggregateField2() - { - // This test should produce something like this: - // select group_concat(first) AS first from "users" where "id" < 20; - Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", - GetTestBuilder() - .Where(x => x.id < 20) - .SelectColumn("group_concat(first):first") - .Scalar()); - } - - /// Test something fancy... like: select "first", count("first") occurs from "users" group by "first" order by 2 desc;. - [TestMethod] - public void TestFancyAggregateQuery() - { - var v = (GetTestTable().Query(columns: "first,first:occurs:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); - - Assert.IsNotNull(v); - Assert.AreEqual(187, v.Count()); - Assert.AreEqual(4, v.First().occurs); - Assert.AreEqual("Logan", v.First().first); - Assert.AreEqual(2, v.Take(10).Last().occurs); - Assert.AreEqual(1, v.Take(11).Last().occurs); - Assert.AreEqual(1, v.Last().occurs); - } - - /// Test something fancy... like: select "first", count("first") occurs from "users" group by "first" order by 2 desc;. - [TestMethod] - public void TestFancyAggregateQuery2() - { - var v = GetTestBuilder() - .Select(x => x.first, x => x.Count(x.first).As(x.occurs)) - .GroupBy(x => x.first) - .OrderBy(x => x.Desc(2)) - .Execute() - .ToList(); - - Assert.IsNotNull(v); - Assert.AreEqual(187, v.Count()); - Assert.AreEqual(4, v.First().occurs); - Assert.AreEqual("Logan", v.First().first); - Assert.AreEqual(2, v.Take(10).Last().occurs); - Assert.AreEqual(1, v.Take(11).Last().occurs); - Assert.AreEqual(1, v.Last().occurs); - } - - /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. - [TestMethod] - public void TestAggregateInAggregate() - { - Assert.AreEqual(12.77, GetTestTable().Scalar(columns: @"length(""login""):len:avg")); - } - - /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. - [TestMethod] - public void TestAggregateInAggregate2() - { - Assert.AreEqual(12.77, GetTestBuilder() - .Select(x => x.Avg(x.Length(x.login)).As(x.len)) - .Scalar()); - } - - /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. - [TestMethod] - public void TestAggregateInAggregateMark2() - { - Assert.AreEqual(27.7, GetTestTable().Avg(columns: @"length(""email""):len")); - } - - /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. - [TestMethod] - public void TestAggregateInAggregateMark3() - { - Assert.AreEqual(27.7, GetTestBuilder() - .Select(x => "AVG(LENGTH(email)) AS LEN") - .Scalar()); - } - - /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. - public void TestFunctionInWhere() - { - Assert.AreEqual(97, - GetTestTable().Count(condition1: - new DynamicColumn() - { - ColumnName = "email", - Aggregate = "length", - Operator = DynamicColumn.CompareOperator.Gt, - Value = 27 - })); - } - - /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. - public void TestFunctionInWhere2() - { - Assert.AreEqual(97, GetTestBuilder() - .Where(x => x.Length(x.email) > 27) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic Single multi. - [TestMethod] - public void TestSingleObject() - { - var exp = new { id = 19, first = "Ori", last = "Ellis" }; - var o = GetTestTable().Single(columns: "id,first,last", id: 19); - - Assert.AreEqual(exp.id, o.id); - Assert.AreEqual(exp.first, o.first); - Assert.AreEqual(exp.last, o.last); - } - - /// Test dynamic Single multi. - [TestMethod] - public void TestSingleObject2() - { - var exp = new { id = 19, first = "Ori", last = "Ellis" }; - var o = GetTestBuilder() - .Where(x => x.id == 19) - .Select(x => new { id = x.id, first = x.first, last = x.last }) - .Execute() - .First(); - - Assert.AreEqual(exp.id, o.id); - Assert.AreEqual(exp.first, o.first); - Assert.AreEqual(exp.last, o.last); - } - - /// Test dynamic duplicate column name occurrence. - [TestMethod] - public void TestDuplicateColumnNameException() - { - Assert.ThrowsException(() => GetTestBuilder() - .Where(x => x.id == 19) - .Select(x => new - { - id = x.id, - first = x.first, - last = x.last, - }) - .Select(x => x.last.As(x.first)) // Make last be first - .Execute() - .First()); - } - - #endregion Select - - #region Where - - /// Test dynamic where expression equal. - [TestMethod] - public void TestWhereEq() - { - Assert.AreEqual("hoyt.tran", GetTestTable().Single(where: new DynamicColumn("id").Eq(100)).login); - } - - /// Test dynamic where expression equal. - [TestMethod] - public void TestWhereEq2() - { - Assert.AreEqual("hoyt.tran", GetTestBuilder() - .Where(x => x.id == 100).Execute().First().login); - } - - /// Test dynamic where expression not equal. - [TestMethod] - public void TestWhereNot() - { - Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("id").Not(100))); - } - - /// Test dynamic where expression not equal. - [TestMethod] - public void TestWhereNot2() - { - Assert.AreEqual(199L, GetTestBuilder() - .Where(x => x.id != 100) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic where expression like. - [TestMethod] - public void TestWhereLike() - { - Assert.AreEqual(100, GetTestTable().Single(where: new DynamicColumn("login").Like("Hoyt.%")).id); - } - - /// Test dynamic where expression like. - [TestMethod] - public void TestWhereLike2() - { - Assert.AreEqual(100, GetTestBuilder() - .Where(x => x.login.Like("Hoyt.%")).Execute().First().id); - } - - /// Test dynamic where expression not like. - [TestMethod] - public void TestWhereNotLike() - { - Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("login").NotLike("Hoyt.%"))); - } - - /// Test dynamic where expression not like. - [TestMethod] - public void TestWhereNotLike2() - { - Assert.AreEqual(199L, GetTestBuilder() - .Where(x => x.login.NotLike("Hoyt.%")) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic where expression not like. - [TestMethod] - public void TestWhereNotLike3() - { - Assert.AreEqual(199L, GetTestBuilder() - .Where(x => !x.login.Like("Hoyt.%")) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic where expression greater. - [TestMethod] - public void TestWhereGt() - { - Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").Greater(100))); - } - - /// Test dynamic where expression greater. - [TestMethod] - public void TestWhereGt2() - { - Assert.AreEqual(100L, GetTestBuilder() - .Where(x => x.id > 100) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic where expression greater or equal. - [TestMethod] - public void TestWhereGte() - { - Assert.AreEqual(101, GetTestTable().Count(where: new DynamicColumn("id").GreaterOrEqual(100))); - } - - /// Test dynamic where expression greater or equal. - [TestMethod] - public void TestWhereGte2() - { - Assert.AreEqual(101L, GetTestBuilder() - .Where(x => x.id >= 100) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic where expression less. - [TestMethod] - public void TestWhereLt() - { - Assert.AreEqual(99, GetTestTable().Count(where: new DynamicColumn("id").Less(100))); - } - - /// Test dynamic where expression less. - [TestMethod] - public void TestWhereLt2() - { - Assert.AreEqual(99L, GetTestBuilder() - .Where(x => x.id < 100) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic where expression less or equal. - [TestMethod] - public void TestWhereLte() - { - Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").LessOrEqual(100))); - } - - /// Test dynamic where expression less or equal. - [TestMethod] - public void TestWhereLte2() - { - Assert.AreEqual(100L, GetTestBuilder() - .Where(x => x.id <= 100) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic where expression between. - [TestMethod] - public void TestWhereBetween() - { - Assert.AreEqual(26, GetTestTable().Count(where: new DynamicColumn("id").Between(75, 100))); - } - - /// Test dynamic where expression between. - [TestMethod] - public void TestWhereBetween2() - { - Assert.AreEqual(26L, GetTestBuilder() - .Where(x => x.id.Between(75, 100)) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic where expression in parameters. - [TestMethod] - public void TestWhereIn1() - { - Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(75, 99, 100))); - } - - /// Test dynamic where expression in array. - [TestMethod] - public void TestWhereIn2() - { - Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(new[] { 75, 99, 100 }))); - } - - /// Test dynamic where expression in parameters. - [TestMethod] - public void TestWhereIn3() - { - Assert.AreEqual(3L, GetTestBuilder() - .Where(x => x.id.In(75, 99, 100)) - .Select(x => x.Count()) - .Scalar()); - } - - /// Test dynamic where expression in parameters. - [TestMethod] - public void TestWhereIn4() - { - Assert.AreEqual(3L, GetTestBuilder() - .Where(x => x.id.In(new[] { 75, 99, 100 })) - .Select(x => x.Count()) - .Scalar()); - } - - #endregion Where - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using DynamORM.Builders; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Test standard dynamic access ORM. + [TestFixture] + public class DynamicAccessTests : TestsBase + { + /// Setup test parameters. + [SetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase(); + } + + /// Tear down test objects. + [TearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// Create table using specified method. + /// Dynamic table. + public virtual dynamic GetTestTable() + { + return Database.Table("sample_users"); + } + + /// Create table using specified method. + /// Dynamic table. + public virtual IDynamicSelectQueryBuilder GetTestBuilder() + { + return Database.Table("sample_users").Query() as IDynamicSelectQueryBuilder; + } + + #region Select + + /// Test unknown op. + [Test] + public void TestUnknownOperation() + { + Assert.Throws(() => GetTestTable().MakeMeASandwitch(with: "cheese")); + } + + /// Test dynamic Count method. + [Test] + public void TestCount() + { + Assert.AreEqual(200, GetTestTable().Count(columns: "id")); + } + + /// Test dynamic Count method. + [Test] + public void TestCount2() + { + Assert.AreEqual(200L, GetTestBuilder().Select(x => x.Count(x.id)).Scalar()); + } + + /// Test count with in statement. + [Test] + public void TestSelectInEnumerableCount() + { + Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3) + })); + } + + /// Test count with in statement. + [Test] + public void TestSelectInEnumerableCount2() + { + Assert.AreEqual(4L, GetTestBuilder() + .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3))) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test count with in statement. + [Test] + public void TestSelectInArrayCount() + { + Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" } + })); + } + + /// Test count with in statement. + [Test] + public void TestSelectInArrayCount2() + { + Assert.AreEqual(4L, GetTestBuilder() + .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" })) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic First method. + [Test] + public void TestFirst() + { + Assert.AreEqual(1, GetTestTable().First(columns: "id").id); + } + + /// Test dynamic First method. + [Test] + public void TestFirst2() + { + Assert.AreEqual(1, GetTestBuilder() + .Select(x => x.id) + .Execute() + .First().id); + } + + /// Test dynamic Last method. + [Test] + public void TestLast() + { + Assert.AreEqual(200, GetTestTable().Last(columns: "id").id); + } + + /// Test dynamic Last method. + [Test] + public void TestLast2() + { + Assert.AreEqual(200, GetTestBuilder() + .Select(x => x.id) + .Execute() + .Last().id); + } + + /// Test dynamic Count method. + [Test] + public void TestCountSpecificRecord() + { + Assert.AreEqual(1, GetTestTable().Count(first: "Ori")); + } + + /// Test dynamic Count method. + [Test] + public void TestCountSpecificRecord2() + { + Assert.AreEqual(1L, GetTestBuilder() + .Where(x => x.first == "Ori") + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic Min method. + [Test] + public void TestMin() + { + Assert.AreEqual(1, GetTestTable().Min(columns: "id")); + } + + /// Test dynamic Min method. + [Test] + public void TestMin2() + { + Assert.AreEqual(1L, GetTestBuilder() + .Select(x => x.Min(x.id)) + .Scalar()); + } + + /// Test dynamic Min method. + [Test] + public void TestMax() + { + Assert.AreEqual(200, GetTestTable().Max(columns: "id")); + } + + /// Test dynamic Min method. + [Test] + public void TestMax2() + { + Assert.AreEqual(200L, GetTestBuilder() + .Select(x => x.Max(x.id)) + .Scalar()); + } + + /// Test dynamic Min method. + [Test] + public void TesttAvg() + { + Assert.AreEqual(100.5, GetTestTable().Avg(columns: "id")); + } + + /// Test dynamic Min method. + [Test] + public void TesttAvg2() + { + Assert.AreEqual(100.5, GetTestBuilder() + .Select(x => x.Avg(x.id)) + .Scalar()); + } + + /// Test dynamic Sum method. + [Test] + public void TestSum() + { + Assert.AreEqual(20100, GetTestTable().Sum(columns: "id")); + } + + /// Test dynamic Sum method. + [Test] + public void TestSum2() + { + Assert.AreEqual(20100L, GetTestBuilder() + .Select(x => x.Sum(x.id)) + .Scalar()); + } + + /// Test dynamic Scalar method for invalid operation exception. + [Test] + public void TestScalarException() + { + Assert.Throws(() => GetTestTable().Scalar(id: 19)); + } + + /// Test dynamic Scalar method. + [Test] + public void TestScalar() + { + Assert.AreEqual("Ori", GetTestTable().Scalar(columns: "first", id: 19)); + } + + /// Test dynamic Scalar method. + [Test] + public void TestScalar2() + { + Assert.AreEqual("Ori", GetTestBuilder() + .Where(x => x.id == 19) + .Select(x => x.first) + .Scalar()); + } + + /// Test dynamic Scalar method with SQLite specific aggregate. + [Test] + public void TestScalarGroupConcat() + { + // This test should produce something like this: + // select group_concat("first") AS first from "sample_users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test dynamic Scalar method with SQLite specific aggregate. + [Test] + public void TestScalarGroupConcat2() + { + // This test should produce something like this: + // select group_concat("first") AS first from "sample_users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestBuilder() + .Where(x => x.id < 20) + .Select(x => x.group_concat(x.first).As(x.first)) + .Scalar()); + } + + /// Test dynamic Scalar method with SQLite specific aggregate not using aggregate field. + [Test] + public void TestScalarGroupConcatNoAggregateField() + { + // This test should produce something like this: + // select group_concat(first) AS first from "sample_users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test dynamic Scalar method with SQLite specific aggregate not using aggregate field. + [Test] + public void TestScalarGroupConcatNoAggregateField2() + { + // This test should produce something like this: + // select group_concat(first) AS first from "sample_users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestBuilder() + .Where(x => x.id < 20) + .SelectColumn("group_concat(first):first") + .Scalar()); + } + + /// Test something fancy... like: select "first", count("first") occurs from "sample_users" group by "first" order by 2 desc;. + [Test] + public void TestFancyAggregateQuery() + { + var v = (GetTestTable().Query(columns: "first,first:occurs:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().occurs); + Assert.AreEqual("Logan", v.First().first); + Assert.AreEqual(2, v.Take(10).Last().occurs); + Assert.AreEqual(1, v.Take(11).Last().occurs); + Assert.AreEqual(1, v.Last().occurs); + } + + /// Test something fancy... like: select "first", count("first") occurs from "sample_users" group by "first" order by 2 desc;. + [Test] + public void TestFancyAggregateQuery2() + { + var v = GetTestBuilder() + .Select(x => x.first, x => x.Count(x.first).As(x.occurs)) + .GroupBy(x => x.first) + .OrderBy(x => x.Desc(2)) + .Execute() + .ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().occurs); + Assert.AreEqual("Logan", v.First().first); + Assert.AreEqual(2, v.Take(10).Last().occurs); + Assert.AreEqual(1, v.Take(11).Last().occurs); + Assert.AreEqual(1, v.Last().occurs); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "sample_users";. + [Test] + public void TestAggregateInAggregate() + { + Assert.AreEqual(12.77, GetTestTable().Scalar(columns: @"length(""login""):len:avg")); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "sample_users";. + [Test] + public void TestAggregateInAggregate2() + { + Assert.AreEqual(12.77, GetTestBuilder() + .Select(x => x.Avg(x.Length(x.login)).As(x.len)) + .Scalar()); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "sample_users";. + [Test] + public void TestAggregateInAggregateMark2() + { + Assert.AreEqual(27.7, GetTestTable().Avg(columns: @"length(""email""):len")); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "sample_users";. + [Test] + public void TestAggregateInAggregateMark3() + { + Assert.AreEqual(27.7, GetTestBuilder() + .Select(x => "AVG(LENGTH(email)) AS LEN") + .Scalar()); + } + + /// Test emails longer than 27 chars. select count(*) from "sample_users" where length("email") > 27;. + public void TestFunctionInWhere() + { + Assert.AreEqual(97, + GetTestTable().Count(condition1: + new DynamicColumn() + { + ColumnName = "email", + Aggregate = "length", + Operator = DynamicColumn.CompareOperator.Gt, + Value = 27 + })); + } + + /// Test emails longer than 27 chars. select count(*) from "sample_users" where length("email") > 27;. + public void TestFunctionInWhere2() + { + Assert.AreEqual(97, GetTestBuilder() + .Where(x => x.Length(x.email) > 27) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic Single multi. + [Test] + public void TestSingleObject() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestTable().Single(columns: "id,first,last", id: 19); + + Assert.AreEqual(exp.id, o.id); + Assert.AreEqual(exp.first, o.first); + Assert.AreEqual(exp.last, o.last); + } + + /// Test dynamic Single multi. + [Test] + public void TestSingleObject2() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestBuilder() + .Where(x => x.id == 19) + .Select(x => new { id = x.id, first = x.first, last = x.last }) + .Execute() + .First(); + + Assert.AreEqual(exp.id, o.id); + Assert.AreEqual(exp.first, o.first); + Assert.AreEqual(exp.last, o.last); + } + + /// Test dynamic duplicate column name occurrence. + [Test] + public void TestDuplicateColumnNameException() + { + Assert.Throws(() => GetTestBuilder() + .Where(x => x.id == 19) + .Select(x => new + { + id = x.id, + first = x.first, + last = x.last, + }) + .Select(x => x.last.As(x.first)) // Make last be first + .Execute() + .First()); + } + + #endregion Select + + #region Where + + /// Test dynamic where expression equal. + [Test] + public void TestWhereEq() + { + Assert.AreEqual("hoyt.tran", GetTestTable().Single(where: new DynamicColumn("id").Eq(100)).login); + } + + /// Test dynamic where expression equal. + [Test] + public void TestWhereEq2() + { + Assert.AreEqual("hoyt.tran", GetTestBuilder() + .Where(x => x.id == 100).Execute().First().login); + } + + /// Test dynamic where expression not equal. + [Test] + public void TestWhereNot() + { + Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("id").Not(100))); + } + + /// Test dynamic where expression not equal. + [Test] + public void TestWhereNot2() + { + Assert.AreEqual(199L, GetTestBuilder() + .Where(x => x.id != 100) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression like. + [Test] + public void TestWhereLike() + { + Assert.AreEqual(100, GetTestTable().Single(where: new DynamicColumn("login").Like("Hoyt.%")).id); + } + + /// Test dynamic where expression like. + [Test] + public void TestWhereLike2() + { + Assert.AreEqual(100, GetTestBuilder() + .Where(x => x.login.Like("Hoyt.%")).Execute().First().id); + } + + /// Test dynamic where expression not like. + [Test] + public void TestWhereNotLike() + { + Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("login").NotLike("Hoyt.%"))); + } + + /// Test dynamic where expression not like. + [Test] + public void TestWhereNotLike2() + { + Assert.AreEqual(199L, GetTestBuilder() + .Where(x => x.login.NotLike("Hoyt.%")) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression not like. + [Test] + public void TestWhereNotLike3() + { + Assert.AreEqual(199L, GetTestBuilder() + .Where(x => !x.login.Like("Hoyt.%")) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression greater. + [Test] + public void TestWhereGt() + { + Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").Greater(100))); + } + + /// Test dynamic where expression greater. + [Test] + public void TestWhereGt2() + { + Assert.AreEqual(100L, GetTestBuilder() + .Where(x => x.id > 100) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression greater or equal. + [Test] + public void TestWhereGte() + { + Assert.AreEqual(101, GetTestTable().Count(where: new DynamicColumn("id").GreaterOrEqual(100))); + } + + /// Test dynamic where expression greater or equal. + [Test] + public void TestWhereGte2() + { + Assert.AreEqual(101L, GetTestBuilder() + .Where(x => x.id >= 100) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression less. + [Test] + public void TestWhereLt() + { + Assert.AreEqual(99, GetTestTable().Count(where: new DynamicColumn("id").Less(100))); + } + + /// Test dynamic where expression less. + [Test] + public void TestWhereLt2() + { + Assert.AreEqual(99L, GetTestBuilder() + .Where(x => x.id < 100) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression less or equal. + [Test] + public void TestWhereLte() + { + Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").LessOrEqual(100))); + } + + /// Test dynamic where expression less or equal. + [Test] + public void TestWhereLte2() + { + Assert.AreEqual(100L, GetTestBuilder() + .Where(x => x.id <= 100) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression between. + [Test] + public void TestWhereBetween() + { + Assert.AreEqual(26, GetTestTable().Count(where: new DynamicColumn("id").Between(75, 100))); + } + + /// Test dynamic where expression between. + [Test] + public void TestWhereBetween2() + { + Assert.AreEqual(26L, GetTestBuilder() + .Where(x => x.id.Between(75, 100)) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression in parameters. + [Test] + public void TestWhereIn1() + { + Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(75, 99, 100))); + } + + /// Test dynamic where expression in array. + [Test] + public void TestWhereIn2() + { + Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(new[] { 75, 99, 100 }))); + } + + /// Test dynamic where expression in parameters. + [Test] + public void TestWhereIn3() + { + Assert.AreEqual(3L, GetTestBuilder() + .Where(x => x.id.In(75, 99, 100)) + .Select(x => x.Count()) + .Scalar()); + } + + /// Test dynamic where expression in parameters. + [Test] + public void TestWhereIn4() + { + Assert.AreEqual(3L, GetTestBuilder() + .Where(x => x.id.In(new[] { 75, 99, 100 })) + .Select(x => x.Count()) + .Scalar()); + } + + #endregion Where + } } \ No newline at end of file diff --git a/DynamORM.Tests/Select/DynamicNoSchemaAccessTests.cs b/DynamORM.Tests/Select/DynamicNoSchemaAccessTests.cs index 51aafcc..02778d0 100644 --- a/DynamORM.Tests/Select/DynamicNoSchemaAccessTests.cs +++ b/DynamORM.Tests/Select/DynamicNoSchemaAccessTests.cs @@ -1,55 +1,55 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Select -{ - /// Test standard dynamic access ORM. With out schema information from database. - [TestClass] - public class DynamicNoSchemaAccessTests : DynamicAccessTests - { - /// Setup test parameters. - [TestInitialize] - public override void SetUp() - { - CreateTestDatabase(); - CreateDynamicDatabase( - DynamicDatabaseOptions.SingleConnection | - DynamicDatabaseOptions.SingleTransaction | - DynamicDatabaseOptions.SupportLimitOffset); - } - - /// Create table using specified method. - /// Dynamic table. - public override dynamic GetTestTable() - { - return Database.Table("users", new string[] { "id" }); - } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Test standard dynamic access ORM. With out schema information from database. + [TestFixture] + public class DynamicNoSchemaAccessTests : DynamicAccessTests + { + /// Setup test parameters. + [SetUp] + public override void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase( + DynamicDatabaseOptions.SingleConnection | + DynamicDatabaseOptions.SingleTransaction | + DynamicDatabaseOptions.SupportLimitOffset); + } + + /// Create table using specified method. + /// Dynamic table. + public override dynamic GetTestTable() + { + return Database.Table("sample_users", new string[] { "id" }); + } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Select/DynamicTypeSchemaAccessTests.cs b/DynamORM.Tests/Select/DynamicTypeSchemaAccessTests.cs index 67ce7c5..9db7d81 100644 --- a/DynamORM.Tests/Select/DynamicTypeSchemaAccessTests.cs +++ b/DynamORM.Tests/Select/DynamicTypeSchemaAccessTests.cs @@ -1,45 +1,45 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using DynamORM.Tests.Helpers; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Select -{ - /// Test standard dynamic access ORM. With out schema information from database. - [TestClass] - public class DynamicTypeSchemaAccessTests : DynamicNoSchemaAccessTests - { - /// Create table using specified method. - /// Dynamic table. - public override dynamic GetTestTable() - { - return Database.Table(); - } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using DynamORM.Tests.Helpers; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Test standard dynamic access ORM. With out schema information from database. + [TestFixture] + public class DynamicTypeSchemaAccessTests : DynamicNoSchemaAccessTests + { + /// Create table using specified method. + /// Dynamic table. + public override dynamic GetTestTable() + { + return Database.Table(); + } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Select/LegacyParserTests.cs b/DynamORM.Tests/Select/LegacyParserTests.cs index 3f0069f..f990667 100644 --- a/DynamORM.Tests/Select/LegacyParserTests.cs +++ b/DynamORM.Tests/Select/LegacyParserTests.cs @@ -1,321 +1,321 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System.Linq; -using DynamORM.Builders; -using DynamORM.Builders.Implementation; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Select -{ - /// Tests of legacy parser methods. - [TestClass] - public class LegacyParserTests : TestsBase - { - /// Setup test parameters. - [TestInitialize] - public virtual void SetUp() - { - CreateTestDatabase(); - CreateDynamicDatabase( - DynamicDatabaseOptions.SingleConnection | - DynamicDatabaseOptions.SingleTransaction | - DynamicDatabaseOptions.SupportLimitOffset); - } - - /// Tear down test objects. - [TestCleanup] - public virtual void TearDown() - { - DestroyDynamicDatabase(); - DestroyTestDatabase(); - } - - /// - /// Tests the where expression equal. - /// - [TestMethod] - public void TestWhereEq() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").Eq(0)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests the where expression equal with brackets. - /// - [TestMethod] - public void TestWhereBracketsEq() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock()) - .Where(new DynamicColumn("u.IsActive").Eq(1).SetEndBlock()); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE ((u.\"Deleted\" = [${0}]) AND (u.\"IsActive\" = [${1}]))", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); - } - - /// - /// Tests the where expression equal with brackets and or condition. - /// - [TestMethod] - public void TestWhereBracketsOrEq() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock()) - .Where(new DynamicColumn("u.IsActive").Eq(1).SetOr().SetEndBlock()); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE ((u.\"Deleted\" = [${0}]) OR (u.\"IsActive\" = [${1}]))", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); - } - - /// - /// Tests the where expression equal with brackets. - /// - [TestMethod] - public void TestWhereBracketsOrEq2() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Id_User").Greater(1)) - .Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock()) - .Where(new DynamicColumn("u.IsActive").Eq(1).SetOr().SetEndBlock()); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Id_User\" > [${0}]) AND ((u.\"Deleted\" = [${1}]) OR (u.\"IsActive\" = [${2}]))", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); - } - - /// - /// Tests the where expression equal with brackets. - /// - [TestMethod] - public void TestWhereBracketsOrEqForgotToEnd() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Id_User").Greater(1)) - .Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock()) - .Where(new DynamicColumn("u.IsActive").Eq(1).SetOr()); - - using (var con = Database.Open()) - using (var c = con.CreateCommand()) - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Id_User\" > @0) AND ((u.\"Deleted\" = @1) OR (u.\"IsActive\" = @2))"), - c.SetCommand(cmd).CommandText); - } - - /// - /// Tests the where expression not equal. - /// - [TestMethod] - public void TestWhereNotEq() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").Not(0)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" <> [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests the where expression greater. - /// - [TestMethod] - public void TestWhereGreater() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").Greater(0)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" > [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests the where expression greater or equal. - /// - [TestMethod] - public void TestWhereGreaterOrEqual() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").GreaterOrEqual(0)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" >= [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests the where expression less. - /// - [TestMethod] - public void TestWhereLess() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").Less(1)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" < [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests the where expression less or equal. - /// - [TestMethod] - public void TestWhereLessOrEqual() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").LessOrEqual(1)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" <= [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests the where expression like. - /// - [TestMethod] - public void TestWhereLike() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").Like("%1")); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" LIKE [${0}]", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests the where expression not like. - /// - [TestMethod] - public void TestWhereNotLike() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").NotLike("%1")); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" NOT LIKE [${0}]", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests the where expression between. - /// - [TestMethod] - public void TestWhereBetween() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").Between(0, 1)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" BETWEEN [${0}] AND [${1}]", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); - } - - /// - /// Tests the where expression in. - /// - [TestMethod] - public void TestWhereIn() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new DynamicColumn("u.Deleted").In(0, 1)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" IN([${0}], [${1}])", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); - } - - /// - /// Tests the where expression using anonymous types. - /// - [TestMethod] - public void TestWhereAnon() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .Where(new { Deleted = 0, IsActive = 1, _table = "u" }); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" = [${0}]) AND (u.\"IsActive\" = [${1}])", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); - } - - /// - /// Tests the order by column. - /// - [TestMethod] - public void TestOrderByCol() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .OrderByColumn(new DynamicColumn("u.Name").Desc()); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u ORDER BY u.\"Name\" DESC"), cmd.CommandText()); - } - - /// - /// Tests the order by column number. - /// - [TestMethod] - public void TestOrderByNum() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .OrderByColumn(new DynamicColumn("u.Name").SetAlias("1").Desc()); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u ORDER BY 1 DESC"), cmd.CommandText()); - } - - /// - /// Tests the group by column. - /// - [TestMethod] - public void TestGroupByCol() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(x => x.dbo.Users.As(x.u)) - .GroupByColumn(new DynamicColumn("u.Name")); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u GROUP BY u.\"Name\""), cmd.CommandText()); - } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Linq; +using DynamORM.Builders; +using DynamORM.Builders.Implementation; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Tests of legacy parser methods. + [TestFixture] + public class LegacyParserTests : TestsBase + { + /// Setup test parameters. + [SetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase( + DynamicDatabaseOptions.SingleConnection | + DynamicDatabaseOptions.SingleTransaction | + DynamicDatabaseOptions.SupportLimitOffset); + } + + /// Tear down test objects. + [TearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// + /// Tests the where expression equal. + /// + [Test] + public void TestWhereEq() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Eq(0)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression equal with brackets. + /// + [Test] + public void TestWhereBracketsEq() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock()) + .Where(new DynamicColumn("u.IsActive").Eq(1).SetEndBlock()); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE ((u.\"Deleted\" = [${0}]) AND (u.\"IsActive\" = [${1}]))", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests the where expression equal with brackets and or condition. + /// + [Test] + public void TestWhereBracketsOrEq() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock()) + .Where(new DynamicColumn("u.IsActive").Eq(1).SetOr().SetEndBlock()); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE ((u.\"Deleted\" = [${0}]) OR (u.\"IsActive\" = [${1}]))", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests the where expression equal with brackets. + /// + [Test] + public void TestWhereBracketsOrEq2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Id_User").Greater(1)) + .Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock()) + .Where(new DynamicColumn("u.IsActive").Eq(1).SetOr().SetEndBlock()); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Id_User\" > [${0}]) AND ((u.\"Deleted\" = [${1}]) OR (u.\"IsActive\" = [${2}]))", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + } + + /// + /// Tests the where expression equal with brackets. + /// + [Test] + public void TestWhereBracketsOrEqForgotToEnd() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Id_User").Greater(1)) + .Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock()) + .Where(new DynamicColumn("u.IsActive").Eq(1).SetOr()); + + using (var con = Database.Open()) + using (var c = con.CreateCommand()) + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Id_User\" > @0) AND ((u.\"Deleted\" = @1) OR (u.\"IsActive\" = @2))"), + c.SetCommand(cmd).CommandText); + } + + /// + /// Tests the where expression not equal. + /// + [Test] + public void TestWhereNotEq() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Not(0)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" <> [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression greater. + /// + [Test] + public void TestWhereGreater() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Greater(0)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" > [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression greater or equal. + /// + [Test] + public void TestWhereGreaterOrEqual() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").GreaterOrEqual(0)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" >= [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression less. + /// + [Test] + public void TestWhereLess() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Less(1)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" < [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression less or equal. + /// + [Test] + public void TestWhereLessOrEqual() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").LessOrEqual(1)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" <= [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression like. + /// + [Test] + public void TestWhereLike() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Like("%1")); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" LIKE [${0}]", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression not like. + /// + [Test] + public void TestWhereNotLike() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").NotLike("%1")); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" NOT LIKE [${0}]", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests the where expression between. + /// + [Test] + public void TestWhereBetween() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").Between(0, 1)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" BETWEEN [${0}] AND [${1}]", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests the where expression in. + /// + [Test] + public void TestWhereIn() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new DynamicColumn("u.Deleted").In(0, 1)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE u.\"Deleted\" IN([${0}], [${1}])", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests the where expression using anonymous types. + /// + [Test] + public void TestWhereAnon() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .Where(new { Deleted = 0, IsActive = 1, _table = "u" }); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Deleted\" = [${0}]) AND (u.\"IsActive\" = [${1}])", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests the order by column. + /// + [Test] + public void TestOrderByCol() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .OrderByColumn(new DynamicColumn("u.Name").Desc()); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u ORDER BY u.\"Name\" DESC"), cmd.CommandText()); + } + + /// + /// Tests the order by column number. + /// + [Test] + public void TestOrderByNum() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .OrderByColumn(new DynamicColumn("u.Name").SetAlias("1").Desc()); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u ORDER BY 1 DESC"), cmd.CommandText()); + } + + /// + /// Tests the group by column. + /// + [Test] + public void TestGroupByCol() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(x => x.dbo.Users.As(x.u)) + .GroupByColumn(new DynamicColumn("u.Name")); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u GROUP BY u.\"Name\""), cmd.CommandText()); + } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Select/ParserTests.cs b/DynamORM.Tests/Select/ParserTests.cs index 260eb8c..518bc31 100644 --- a/DynamORM.Tests/Select/ParserTests.cs +++ b/DynamORM.Tests/Select/ParserTests.cs @@ -1,1020 +1,1020 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System.Linq; -using DynamORM.Builders; -using DynamORM.Builders.Implementation; -using DynamORM.Tests.Helpers; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Select -{ - /// - /// New parser tests. - /// - [TestClass] - public class ParserTests : TestsBase - { - /// Setup test parameters. - [TestInitialize] - public virtual void SetUp() - { - CreateTestDatabase(); - CreateDynamicDatabase( - DynamicDatabaseOptions.SingleConnection | - DynamicDatabaseOptions.SingleTransaction | - DynamicDatabaseOptions.SupportLimitOffset | - DynamicDatabaseOptions.SupportNoLock); - } - - /// Tear down test objects. - [TestCleanup] - public virtual void TearDown() - { - try - { - DestroyDynamicDatabase(); - DestroyTestDatabase(); - } - catch { } - } - - /// - /// Tests from method. - /// - [TestMethod] - public void TestFromGet() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users); - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\"", cmd.CommandText()); - } - - /// - /// Tests from typed method. - /// - [TestMethod] - public void TestFromGetTyped() - { - IDynamicSelectQueryBuilder cmd = Database.From(); - - Assert.AreEqual("SELECT * FROM \"users\"", cmd.CommandText()); - } - - /// - /// Tests from typed method. - /// - [TestMethod] - public void TestFromGetTypedAs() - { - IDynamicSelectQueryBuilder cmd = Database.From("u"); - - Assert.AreEqual("SELECT * FROM \"users\" AS u", cmd.CommandText()); - } - - /// - /// Tests from method with multi tables. - /// - [TestMethod] - public void TestFromGetMultiKulti() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users, c => c.Clients); - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\", \"Clients\"", cmd.CommandText()); - } - - /// - /// Tests from method with as expression in text. - /// - [TestMethod] - public void TestFromGetAs1() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As("c")); - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); - } - - /// - /// Tests from method with as expression in text. - /// - [TestMethod] - public void TestFromGetAsNoLock1() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As("c").NoLock()); - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c WITH(NOLOCK)", cmd.CommandText()); - } - - /// - /// Tests from method with as expression using lambda. - /// - [TestMethod] - public void TestFromGetAs2() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)); - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); - } - - /// - /// Tests from method using text. - /// - [TestMethod] - public void TestFromText() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => "dbo.Users"); - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\"", cmd.CommandText()); - } - - /// - /// Tests from method using text with decorators. - /// - [TestMethod] - public void TestFromDecoratedText() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => "\"dbo\".\"Users\""); - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\"", cmd.CommandText()); - } - - /// - /// Tests from method using text with as. - /// - [TestMethod] - public void TestFromTextAs1() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => "dbo.Users AS c"); - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); - } - - /// - /// Tests from method using invoke with as. - /// - [TestMethod] - public void TestFromTextAs2() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u(u.dbo.Users).As(u.u)); - - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS u", cmd.CommandText()); - } - - /// - /// Tests from method using invoke with as. - /// - [TestMethod] - public void TestFromTextAs3() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u("\"dbo\".\"Users\"").As(u.u)); - - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS u", cmd.CommandText()); - } - - /// - /// Tests from method using invoke with sub query. - /// - [TestMethod] - public void TestFromSubQuery1() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u(new DynamicSelectQueryBuilder(Database).From(x => x.dbo.Users)).As("u")); - - Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\") AS u", cmd.CommandText()); - } - - /// - /// Tests from method using invoke with sub query. - /// - [TestMethod] - public void TestFromSubQuery2() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u(cmd.SubQuery(x => x.dbo.Users)).As("u")); - - Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\") AS u", cmd.CommandText()); - } - - /// - /// Tests from method using invoke with sub query. - /// - [TestMethod] - public void TestFromSubQuery3() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.SubQuery((b, s) => b.From(y => y(s.From(x => x.dbo.Users)).As("u"))); - - Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\") AS u", cmd.CommandText()); - } - - /// - /// Tests from method using invoke with sub query. - /// - [TestMethod] - public void TestFromSubQuery4() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u(new DynamicSelectQueryBuilder(Database).From(x => x.dbo.Users.NoLock())).As("u")); - - Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\" WITH(NOLOCK)) AS u", cmd.CommandText()); - } - - /// - /// Tests where method with alias. - /// - [TestMethod] - public void TestWhereAlias() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Where(u => u.c.UserName == "admin"); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE (c.\"UserName\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests where method with alias. - /// - [TestMethod] - public void TestHavingAlias() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Having(u => u.Sum(u.c.ClientsCount) > 10); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c HAVING (Sum(c.\"ClientsCount\") > [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests complex where method with alias. - /// - [TestMethod] - public void TestWhereAliasComplex() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") - .Where(u => u.c.IsActive = true); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND c.\"IsActive\" = ([${2}])", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); - } - - /// - /// Tests where method with alias using in. - /// - [TestMethod] - public void TestWhereAliasIn() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - int[] ids = new int[] { 0, 1, 2, 3, 4, 5 }; - - cmd.From(u => u.dbo.Users.As(u.c)) - .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") - .Where(u => u.c.IsActive == true) - .Where(u => u.c.Id_User.In(ids)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND (c.\"IsActive\" = [${2}]) AND c.\"Id_User\" IN({3})", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], - string.Join(", ", cmd.Parameters.Keys.Skip(3).Select(p => string.Format("[${0}]", p)))), cmd.CommandText()); - } - - /// - /// Tests where method with alias using between. - /// - [TestMethod] - public void TestWhereAliasBetween1() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - int[] ids = new int[] { 0, 5 }; - - cmd.From(u => u.dbo.Users.As(u.c)) - .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") - .Where(u => u.c.IsActive == true) - .Where(u => u.c.Id_User.Between(ids)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND (c.\"IsActive\" = [${2}]) AND c.\"Id_User\" BETWEEN [${3}] AND [${4}]", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], - cmd.Parameters.Keys.ToArray()[3], cmd.Parameters.Keys.ToArray()[4]), cmd.CommandText()); - } - - /// - /// Tests where method with alias using between. - /// - [TestMethod] - public void TestWhereAliasBetween2() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - int[] ids = new int[] { 0, 5 }; - - cmd.From(u => u.dbo.Users.As(u.c)) - .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") - .Where(u => u.c.IsActive == true) - .Where(u => u.c.Id_User.Between(ids[0], ids[1])); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND (c.\"IsActive\" = [${2}]) AND c.\"Id_User\" BETWEEN [${3}] AND [${4}]", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], - cmd.Parameters.Keys.ToArray()[3], cmd.Parameters.Keys.ToArray()[4]), cmd.CommandText()); - } - - /// - /// Tests where method without alias. - /// - [TestMethod] - public void TestWhereNoAlias() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users) - .Where(u => u.UserName == "admin"); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" WHERE (\"UserName\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests where method with full column name. - /// - [TestMethod] - public void TestWhereNoAliasTableName() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users) - .Where(u => u.dbo.Users.UserName == "admin"); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" WHERE (\"dbo\".\"Users\".\"UserName\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests simple join method. - /// - [TestMethod] - public void TestJoinClassic() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr)) - .Join(u => u.dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); - } - - /// - /// Tests inner join method. - /// - [TestMethod] - public void TestInnerJoin() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr)) - .Join(u => u.Inner().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr INNER JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); - } - - /// - /// Tests inner join method with aliases mix. - /// - [TestMethod] - public void TestInnerJoin2() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr)) - .Join(usr => usr.Inner().dbo.UserClients.AS(usr.uc).On(usr.Id_User == usr.uc.User_Id && usr.uc.Users != null)) - .Select(usr => usr.All(), uc => uc.Users); - - Assert.AreEqual(string.Format("SELECT usr.*, uc.\"Users\" FROM \"dbo\".\"Users\" AS usr INNER JOIN \"dbo\".\"UserClients\" AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Users\" IS NOT NULL))"), cmd.CommandText()); - } - - /// - /// Tests from method using invoke with sub query. - /// - [TestMethod] - public void TestInnerJoin3() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr)) - .SubQuery((b, s) => b.Join(usr => usr(s.From(x => x.dbo.UserClients)).Inner().As(usr.uc).On(usr.Id_User == usr.uc.User_Id && usr.uc.Users != null))) - .Select(usr => usr.All(), uc => uc.Users); - - Assert.AreEqual(string.Format("SELECT usr.*, uc.\"Users\" FROM \"dbo\".\"Users\" AS usr INNER JOIN (SELECT * FROM \"dbo\".\"UserClients\") AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Users\" IS NOT NULL))"), cmd.CommandText()); - } - - /// - /// Tests from method using invoke with sub query. - /// - [TestMethod] - public void TestInnerJoin4() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr)) - .SubQuery((b, s) => b.Join(usr => usr(s.From(x => x.dbo.UserClients).Where(x => x.Deleted == 0)).Inner().As(usr.uc).On(usr.Id_User == usr.uc.User_Id && usr.uc.Users != null))) - .Select(usr => usr.All(), uc => uc.Users); - - Assert.AreEqual(string.Format("SELECT usr.*, uc.\"Users\" FROM \"dbo\".\"Users\" AS usr INNER JOIN (SELECT * FROM \"dbo\".\"UserClients\" WHERE (\"Deleted\" = [${0}])) AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Users\" IS NOT NULL))", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests from method using invoke with sub query an no lock. - /// - [TestMethod] - public void TestInnerJoin5() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr).NoLock()) - .SubQuery((b, s) => b.Join(usr => usr(s.From(x => x.dbo.UserClients.NoLock()).Where(x => x.Deleted == 0)).Inner().As(usr.uc).On(usr.Id_User == usr.uc.User_Id && usr.uc.Users != null))) - .Select(usr => usr.All(), uc => uc.Users); - - Assert.AreEqual(string.Format("SELECT usr.*, uc.\"Users\" FROM \"dbo\".\"Users\" AS usr WITH(NOLOCK) INNER JOIN (SELECT * FROM \"dbo\".\"UserClients\" WITH(NOLOCK) WHERE (\"Deleted\" = [${0}])) AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Users\" IS NOT NULL))", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests inner join method with no lock. - /// - [TestMethod] - public void TestInnerJoin6() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr).NoLock()) - .Join(u => u.Inner().dbo.UserClients.AS(u.uc).NoLock().On(u.usr.Id_User == u.uc.User_Id)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr WITH(NOLOCK) INNER JOIN \"dbo\".\"UserClients\" AS uc WITH(NOLOCK) ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); - } - - /// - /// Tests left outer join method. - /// - [TestMethod] - public void TestLeftOuterJoin() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr)) - .Join(u => u.LeftOuter().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr LEFT OUTER JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); - } - - /// - /// Tests left join method. - /// - [TestMethod] - public void TestLeftJoin() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr)) - .Join(u => u.Left().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr LEFT JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); - } - - /// - /// Tests right outer join method. - /// - [TestMethod] - public void TestRightOuterJoin() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr)) - .Join(u => u.RightOuter().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr RIGHT OUTER JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); - } - - /// - /// Tests right join method. - /// - [TestMethod] - public void TestRightJoin() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr)) - .Join(u => u.Right().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr RIGHT JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); - } - - /// - /// Tests complex join with parameters. - /// - [TestMethod] - public void TestJoinClassicWithParamAndWhere() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.usr)) - .Join(u => u.dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id && u.uc.Deleted == 0)) - .Where(u => u.usr.Active == true); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr JOIN \"dbo\".\"UserClients\" AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Deleted\" = [${0}])) WHERE (usr.\"Active\" = [${1}])", - cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); - } - - /// - /// Tests select all. - /// - [TestMethod] - public void TestSelectAll1() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u.c.All()); - - Assert.AreEqual("SELECT c.* FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); - } - - /// - /// Tests select all. - /// - [TestMethod] - public void TestSelectAll2() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users) - .Select(u => u.dbo.Users.All()); - - Assert.AreEqual("SELECT \"dbo\".\"Users\".* FROM \"dbo\".\"Users\"", cmd.CommandText()); - } - - /// - /// Tests select field. - /// - [TestMethod] - public void TestSelectField1() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u.c.UserName); - - Assert.AreEqual("SELECT c.\"UserName\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); - } - - /// - /// Tests select field. - /// - [TestMethod] - public void TestSelectField2() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users) - .Select(u => u.dbo.Users.UserName); - - Assert.AreEqual("SELECT \"dbo\".\"Users\".\"UserName\" FROM \"dbo\".\"Users\"", cmd.CommandText()); - } - - /// - /// Tests select field with alias. - /// - [TestMethod] - public void TestSelectFieldAlias1() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u.c.UserName.As(u.Name)); - - Assert.AreEqual("SELECT c.\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); - } - - /// - /// Tests select field with alias. - /// - [TestMethod] - public void TestSelectFieldAlias2() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users) - .Select(u => u.dbo.Users.UserName.As(u.Name)); - - Assert.AreEqual("SELECT \"dbo\".\"Users\".\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\"", cmd.CommandText()); - } - - /// - /// Tests select field with alias. - /// - [TestMethod] - public void TestSelectFieldAlias3() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.u)) - .Select(u => u.UserName.As(u.Name)); - - Assert.AreEqual("SELECT u.\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\" AS u", cmd.CommandText()); - } - - /// - /// Tests select field with alias. - /// - [TestMethod] - public void TestSelectFieldAlias4() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.u)) - .Select(u => u.UserName.As(u.u.Name)); - - Assert.AreEqual("SELECT u.\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\" AS u", cmd.CommandText()); - } - - /// - /// Tests select aggregate field with alias (Sum). - /// - [TestMethod] - public void TestSelectAggregateField1() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u.Sum(u.c.UserName).As(u.Name)); - - Assert.AreEqual("SELECT Sum(c.\"UserName\") AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); - } - - /// - /// Tests select aggregate field with alias (Coalesce). - /// - [TestMethod] - public void TestSelectAggregateField2() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u.Coalesce(u.c.UserName, u.c.FirstName + " " + u.c.LastName).As(u.Name)); - - Assert.AreEqual(string.Format("SELECT Coalesce(c.\"UserName\", ((c.\"FirstName\" + [${0}]) + c.\"LastName\")) AS \"Name\" FROM \"dbo\".\"Users\" AS c", - cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests select aggregate field with alias (Sum). - /// - [TestMethod] - public void TestSelectAggregateField3() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users) - .Select(u => u.Sum(u.dbo.Users.UserName)); - - Assert.AreEqual("SELECT Sum(\"dbo\".\"Users\".\"UserName\") FROM \"dbo\".\"Users\"", cmd.CommandText()); - } - - /// - /// Tests select aggregate field with alias (Sum). - /// - [TestMethod] - public void TestSelectAggregateField4() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u.Sum(u("\"UserName\"")).As(u.Name)); - - Assert.AreEqual("SELECT Sum(\"UserName\") AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); - } - - /// - /// Tests select aggregate field with alias (Sum). - /// - [TestMethod] - public void TestSelectAggregateField5() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u(u.Sum(u("\"UserName\"")), " + 1").As(u.Name)); - - Assert.AreEqual("SELECT Sum(\"UserName\") + 1 AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); - } - - /// - /// Tests select from anonymous type. - /// - [TestMethod] - public void TestSelectAnon() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => new - { - Id_User = u.c.Id_User, - Name = u.c.UserName, - }); - - Assert.AreEqual("SELECT c.\"Id_User\" AS \"Id_User\", c.\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); - } - - /// - /// Tests select escaped case. - /// - [TestMethod] - public void TestSelectCaseEscaped1() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u("CASE ", u.c.IsActive, " WHEN ", 1, " THEN ", 0, " ELSE ", 1, " END").As(u.Deleted)); - - Assert.AreEqual(string.Format("SELECT CASE c.\"IsActive\" WHEN [${0}] THEN [${1}] ELSE [${2}] END AS \"Deleted\" FROM \"dbo\".\"Users\" AS c", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); - } - - /// - /// Tests select escaped case. - /// - [TestMethod] - public void TestSelectCaseEscaped2() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u("CASE WHEN ", u.c.IsActive == 1, " THEN ", 0, " ELSE ", 1, " END").As(u.Deleted)); - - Assert.AreEqual(string.Format("SELECT CASE WHEN (c.\"IsActive\" = [${0}]) THEN [${1}] ELSE [${2}] END AS \"Deleted\" FROM \"dbo\".\"Users\" AS c", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); - } - - /// - /// Tests select escaped case. - /// - [TestMethod] - public void TestCoalesceEscaped() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u("COALESCE(", Database.DecorateName("ServerHash"), ", ", new byte[16] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, ")").As(u.Hash)); - - Assert.AreEqual(string.Format("SELECT COALESCE(\"ServerHash\", [${0}]) AS \"Hash\" FROM \"dbo\".\"Users\" AS c", - cmd.Parameters.Keys.ToArray()[0]), cmd.CommandText()); - } - - /// - /// Tests select escaped case. - /// - [TestMethod] - public void TestCoalesce() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u.Coalesce(u.c.ServerHash, new byte[16] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }).As(u.Hash)); - - Assert.AreEqual(string.Format("SELECT Coalesce(c.\"ServerHash\", [${0}]) AS \"Hash\" FROM \"dbo\".\"Users\" AS c", - cmd.Parameters.Keys.ToArray()[0]), cmd.CommandText()); - } - - /// - /// Tests select escaped case. - /// - [TestMethod] - public void TestCoalesceCalculatedArgs() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u.Coalesce(u.c.Test1 + "_", u.c.Test2 + "_", u.c.Test3 + "_").As(u.Hash)); - - Assert.AreEqual(string.Format("SELECT Coalesce((c.\"Test1\" + [${0}]), (c.\"Test2\" + [${1}]), (c.\"Test3\" + [${2}])) AS \"Hash\" FROM \"dbo\".\"Users\" AS c", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); - - //var c = Database.Open().CreateCommand(); - //cmd.FillCommand(c); - //c.Dispose(); - } - - /// - /// Tests select escaped case. - /// - [TestMethod] - public void TestCoalesceInWhere() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u.ServerHash.As(u.Hash)) - .Where(u => u.Coalesce(u.c.ServerHash, new byte[16] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }) == new byte[16] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); - - Assert.AreEqual(string.Format("SELECT \"ServerHash\" AS \"Hash\" FROM \"dbo\".\"Users\" AS c WHERE (Coalesce(c.\"ServerHash\", [${0}]) = [${1}])", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1]), cmd.CommandText()); - } - - /// - /// Tests select escaped case with sub query. - /// - [TestMethod] - public void TestSelectCaseEscapedAndSub() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u("CASE WHEN ", u.c.IsActive == 1, " AND ", u.c.IsAdmin == u(cmd.SubQuery() - .From(x => x.dbo.AccessRights.As(x.a)) - .Where(x => x.a.User_Id == x.c.Id_User) - .Select(x => x.a.IsAdmin)), " THEN ", 0, " ELSE ", 1, " END").As(u.Deleted)); - - Assert.AreEqual(string.Format("SELECT CASE WHEN (c.\"IsActive\" = [${0}]) AND (c.\"IsAdmin\" = (SELECT a.\"IsAdmin\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"User_Id\" = c.\"Id_User\"))) THEN [${1}] ELSE [${2}] END AS \"Deleted\" FROM \"dbo\".\"Users\" AS c", - cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); - } - - /// - /// Tests group by. - /// - [TestMethod] - public void TestGroupBy() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .GroupBy(u => u.c.UserName); - - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c GROUP BY c.\"UserName\"", cmd.CommandText()); - } - - /// - /// Tests order by. - /// - [TestMethod] - public void TestOrderBy() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .OrderBy(u => u.c.UserName); - - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY c.\"UserName\" ASC", cmd.CommandText()); - } - - /// - /// Tests order by using string with number. - /// - [TestMethod] - public void TestOrderByNumberedColumnStr() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .OrderBy(u => "1 DESC"); - - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY 1 DESC", cmd.CommandText()); - } - - /// - /// Tests order by using member with number. - /// - [TestMethod] - public void TestOrderByNumberedColFn() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .OrderBy(u => u.Desc(1)); - - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY 1 DESC", cmd.CommandText()); - } - - /// - /// Tests order by using member with field. - /// - [TestMethod] - public void TestOrderByAlt() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .OrderBy(u => u.Desc(u.c.UserName)); - - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY c.\"UserName\" DESC", cmd.CommandText()); - } - - /// - /// Tests sub query select. - /// - [TestMethod] - public void TestSubQuerySelect() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Select(u => u(cmd.SubQuery() - .From(x => x.dbo.AccessRights.As(x.a)) - .Where(x => x.a.User_Id == x.c.Id_User) - .Select(x => x.a.IsAdmin)).As(u.IsAdmin)); - - Assert.AreEqual("SELECT (SELECT a.\"IsAdmin\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"User_Id\" = c.\"Id_User\")) AS \"IsAdmin\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); - } - - /// - /// Tests sub query where. - /// - [TestMethod] - public void TestSubQueryWhere() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Where(u => u.c.IsAdmin == u(cmd.SubQuery() - .From(x => x.dbo.AccessRights.As(x.a)) - .Where(x => x.a.User_Id == x.c.Id_User) - .Select(x => x.a.IsAdmin))); - - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c WHERE (c.\"IsAdmin\" = (SELECT a.\"IsAdmin\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"User_Id\" = c.\"Id_User\")))", cmd.CommandText()); - } - - /// - /// Tests sub query in. - /// - [TestMethod] - public void TestSubQueryWhereIn() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Where(u => u.c.Id_User.In(u(cmd.SubQuery() - .From(x => x.dbo.AccessRights.As(x.a)) - .Where(x => x.a.IsAdmin == 1) - .Select(x => x.a.User_Id)))); - - Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE c.\"Id_User\" IN((SELECT a.\"User_Id\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"IsAdmin\" = [${0}])))", cmd.Parameters.Keys.First()), cmd.CommandText()); - } - - /// - /// Tests sub query join. - /// - [TestMethod] - public void TestSubQueryJoin() - { - IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); - - cmd.From(u => u.dbo.Users.As(u.c)) - .Join(u => u.Inner()(cmd.SubQuery() - .From(x => x.dbo.AccessRights.As(x.a)) - .Select(x => x.a.IsAdmin, x => x.a.User_Id)).As(u.ar).On(u.ar.User_Id == u.c.Id_User)); - - Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c INNER JOIN (SELECT a.\"IsAdmin\", a.\"User_Id\" FROM \"dbo\".\"AccessRights\" AS a) AS ar ON (ar.\"User_Id\" = c.\"Id_User\")", cmd.CommandText()); - } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Linq; +using DynamORM.Builders; +using DynamORM.Builders.Implementation; +using DynamORM.Tests.Helpers; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// + /// New parser tests. + /// + [TestFixture] + public class ParserTests : TestsBase + { + /// Setup test parameters. + [SetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase( + DynamicDatabaseOptions.SingleConnection | + DynamicDatabaseOptions.SingleTransaction | + DynamicDatabaseOptions.SupportLimitOffset | + DynamicDatabaseOptions.SupportNoLock); + } + + /// Tear down test objects. + [TearDown] + public virtual void TearDown() + { + try + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + catch { } + } + + /// + /// Tests from method. + /// + [Test] + public void TestFromGet() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests from typed method. + /// + [Test] + public void TestFromGetTyped() + { + IDynamicSelectQueryBuilder cmd = Database.From(); + + Assert.AreEqual("SELECT * FROM \"sample_users\"", cmd.CommandText()); + } + + /// + /// Tests from typed method. + /// + [Test] + public void TestFromGetTypedAs() + { + IDynamicSelectQueryBuilder cmd = Database.From("u"); + + Assert.AreEqual("SELECT * FROM \"sample_users\" AS u", cmd.CommandText()); + } + + /// + /// Tests from method with multi tables. + /// + [Test] + public void TestFromGetMultiKulti() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users, c => c.Clients); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\", \"Clients\"", cmd.CommandText()); + } + + /// + /// Tests from method with as expression in text. + /// + [Test] + public void TestFromGetAs1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As("c")); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests from method with as expression in text. + /// + [Test] + public void TestFromGetAsNoLock1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As("c").NoLock()); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c WITH(NOLOCK)", cmd.CommandText()); + } + + /// + /// Tests from method with as expression using lambda. + /// + [Test] + public void TestFromGetAs2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests from method using text. + /// + [Test] + public void TestFromText() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => "dbo.Users"); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests from method using text with decorators. + /// + [Test] + public void TestFromDecoratedText() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => "\"dbo\".\"Users\""); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests from method using text with as. + /// + [Test] + public void TestFromTextAs1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => "dbo.Users AS c"); + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests from method using invoke with as. + /// + [Test] + public void TestFromTextAs2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u(u.dbo.Users).As(u.u)); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS u", cmd.CommandText()); + } + + /// + /// Tests from method using invoke with as. + /// + [Test] + public void TestFromTextAs3() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u("\"dbo\".\"Users\"").As(u.u)); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS u", cmd.CommandText()); + } + + /// + /// Tests from method using invoke with sub query. + /// + [Test] + public void TestFromSubQuery1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u(new DynamicSelectQueryBuilder(Database).From(x => x.dbo.Users)).As("u")); + + Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\") AS u", cmd.CommandText()); + } + + /// + /// Tests from method using invoke with sub query. + /// + [Test] + public void TestFromSubQuery2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u(cmd.SubQuery(x => x.dbo.Users)).As("u")); + + Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\") AS u", cmd.CommandText()); + } + + /// + /// Tests from method using invoke with sub query. + /// + [Test] + public void TestFromSubQuery3() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.SubQuery((b, s) => b.From(y => y(s.From(x => x.dbo.Users)).As("u"))); + + Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\") AS u", cmd.CommandText()); + } + + /// + /// Tests from method using invoke with sub query. + /// + [Test] + public void TestFromSubQuery4() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u(new DynamicSelectQueryBuilder(Database).From(x => x.dbo.Users.NoLock())).As("u")); + + Assert.AreEqual("SELECT * FROM (SELECT * FROM \"dbo\".\"Users\" WITH(NOLOCK)) AS u", cmd.CommandText()); + } + + /// + /// Tests where method with alias. + /// + [Test] + public void TestWhereAlias() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.UserName == "admin"); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE (c.\"UserName\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests where method with alias. + /// + [Test] + public void TestHavingAlias() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Having(u => u.Sum(u.c.ClientsCount) > 10); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c HAVING (Sum(c.\"ClientsCount\") > [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests complex where method with alias. + /// + [Test] + public void TestWhereAliasComplex() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") + .Where(u => u.c.IsActive = true); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND c.\"IsActive\" = ([${2}])", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + } + + /// + /// Tests where method with alias using in. + /// + [Test] + public void TestWhereAliasIn() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + int[] ids = new int[] { 0, 1, 2, 3, 4, 5 }; + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") + .Where(u => u.c.IsActive == true) + .Where(u => u.c.Id_User.In(ids)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND (c.\"IsActive\" = [${2}]) AND c.\"Id_User\" IN({3})", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], + string.Join(", ", cmd.Parameters.Keys.Skip(3).Select(p => string.Format("[${0}]", p)))), cmd.CommandText()); + } + + /// + /// Tests where method with alias using between. + /// + [Test] + public void TestWhereAliasBetween1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + int[] ids = new int[] { 0, 5 }; + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") + .Where(u => u.c.IsActive == true) + .Where(u => u.c.Id_User.Between(ids)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND (c.\"IsActive\" = [${2}]) AND c.\"Id_User\" BETWEEN [${3}] AND [${4}]", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], + cmd.Parameters.Keys.ToArray()[3], cmd.Parameters.Keys.ToArray()[4]), cmd.CommandText()); + } + + /// + /// Tests where method with alias using between. + /// + [Test] + public void TestWhereAliasBetween2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + int[] ids = new int[] { 0, 5 }; + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.UserName == "admin" || u.c.UserName == "root") + .Where(u => u.c.IsActive == true) + .Where(u => u.c.Id_User.Between(ids[0], ids[1])); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE ((c.\"UserName\" = [${0}]) OR (c.\"UserName\" = [${1}])) AND (c.\"IsActive\" = [${2}]) AND c.\"Id_User\" BETWEEN [${3}] AND [${4}]", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2], + cmd.Parameters.Keys.ToArray()[3], cmd.Parameters.Keys.ToArray()[4]), cmd.CommandText()); + } + + /// + /// Tests where method without alias. + /// + [Test] + public void TestWhereNoAlias() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Where(u => u.UserName == "admin"); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" WHERE (\"UserName\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests where method with full column name. + /// + [Test] + public void TestWhereNoAliasTableName() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Where(u => u.dbo.Users.UserName == "admin"); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" WHERE (\"dbo\".\"Users\".\"UserName\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests simple join method. + /// + [Test] + public void TestJoinClassic() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests inner join method. + /// + [Test] + public void TestInnerJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.Inner().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr INNER JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests inner join method with aliases mix. + /// + [Test] + public void TestInnerJoin2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(usr => usr.Inner().dbo.UserClients.AS(usr.uc).On(usr.Id_User == usr.uc.User_Id && usr.uc.Users != null)) + .Select(usr => usr.All(), uc => uc.Users); + + Assert.AreEqual(string.Format("SELECT usr.*, uc.\"Users\" FROM \"dbo\".\"Users\" AS usr INNER JOIN \"dbo\".\"UserClients\" AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Users\" IS NOT NULL))"), cmd.CommandText()); + } + + /// + /// Tests from method using invoke with sub query. + /// + [Test] + public void TestInnerJoin3() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .SubQuery((b, s) => b.Join(usr => usr(s.From(x => x.dbo.UserClients)).Inner().As(usr.uc).On(usr.Id_User == usr.uc.User_Id && usr.uc.Users != null))) + .Select(usr => usr.All(), uc => uc.Users); + + Assert.AreEqual(string.Format("SELECT usr.*, uc.\"Users\" FROM \"dbo\".\"Users\" AS usr INNER JOIN (SELECT * FROM \"dbo\".\"UserClients\") AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Users\" IS NOT NULL))"), cmd.CommandText()); + } + + /// + /// Tests from method using invoke with sub query. + /// + [Test] + public void TestInnerJoin4() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .SubQuery((b, s) => b.Join(usr => usr(s.From(x => x.dbo.UserClients).Where(x => x.Deleted == 0)).Inner().As(usr.uc).On(usr.Id_User == usr.uc.User_Id && usr.uc.Users != null))) + .Select(usr => usr.All(), uc => uc.Users); + + Assert.AreEqual(string.Format("SELECT usr.*, uc.\"Users\" FROM \"dbo\".\"Users\" AS usr INNER JOIN (SELECT * FROM \"dbo\".\"UserClients\" WHERE (\"Deleted\" = [${0}])) AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Users\" IS NOT NULL))", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests from method using invoke with sub query an no lock. + /// + [Test] + public void TestInnerJoin5() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr).NoLock()) + .SubQuery((b, s) => b.Join(usr => usr(s.From(x => x.dbo.UserClients.NoLock()).Where(x => x.Deleted == 0)).Inner().As(usr.uc).On(usr.Id_User == usr.uc.User_Id && usr.uc.Users != null))) + .Select(usr => usr.All(), uc => uc.Users); + + Assert.AreEqual(string.Format("SELECT usr.*, uc.\"Users\" FROM \"dbo\".\"Users\" AS usr WITH(NOLOCK) INNER JOIN (SELECT * FROM \"dbo\".\"UserClients\" WITH(NOLOCK) WHERE (\"Deleted\" = [${0}])) AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Users\" IS NOT NULL))", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests inner join method with no lock. + /// + [Test] + public void TestInnerJoin6() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr).NoLock()) + .Join(u => u.Inner().dbo.UserClients.AS(u.uc).NoLock().On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr WITH(NOLOCK) INNER JOIN \"dbo\".\"UserClients\" AS uc WITH(NOLOCK) ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests left outer join method. + /// + [Test] + public void TestLeftOuterJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.LeftOuter().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr LEFT OUTER JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests left join method. + /// + [Test] + public void TestLeftJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.Left().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr LEFT JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests right outer join method. + /// + [Test] + public void TestRightOuterJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.RightOuter().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr RIGHT OUTER JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests right join method. + /// + [Test] + public void TestRightJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.Right().dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id)); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr RIGHT JOIN \"dbo\".\"UserClients\" AS uc ON (usr.\"Id_User\" = uc.\"User_Id\")"), cmd.CommandText()); + } + + /// + /// Tests complex join with parameters. + /// + [Test] + public void TestJoinClassicWithParamAndWhere() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.usr)) + .Join(u => u.dbo.UserClients.AS(u.uc).On(u.usr.Id_User == u.uc.User_Id && u.uc.Deleted == 0)) + .Where(u => u.usr.Active == true); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS usr JOIN \"dbo\".\"UserClients\" AS uc ON ((usr.\"Id_User\" = uc.\"User_Id\") AND (uc.\"Deleted\" = [${0}])) WHERE (usr.\"Active\" = [${1}])", + cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText()); + } + + /// + /// Tests select all. + /// + [Test] + public void TestSelectAll1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.c.All()); + + Assert.AreEqual("SELECT c.* FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select all. + /// + [Test] + public void TestSelectAll2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Select(u => u.dbo.Users.All()); + + Assert.AreEqual("SELECT \"dbo\".\"Users\".* FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests select field. + /// + [Test] + public void TestSelectField1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.c.UserName); + + Assert.AreEqual("SELECT c.\"UserName\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select field. + /// + [Test] + public void TestSelectField2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Select(u => u.dbo.Users.UserName); + + Assert.AreEqual("SELECT \"dbo\".\"Users\".\"UserName\" FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests select field with alias. + /// + [Test] + public void TestSelectFieldAlias1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.c.UserName.As(u.Name)); + + Assert.AreEqual("SELECT c.\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select field with alias. + /// + [Test] + public void TestSelectFieldAlias2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Select(u => u.dbo.Users.UserName.As(u.Name)); + + Assert.AreEqual("SELECT \"dbo\".\"Users\".\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests select field with alias. + /// + [Test] + public void TestSelectFieldAlias3() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.u)) + .Select(u => u.UserName.As(u.Name)); + + Assert.AreEqual("SELECT u.\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\" AS u", cmd.CommandText()); + } + + /// + /// Tests select field with alias. + /// + [Test] + public void TestSelectFieldAlias4() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.u)) + .Select(u => u.UserName.As(u.u.Name)); + + Assert.AreEqual("SELECT u.\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\" AS u", cmd.CommandText()); + } + + /// + /// Tests select aggregate field with alias (Sum). + /// + [Test] + public void TestSelectAggregateField1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.Sum(u.c.UserName).As(u.Name)); + + Assert.AreEqual("SELECT Sum(c.\"UserName\") AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select aggregate field with alias (Coalesce). + /// + [Test] + public void TestSelectAggregateField2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.Coalesce(u.c.UserName, u.c.FirstName + " " + u.c.LastName).As(u.Name)); + + Assert.AreEqual(string.Format("SELECT Coalesce(c.\"UserName\", ((c.\"FirstName\" + [${0}]) + c.\"LastName\")) AS \"Name\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests select aggregate field with alias (Sum). + /// + [Test] + public void TestSelectAggregateField3() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users) + .Select(u => u.Sum(u.dbo.Users.UserName)); + + Assert.AreEqual("SELECT Sum(\"dbo\".\"Users\".\"UserName\") FROM \"dbo\".\"Users\"", cmd.CommandText()); + } + + /// + /// Tests select aggregate field with alias (Sum). + /// + [Test] + public void TestSelectAggregateField4() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.Sum(u("\"UserName\"")).As(u.Name)); + + Assert.AreEqual("SELECT Sum(\"UserName\") AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select aggregate field with alias (Sum). + /// + [Test] + public void TestSelectAggregateField5() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u(u.Sum(u("\"UserName\"")), " + 1").As(u.Name)); + + Assert.AreEqual("SELECT Sum(\"UserName\") + 1 AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select from anonymous type. + /// + [Test] + public void TestSelectAnon() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => new + { + Id_User = u.c.Id_User, + Name = u.c.UserName, + }); + + Assert.AreEqual("SELECT c.\"Id_User\" AS \"Id_User\", c.\"UserName\" AS \"Name\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests select escaped case. + /// + [Test] + public void TestSelectCaseEscaped1() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u("CASE ", u.c.IsActive, " WHEN ", 1, " THEN ", 0, " ELSE ", 1, " END").As(u.Deleted)); + + Assert.AreEqual(string.Format("SELECT CASE c.\"IsActive\" WHEN [${0}] THEN [${1}] ELSE [${2}] END AS \"Deleted\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + } + + /// + /// Tests select escaped case. + /// + [Test] + public void TestSelectCaseEscaped2() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u("CASE WHEN ", u.c.IsActive == 1, " THEN ", 0, " ELSE ", 1, " END").As(u.Deleted)); + + Assert.AreEqual(string.Format("SELECT CASE WHEN (c.\"IsActive\" = [${0}]) THEN [${1}] ELSE [${2}] END AS \"Deleted\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + } + + /// + /// Tests select escaped case. + /// + [Test] + public void TestCoalesceEscaped() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u("COALESCE(", Database.DecorateName("ServerHash"), ", ", new byte[16] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, ")").As(u.Hash)); + + Assert.AreEqual(string.Format("SELECT COALESCE(\"ServerHash\", [${0}]) AS \"Hash\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.ToArray()[0]), cmd.CommandText()); + } + + /// + /// Tests select escaped case. + /// + [Test] + public void TestCoalesce() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.Coalesce(u.c.ServerHash, new byte[16] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }).As(u.Hash)); + + Assert.AreEqual(string.Format("SELECT Coalesce(c.\"ServerHash\", [${0}]) AS \"Hash\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.ToArray()[0]), cmd.CommandText()); + } + + /// + /// Tests select escaped case. + /// + [Test] + public void TestCoalesceCalculatedArgs() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.Coalesce(u.c.Test1 + "_", u.c.Test2 + "_", u.c.Test3 + "_").As(u.Hash)); + + Assert.AreEqual(string.Format("SELECT Coalesce((c.\"Test1\" + [${0}]), (c.\"Test2\" + [${1}]), (c.\"Test3\" + [${2}])) AS \"Hash\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + + //var c = Database.Open().CreateCommand(); + //cmd.FillCommand(c); + //c.Dispose(); + } + + /// + /// Tests select escaped case. + /// + [Test] + public void TestCoalesceInWhere() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.ServerHash.As(u.Hash)) + .Where(u => u.Coalesce(u.c.ServerHash, new byte[16] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }) == new byte[16] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + + Assert.AreEqual(string.Format("SELECT \"ServerHash\" AS \"Hash\" FROM \"dbo\".\"Users\" AS c WHERE (Coalesce(c.\"ServerHash\", [${0}]) = [${1}])", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1]), cmd.CommandText()); + } + + /// + /// Tests select escaped case with sub query. + /// + [Test] + public void TestSelectCaseEscapedAndSub() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u("CASE WHEN ", u.c.IsActive == 1, " AND ", u.c.IsAdmin == u(cmd.SubQuery() + .From(x => x.dbo.AccessRights.As(x.a)) + .Where(x => x.a.User_Id == x.c.Id_User) + .Select(x => x.a.IsAdmin)), " THEN ", 0, " ELSE ", 1, " END").As(u.Deleted)); + + Assert.AreEqual(string.Format("SELECT CASE WHEN (c.\"IsActive\" = [${0}]) AND (c.\"IsAdmin\" = (SELECT a.\"IsAdmin\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"User_Id\" = c.\"Id_User\"))) THEN [${1}] ELSE [${2}] END AS \"Deleted\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + } + + /// + /// Tests group by. + /// + [Test] + public void TestGroupBy() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .GroupBy(u => u.c.UserName); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c GROUP BY c.\"UserName\"", cmd.CommandText()); + } + + /// + /// Tests order by. + /// + [Test] + public void TestOrderBy() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .OrderBy(u => u.c.UserName); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY c.\"UserName\" ASC", cmd.CommandText()); + } + + /// + /// Tests order by using string with number. + /// + [Test] + public void TestOrderByNumberedColumnStr() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .OrderBy(u => "1 DESC"); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY 1 DESC", cmd.CommandText()); + } + + /// + /// Tests order by using member with number. + /// + [Test] + public void TestOrderByNumberedColFn() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .OrderBy(u => u.Desc(1)); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY 1 DESC", cmd.CommandText()); + } + + /// + /// Tests order by using member with field. + /// + [Test] + public void TestOrderByAlt() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .OrderBy(u => u.Desc(u.c.UserName)); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c ORDER BY c.\"UserName\" DESC", cmd.CommandText()); + } + + /// + /// Tests sub query select. + /// + [Test] + public void TestSubQuerySelect() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u(cmd.SubQuery() + .From(x => x.dbo.AccessRights.As(x.a)) + .Where(x => x.a.User_Id == x.c.Id_User) + .Select(x => x.a.IsAdmin)).As(u.IsAdmin)); + + Assert.AreEqual("SELECT (SELECT a.\"IsAdmin\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"User_Id\" = c.\"Id_User\")) AS \"IsAdmin\" FROM \"dbo\".\"Users\" AS c", cmd.CommandText()); + } + + /// + /// Tests sub query where. + /// + [Test] + public void TestSubQueryWhere() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.IsAdmin == u(cmd.SubQuery() + .From(x => x.dbo.AccessRights.As(x.a)) + .Where(x => x.a.User_Id == x.c.Id_User) + .Select(x => x.a.IsAdmin))); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c WHERE (c.\"IsAdmin\" = (SELECT a.\"IsAdmin\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"User_Id\" = c.\"Id_User\")))", cmd.CommandText()); + } + + /// + /// Tests sub query in. + /// + [Test] + public void TestSubQueryWhereIn() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Where(u => u.c.Id_User.In(u(cmd.SubQuery() + .From(x => x.dbo.AccessRights.As(x.a)) + .Where(x => x.a.IsAdmin == 1) + .Select(x => x.a.User_Id)))); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE c.\"Id_User\" IN((SELECT a.\"User_Id\" FROM \"dbo\".\"AccessRights\" AS a WHERE (a.\"IsAdmin\" = [${0}])))", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + + /// + /// Tests sub query join. + /// + [Test] + public void TestSubQueryJoin() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Join(u => u.Inner()(cmd.SubQuery() + .From(x => x.dbo.AccessRights.As(x.a)) + .Select(x => x.a.IsAdmin, x => x.a.User_Id)).As(u.ar).On(u.ar.User_Id == u.c.Id_User)); + + Assert.AreEqual("SELECT * FROM \"dbo\".\"Users\" AS c INNER JOIN (SELECT a.\"IsAdmin\", a.\"User_Id\" FROM \"dbo\".\"AccessRights\" AS a) AS ar ON (ar.\"User_Id\" = c.\"Id_User\")", cmd.CommandText()); + } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Select/RenamedTypedAccessTests.cs b/DynamORM.Tests/Select/RenamedTypedAccessTests.cs index 4cb7a33..5cae57a 100644 --- a/DynamORM.Tests/Select/RenamedTypedAccessTests.cs +++ b/DynamORM.Tests/Select/RenamedTypedAccessTests.cs @@ -1,150 +1,150 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System.Collections.Generic; -using System.Linq; -using DynamORM.Tests.Helpers; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Select -{ - /// Test typed ORM. - [TestClass] - public class RenamedTypedAccessTests : TypedAccessTests - { - /// Test something fancy... like: select "first", count("first") aggregatefield from "users" group by "first" order by 2 desc;. - [TestMethod] - public override void TestTypedFancyAggregateQuery() - { - var v = (GetTestTable().Query(type: typeof(Users), columns: "first,first:AggregateField:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); - - Assert.IsNotNull(v); - Assert.AreEqual(187, v.Count()); - Assert.AreEqual(4, v.First().AggregateField); - Assert.AreEqual("Logan", v.First().First); - Assert.AreEqual(2, v.Take(10).Last().AggregateField); - Assert.AreEqual(1, v.Take(11).Last().AggregateField); - Assert.AreEqual(1, v.Last().AggregateField); - } - - /// Test something fancy... like: select "first", count("first") aggregatefield from "users" group by "first" order by 2 desc;. - [TestMethod] - public override void TestGenericFancyAggregateQuery() - { - var v = (GetTestTable().Query(columns: "first,first:AggregateField:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); - - Assert.IsNotNull(v); - Assert.AreEqual(187, v.Count()); - Assert.AreEqual(4, v.First().AggregateField); - Assert.AreEqual("Logan", v.First().First); - Assert.AreEqual(2, v.Take(10).Last().AggregateField); - Assert.AreEqual(1, v.Take(11).Last().AggregateField); - Assert.AreEqual(1, v.Last().AggregateField); - } - - /// Test typed First method. - [TestMethod] - public override void TestTypedFirst() - { - Assert.AreEqual(1, GetTestTable().First(type: typeof(Users), columns: "id").Id); - } - - /// Test typed Last method. - [TestMethod] - public override void TestTypedLast() - { - Assert.AreEqual(200, GetTestTable().Last(type: typeof(Users), columns: "id").Id); - } - - /// Test typed Single multi. - [TestMethod] - public override void TestTypedSingleObject() - { - var exp = new { id = 19, first = "Ori", last = "Ellis" }; - var o = GetTestTable().Single(type: typeof(Users), columns: "id,first,last", id: 19); - - Assert.AreEqual(exp.id, o.Id); - Assert.AreEqual(exp.first, o.First); - Assert.AreEqual(exp.last, o.Last); - } - - /// Test typed where expression equal. - [TestMethod] - public override void TestTypedWhereEq() - { - Assert.AreEqual("hoyt.tran", GetTestTable().Single(type: typeof(Users), where: new DynamicColumn("id").Eq(100)).Login); - } - - /// Test typed where expression like. - [TestMethod] - public override void TestTypedWhereLike() - { - Assert.AreEqual(100, GetTestTable().Single(type: typeof(Users), where: new DynamicColumn("login").Like("Hoyt.%")).Id); - } - - /// Test generic First method. - [TestMethod] - public override void TestGenericFirst() - { - Assert.AreEqual(1, GetTestTable().First(columns: "id").Id); - } - - /// Test generic Last method. - [TestMethod] - public override void TestGenericLast() - { - Assert.AreEqual(200, GetTestTable().Last(columns: "id").Id); - } - - /// Test generic Single multi. - [TestMethod] - public override void TestGenericSingleObject() - { - var exp = new { id = 19, first = "Ori", last = "Ellis" }; - var o = GetTestTable().Single(columns: "id,first,last", id: 19); - - Assert.AreEqual(exp.id, o.Id); - Assert.AreEqual(exp.first, o.First); - Assert.AreEqual(exp.last, o.Last); - } - - /// Test generic where expression equal. - [TestMethod] - public override void TestGenericWhereEq() - { - Assert.AreEqual("hoyt.tran", GetTestTable().Single(where: new DynamicColumn("id").Eq(100)).Login); - } - - /// Test generic where expression like. - [TestMethod] - public override void TestGenericWhereLike() - { - Assert.AreEqual(100, GetTestTable().Single(where: new DynamicColumn("login").Like("Hoyt.%")).Id); - } - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Collections.Generic; +using System.Linq; +using DynamORM.Tests.Helpers; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Test typed ORM. + [TestFixture] + public class RenamedTypedAccessTests : TypedAccessTests + { + /// Test something fancy... like: select "first", count("first") aggregatefield from "sample_users" group by "first" order by 2 desc;. + [Test] + public override void TestTypedFancyAggregateQuery() + { + var v = (GetTestTable().Query(type: typeof(Users), columns: "first,first:AggregateField:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().AggregateField); + Assert.AreEqual("Logan", v.First().First); + Assert.AreEqual(2, v.Take(10).Last().AggregateField); + Assert.AreEqual(1, v.Take(11).Last().AggregateField); + Assert.AreEqual(1, v.Last().AggregateField); + } + + /// Test something fancy... like: select "first", count("first") aggregatefield from "sample_users" group by "first" order by 2 desc;. + [Test] + public override void TestGenericFancyAggregateQuery() + { + var v = (GetTestTable().Query(columns: "first,first:AggregateField:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().AggregateField); + Assert.AreEqual("Logan", v.First().First); + Assert.AreEqual(2, v.Take(10).Last().AggregateField); + Assert.AreEqual(1, v.Take(11).Last().AggregateField); + Assert.AreEqual(1, v.Last().AggregateField); + } + + /// Test typed First method. + [Test] + public override void TestTypedFirst() + { + Assert.AreEqual(1, GetTestTable().First(type: typeof(Users), columns: "id").Id); + } + + /// Test typed Last method. + [Test] + public override void TestTypedLast() + { + Assert.AreEqual(200, GetTestTable().Last(type: typeof(Users), columns: "id").Id); + } + + /// Test typed Single multi. + [Test] + public override void TestTypedSingleObject() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestTable().Single(type: typeof(Users), columns: "id,first,last", id: 19); + + Assert.AreEqual(exp.id, o.Id); + Assert.AreEqual(exp.first, o.First); + Assert.AreEqual(exp.last, o.Last); + } + + /// Test typed where expression equal. + [Test] + public override void TestTypedWhereEq() + { + Assert.AreEqual("hoyt.tran", GetTestTable().Single(type: typeof(Users), where: new DynamicColumn("id").Eq(100)).Login); + } + + /// Test typed where expression like. + [Test] + public override void TestTypedWhereLike() + { + Assert.AreEqual(100, GetTestTable().Single(type: typeof(Users), where: new DynamicColumn("login").Like("Hoyt.%")).Id); + } + + /// Test generic First method. + [Test] + public override void TestGenericFirst() + { + Assert.AreEqual(1, GetTestTable().First(columns: "id").Id); + } + + /// Test generic Last method. + [Test] + public override void TestGenericLast() + { + Assert.AreEqual(200, GetTestTable().Last(columns: "id").Id); + } + + /// Test generic Single multi. + [Test] + public override void TestGenericSingleObject() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestTable().Single(columns: "id,first,last", id: 19); + + Assert.AreEqual(exp.id, o.Id); + Assert.AreEqual(exp.first, o.First); + Assert.AreEqual(exp.last, o.Last); + } + + /// Test generic where expression equal. + [Test] + public override void TestGenericWhereEq() + { + Assert.AreEqual("hoyt.tran", GetTestTable().Single(where: new DynamicColumn("id").Eq(100)).Login); + } + + /// Test generic where expression like. + [Test] + public override void TestGenericWhereLike() + { + Assert.AreEqual(100, GetTestTable().Single(where: new DynamicColumn("login").Like("Hoyt.%")).Id); + } + } } \ No newline at end of file diff --git a/DynamORM.Tests/Select/TypedAccessTests.cs b/DynamORM.Tests/Select/TypedAccessTests.cs index 99a0be5..89803e9 100644 --- a/DynamORM.Tests/Select/TypedAccessTests.cs +++ b/DynamORM.Tests/Select/TypedAccessTests.cs @@ -1,963 +1,963 @@ -/* - * DynamORM - Dynamic Object-Relational Mapping library. - * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using DynamORM.Builders; -using DynamORM.Tests.Helpers; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DynamORM.Tests.Select -{ - /// Test typed ORM. - /// Type to test. - [TestClass] - public class TypedAccessTests : TestsBase where T : class - { - /// Setup test parameters. - [TestInitialize] - public virtual void SetUp() - { - CreateTestDatabase(); - CreateDynamicDatabase(); - } - - /// Tear down test objects. - [TestCleanup] - public virtual void TearDown() - { - DestroyDynamicDatabase(); - DestroyTestDatabase(); - } - - /// Create table using specified method. - /// Dynamic table. - public virtual dynamic GetTestTable() - { - return Database.Table(); - } - - /// Create table using specified method. - /// Dynamic table. - public virtual IDynamicSelectQueryBuilder GetTestBuilder() - { - return Database.Table().Query() as IDynamicSelectQueryBuilder; - } - - #region Select typed - - /// Test load all rows into mapped list alternate way. - [TestMethod] - public virtual void TestTypedGetAll() - { - var list = (GetTestTable().Query(type: typeof(T)) as IEnumerable).Cast().ToList(); - - Assert.AreEqual(200, list.Count); - } - - /// Test load all rows into mapped list alternate way. - [TestMethod] - public virtual void TestTypedGetAll2() - { - var list = GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Execute() - .MapEnumerable() - .ToList(); - - Assert.AreEqual(200, list.Count); - } - - /// Test load all rows into mapped list alternate way. - [TestMethod] - public virtual void TestTypedGetAll3() - { - var list = GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Execute() - .ToList(); - - Assert.AreEqual(200, list.Count); - } - - /// Test unknown op. - [TestMethod] - public virtual void TestTypedUnknownOperation() - { - Assert.ThrowsException(() => GetTestTable().MakeMeASandwitch(type: typeof(T), with: "cheese")); - } - - /// Test typed Count method. - [TestMethod] - public virtual void TestTypedCount() - { - Assert.AreEqual(200, GetTestTable().Count(type: typeof(T), columns: "id")); - } - - /// Test typed Count method. - [TestMethod] - public virtual void TestTypedCount2() - { - Assert.AreEqual(200, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Select(x => x.Count(x.t.id)) - .ScalarAs()); - } - - /// Test count with in statement. - [TestMethod] - public virtual void TestTypedSelectInEnumerableCount() - { - Assert.AreEqual(4, GetTestTable().Count(type: typeof(T), last: new DynamicColumn - { - Operator = DynamicColumn.CompareOperator.In, - Value = new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3) - })); - } - - /// Test count with in statement. - [TestMethod] - public virtual void TestTypedSelectInEnumerableCount2() - { - Assert.AreEqual(4, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3))) - .Select(x => x.Count()) - .ScalarAs()); - } - - /// Test count with in statement. - [TestMethod] - public virtual void TestTypedSelectInArrayCount() - { - Assert.AreEqual(4, GetTestTable().Count(type: typeof(T), last: new DynamicColumn - { - Operator = DynamicColumn.CompareOperator.In, - Value = new object[] { "Hendricks", "Goodwin", "Freeman" } - })); - } - - /// Test count with in statement. - [TestMethod] - public virtual void TestTypedSelectInArrayCount2() - { - Assert.AreEqual(4, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" })) - .Select(x => x.Count()) - .ScalarAs()); - } - - /// Test typed First method. - [TestMethod] - public virtual void TestTypedFirst() - { - Assert.AreEqual(1, GetTestTable().First(type: typeof(T), columns: "id").id); - } - - /// Test typed First method. - [TestMethod] - public virtual void TestTypedFirst2() - { - Assert.AreEqual(1, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Select(x => x.t.id) - .Execute() - .First().id); - } - - /// Test typed Last method. - [TestMethod] - public virtual void TestTypedLast() - { - Assert.AreEqual(200, GetTestTable().Last(type: typeof(T), columns: "id").id); - } - - /// Test typed Last method. - [TestMethod] - public virtual void TestTypedLast2() - { - Assert.AreEqual(200, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Select(x => x.t.id) - .Execute() - .Last().id); - } - - /// Test typed Count method. - [TestMethod] - public virtual void TestTypedCountSpecificRecord() - { - Assert.AreEqual(1, GetTestTable().Count(type: typeof(T), first: "Ori")); - } - - /// Test typed Min method. - [TestMethod] - public virtual void TestTypedMin() - { - Assert.AreEqual(1, GetTestTable().Min(type: typeof(T), columns: "id")); - } - - /// Test typed Min method. - [TestMethod] - public virtual void TestTypedMin2() - { - Assert.AreEqual(1, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Select(x => x.Min(x.t.id)) - .ScalarAs()); - } - - /// Test typed Min method. - [TestMethod] - public virtual void TestTypedMax() - { - Assert.AreEqual(200, GetTestTable().Max(type: typeof(T), columns: "id")); - } - - /// Test typed Min method. - [TestMethod] - public virtual void TestTypedMax2() - { - Assert.AreEqual(200, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Select(x => x.Max(x.t.id)) - .ScalarAs()); - } - - /// Test typed Min method. - [TestMethod] - public virtual void TestTypedtAvg() - { - Assert.AreEqual(100.5, GetTestTable().Avg(type: typeof(T), columns: "id")); - } - - /// Test typed Min method. - [TestMethod] - public virtual void TestTypedtAvg2() - { - Assert.AreEqual(100.5, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Select(x => x.Avg(x.t.id)) - .Scalar()); - } - - /// Test typed Sum method. - [TestMethod] - public virtual void TestTypedSum() - { - Assert.AreEqual(20100, GetTestTable().Sum(type: typeof(T), columns: "id")); - } - - /// Test typed Sum method. - [TestMethod] - public virtual void TestTypedSum2() - { - Assert.AreEqual(20100, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Select(x => x.Sum(x.t.id)) - .ScalarAs()); - } - - /// Test typed Scalar method for invalid operation exception. - [TestMethod] - public virtual void TestTypedScalarException() - { - Assert.ThrowsException(() => GetTestTable().Scalar(type: typeof(T), id: 19)); - } - - /// Test typed Scalar method. - [TestMethod] - public virtual void TestTypedScalar() - { - Assert.AreEqual("Ori", GetTestTable().Scalar(type: typeof(T), columns: "first", id: 19)); - } - - /// Test typed Scalar method. - [TestMethod] - public void TestTypedScalar2() - { - Assert.AreEqual("Ori", GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id == 19) - .Select(x => x.t.first) - .Scalar()); - } - - /// Test typed Scalar method with SQLite specific aggregate. - [TestMethod] - public virtual void TestTypedScalarGroupConcat() - { - // This test should produce something like this: - // select group_concat("first") AS first from "users" where "id" < 20; - Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", - GetTestTable().Scalar(type: typeof(T), columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); - } - - /// Test typed Scalar method with SQLite specific aggregate. - [TestMethod] - public virtual void TestTypedScalarGroupConcat2() - { - // This test should produce something like this: - // select group_concat("first") AS first from "users" where "id" < 20; - Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", - GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id < 20) - .Select(x => x.group_concat(x.t.first).As(x.first)) - .Scalar()); - } - - /// Test typed Scalar method with SQLite specific aggregate not using aggregate field. - [TestMethod] - public virtual void TestTypedScalarGroupConcatNoAggregateField() - { - // This test should produce something like this: - // select group_concat(first) AS first from "users" where "id" < 20; - Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", - GetTestTable().Scalar(type: typeof(T), columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); - } - - /// Test typed Scalar method with SQLite specific aggregate not using aggregate field. - [TestMethod] - public virtual void TestTypedScalarGroupConcatNoAggregateField2() - { - // This test should produce something like this: - // select group_concat("first") AS first from "users" where "id" < 20; - Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", - GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id < 20) - .SelectColumn("group_concat(first):first") - .Scalar()); - } - - /// Test something fancy... like: select "first", count("first") aggregatefield from "users" group by "first" order by 2 desc;. - [TestMethod] - public virtual void TestTypedFancyAggregateQuery() - { - var v = (GetTestTable().Query(type: typeof(T), columns: "first,first:aggregatefield:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); - - Assert.IsNotNull(v); - Assert.AreEqual(187, v.Count()); - Assert.AreEqual(4, v.First().aggregatefield); - Assert.AreEqual("Logan", v.First().first); - Assert.AreEqual(2, v.Take(10).Last().aggregatefield); - Assert.AreEqual(1, v.Take(11).Last().aggregatefield); - Assert.AreEqual(1, v.Last().aggregatefield); - } - - /// Test something fancy... like: select "first", count("first") aggregatefield from "users" group by "first" order by 2 desc;. - [TestMethod] - public virtual void TestTypedFancyAggregateQuery2() - { - var v = GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Select(x => x.t.first, x => x.Count(x.t.first).As(x.aggregatefield)) - .GroupBy(x => x.t.first) - .OrderBy(x => x.Desc(2)) - .Execute() - .ToList(); - - Assert.IsNotNull(v); - Assert.AreEqual(187, v.Count()); - Assert.AreEqual(4, v.First().aggregatefield); - Assert.AreEqual("Logan", v.First().first); - Assert.AreEqual(2, v.Take(10).Last().aggregatefield); - Assert.AreEqual(1, v.Take(11).Last().aggregatefield); - Assert.AreEqual(1, v.Last().aggregatefield); - } - - /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. - [TestMethod] - public virtual void TestTypedAggregateInAggregate() - { - Assert.AreEqual(12.77, GetTestTable().Scalar(type: typeof(T), columns: @"length(""login""):len:avg")); - } - - /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. - [TestMethod] - public virtual void TestTypedAggregateInAggregate2() - { - Assert.AreEqual(12.77, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Select(x => x.Avg(x.Length(x.t.login)).As(x.len)) - .Scalar()); - } - - /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. - [TestMethod] - public virtual void TestTypedAggregateInAggregateMark2() - { - Assert.AreEqual(27.7, GetTestTable().Avg(type: typeof(T), columns: @"length(""email""):len")); - } - - /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. - [TestMethod] - public virtual void TestTypedAggregateInAggregateMark3() - { - Assert.AreEqual(27.7, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Select(x => "AVG(LENGTH(t.email)) AS LEN") - .Scalar()); - } - - /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. - public virtual void TestTypedFunctionInWhere() - { - Assert.AreEqual(97, - GetTestTable().Count(type: typeof(T), condition1: - new DynamicColumn() - { - ColumnName = "email", - Aggregate = "length", - Operator = DynamicColumn.CompareOperator.Gt, - Value = 27 - })); - } - - /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. - public virtual void TestTypedFunctionInWhere2() - { - Assert.AreEqual(97, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.Length(x.t.email) > 27) - .Select(x => x.Count(x.t.All())) - .Scalar()); - } - - /// Test typed Single multi. - [TestMethod] - public virtual void TestTypedSingleObject() - { - var exp = new { id = 19, first = "Ori", last = "Ellis" }; - var o = GetTestTable().Single(type: typeof(T), columns: "id,first,last", id: 19); - - Assert.AreEqual(exp.id, o.id); - Assert.AreEqual(exp.first, o.first); - Assert.AreEqual(exp.last, o.last); - } - - /// Test typed Single multi. - [TestMethod] - public virtual void TestTypedSingleObject2() - { - var exp = new { id = 19, first = "Ori", last = "Ellis" }; - var o = GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id == 19) - .Select(x => new { id = x.t.id, first = x.t.first, last = x.t.last }) - .Execute() - .First(); - - Assert.AreEqual(exp.id, o.id); - Assert.AreEqual(exp.first, o.first); - Assert.AreEqual(exp.last, o.last); - } - - #endregion Select typed - - #region Where typed - - /// Test typed where expression equal. - [TestMethod] - public virtual void TestTypedWhereEq() - { - Assert.AreEqual("hoyt.tran", GetTestTable().Single(type: typeof(T), where: new DynamicColumn("id").Eq(100)).login); - } - - /// Test typed where expression equal. - [TestMethod] - public virtual void TestTypedWhereEq2() - { - Assert.AreEqual("hoyt.tran", GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id == 100).Execute().First().login); - } - - /// Test typed where expression not equal. - [TestMethod] - public virtual void TestTypedWhereNot() - { - Assert.AreEqual(199, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Not(100))); - } - - /// Test typed where expression not equal. - [TestMethod] - public virtual void TestTypedWhereNot2() - { - Assert.AreEqual(199, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id != 100) - .Select(x => x.Count()) - .ScalarAs()); - } - - /// Test typed where expression like. - [TestMethod] - public virtual void TestTypedWhereLike() - { - Assert.AreEqual(100, GetTestTable().Single(type: typeof(T), where: new DynamicColumn("login").Like("Hoyt.%")).id); - } - - /// Test typed where expression like. - [TestMethod] - public virtual void TestTypedWhereLike2() - { - Assert.AreEqual(100, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.login.Like("Hoyt.%")).Execute().First().id); - } - - /// Test typed where expression not like. - [TestMethod] - public virtual void TestTypedWhereNotLike() - { - Assert.AreEqual(199, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("login").NotLike("Hoyt.%"))); - } - - /// Test typed where expression not like. - [TestMethod] - public virtual void TestTypedWhereNotLike2() - { - Assert.AreEqual(199, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.login.NotLike("Hoyt.%")) - .Select(x => x.Count()) - .ScalarAs()); - } - - /// Test typed where expression not like. - [TestMethod] - public virtual void TestTypedWhereNotLike3() - { - Assert.AreEqual(199, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => !x.t.login.Like("Hoyt.%")) - .Select(x => x.Count()) - .ScalarAs()); - } - - /// Test typed where expression greater. - [TestMethod] - public virtual void TestTypedWhereGt() - { - Assert.AreEqual(100, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Greater(100))); - } - - /// Test typed where expression greater. - [TestMethod] - public virtual void TestTypedWhereGt2() - { - Assert.AreEqual(100, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id > 100) - .Select(x => x.Count()) - .ScalarAs()); - } - - /// Test typed where expression greater or equal. - [TestMethod] - public virtual void TestTypedWhereGte() - { - Assert.AreEqual(101, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").GreaterOrEqual(100))); - } - - /// Test typed where expression greater or equal. - [TestMethod] - public virtual void TestTypedWhereGte2() - { - Assert.AreEqual(101, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id >= 100) - .Select(x => x.Count()) - .ScalarAs()); - } - - /// Test typed where expression less. - [TestMethod] - public virtual void TestTypedWhereLt() - { - Assert.AreEqual(99, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Less(100))); - } - - /// Test typed where expression less. - [TestMethod] - public virtual void TestTypedWhereLt2() - { - Assert.AreEqual(99, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id < 100) - .Select(x => x.Count()) - .ScalarAs()); - } - - /// Test typed where expression less or equal. - [TestMethod] - public virtual void TestTypedWhereLte() - { - Assert.AreEqual(100, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").LessOrEqual(100))); - } - - /// Test typed where expression less or equal. - [TestMethod] - public virtual void TestTypedWhereLte2() - { - Assert.AreEqual(100, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id <= 100) - .Select(x => x.Count()) - .ScalarAs()); - } - - /// Test typed where expression between. - [TestMethod] - public virtual void TestTypedWhereBetween() - { - Assert.AreEqual(26, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Between(75, 100))); - } - - /// Test typed where expression between. - [TestMethod] - public virtual void TestTypedWhereBetween2() - { - Assert.AreEqual(26, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id.Between(75, 100)) - .Select(x => x.Count()) - .ScalarAs()); - } - - /// Test typed where expression in parameters. - [TestMethod] - public virtual void TestTypedWhereIn1() - { - Assert.AreEqual(3, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").In(75, 99, 100))); - } - - /// Test typed where expression in array. - [TestMethod] - public virtual void TestTypedWhereIn2() - { - Assert.AreEqual(3, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").In(new[] { 75, 99, 100 }))); - } - - /// Test typed where expression in parameters. - [TestMethod] - public virtual void TestTypedWhereIn3() - { - Assert.AreEqual(3, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id.In(75, 99, 100)) - .Select(x => x.Count()) - .ScalarAs()); - } - - /// Test typed where expression in array. - [TestMethod] - public virtual void TestTypedWhereIn4() - { - Assert.AreEqual(3, GetTestBuilder() - .From(x => x(typeof(T)).As(x.t)) - .Where(x => x.t.id.In(new[] { 75, 99, 100 })) - .Select(x => x.Count()) - .ScalarAs()); - } - - #endregion Where typed - - #region Select generic - - /// Test load all rows into mapped list alternate way. - [TestMethod] - public virtual void TestGenericGetAll() - { - var list = (GetTestTable().Query() as IEnumerable).Cast().ToList(); - - Assert.AreEqual(200, list.Count); - } - - /// Test unknown op. - [TestMethod] - public virtual void TestGenericUnknownOperation() - { - Assert.ThrowsException(() => GetTestTable().MakeMeASandwitch(with: "cheese")); - } - - /// Test generic Count method. - [TestMethod] - public virtual void TestGenericCount() - { - Assert.AreEqual(200, GetTestTable().Count(columns: "id")); - } - - /// Test count with in statement. - [TestMethod] - public virtual void TestGenericSelectInEnumerableCount() - { - Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn - { - Operator = DynamicColumn.CompareOperator.In, - Value = new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3) - })); - } - - /// Test count with in statement. - [TestMethod] - public virtual void TestGenericSelectInArrayCount() - { - Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn - { - Operator = DynamicColumn.CompareOperator.In, - Value = new object[] { "Hendricks", "Goodwin", "Freeman" } - })); - } - - /// Test generic First method. - [TestMethod] - public virtual void TestGenericFirst() - { - Assert.AreEqual(1, GetTestTable().First(columns: "id").id); - } - - /// Test generic Last method. - [TestMethod] - public virtual void TestGenericLast() - { - Assert.AreEqual(200, GetTestTable().Last(columns: "id").id); - } - - /// Test generic Count method. - [TestMethod] - public virtual void TestGenericCountSpecificRecord() - { - Assert.AreEqual(1, GetTestTable().Count(first: "Ori")); - } - - /// Test generic Min method. - [TestMethod] - public virtual void TestGenericMin() - { - Assert.AreEqual(1, GetTestTable().Min(columns: "id")); - } - - /// Test generic Min method. - [TestMethod] - public virtual void TestGenericMax() - { - Assert.AreEqual(200, GetTestTable().Max(columns: "id")); - } - - /// Test generic Min method. - [TestMethod] - public virtual void TestGenerictAvg() - { - Assert.AreEqual(100.5, GetTestTable().Avg(columns: "id")); - } - - /// Test generic Sum method. - [TestMethod] - public virtual void TestGenericSum() - { - Assert.AreEqual(20100, GetTestTable().Sum(columns: "id")); - } - - /// Test generic Scalar method for invalid operation exception. - [TestMethod] - public virtual void TestGenericScalarException() - { - Assert.ThrowsException(() => GetTestTable().Scalar(id: 19)); - } - - /// Test generic Scalar method. - [TestMethod] - public virtual void TestGenericScalar() - { - Assert.AreEqual("Ori", GetTestTable().Scalar(columns: "first", id: 19)); - } - - /// Test generic Scalar method with SQLite specific aggregate. - [TestMethod] - public virtual void TestGenericScalarGroupConcat() - { - // This test should produce something like this: - // select group_concat("first") AS first from "users" where "id" < 20; - Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", - GetTestTable().Scalar(columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); - } - - /// Test generic Scalar method with SQLite specific aggregate not using aggregate field. - [TestMethod] - public virtual void TestGenericScalarGroupConcatNoAggregateField() - { - // This test should produce something like this: - // select group_concat(first) AS first from "users" where "id" < 20; - Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", - GetTestTable().Scalar(columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); - } - - /// Test something fancy... like: select "first", count("first") aggregatefield from "users" group by "first" order by 2 desc;. - [TestMethod] - public virtual void TestGenericFancyAggregateQuery() - { - var v = (GetTestTable().Query(columns: "first,first:aggregatefield:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); - - Assert.IsNotNull(v); - Assert.AreEqual(187, v.Count()); - Assert.AreEqual(4, v.First().aggregatefield); - Assert.AreEqual("Logan", v.First().first); - Assert.AreEqual(2, v.Take(10).Last().aggregatefield); - Assert.AreEqual(1, v.Take(11).Last().aggregatefield); - Assert.AreEqual(1, v.Last().aggregatefield); - } - - /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "users";. - [TestMethod] - public virtual void TestGenericAggregateInAggregate() - { - Assert.AreEqual(12.77, GetTestTable().Scalar(columns: @"length(""login""):len:avg")); - } - - /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "users";. - [TestMethod] - public virtual void TestGenericAggregateInAggregateMark2() - { - Assert.AreEqual(27.7, GetTestTable().Avg(columns: @"length(""email""):len")); - } - - /// Test emails longer than 27 chars. select count(*) from "users" where length("email") > 27;. - public virtual void TestGenericFunctionInWhere() - { - Assert.AreEqual(97, - GetTestTable().Count(condition1: - new DynamicColumn() - { - ColumnName = "email", - Aggregate = "length", - Operator = DynamicColumn.CompareOperator.Gt, - Value = 27 - })); - } - - /// Test generic Single multi. - [TestMethod] - public virtual void TestGenericSingleObject() - { - var exp = new { id = 19, first = "Ori", last = "Ellis" }; - var o = GetTestTable().Single(columns: "id,first,last", id: 19); - - Assert.AreEqual(exp.id, o.id); - Assert.AreEqual(exp.first, o.first); - Assert.AreEqual(exp.last, o.last); - } - - #endregion Select generic - - #region Where generic - - /// Test generic where expression equal. - [TestMethod] - public virtual void TestGenericWhereEq() - { - Assert.AreEqual("hoyt.tran", GetTestTable().Single(where: new DynamicColumn("id").Eq(100)).login); - } - - /// Test generic where expression not equal. - [TestMethod] - public virtual void TestGenericWhereNot() - { - Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("id").Not(100))); - } - - /// Test generic where expression like. - [TestMethod] - public virtual void TestGenericWhereLike() - { - Assert.AreEqual(100, GetTestTable().Single(where: new DynamicColumn("login").Like("Hoyt.%")).id); - } - - /// Test generic where expression not like. - [TestMethod] - public virtual void TestGenericWhereNotLike() - { - Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("login").NotLike("Hoyt.%"))); - } - - /// Test generic where expression greater. - [TestMethod] - public virtual void TestGenericWhereGt() - { - Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").Greater(100))); - } - - /// Test generic where expression greater or equal. - [TestMethod] - public virtual void TestGenericWhereGte() - { - Assert.AreEqual(101, GetTestTable().Count(where: new DynamicColumn("id").GreaterOrEqual(100))); - } - - /// Test generic where expression less. - [TestMethod] - public virtual void TestGenericWhereLt() - { - Assert.AreEqual(99, GetTestTable().Count(where: new DynamicColumn("id").Less(100))); - } - - /// Test generic where expression less or equal. - [TestMethod] - public virtual void TestGenericWhereLte() - { - Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").LessOrEqual(100))); - } - - /// Test generic where expression between. - [TestMethod] - public virtual void TestGenericWhereBetween() - { - Assert.AreEqual(26, GetTestTable().Count(where: new DynamicColumn("id").Between(75, 100))); - } - - /// Test generic where expression in parameters. - [TestMethod] - public virtual void TestGenericWhereIn1() - { - Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(75, 99, 100))); - } - - /// Test generic where expression in array. - [TestMethod] - public virtual void TestGenericWhereIn2() - { - Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(new[] { 75, 99, 100 }))); - } - - #endregion Where generic - } +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using DynamORM.Builders; +using DynamORM.Tests.Helpers; +using NUnit.Framework; + +namespace DynamORM.Tests.Select +{ + /// Test typed ORM. + /// Type to test. + [TestFixture] + public class TypedAccessTests : TestsBase where T : class + { + /// Setup test parameters. + [SetUp] + public virtual void SetUp() + { + CreateTestDatabase(); + CreateDynamicDatabase(); + } + + /// Tear down test objects. + [TearDown] + public virtual void TearDown() + { + DestroyDynamicDatabase(); + DestroyTestDatabase(); + } + + /// Create table using specified method. + /// Dynamic table. + public virtual dynamic GetTestTable() + { + return Database.Table(); + } + + /// Create table using specified method. + /// Dynamic table. + public virtual IDynamicSelectQueryBuilder GetTestBuilder() + { + return Database.Table().Query() as IDynamicSelectQueryBuilder; + } + + #region Select typed + + /// Test load all rows into mapped list alternate way. + [Test] + public virtual void TestTypedGetAll() + { + var list = (GetTestTable().Query(type: typeof(T)) as IEnumerable).Cast().ToList(); + + Assert.AreEqual(200, list.Count); + } + + /// Test load all rows into mapped list alternate way. + [Test] + public virtual void TestTypedGetAll2() + { + var list = GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Execute() + .MapEnumerable() + .ToList(); + + Assert.AreEqual(200, list.Count); + } + + /// Test load all rows into mapped list alternate way. + [Test] + public virtual void TestTypedGetAll3() + { + var list = GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Execute() + .ToList(); + + Assert.AreEqual(200, list.Count); + } + + /// Test unknown op. + [Test] + public virtual void TestTypedUnknownOperation() + { + Assert.Throws(() => GetTestTable().MakeMeASandwitch(type: typeof(T), with: "cheese")); + } + + /// Test typed Count method. + [Test] + public virtual void TestTypedCount() + { + Assert.AreEqual(200, GetTestTable().Count(type: typeof(T), columns: "id")); + } + + /// Test typed Count method. + [Test] + public virtual void TestTypedCount2() + { + Assert.AreEqual(200, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Count(x.t.id)) + .ScalarAs()); + } + + /// Test count with in statement. + [Test] + public virtual void TestTypedSelectInEnumerableCount() + { + Assert.AreEqual(4, GetTestTable().Count(type: typeof(T), last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3) + })); + } + + /// Test count with in statement. + [Test] + public virtual void TestTypedSelectInEnumerableCount2() + { + Assert.AreEqual(4, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3))) + .Select(x => x.Count()) + .ScalarAs()); + } + + /// Test count with in statement. + [Test] + public virtual void TestTypedSelectInArrayCount() + { + Assert.AreEqual(4, GetTestTable().Count(type: typeof(T), last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" } + })); + } + + /// Test count with in statement. + [Test] + public virtual void TestTypedSelectInArrayCount2() + { + Assert.AreEqual(4, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.last.In(new object[] { "Hendricks", "Goodwin", "Freeman" })) + .Select(x => x.Count()) + .ScalarAs()); + } + + /// Test typed First method. + [Test] + public virtual void TestTypedFirst() + { + Assert.AreEqual(1, GetTestTable().First(type: typeof(T), columns: "id").id); + } + + /// Test typed First method. + [Test] + public virtual void TestTypedFirst2() + { + Assert.AreEqual(1, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.t.id) + .Execute() + .First().id); + } + + /// Test typed Last method. + [Test] + public virtual void TestTypedLast() + { + Assert.AreEqual(200, GetTestTable().Last(type: typeof(T), columns: "id").id); + } + + /// Test typed Last method. + [Test] + public virtual void TestTypedLast2() + { + Assert.AreEqual(200, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.t.id) + .Execute() + .Last().id); + } + + /// Test typed Count method. + [Test] + public virtual void TestTypedCountSpecificRecord() + { + Assert.AreEqual(1, GetTestTable().Count(type: typeof(T), first: "Ori")); + } + + /// Test typed Min method. + [Test] + public virtual void TestTypedMin() + { + Assert.AreEqual(1, GetTestTable().Min(type: typeof(T), columns: "id")); + } + + /// Test typed Min method. + [Test] + public virtual void TestTypedMin2() + { + Assert.AreEqual(1, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Min(x.t.id)) + .ScalarAs()); + } + + /// Test typed Min method. + [Test] + public virtual void TestTypedMax() + { + Assert.AreEqual(200, GetTestTable().Max(type: typeof(T), columns: "id")); + } + + /// Test typed Min method. + [Test] + public virtual void TestTypedMax2() + { + Assert.AreEqual(200, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Max(x.t.id)) + .ScalarAs()); + } + + /// Test typed Min method. + [Test] + public virtual void TestTypedtAvg() + { + Assert.AreEqual(100.5, GetTestTable().Avg(type: typeof(T), columns: "id")); + } + + /// Test typed Min method. + [Test] + public virtual void TestTypedtAvg2() + { + Assert.AreEqual(100.5, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Avg(x.t.id)) + .Scalar()); + } + + /// Test typed Sum method. + [Test] + public virtual void TestTypedSum() + { + Assert.AreEqual(20100, GetTestTable().Sum(type: typeof(T), columns: "id")); + } + + /// Test typed Sum method. + [Test] + public virtual void TestTypedSum2() + { + Assert.AreEqual(20100, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Sum(x.t.id)) + .ScalarAs()); + } + + /// Test typed Scalar method for invalid operation exception. + [Test] + public virtual void TestTypedScalarException() + { + Assert.Throws(() => GetTestTable().Scalar(type: typeof(T), id: 19)); + } + + /// Test typed Scalar method. + [Test] + public virtual void TestTypedScalar() + { + Assert.AreEqual("Ori", GetTestTable().Scalar(type: typeof(T), columns: "first", id: 19)); + } + + /// Test typed Scalar method. + [Test] + public void TestTypedScalar2() + { + Assert.AreEqual("Ori", GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id == 19) + .Select(x => x.t.first) + .Scalar()); + } + + /// Test typed Scalar method with SQLite specific aggregate. + [Test] + public virtual void TestTypedScalarGroupConcat() + { + // This test should produce something like this: + // select group_concat("first") AS first from "sample_users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(type: typeof(T), columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test typed Scalar method with SQLite specific aggregate. + [Test] + public virtual void TestTypedScalarGroupConcat2() + { + // This test should produce something like this: + // select group_concat("first") AS first from "sample_users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id < 20) + .Select(x => x.group_concat(x.t.first).As(x.first)) + .Scalar()); + } + + /// Test typed Scalar method with SQLite specific aggregate not using aggregate field. + [Test] + public virtual void TestTypedScalarGroupConcatNoAggregateField() + { + // This test should produce something like this: + // select group_concat(first) AS first from "sample_users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(type: typeof(T), columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test typed Scalar method with SQLite specific aggregate not using aggregate field. + [Test] + public virtual void TestTypedScalarGroupConcatNoAggregateField2() + { + // This test should produce something like this: + // select group_concat("first") AS first from "sample_users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id < 20) + .SelectColumn("group_concat(first):first") + .Scalar()); + } + + /// Test something fancy... like: select "first", count("first") aggregatefield from "sample_users" group by "first" order by 2 desc;. + [Test] + public virtual void TestTypedFancyAggregateQuery() + { + var v = (GetTestTable().Query(type: typeof(T), columns: "first,first:aggregatefield:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().aggregatefield); + Assert.AreEqual("Logan", v.First().first); + Assert.AreEqual(2, v.Take(10).Last().aggregatefield); + Assert.AreEqual(1, v.Take(11).Last().aggregatefield); + Assert.AreEqual(1, v.Last().aggregatefield); + } + + /// Test something fancy... like: select "first", count("first") aggregatefield from "sample_users" group by "first" order by 2 desc;. + [Test] + public virtual void TestTypedFancyAggregateQuery2() + { + var v = GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.t.first, x => x.Count(x.t.first).As(x.aggregatefield)) + .GroupBy(x => x.t.first) + .OrderBy(x => x.Desc(2)) + .Execute() + .ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().aggregatefield); + Assert.AreEqual("Logan", v.First().first); + Assert.AreEqual(2, v.Take(10).Last().aggregatefield); + Assert.AreEqual(1, v.Take(11).Last().aggregatefield); + Assert.AreEqual(1, v.Last().aggregatefield); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "sample_users";. + [Test] + public virtual void TestTypedAggregateInAggregate() + { + Assert.AreEqual(12.77, GetTestTable().Scalar(type: typeof(T), columns: @"length(""login""):len:avg")); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "sample_users";. + [Test] + public virtual void TestTypedAggregateInAggregate2() + { + Assert.AreEqual(12.77, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => x.Avg(x.Length(x.t.login)).As(x.len)) + .Scalar()); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "sample_users";. + [Test] + public virtual void TestTypedAggregateInAggregateMark2() + { + Assert.AreEqual(27.7, GetTestTable().Avg(type: typeof(T), columns: @"length(""email""):len")); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "sample_users";. + [Test] + public virtual void TestTypedAggregateInAggregateMark3() + { + Assert.AreEqual(27.7, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Select(x => "AVG(LENGTH(t.email)) AS LEN") + .Scalar()); + } + + /// Test emails longer than 27 chars. select count(*) from "sample_users" where length("email") > 27;. + public virtual void TestTypedFunctionInWhere() + { + Assert.AreEqual(97, + GetTestTable().Count(type: typeof(T), condition1: + new DynamicColumn() + { + ColumnName = "email", + Aggregate = "length", + Operator = DynamicColumn.CompareOperator.Gt, + Value = 27 + })); + } + + /// Test emails longer than 27 chars. select count(*) from "sample_users" where length("email") > 27;. + public virtual void TestTypedFunctionInWhere2() + { + Assert.AreEqual(97, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.Length(x.t.email) > 27) + .Select(x => x.Count(x.t.All())) + .Scalar()); + } + + /// Test typed Single multi. + [Test] + public virtual void TestTypedSingleObject() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestTable().Single(type: typeof(T), columns: "id,first,last", id: 19); + + Assert.AreEqual(exp.id, o.id); + Assert.AreEqual(exp.first, o.first); + Assert.AreEqual(exp.last, o.last); + } + + /// Test typed Single multi. + [Test] + public virtual void TestTypedSingleObject2() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id == 19) + .Select(x => new { id = x.t.id, first = x.t.first, last = x.t.last }) + .Execute() + .First(); + + Assert.AreEqual(exp.id, o.id); + Assert.AreEqual(exp.first, o.first); + Assert.AreEqual(exp.last, o.last); + } + + #endregion Select typed + + #region Where typed + + /// Test typed where expression equal. + [Test] + public virtual void TestTypedWhereEq() + { + Assert.AreEqual("hoyt.tran", GetTestTable().Single(type: typeof(T), where: new DynamicColumn("id").Eq(100)).login); + } + + /// Test typed where expression equal. + [Test] + public virtual void TestTypedWhereEq2() + { + Assert.AreEqual("hoyt.tran", GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id == 100).Execute().First().login); + } + + /// Test typed where expression not equal. + [Test] + public virtual void TestTypedWhereNot() + { + Assert.AreEqual(199, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Not(100))); + } + + /// Test typed where expression not equal. + [Test] + public virtual void TestTypedWhereNot2() + { + Assert.AreEqual(199, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id != 100) + .Select(x => x.Count()) + .ScalarAs()); + } + + /// Test typed where expression like. + [Test] + public virtual void TestTypedWhereLike() + { + Assert.AreEqual(100, GetTestTable().Single(type: typeof(T), where: new DynamicColumn("login").Like("Hoyt.%")).id); + } + + /// Test typed where expression like. + [Test] + public virtual void TestTypedWhereLike2() + { + Assert.AreEqual(100, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.login.Like("Hoyt.%")).Execute().First().id); + } + + /// Test typed where expression not like. + [Test] + public virtual void TestTypedWhereNotLike() + { + Assert.AreEqual(199, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("login").NotLike("Hoyt.%"))); + } + + /// Test typed where expression not like. + [Test] + public virtual void TestTypedWhereNotLike2() + { + Assert.AreEqual(199, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.login.NotLike("Hoyt.%")) + .Select(x => x.Count()) + .ScalarAs()); + } + + /// Test typed where expression not like. + [Test] + public virtual void TestTypedWhereNotLike3() + { + Assert.AreEqual(199, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => !x.t.login.Like("Hoyt.%")) + .Select(x => x.Count()) + .ScalarAs()); + } + + /// Test typed where expression greater. + [Test] + public virtual void TestTypedWhereGt() + { + Assert.AreEqual(100, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Greater(100))); + } + + /// Test typed where expression greater. + [Test] + public virtual void TestTypedWhereGt2() + { + Assert.AreEqual(100, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id > 100) + .Select(x => x.Count()) + .ScalarAs()); + } + + /// Test typed where expression greater or equal. + [Test] + public virtual void TestTypedWhereGte() + { + Assert.AreEqual(101, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").GreaterOrEqual(100))); + } + + /// Test typed where expression greater or equal. + [Test] + public virtual void TestTypedWhereGte2() + { + Assert.AreEqual(101, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id >= 100) + .Select(x => x.Count()) + .ScalarAs()); + } + + /// Test typed where expression less. + [Test] + public virtual void TestTypedWhereLt() + { + Assert.AreEqual(99, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Less(100))); + } + + /// Test typed where expression less. + [Test] + public virtual void TestTypedWhereLt2() + { + Assert.AreEqual(99, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id < 100) + .Select(x => x.Count()) + .ScalarAs()); + } + + /// Test typed where expression less or equal. + [Test] + public virtual void TestTypedWhereLte() + { + Assert.AreEqual(100, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").LessOrEqual(100))); + } + + /// Test typed where expression less or equal. + [Test] + public virtual void TestTypedWhereLte2() + { + Assert.AreEqual(100, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id <= 100) + .Select(x => x.Count()) + .ScalarAs()); + } + + /// Test typed where expression between. + [Test] + public virtual void TestTypedWhereBetween() + { + Assert.AreEqual(26, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").Between(75, 100))); + } + + /// Test typed where expression between. + [Test] + public virtual void TestTypedWhereBetween2() + { + Assert.AreEqual(26, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id.Between(75, 100)) + .Select(x => x.Count()) + .ScalarAs()); + } + + /// Test typed where expression in parameters. + [Test] + public virtual void TestTypedWhereIn1() + { + Assert.AreEqual(3, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").In(75, 99, 100))); + } + + /// Test typed where expression in array. + [Test] + public virtual void TestTypedWhereIn2() + { + Assert.AreEqual(3, GetTestTable().Count(type: typeof(T), where: new DynamicColumn("id").In(new[] { 75, 99, 100 }))); + } + + /// Test typed where expression in parameters. + [Test] + public virtual void TestTypedWhereIn3() + { + Assert.AreEqual(3, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id.In(75, 99, 100)) + .Select(x => x.Count()) + .ScalarAs()); + } + + /// Test typed where expression in array. + [Test] + public virtual void TestTypedWhereIn4() + { + Assert.AreEqual(3, GetTestBuilder() + .From(x => x(typeof(T)).As(x.t)) + .Where(x => x.t.id.In(new[] { 75, 99, 100 })) + .Select(x => x.Count()) + .ScalarAs()); + } + + #endregion Where typed + + #region Select generic + + /// Test load all rows into mapped list alternate way. + [Test] + public virtual void TestGenericGetAll() + { + var list = (GetTestTable().Query() as IEnumerable).Cast().ToList(); + + Assert.AreEqual(200, list.Count); + } + + /// Test unknown op. + [Test] + public virtual void TestGenericUnknownOperation() + { + Assert.Throws(() => GetTestTable().MakeMeASandwitch(with: "cheese")); + } + + /// Test generic Count method. + [Test] + public virtual void TestGenericCount() + { + Assert.AreEqual(200, GetTestTable().Count(columns: "id")); + } + + /// Test count with in statement. + [Test] + public virtual void TestGenericSelectInEnumerableCount() + { + Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3) + })); + } + + /// Test count with in statement. + [Test] + public virtual void TestGenericSelectInArrayCount() + { + Assert.AreEqual(4, GetTestTable().Count(last: new DynamicColumn + { + Operator = DynamicColumn.CompareOperator.In, + Value = new object[] { "Hendricks", "Goodwin", "Freeman" } + })); + } + + /// Test generic First method. + [Test] + public virtual void TestGenericFirst() + { + Assert.AreEqual(1, GetTestTable().First(columns: "id").id); + } + + /// Test generic Last method. + [Test] + public virtual void TestGenericLast() + { + Assert.AreEqual(200, GetTestTable().Last(columns: "id").id); + } + + /// Test generic Count method. + [Test] + public virtual void TestGenericCountSpecificRecord() + { + Assert.AreEqual(1, GetTestTable().Count(first: "Ori")); + } + + /// Test generic Min method. + [Test] + public virtual void TestGenericMin() + { + Assert.AreEqual(1, GetTestTable().Min(columns: "id")); + } + + /// Test generic Min method. + [Test] + public virtual void TestGenericMax() + { + Assert.AreEqual(200, GetTestTable().Max(columns: "id")); + } + + /// Test generic Min method. + [Test] + public virtual void TestGenerictAvg() + { + Assert.AreEqual(100.5, GetTestTable().Avg(columns: "id")); + } + + /// Test generic Sum method. + [Test] + public virtual void TestGenericSum() + { + Assert.AreEqual(20100, GetTestTable().Sum(columns: "id")); + } + + /// Test generic Scalar method for invalid operation exception. + [Test] + public virtual void TestGenericScalarException() + { + Assert.Throws(() => GetTestTable().Scalar(id: 19)); + } + + /// Test generic Scalar method. + [Test] + public virtual void TestGenericScalar() + { + Assert.AreEqual("Ori", GetTestTable().Scalar(columns: "first", id: 19)); + } + + /// Test generic Scalar method with SQLite specific aggregate. + [Test] + public virtual void TestGenericScalarGroupConcat() + { + // This test should produce something like this: + // select group_concat("first") AS first from "sample_users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test generic Scalar method with SQLite specific aggregate not using aggregate field. + [Test] + public virtual void TestGenericScalarGroupConcatNoAggregateField() + { + // This test should produce something like this: + // select group_concat(first) AS first from "sample_users" where "id" < 20; + Assert.AreEqual("Clarke,Marny,Dai,Forrest,Blossom,George,Ivory,Inez,Sigourney,Fulton,Logan,Anne,Alexandra,Adena,Lionel,Aimee,Selma,Lara,Ori", + GetTestTable().Scalar(columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 })); + } + + /// Test something fancy... like: select "first", count("first") aggregatefield from "sample_users" group by "first" order by 2 desc;. + [Test] + public virtual void TestGenericFancyAggregateQuery() + { + var v = (GetTestTable().Query(columns: "first,first:aggregatefield:count", group: "first", order: ":desc:2") as IEnumerable).ToList(); + + Assert.IsNotNull(v); + Assert.AreEqual(187, v.Count()); + Assert.AreEqual(4, v.First().aggregatefield); + Assert.AreEqual("Logan", v.First().first); + Assert.AreEqual(2, v.Take(10).Last().aggregatefield); + Assert.AreEqual(1, v.Take(11).Last().aggregatefield); + Assert.AreEqual(1, v.Last().aggregatefield); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("login")) len from "sample_users";. + [Test] + public virtual void TestGenericAggregateInAggregate() + { + Assert.AreEqual(12.77, GetTestTable().Scalar(columns: @"length(""login""):len:avg")); + } + + /// This time also something fancy... aggregate in aggregate select AVG(LENGTH("email")) len from "sample_users";. + [Test] + public virtual void TestGenericAggregateInAggregateMark2() + { + Assert.AreEqual(27.7, GetTestTable().Avg(columns: @"length(""email""):len")); + } + + /// Test emails longer than 27 chars. select count(*) from "sample_users" where length("email") > 27;. + public virtual void TestGenericFunctionInWhere() + { + Assert.AreEqual(97, + GetTestTable().Count(condition1: + new DynamicColumn() + { + ColumnName = "email", + Aggregate = "length", + Operator = DynamicColumn.CompareOperator.Gt, + Value = 27 + })); + } + + /// Test generic Single multi. + [Test] + public virtual void TestGenericSingleObject() + { + var exp = new { id = 19, first = "Ori", last = "Ellis" }; + var o = GetTestTable().Single(columns: "id,first,last", id: 19); + + Assert.AreEqual(exp.id, o.id); + Assert.AreEqual(exp.first, o.first); + Assert.AreEqual(exp.last, o.last); + } + + #endregion Select generic + + #region Where generic + + /// Test generic where expression equal. + [Test] + public virtual void TestGenericWhereEq() + { + Assert.AreEqual("hoyt.tran", GetTestTable().Single(where: new DynamicColumn("id").Eq(100)).login); + } + + /// Test generic where expression not equal. + [Test] + public virtual void TestGenericWhereNot() + { + Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("id").Not(100))); + } + + /// Test generic where expression like. + [Test] + public virtual void TestGenericWhereLike() + { + Assert.AreEqual(100, GetTestTable().Single(where: new DynamicColumn("login").Like("Hoyt.%")).id); + } + + /// Test generic where expression not like. + [Test] + public virtual void TestGenericWhereNotLike() + { + Assert.AreEqual(199, GetTestTable().Count(where: new DynamicColumn("login").NotLike("Hoyt.%"))); + } + + /// Test generic where expression greater. + [Test] + public virtual void TestGenericWhereGt() + { + Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").Greater(100))); + } + + /// Test generic where expression greater or equal. + [Test] + public virtual void TestGenericWhereGte() + { + Assert.AreEqual(101, GetTestTable().Count(where: new DynamicColumn("id").GreaterOrEqual(100))); + } + + /// Test generic where expression less. + [Test] + public virtual void TestGenericWhereLt() + { + Assert.AreEqual(99, GetTestTable().Count(where: new DynamicColumn("id").Less(100))); + } + + /// Test generic where expression less or equal. + [Test] + public virtual void TestGenericWhereLte() + { + Assert.AreEqual(100, GetTestTable().Count(where: new DynamicColumn("id").LessOrEqual(100))); + } + + /// Test generic where expression between. + [Test] + public virtual void TestGenericWhereBetween() + { + Assert.AreEqual(26, GetTestTable().Count(where: new DynamicColumn("id").Between(75, 100))); + } + + /// Test generic where expression in parameters. + [Test] + public virtual void TestGenericWhereIn1() + { + Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(75, 99, 100))); + } + + /// Test generic where expression in array. + [Test] + public virtual void TestGenericWhereIn2() + { + Assert.AreEqual(3, GetTestTable().Count(where: new DynamicColumn("id").In(new[] { 75, 99, 100 }))); + } + + #endregion Where generic + } } \ No newline at end of file diff --git a/DynamORM.sln b/DynamORM.sln index 1ee157a..ea72591 100644 --- a/DynamORM.sln +++ b/DynamORM.sln @@ -7,10 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamORM", "DynamORM\DynamO EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamORM.Tests", "DynamORM.Tests\DynamORM.Tests.csproj", "{D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AmalgamationTool", "AmalgamationTool\AmalgamationTool.csproj", "{A64D2052-D0CD-488E-BF05-E5952615D926}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tester", "Tester\Tester.csproj", "{F747AA57-BEA7-4FB8-B371-546296789AEF}" -EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AmalgamationTool", "AmalgamationTool\AmalgamationTool.csproj", "{A64D2052-D0CD-488E-BF05-E5952615D926}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,29 +39,18 @@ Global {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}.Release|Mixed Platforms.Build.0 = Release|Any CPU {D5013B4E-8A1B-4DBB-8FB5-E09935F4F764}.Release|x86.ActiveCfg = Release|Any CPU - {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|Any CPU.ActiveCfg = Debug|x86 - {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|Any CPU.Build.0 = Debug|x86 - {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 - {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|Mixed Platforms.Build.0 = Debug|x86 - {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|x86.ActiveCfg = Debug|x86 - {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|x86.Build.0 = Debug|x86 - {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|Any CPU.ActiveCfg = Release|x86 - {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|Any CPU.Build.0 = Release|x86 - {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|Mixed Platforms.ActiveCfg = Release|x86 - {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|Mixed Platforms.Build.0 = Release|x86 - {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|x86.ActiveCfg = Release|x86 - {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|x86.Build.0 = Release|x86 - {F747AA57-BEA7-4FB8-B371-546296789AEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F747AA57-BEA7-4FB8-B371-546296789AEF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F747AA57-BEA7-4FB8-B371-546296789AEF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {F747AA57-BEA7-4FB8-B371-546296789AEF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {F747AA57-BEA7-4FB8-B371-546296789AEF}.Debug|x86.ActiveCfg = Debug|x86 - {F747AA57-BEA7-4FB8-B371-546296789AEF}.Debug|x86.Build.0 = Debug|x86 - {F747AA57-BEA7-4FB8-B371-546296789AEF}.Release|Any CPU.ActiveCfg = Release|x86 - {F747AA57-BEA7-4FB8-B371-546296789AEF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {F747AA57-BEA7-4FB8-B371-546296789AEF}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {F747AA57-BEA7-4FB8-B371-546296789AEF}.Release|x86.ActiveCfg = Release|x86 - {F747AA57-BEA7-4FB8-B371-546296789AEF}.Release|x86.Build.0 = Release|x86 + {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|x86.ActiveCfg = Debug|Any CPU + {A64D2052-D0CD-488E-BF05-E5952615D926}.Debug|x86.Build.0 = Debug|Any CPU + {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|Any CPU.Build.0 = Release|Any CPU + {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|x86.ActiveCfg = Release|Any CPU + {A64D2052-D0CD-488E-BF05-E5952615D926}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -72,6 +59,6 @@ Global SolutionGuid = {22781EB3-2148-4CA4-845A-B55265A7B5C2} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = Tester\Tester.csproj - EndGlobalSection -EndGlobal + StartupItem = DynamORM.Tests\DynamORM.Tests.csproj + EndGlobalSection +EndGlobal diff --git a/DynamORM/DynamORM.csproj b/DynamORM/DynamORM.csproj index 3c93529..103f8ef 100644 --- a/DynamORM/DynamORM.csproj +++ b/DynamORM/DynamORM.csproj @@ -1,7 +1,7 @@ - - netstandard2.0;net472;net6.0;net8.0;net10.0 + + netstandard2.0;net472;net6.0;net8.0;net10.0 Dynamic Object-Relational Mapping library. Copyright © RUSSEK Software 2012-2026 RUSSEK Software @@ -10,8 +10,14 @@ https://git.dr4cul4.pl/RUSSEK-Software/DynamORM https://dr4cul4.pl DynamORM - MIT - + MIT + + + + true + $(MSBuildProjectDirectory) + $(MSBuildProjectDirectory)\..\AmalgamationTool\DynamORM.Amalgamation.cs + true @@ -22,7 +28,14 @@ - - - - \ No newline at end of file + + + + + + + + + diff --git a/DynamORM/DynamicProcedureInvoker.cs b/DynamORM/DynamicProcedureInvoker.cs index 14f0770..7758da2 100644 --- a/DynamORM/DynamicProcedureInvoker.cs +++ b/DynamORM/DynamicProcedureInvoker.cs @@ -51,11 +51,11 @@ namespace DynamORM /// public class ProcResult { [Column("outp")] public Guid Output { get; set; } } /// ProcResult res4 = db.Procedures.sp_Test_Scalar_In_Out<ProcResult>(inp: Guid.NewGuid(), out_outp: Guid.Empty) as ProcResult; /// As you can se, you can use mapper to do job for you. - public class DynamicProcedureInvoker : DynamicObject, IDisposable - { - private DynamicDatabase _db; - private List _prefixes; - private bool _isDisposed; + public class DynamicProcedureInvoker : DynamicObject, IDisposable + { + private DynamicDatabase _db; + private List _prefixes; + private bool _isDisposed; internal DynamicProcedureInvoker(DynamicDatabase db, List prefixes = null) { @@ -314,8 +314,6 @@ namespace DynamORM Type listType = typeof(List<>).MakeGenericType(new Type[] { argType }); IList listInstance = (IList)Activator.CreateInstance(listType); - object defVal = listType.GetDefaultValue(); - using (IDataReader rdr = cmd.ExecuteReader()) using (IDataReader cache = rdr.CachedReader()) while (cache.Read()) @@ -447,16 +445,16 @@ namespace DynamORM return true; } - /// Performs application-defined tasks associated with - /// freeing, releasing, or resetting unmanaged resources. - public void Dispose() - { - if (_isDisposed) - return; - - _isDisposed = true; - _db = null; - _prefixes = null; - } - } -} + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + _db = null; + _prefixes = null; + } + } +} diff --git a/README.md b/README.md index 7463a88..6f15433 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,4 @@ Full documentation is available in [`docs/README.md`](docs/README.md): - `DynamORM.Tests/`: test suite - `AmalgamationTool/`: amalgamation generator and generated single-file output - `DynamORM.Net40.csproj`: net40 build for amalgamated source compatibility +- `scripts/docker/`: dockerized build, test, amalgamation generation, and mono net40 smoke scripts diff --git a/Tester/Program.cs b/Tester/Program.cs deleted file mode 100644 index 2399ba9..0000000 --- a/Tester/Program.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using DynamORM; -using DynamORM.Helpers; -using DynamORM.Mapper; -using Tester.RealTests; - -namespace Tester -{ - internal class Program - { - private static DynamicDatabase GetORM() - { - return new DynamicDatabase(System.Data.SqlClient.SqlClientFactory.Instance, - //"packet size=4096;User Id=sa;Password=sWe7PepR;data source=192.168.22.10;initial catalog=PLAYGROUND;", - "packet size=4096;User Id=sa;Password=sWe7PepR;data source=192.168.22.10;initial catalog=MOM_DEMO_WMS_FILLED;", - DynamicDatabaseOptions.SingleConnection | DynamicDatabaseOptions.SingleTransaction | DynamicDatabaseOptions.SupportSchema | - DynamicDatabaseOptions.SupportStoredProcedures | DynamicDatabaseOptions.SupportTop | DynamicDatabaseOptions.DumpCommands | - DynamicDatabaseOptions.SupportStoredProceduresResult); - - ////return new DynamORM.DynamicDatabase(System.Data.SQLite.SQLiteFactory.Instance, - //// "Data Source=test.db3;", - //// DynamORM.DynamicDatabaseOptions.SingleConnection | DynamORM.DynamicDatabaseOptions.SingleTransaction | - //// DynamORM.DynamicDatabaseOptions.SupportSchema | DynamORM.DynamicDatabaseOptions.SupportLimitOffset); - /// - } - - private static void Main(string[] args) - { - - using (var db = GetORM()) - { - var num = db.Procedures.usp_NewNumber(counterName: "TRAY"); - - //ProcedureHell(db); - //OddNullabeleProblem.Test(db); - - //TableFun(db); - } - } - - private static void TableFun(DynamicDatabase db) - { - try - { - db.Execute("DROP TABLE Experiments "); - } - catch { } - - db.Execute(@"CREATE TABLE Experiments ( - id int NOT NULL PRIMARY KEY, - t1 nvarchar(50) NOT NULL DEFAULT N'', - t2 varchar(50) NOT NULL DEFAULT '', - dd date, - tt time);"); - - db.Insert().Insert(new Ex - { - id = 1, - t1 = "T1", - t2 = "T1", - dd = DateTime.Now, - tt = TimeSpan.FromDays(2) + TimeSpan.FromHours(10), - }).Execute(); - - var tt = db.From().Where(x => x.id == 1).ToList().FirstOrDefault(); - - db.Update().Where(x => x.id == 1).Set(x => x.tt = TimeSpan.FromMinutes(10), x => x.dd = DateTime.Now.AddDays(2)).Execute(); - - db.Execute("DROP TABLE Experiments "); - } - - [Table(Name = "Experiments")] - private class Ex - { - public int id { get; set; } - public string t1 { get; set; } - public string t2 { get; set; } - public DateTime dd { get; set; } - public TimeSpan tt { get; set; } - } - - private static void ProcedureHell(DynamicDatabase db) - { - db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_Scalar AS SELECT 42;"); - var res0 = db.Procedures.sp_Exp_Scalar(); - var res1 = db.Procedures.sp_Exp_Scalar(); - - db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_ReturnInt AS RETURN 42;"); - var res2 = db.Procedures.sp_Exp_ReturnInt(); - - db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeData AS -SELECT 1 Id, 'Some Name 1' [Name], 'Some Desc 1' [Desc], GETDATE() [Date] -UNION ALL SELECT 2 Id, 'Some Name 2', 'Some Desc 2', GETDATE() [Date];"); - var res3 = db.Procedures.sp_Exp_SomeData(); - var res4 = db.Procedures.sp_Exp_SomeData>(); - - db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeInputAndOutput - @Name nvarchar(50), - @Result nvarchar(256) OUTPUT -AS -SELECT @Result = 'Hi, ' + @Name + ' your lucky number is 42!';"); - var res5 = db.Procedures.sp_Exp_SomeInputAndOutput(Name: "G4g4r1n", out_Result: new DynamicColumn - { - Schema = new DynamicSchemaColumn - { - Size = 256, - }, - }, ret_Return: 0); - var res6 = db.Procedures.sp_Exp_SomeInputAndOutput(Name: "G4g4r1n", out_Result: new DynamicSchemaColumn - { - Size = 256, - }, ret_Return: 0); - - db.Execute(@"CREATE OR ALTER PROCEDURE sp_Exp_SomeInputAndOutputWithDataAndReturn - @Name nvarchar(50), - @Result nvarchar(256) OUTPUT -AS -SELECT @Result = 'Hi, ' + @Name + ' your lucky number is 42!' - -SELECT 1 Id, 'Some Name 1' [Name], 'Some Desc 1' [Desc], GETDATE() [Date] -UNION ALL SELECT 2 Id, 'Some Name 2', 'Some Desc 2', GETDATE() [Date] - -RETURN 42;"); - var res7 = db.Procedures.sp_Exp_SomeInputAndOutputWithDataAndReturn, sp_Exp_SomeInputAndOutputWithDataAndReturn_Result>(Name: "G4g4r1n", out_Result: new DynamicColumn - { - Schema = new DynamicSchemaColumn - { - Size = 256, - }, - }, ret_Return: 0); - var res8 = db.Procedures.sp_Exp_SomeInputAndOutputWithDataAndReturn, sp_Exp_SomeInputAndOutputWithDataAndReturn_Result>(Name: "G4g4r1n", out_Result: new DynamicSchemaColumn - { - Size = 256, - }, ret_Return: 0); - } - - private class sp_Exp_SomeData_Result - { - public virtual int Id { get; set; } - public virtual string Name { get; set; } - public virtual string Desc { get; set; } - public virtual DateTime Date { get; set; } - } - - private class sp_Exp_SomeInputAndOutput_Result - { - public virtual string Result { get; set; } - public virtual string Return { get; set; } - } - - private class sp_Exp_SomeInputAndOutputWithDataAndReturn_Result - { - public class Data - { - public virtual int Id { get; set; } - public virtual string Name { get; set; } - public virtual string Desc { get; set; } - public virtual DateTime Date { get; set; } - } - - public virtual List sp_Exp_SomeInputAndOutputWithDataAndReturn { get; set; } - public virtual string Result { get; set; } - public virtual string Return { get; set; } - } - } -} \ No newline at end of file diff --git a/Tester/RealTests/OddNullabeleProblem.cs b/Tester/RealTests/OddNullabeleProblem.cs deleted file mode 100644 index 67c353d..0000000 --- a/Tester/RealTests/OddNullabeleProblem.cs +++ /dev/null @@ -1,448 +0,0 @@ -using DynamORM; -using DynamORM.Mapper; -using DynamORM.Objects; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Tester.RealTests -{ - internal static class OddNullabeleProblem - { - public static void Test(DynamicDatabase db) - { - var ca = new mom_Contractors_Articles(); - ca.mca_min_date_valid_out = 5; - ca.mca_mar_code = "342"; - ca.mca_mar_name = "234"; - ca.SetDynamicEntityState(DynamicEntityState.Existing); - ca.mca_min_date_valid_out = null; - ca.mca_mar_code = null; - ca.mca_mar_name = null; - - var r = new DynamicRepositoryBase(db); - r.Save(ca); - } - - [Table(Name = "mom_Contractors_Articles")] - public class mom_Contractors_Articles : DynamicEntityBase - { - private System.Guid _mca_id; - - [Column("mca_id", true, AllowNull = false)] - public virtual System.Guid mca_id - { - get { return _mca_id; } - set - { - if (_mca_id != value) - { - this.OnPropertyChanging("mca_id", _mca_id, value); - _mca_id = value; - } - } - } - - private System.Nullable _mca_mc_id; - - [Column("mca_mc_id")] - public virtual System.Nullable mca_mc_id - { - get { return _mca_mc_id; } - set - { - if (_mca_mc_id != value) - { - this.OnPropertyChanging("mca_mc_id", _mca_mc_id, value); - _mca_mc_id = value; - } - } - } - - private System.Nullable _mca_mar_id; - - [Column("mca_mar_id")] - public virtual System.Nullable mca_mar_id - { - get { return _mca_mar_id; } - set - { - if (_mca_mar_id != value) - { - this.OnPropertyChanging("mca_mar_id", _mca_mar_id, value); - _mca_mar_id = value; - } - } - } - - private System.Nullable _mca_min_date_valid_out; - - [Column("mca_min_date_valid_out", AllowNull = true)] - public virtual System.Nullable mca_min_date_valid_out - { - get { return _mca_min_date_valid_out; } - set - { - if (_mca_min_date_valid_out != value) - { - this.OnPropertyChanging("mca_min_date_valid_out", _mca_min_date_valid_out, value); - _mca_min_date_valid_out = value; - } - } - } - - private System.Nullable _mca_min_date_valid_in; - - [Column("mca_min_date_valid_in")] - public virtual System.Nullable mca_min_date_valid_in - { - get { return _mca_min_date_valid_in; } - set - { - if (_mca_min_date_valid_in != value) - { - this.OnPropertyChanging("mca_min_date_valid_in", _mca_min_date_valid_in, value); - _mca_min_date_valid_in = value; - } - } - } - - private System.String _mca_mar_code; - - [Column("mca_mar_code", AllowNull = true)] - public virtual System.String mca_mar_code - { - get { return _mca_mar_code; } - set - { - if (_mca_mar_code != value) - { - this.OnPropertyChanging("mca_mar_code", _mca_mar_code, value); - _mca_mar_code = value; - } - } - } - - private System.String _mca_mar_name; - - [Column("mca_mar_name", AllowNull = true)] - public virtual System.String mca_mar_name - { - get { return _mca_mar_name; } - set - { - if (_mca_mar_name != value) - { - this.OnPropertyChanging("mca_mar_name", _mca_mar_name, value); - _mca_mar_name = value; - } - } - } - - private System.String _mca_gid; - - [Column("mca_GID")] - public virtual System.String mca_GID - { - get { return _mca_gid; } - set - { - if (_mca_gid != value) - { - this.OnPropertyChanging("mca_GID", _mca_gid, value); - _mca_gid = value; - } - } - } - - private System.Nullable _mca_percent_wz; - - [Column("mca_percent_WZ")] - public virtual System.Nullable mca_percent_WZ - { - get { return _mca_percent_wz; } - set - { - if (_mca_percent_wz != value) - { - this.OnPropertyChanging("mca_percent_WZ", _mca_percent_wz, value); - _mca_percent_wz = value; - } - } - } - - private System.Nullable _mca_percent_pz; - - [Column("mca_percent_PZ")] - public virtual System.Nullable mca_percent_PZ - { - get { return _mca_percent_pz; } - set - { - if (_mca_percent_pz != value) - { - this.OnPropertyChanging("mca_percent_PZ", _mca_percent_pz, value); - _mca_percent_pz = value; - } - } - } - - private System.Byte _mca_tss_ignore; - - [Column("mca_tss_ignore", AllowNull = false)] - public virtual System.Byte mca_tss_ignore - { - get { return _mca_tss_ignore; } - set - { - if (_mca_tss_ignore != value) - { - this.OnPropertyChanging("mca_tss_ignore", _mca_tss_ignore, value); - _mca_tss_ignore = value; - } - } - } - - private System.String _mca_mar_bar_code; - - [Column("mca_mar_bar_code")] - public virtual System.String mca_mar_bar_code - { - get { return _mca_mar_bar_code; } - set - { - if (_mca_mar_bar_code != value) - { - this.OnPropertyChanging("mca_mar_bar_code", _mca_mar_bar_code, value); - _mca_mar_bar_code = value; - } - } - } - - private System.Nullable _mca_mus_id_modified; - - [Column("mca_mus_id_modified")] - public virtual System.Nullable mca_mus_id_modified - { - get { return _mca_mus_id_modified; } - set - { - if (_mca_mus_id_modified != value) - { - this.OnPropertyChanging("mca_mus_id_modified", _mca_mus_id_modified, value); - _mca_mus_id_modified = value; - } - } - } - - private System.Nullable _mca_date_modified; - - [Column("mca_date_modified")] - public virtual System.Nullable mca_date_modified - { - get { return _mca_date_modified; } - set - { - if (_mca_date_modified != value) - { - this.OnPropertyChanging("mca_date_modified", _mca_date_modified, value); - _mca_date_modified = value; - } - } - } - - private System.String _mca_ean_label_unit; - - [Column("mca_ean_label_unit")] - public virtual System.String mca_ean_label_unit - { - get { return _mca_ean_label_unit; } - set - { - if (_mca_ean_label_unit != value) - { - this.OnPropertyChanging("mca_ean_label_unit", _mca_ean_label_unit, value); - _mca_ean_label_unit = value; - } - } - } - - private System.String _mca_ean_label_pckg; - - [Column("mca_ean_label_pckg")] - public virtual System.String mca_ean_label_pckg - { - get { return _mca_ean_label_pckg; } - set - { - if (_mca_ean_label_pckg != value) - { - this.OnPropertyChanging("mca_ean_label_pckg", _mca_ean_label_pckg, value); - _mca_ean_label_pckg = value; - } - } - } - - private System.Nullable _mca_price; - - [Column("mca_price")] - public virtual System.Nullable mca_price - { - get { return _mca_price; } - set - { - if (_mca_price != value) - { - this.OnPropertyChanging("mca_price", _mca_price, value); - _mca_price = value; - } - } - } - - private System.String _mca_currency_code; - - [Column("mca_currency_code")] - public virtual System.String mca_currency_code - { - get { return _mca_currency_code; } - set - { - if (_mca_currency_code != value) - { - this.OnPropertyChanging("mca_currency_code", _mca_currency_code, value); - _mca_currency_code = value; - } - } - } - - private System.Nullable _mca_time_ahead; - - [Column("mca_time_ahead")] - public virtual System.Nullable mca_time_ahead - { - get { return _mca_time_ahead; } - set - { - if (_mca_time_ahead != value) - { - this.OnPropertyChanging("mca_time_ahead", _mca_time_ahead, value); - _mca_time_ahead = value; - } - } - } - - private System.Nullable _mca_last_price; - - [Column("mca_last_price")] - public virtual System.Nullable mca_last_price - { - get { return _mca_last_price; } - set - { - if (_mca_last_price != value) - { - this.OnPropertyChanging("mca_last_price", _mca_last_price, value); - _mca_last_price = value; - } - } - } - - private System.Nullable _mca_logistic_minimum_value; - - [Column("mca_logistic_minimum_value")] - public virtual System.Nullable mca_logistic_minimum_value - { - get { return _mca_logistic_minimum_value; } - set - { - if (_mca_logistic_minimum_value != value) - { - this.OnPropertyChanging("mca_logistic_minimum_value", _mca_logistic_minimum_value, value); - _mca_logistic_minimum_value = value; - } - } - } - - private System.Nullable _mca_logistic_minimum_mplt; - - [Column("mca_logistic_minimum_mplt")] - public virtual System.Nullable mca_logistic_minimum_mplt - { - get { return _mca_logistic_minimum_mplt; } - set - { - if (_mca_logistic_minimum_mplt != value) - { - this.OnPropertyChanging("mca_logistic_minimum_mplt", _mca_logistic_minimum_mplt, value); - _mca_logistic_minimum_mplt = value; - } - } - } - - private System.Nullable _mca_logistic_minimum_qty; - - [Column("mca_logistic_minimum_qty")] - public virtual System.Nullable mca_logistic_minimum_qty - { - get { return _mca_logistic_minimum_qty; } - set - { - if (_mca_logistic_minimum_qty != value) - { - this.OnPropertyChanging("mca_logistic_minimum_qty", _mca_logistic_minimum_qty, value); - _mca_logistic_minimum_qty = value; - } - } - } - - private System.Nullable _mca_req_kj_percent; - - [Column("mca_req_kj_percent")] - public virtual System.Nullable mca_req_kj_percent - { - get { return _mca_req_kj_percent; } - set - { - if (_mca_req_kj_percent != value) - { - this.OnPropertyChanging("mca_req_kj_percent", _mca_req_kj_percent, value); - _mca_req_kj_percent = value; - } - } - } - - private System.Nullable _mca_min_order_qty; - - [Column("mca_min_order_qty")] - public virtual System.Nullable mca_min_order_qty - { - get { return _mca_min_order_qty; } - set - { - if (_mca_min_order_qty != value) - { - this.OnPropertyChanging("mca_min_order_qty", _mca_min_order_qty, value); - _mca_min_order_qty = value; - } - } - } - - private System.Byte _mca_default_contractor; - - [Column("mca_default_contractor", AllowNull = false)] - public virtual System.Byte mca_default_contractor - { - get { return _mca_default_contractor; } - set - { - if (_mca_default_contractor != value) - { - this.OnPropertyChanging("mca_default_contractor", _mca_default_contractor, value); - _mca_default_contractor = value; - } - } - } - - } - } -} diff --git a/Tester/Tester.csproj b/Tester/Tester.csproj deleted file mode 100644 index a3a687c..0000000 --- a/Tester/Tester.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - Exe - netcoreapp3.1 - Copyright © RUSSEK Software 2012-2022 - RUSSEK Software - Grzegorz Russek - 1.2.1 - https://svn.dr4cul4.pl/svn/DynamORM/ - https://dr4cul4.pl - DynamORM - MIT - Exe - - - - - - - - - - - - - - - - - - diff --git a/scripts/docker/build.sh b/scripts/docker/build.sh new file mode 100755 index 0000000..525de28 --- /dev/null +++ b/scripts/docker/build.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +IMAGE="${DOTNET_IMAGE:-mcr.microsoft.com/dotnet/sdk:10.0}" + +docker run --rm \ + -v "${ROOT_DIR}:/workspace" \ + -w /workspace \ + "${IMAGE}" \ + bash -lc " + set -euo pipefail + dotnet --info + dotnet restore DynamORM.sln + dotnet build DynamORM.sln -c Release --no-restore + dotnet pack DynamORM/DynamORM.csproj -c Release --no-build -o /workspace/artifacts/nuget + " + diff --git a/scripts/docker/ci-local.sh b/scripts/docker/ci-local.sh new file mode 100755 index 0000000..1e04680 --- /dev/null +++ b/scripts/docker/ci-local.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +"${ROOT_DIR}/scripts/docker/generate-amalgamation.sh" +"${ROOT_DIR}/scripts/docker/build.sh" +"${ROOT_DIR}/scripts/docker/test.sh" +"${ROOT_DIR}/scripts/docker/mono-net40-smoke.sh" + +echo "All docker checks completed." diff --git a/scripts/docker/generate-amalgamation.sh b/scripts/docker/generate-amalgamation.sh new file mode 100755 index 0000000..b6612c4 --- /dev/null +++ b/scripts/docker/generate-amalgamation.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +IMAGE="${DOTNET_IMAGE:-mcr.microsoft.com/dotnet/sdk:10.0}" + +docker run --rm \ + -v "${ROOT_DIR}:/workspace" \ + -w /workspace \ + "${IMAGE}" \ + bash -lc " + set -euo pipefail + dotnet run --project AmalgamationTool/AmalgamationTool.csproj -- DynamORM AmalgamationTool/DynamORM.Amalgamation.cs + " + diff --git a/scripts/docker/mono-net40-smoke.sh b/scripts/docker/mono-net40-smoke.sh new file mode 100755 index 0000000..57f899e --- /dev/null +++ b/scripts/docker/mono-net40-smoke.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +IMAGE="${MONO_IMAGE:-mono:6.12}" + +docker run --rm \ + -v "${ROOT_DIR}:/workspace" \ + -w /workspace \ + "${IMAGE}" \ + bash -lc " + set -euo pipefail + mono --version + mcs -langversion:latest -target:library -sdk:4 \ + -r:System \ + -r:System.Core \ + -r:System.Data \ + -r:Microsoft.CSharp \ + -out:/tmp/DynamORM.Net40.Smoke.dll \ + AmalgamationTool/DynamORM.Amalgamation.cs + ls -lh /tmp/DynamORM.Net40.Smoke.dll + " + diff --git a/scripts/docker/test.sh b/scripts/docker/test.sh new file mode 100755 index 0000000..d7b8b8c --- /dev/null +++ b/scripts/docker/test.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +IMAGE="${DOTNET_IMAGE:-mcr.microsoft.com/dotnet/sdk:10.0}" + +docker run --rm \ + -v "${ROOT_DIR}:/workspace" \ + -w /workspace \ + "${IMAGE}" \ + bash -lc " + set -euo pipefail + dotnet test DynamORM.Tests/DynamORM.Tests.csproj -c Release + " +