diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs index 7db6a82..fd0ae18 100644 --- a/AmalgamationTool/DynamORM.Amalgamation.cs +++ b/AmalgamationTool/DynamORM.Amalgamation.cs @@ -33,30 +33,30 @@ * * DYNAMORM_OMMIT_TRYPARSE - Remove TryParse helpers (also applies DYNAMORM_OMMIT_GENERICEXECUTION) */ -using DynamORM.Builders.Extensions; -using DynamORM.Builders.Implementation; -using DynamORM.Builders; -using DynamORM.Helpers.Dynamics; -using DynamORM.Helpers; -using DynamORM.Mapper; +using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; -using System.Collections; -using System.Data.Common; using System.Data; +using System.Data.Common; using System.Dynamic; using System.IO; -using System.Linq.Expressions; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; -using System; +using DynamORM.Builders; +using DynamORM.Builders.Extensions; +using DynamORM.Builders.Implementation; +using DynamORM.Helpers; +using DynamORM.Helpers.Dynamics; +using DynamORM.Mapper; [module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "This is a generated file which generates all the necessary support classes.")] [module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1403:FileMayOnlyContainASingleNamespace", Justification = "This is a generated file which generates all the necessary support classes.")] + namespace DynamORM { /// Small utility class to manage single columns. @@ -587,7 +587,10 @@ namespace DynamORM ////_poolStamp = _db.PoolStamp; } - return _db.DumpCommands ? _command.Dump(Console.Out) : _command; + if (_db.DumpCommands) + _db.DumpCommand(_command); + + return _command; } #region IDbCommand Members @@ -1817,6 +1820,43 @@ namespace DynamORM 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)) + Schema.Remove(table.FullName); + } + + /// Clears the all schemas from cache. + /// Use this method to refresh all table information. + public void ClearSchema() + { + lock (SyncLock) + Schema.Clear(); + } + /// Get schema describing objects from reader. /// Table from which extract column info. /// Owner of table from which extract column info. @@ -2033,6 +2073,13 @@ namespace DynamORM sb.AppendFormat(_parameterFormat, parameter.ToString().Replace(" ", "_")); } + /// Dumps the command into console output. + /// The command to dump. + public virtual void DumpCommand(IDbCommand cmd) + { + cmd.Dump(Console.Out); + } + #endregion Decorators #region Connection @@ -3065,7 +3112,16 @@ namespace DynamORM /// Returns instance of builder on which action is set. public static T CreateTemporaryParameterAction(this T b, Action a) where T : IDynamicQueryBuilder { - b.OnCreateTemporaryParameter = a; + if (a == null) + b.OnCreateTemporaryParameter = null; + else + { + if (b.OnCreateTemporaryParameter == null) + b.OnCreateTemporaryParameter = new List>(); + + b.OnCreateTemporaryParameter.Add(a); + } + return b; } @@ -3076,7 +3132,16 @@ namespace DynamORM /// Returns instance of builder on which action is set. public static T CreateParameterAction(this T b, Action a) where T : IDynamicQueryBuilder { - b.OnCreateParameter = a; + if (a == null) + b.OnCreateParameter = null; + else + { + if (b.OnCreateParameter == null) + b.OnCreateParameter = new List>(); + + b.OnCreateParameter.Add(a); + } + return b; } @@ -4065,7 +4130,8 @@ namespace DynamORM using (var con = Database.Open()) using (var cmd = con.CreateCommand()) using (var rdr = cmd - .SetCommand(sql, args) + .SetCommand(sql) + .AddParameters(Database, args) .ExecuteReader()) while (rdr.Read()) { @@ -4891,7 +4957,7 @@ namespace DynamORM } namespace Builders - { + { /// Dynamic delete query builder interface. /// This interface it publically available. Implementation should be hidden. public interface IDynamicDeleteQueryBuilder : IDynamicQueryBuilder @@ -4899,7 +4965,7 @@ namespace DynamORM /// 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 @@ -4911,25 +4977,25 @@ namespace DynamORM /// 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 @@ -4937,8 +5003,7 @@ namespace DynamORM /// Builder instance. IDynamicDeleteQueryBuilder Where(object conditions, bool schema = false); } - - + /// Dynamic insert query builder interface. /// This interface it publically available. Implementation should be hidden. public interface IDynamicInsertQueryBuilder : IDynamicQueryBuilder @@ -4946,7 +5011,7 @@ namespace DynamORM /// 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: @@ -4957,61 +5022,59 @@ namespace DynamORM /// The specifications. /// This instance to permit chaining. IDynamicInsertQueryBuilder Values(Func fn, params Func[] func); - + /// Add insert fields. /// Insert column. /// Insert value. /// Builder instance. IDynamicInsertQueryBuilder Insert(string column, object value); - + /// Add insert fields. /// Set insert value as properties and values of an object. /// Builder instance. IDynamicInsertQueryBuilder Insert(object o); } - - + /// Dynamic query builder base interface. /// This interface it publically available. Implementation should be hidden. public interface IDynamicQueryBuilder { /// Gets instance. DynamicDatabase Database { get; } - + /// Gets tables information. IList Tables { get; } - + /// Gets the tables used in this builder. IDictionary Parameters { get; } - + /// Gets or sets a value indicating whether add virtual parameters. bool VirtualMode { get; set; } - + /// Gets a value indicating whether database supports standard schema. bool SupportSchema { get; } - + /// Fill command with query. /// Command to fill. /// Filled instance of . IDbCommand FillCommand(IDbCommand command); - + /// /// Generates the text this command will execute against the underlying database. /// /// The text to execute against the underlying database. /// This method must be override by derived classes. string CommandText(); - - /// Gets or sets the on create temporary parameter action. + + /// Gets or sets the on create temporary parameter actions. /// This is exposed to allow setting schema of column. - Action OnCreateTemporaryParameter { get; set; } - - /// Gets or sets the on create real parameter action. + List> OnCreateTemporaryParameter { get; set; } + + /// Gets or sets the on create real parameter actions. /// This is exposed to allow modification of parameter. - Action OnCreateParameter { get; set; } + List> OnCreateParameter { get; set; } } - - + /// Dynamic select query builder interface. /// This interface it publically available. Implementation should be hidden. public interface IDynamicSelectQueryBuilder : IDynamicQueryBuilder ////, IEnumerable @@ -5019,22 +5082,22 @@ namespace DynamORM /// Execute this builder. /// Enumerator of objects expanded from query. IEnumerable Execute(); - + /// Execute this builder and map to given type. /// Type of object to map on. /// Enumerator of objects expanded from query. IEnumerable Execute() where T : class; - + /// Execute this builder as a data reader. /// Action containing reader. void ExecuteDataReader(Action reader); - + /// Returns a single result. /// Result of a query. object Scalar(); - + #region From/Join - + /// /// Adds to the 'From' clause the contents obtained by parsing the dynamic lambda expressions given. The supported /// formats are: @@ -5047,7 +5110,7 @@ namespace DynamORM /// 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: @@ -5065,11 +5128,11 @@ namespace DynamORM /// 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 @@ -5081,36 +5144,36 @@ namespace DynamORM /// 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: @@ -5124,23 +5187,23 @@ namespace DynamORM /// 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. /// @@ -5148,23 +5211,23 @@ namespace DynamORM /// The specification. /// This instance to permit chaining. IDynamicSelectQueryBuilder GroupBy(Func fn, params Func[] func); - + /// Add select columns. /// Columns to group by. /// Builder instance. IDynamicSelectQueryBuilder GroupByColumn(params DynamicColumn[] columns); - + /// Add select columns. /// Columns to group by. /// Column format consist of Column Name and /// Alias in this order separated by ':'. /// Builder instance. IDynamicSelectQueryBuilder GroupByColumn(params string[] columns); - + #endregion GroupBy - + #region OrderBy - + /// /// Adds to the 'Order By' clause the contents obtained from from parsing the dynamic lambda expression given. It /// accepts a multipart column specification followed by an optional Ascending() or Descending() virtual methods @@ -5175,47 +5238,46 @@ namespace DynamORM /// The specification. /// This instance to permit chaining. IDynamicSelectQueryBuilder OrderBy(Func fn, params Func[] func); - + /// Add select columns. /// Columns to order by. /// Builder instance. IDynamicSelectQueryBuilder OrderByColumn(params DynamicColumn[] columns); - + /// Add select columns. /// Columns to order by. /// Column format consist of Column Name and /// Alias in this order separated by ':'. /// Builder instance. IDynamicSelectQueryBuilder OrderByColumn(params string[] columns); - + #endregion OrderBy - + #region Top/Limit/Offset/Distinct - + /// Set top if database support it. /// How many objects select. /// Builder instance. IDynamicSelectQueryBuilder Top(int? top); - + /// Set top if database support it. /// How many objects select. /// Builder instance. IDynamicSelectQueryBuilder Limit(int? limit); - + /// Set top if database support it. /// How many objects skip selecting. /// Builder instance. IDynamicSelectQueryBuilder Offset(int? offset); - + /// Set distinct mode. /// Distinct mode. /// Builder instance. IDynamicSelectQueryBuilder Distinct(bool distinct = true); - + #endregion Top/Limit/Offset/Distinct } - - + /// Dynamic update query builder interface. /// This interface it publically available. Implementation should be hidden. public interface IDynamicUpdateQueryBuilder : IDynamicQueryBuilder @@ -5223,24 +5285,24 @@ namespace DynamORM /// 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: @@ -5250,22 +5312,22 @@ namespace DynamORM /// 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 @@ -5277,90 +5339,87 @@ namespace DynamORM /// The specification. /// This instance to permit chaining. IDynamicUpdateQueryBuilder Where(Func func); - + /// Add where condition. /// Condition column with operator and value. /// Builder instance. IDynamicUpdateQueryBuilder Where(DynamicColumn column); - + /// Add where condition. /// Condition column. /// Condition operator. /// Condition value. /// Builder instance. IDynamicUpdateQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value); - + /// Add where condition. /// Condition column. /// Condition value. /// Builder instance. IDynamicUpdateQueryBuilder Where(string column, object value); - + /// Add where condition. /// Set conditions as properties and values of an object. /// If true use schema to determine key columns and ignore those which /// aren't keys. /// Builder instance. IDynamicUpdateQueryBuilder Where(object conditions, bool schema = false); - + #endregion Where } - - + /// Interface describing parameter info. public interface IParameter { /// Gets the parameter position in command. /// Available after filling the command. int Ordinal { get; } - + /// Gets the parameter temporary name. string Name { get; } - + /// Gets or sets the parameter value. object Value { get; set; } - + /// Gets or sets a value indicating whether name of temporary parameter is well known. bool WellKnown { get; set; } - + /// Gets or sets a value indicating whether this is virtual. bool Virtual { get; set; } - + /// Gets or sets the parameter schema information. DynamicSchemaColumn? Schema { get; set; } } - - + /// Interface describing table information. public interface ITableInfo { /// Gets table owner name. string Owner { get; } - + /// Gets table name. string Name { get; } - + /// Gets table alias. string Alias { get; } - + /// Gets table schema. Dictionary Schema { get; } } - namespace Extensions - { + { internal static class DynamicModifyBuilderExtensions { internal static T Table(this T builder, Func func) where T : DynamicModifyBuilder { if (func == null) throw new ArgumentNullException("Function cannot be null."); - + using (var parser = DynamicParser.Parse(func)) { var result = parser.Result; - + // If the expression result is string. if (result is string) return builder.Table((string)result); @@ -5370,38 +5429,38 @@ namespace DynamORM { // Or if it resolves to a dynamic node var node = (DynamicParser.Node)result; - + string owner = null; string main = null; - + while (true) { // Deny support for the AS() virtual method... if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") throw new ArgumentException(string.Format("Alias is not supported on modification builders. (Parsing: {0})", result)); - + // Support for table specifications... if (node is DynamicParser.Node.GetMember) { if (owner != null) throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); - + if (main != null) owner = ((DynamicParser.Node.GetMember)node).Name; else main = ((DynamicParser.Node.GetMember)node).Name; - + node = node.Host; continue; } - + // Support for generic sources... if (node is DynamicParser.Node.Invoke) { if (owner == null && main == null) { var invoke = (DynamicParser.Node.Invoke)node; - + if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type) return builder.Table((Type)invoke.Arguments[0]); else if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is String) @@ -5414,51 +5473,51 @@ namespace DynamORM else if (main != null) throw new ArgumentException(string.Format("Main '{0}' is already set when parsing '{1}'.", main, result)); } - + if (!string.IsNullOrEmpty(main)) return builder.Table(string.Format("{0}{1}", string.IsNullOrEmpty(owner) ? string.Empty : string.Format("{0}.", owner), main)); } } - + throw new ArgumentException(string.Format("Unable to set table parsing '{0}'", result)); } } - + internal static T Table(this T builder, string tableName, Dictionary schema = null) where T : DynamicModifyBuilder { var tuple = tableName.Validated("Table Name").SplitSomethingAndAlias(); - + if (!string.IsNullOrEmpty(tuple.Item2)) throw new ArgumentException(string.Format("Can not use aliases in INSERT steatement. ({0})", tableName), "tableName"); - + var parts = tuple.Item1.Split('.'); - + if (parts.Length > 2) throw new ArgumentException(string.Format("Table name can consist only from name or owner and name. ({0})", tableName), "tableName"); - + builder.Tables.Clear(); builder.Tables.Add(new DynamicQueryBuilder.TableInfo(builder.Database, builder.Database.StripName(parts.Last()).Validated("Table"), null, parts.Length == 2 ? builder.Database.StripName(parts.First()).Validated("Owner", canbeNull: true) : null)); - + if (schema != null) (builder.Tables[0] as DynamicQueryBuilder.TableInfo).Schema = schema; - + return builder; } - + internal static T Table(this T builder, Type type) where T : DynamicQueryBuilder { if (type.IsAnonymous()) throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}).", type.FullName)); - + var mapper = DynamicMapperCache.GetMapper(type); - + if (mapper == null) throw new InvalidOperationException("Cant assign unmapable type as a table."); - + if (builder is DynamicModifyBuilder) { builder.Tables.Clear(); @@ -5466,35 +5525,34 @@ namespace DynamORM } else if (builder is DynamicSelectQueryBuilder) (builder as DynamicSelectQueryBuilder).From(x => x(type)); - + return builder; } } - - + internal static class DynamicWhereQueryExtensions { #region Where - + internal static T InternalWhere(this T builder, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere { return builder.InternalWhere(false, false, func); } - + internal static T InternalWhere(this T builder, bool addBeginBrace, bool addEndBrace, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere { if (func == null) throw new ArgumentNullException("Array of functions cannot be null."); - + using (var parser = DynamicParser.Parse(func)) { string condition = null; bool and = true; - + var result = parser.Result; if (result is string) { condition = (string)result; - + if (condition.ToUpper().IndexOf("OR") == 0) { and = false; @@ -5517,19 +5575,19 @@ namespace DynamORM object[] args = ((DynamicParser.Node.Method)node).Arguments; if (args == null) throw new ArgumentNullException("arg", string.Format("{0} is not a parameterless method.", name)); if (args.Length != 1) throw new ArgumentException(string.Format("{0} requires one and only one parameter: {1}.", name, args.Sketch())); - + and = name == "AND" ? true : false; result = args[0]; } } - + // Just parsing the contents now... condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition"); } - + if (addBeginBrace) builder.OpenBracketsCount++; if (addEndBrace) builder.OpenBracketsCount--; - + if (builder.WhereCondition == null) builder.WhereCondition = string.Format("{0}{1}{2}", addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); @@ -5537,16 +5595,27 @@ namespace DynamORM 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) @@ -5578,31 +5647,32 @@ namespace DynamORM 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) { var v = (DynamicColumn)value; - + if (string.IsNullOrEmpty(v.ColumnName)) v.ColumnName = column; - + return builder.InternalWhere(v); } else if (value is IEnumerable) { foreach (DynamicColumn v in (IEnumerable)value) builder.InternalWhere(v); - + return builder; } - + return builder.InternalWhere(new DynamicColumn { ColumnName = column, @@ -5610,12 +5680,12 @@ namespace DynamORM 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) @@ -5624,52 +5694,50 @@ namespace DynamORM { foreach (DynamicColumn v in (IEnumerable)conditions) builder.InternalWhere(v); - + return builder; } - + var dict = conditions.ToDictionary(); var mapper = DynamicMapperCache.GetMapper(conditions.GetType()); var table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty); - + foreach (var condition in dict) { if (mapper.Ignored.Contains(condition.Key) || condition.Key == "_table") continue; - + string colName = mapper != null ? mapper.PropertyMap.TryGetValue(condition.Key) ?? condition.Key : condition.Key; - + DynamicSchemaColumn? col = null; - + // This should be used on typed queries or update/delete steatements, which usualy operate on a single table. if (schema) { col = builder.GetColumnFromSchema(colName, mapper, table); - + if ((!col.HasValue || !col.Value.IsKey) && (mapper == null || mapper.ColumnsMap.TryGetValue(colName).NullOr(m => m.Ignore || m.Column.NullOr(c => !c.IsKey, true), true))) continue; - + colName = col.HasValue ? col.Value.Name : colName; } - + if (!string.IsNullOrEmpty(table)) builder.InternalWhere(x => x(builder.FixObjectName(string.Format("{0}.{1}", table, colName))) == condition.Value); else builder.InternalWhere(x => x(builder.FixObjectName(colName)) == condition.Value); } - + return builder; } - + #endregion Where } - - } namespace Implementation - { + { /// Implementation of dynamic delete query builder. internal class DynamicDeleteQueryBuilder : DynamicModifyBuilder, IDynamicDeleteQueryBuilder, DynamicQueryBuilder.IQueryWithWhere { @@ -5681,7 +5749,7 @@ namespace DynamORM : base(db) { } - + /// /// Initializes a new instance of the class. /// @@ -5691,7 +5759,7 @@ namespace DynamORM : 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. @@ -5704,9 +5772,9 @@ namespace DynamORM 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 @@ -5721,7 +5789,7 @@ namespace DynamORM { return this.InternalWhere(func); } - + /// Add where condition. /// Condition column with operator and value. /// Builder instance. @@ -5729,7 +5797,7 @@ namespace DynamORM { return this.InternalWhere(column); } - + /// Add where condition. /// Condition column. /// Condition operator. @@ -5739,7 +5807,7 @@ namespace DynamORM { return this.InternalWhere(column, op, value); } - + /// Add where condition. /// Condition column. /// Condition value. @@ -5748,7 +5816,7 @@ namespace DynamORM { 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 @@ -5758,17 +5826,16 @@ namespace DynamORM { 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. /// @@ -5777,7 +5844,7 @@ namespace DynamORM : base(db) { } - + /// /// Initializes a new instance of the class. /// @@ -5787,7 +5854,7 @@ namespace DynamORM : 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. @@ -5798,9 +5865,9 @@ namespace DynamORM 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: @@ -5814,42 +5881,42 @@ namespace DynamORM { if (fn == null) throw new ArgumentNullException("Array of specifications cannot be null."); - + int index = InsertFunc(-1, fn); - + if (func != null) foreach (var f in func) index = InsertFunc(index, f); - + return this; } - + private int InsertFunc(int index, Func f) { index++; - + if (f == null) throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); - + using (var parser = DynamicParser.Parse(f)) { var result = parser.Result; if (result == null) throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); - + string main = null; string value = null; string str = null; - + // When 'x => x.Table.Column = value' or 'x => x.Column = value'... if (result is DynamicParser.Node.SetMember) { var node = (DynamicParser.Node.SetMember)result; - + DynamicSchemaColumn? col = GetColumnFromSchema(node.Name); main = Database.DecorateName(node.Name); - value = Parse(node.Value, pars: Parameters, nulls: true, columnSchema: col); - + value = Parse(node.Value, ref col, pars: Parameters, nulls: true); + _columns = _columns == null ? main : string.Format("{0}, {1}", _columns, main); _values = _values == null ? value : string.Format("{0}, {1}", _values, value); return index; @@ -5859,7 +5926,7 @@ namespace DynamORM Insert(result); return index; } - + // Other specifications are considered invalid... var err = string.Format("Specification '{0}' is invalid.", result); str = Parse(result); @@ -5867,7 +5934,7 @@ namespace DynamORM throw new ArgumentException(err); } } - + /// Add insert fields. /// Insert column. /// Insert value. @@ -5877,20 +5944,20 @@ namespace DynamORM if (value is DynamicColumn) { var v = (DynamicColumn)value; - + if (string.IsNullOrEmpty(v.ColumnName)) v.ColumnName = column; - + return Insert(v); } - + return Insert(new DynamicColumn { ColumnName = column, Value = value, }); } - + /// Add insert fields. /// Set insert value as properties and values of an object. /// Builder instance. @@ -5900,19 +5967,19 @@ namespace DynamORM { var column = (DynamicColumn)o; DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName); - + string main = FixObjectName(column.ColumnName, onlyColumn: true); - string value = Parse(column.Value, pars: Parameters, nulls: true, columnSchema: col); - + string value = Parse(column.Value, ref col, pars: Parameters, nulls: true); + _columns = _columns == null ? main : string.Format("{0}, {1}", _columns, main); _values = _values == null ? value : string.Format("{0}, {1}", _values, value); - + return this; } - + var dict = o.ToDictionary(); var mapper = DynamicMapperCache.GetMapper(o.GetType()); - + if (mapper != null) { foreach (var con in dict) @@ -5920,7 +5987,7 @@ namespace DynamORM { var colName = mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key; var propMap = mapper.ColumnsMap.TryGetValue(colName.ToLower()); - + if (propMap == null || propMap.Column == null || !propMap.Column.IsNoInsert) Insert(colName, con.Value); } @@ -5928,14 +5995,13 @@ namespace DynamORM else foreach (var con in dict) Insert(con.Key, con.Value); - + return this; } - + #endregion Insert } - - + /// Base query builder for insert/update/delete statements. internal abstract class DynamicModifyBuilder : DynamicQueryBuilder { @@ -5948,7 +6014,7 @@ namespace DynamORM { VirtualMode = false; } - + /// /// Initializes a new instance of the class. /// @@ -5960,7 +6026,7 @@ namespace DynamORM VirtualMode = false; this.Table(tableName); } - + /// Execute this builder. /// Result of an execution.. public virtual int Execute() @@ -5974,8 +6040,7 @@ namespace DynamORM } } } - - + /// Implementation of dynamic query builder base interface. internal abstract class DynamicQueryBuilder : IDynamicQueryBuilder { @@ -5984,15 +6049,15 @@ namespace DynamORM { /// Gets or sets the where condition. string WhereCondition { get; set; } - + /// Gets or sets the amount of not closed brackets in where statement. int OpenBracketsCount { get; set; } } - + private DynamicQueryBuilder _parent = null; - + #region TableInfo - + /// Table information. internal class TableInfo : ITableInfo { @@ -6002,7 +6067,7 @@ namespace DynamORM internal TableInfo() { } - + /// /// Initializes a new instance of the class. /// @@ -6015,11 +6080,11 @@ namespace DynamORM Name = name; Alias = alias; Owner = owner; - + if (!name.ContainsAny(StringExtensions.InvalidMemberChars)) Schema = db.GetSchema(name, owner: owner); } - + /// /// Initializes a new instance of the class. /// @@ -6030,29 +6095,29 @@ namespace DynamORM public TableInfo(DynamicDatabase db, Type type, string alias = null, string owner = null) { var mapper = DynamicMapperCache.GetMapper(type); - + Name = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? mapper.Type.Name : mapper.Table.Name; - + Owner = (mapper.Table != null) ? mapper.Table.Owner : owner; Alias = alias; - + Schema = db.GetSchema(type); } - + /// Gets or sets table owner name. public string Owner { get; internal set; } - + /// Gets or sets table name. public string Name { get; internal set; } - + /// Gets or sets table alias. public string Alias { get; internal set; } - + /// Gets or sets table schema. public Dictionary Schema { get; internal set; } } - + /// Generic based table information. /// Type of class that is represented in database. internal class TableInfo : TableInfo @@ -6068,38 +6133,38 @@ namespace DynamORM { } } - + #endregion TableInfo - + #region Parameter - + /// Interface describing parameter info. internal class Parameter : IParameter { /// Gets or sets the parameter position in command. /// Available after filling the command. public int Ordinal { get; internal set; } - + /// Gets or sets the parameter temporary name. public string Name { get; internal set; } - + /// Gets or sets the parameter value. public object Value { get; set; } - + /// Gets or sets a value indicating whether name of temporary parameter is well known. public bool WellKnown { get; set; } - + /// Gets or sets a value indicating whether this is virtual. public bool Virtual { get; set; } - + /// Gets or sets the parameter schema information. public DynamicSchemaColumn? Schema { get; set; } } - + #endregion Parameter - + #region Constructor - + /// /// Initializes a new instance of the class. /// @@ -6109,14 +6174,14 @@ namespace DynamORM VirtualMode = false; Tables = new List(); Parameters = new Dictionary(); - + WhereCondition = null; OpenBracketsCount = 0; - + Database = db; SupportSchema = (db.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema; } - + /// Initializes a new instance of the class. /// The database. /// The parent query. @@ -6125,51 +6190,51 @@ namespace DynamORM { _parent = parent; } - + #endregion Constructor - + #region IQueryWithWhere - + /// Gets or sets the where condition. public string WhereCondition { get; set; } - + /// Gets or sets the amount of not closed brackets in where statement. public int OpenBracketsCount { get; set; } - + #endregion IQueryWithWhere - + #region IDynamicQueryBuilder - + /// Gets instance. public DynamicDatabase Database { get; private set; } - + /// Gets the tables used in this builder. public IList Tables { get; private set; } - + /// Gets the tables used in this builder. public IDictionary Parameters { get; private set; } - + /// Gets or sets a value indicating whether add virtual parameters. public bool VirtualMode { get; set; } - - /// Gets or sets the on create temporary parameter action. + + /// Gets or sets the on create temporary parameter actions. /// This is exposed to allow setting schema of column. - public Action OnCreateTemporaryParameter { get; set; } - - /// Gets or sets the on create real parameter action. + public List> OnCreateTemporaryParameter { get; set; } + + /// Gets or sets the on create real parameter actions. /// This is exposed to allow modification of parameter. - public Action OnCreateParameter { get; set; } - + public List> OnCreateParameter { get; set; } + /// Gets a value indicating whether database supports standard schema. public bool SupportSchema { get; private set; } - + /// /// Generates the text this command will execute against the underlying database. /// /// The text to execute against the underlying database. /// This method must be override by derived classes. public abstract string CommandText(); - + /// Fill command with query. /// Command to fill. /// Filled instance of . @@ -6184,7 +6249,7 @@ namespace DynamORM OpenBracketsCount--; } } - + return command.SetCommand(CommandText() .FillStringWithVariables(s => { @@ -6193,21 +6258,21 @@ namespace DynamORM IDbDataParameter param = (IDbDataParameter)command .AddParameter(this, p.Schema, p.Value) .Parameters[command.Parameters.Count - 1]; - + (p as Parameter).Ordinal = command.Parameters.Count - 1; - + if (OnCreateParameter != null) - OnCreateParameter(p, param); - + 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 @@ -6219,89 +6284,110 @@ namespace DynamORM /// database. If false and the value is null, then an exception is thrown. /// If set to true decorate element. /// If set parse argument as alias. This is workaround for AS method. - /// This parameter is used to determine type of parameter used in query. /// A string containing the result of the parsing, along with the parameters extracted in the /// instance if such is given. /// Null nodes are not accepted. - internal virtual string Parse(object node, IDictionary pars = null, bool rawstr = false, bool nulls = false, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null) + 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, pars, decorate, columnSchema: columnSchema); + + 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, pars, decorate, columnSchema: columnSchema); + else return Dispatch(node, ref columnSchema, pars, decorate); } - + // If node is a delegate, parse it to create the logical tree... if (node is Delegate) { node = DynamicParser.Parse((Delegate)node).Result; - return Parse(node, pars, rawstr, decorate: decorate, columnSchema: columnSchema); // Intercept containers as in (x => "string") + return Parse(node, ref columnSchema, pars, rawstr, decorate: decorate); // Intercept containers as in (x => "string") } - - return Dispatch(node, pars, decorate, isMultiPart, columnSchema); + + return Dispatch(node, ref columnSchema, pars, decorate, isMultiPart); } - - private string Dispatch(object node, IDictionary pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null) + + 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, pars, decorate, isMultiPart, columnSchema); - else if (node is DynamicParser.Node.SetMember) return ParseSetMember((DynamicParser.Node.SetMember)node, pars, decorate, isMultiPart, columnSchema); + else if (node is DynamicParser.Node.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, pars); - else if (node is DynamicParser.Node.Invoke) return ParseInvoke((DynamicParser.Node.Invoke)node, 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) throw new InvalidOperationException(string.Format("The parameters in this command '{0}' cannot be added to a null collection.", node.Parameters)); - + // Copy parameters to new comand foreach (var parameter in node.Parameters) pars.Add(parameter.Key, parameter.Value); - + return string.Format("({0})", str); } - + protected virtual string ParseArgument(DynamicParser.Node.Argument node, bool isMultiPart = true, bool isOwner = false) { if (!string.IsNullOrEmpty(node.Name) && (isOwner || (isMultiPart && IsTableAlias(node.Name)))) return node.Name; - + return null; } - - protected virtual string ParseGetMember(DynamicParser.Node.GetMember node, IDictionary pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null) + + 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. @@ -6318,25 +6404,25 @@ namespace DynamORM else if (isMultiPart) parent = Parse(node.Host, pars, isMultiPart: isMultiPart); } - + ////string parent = node.Host == null || !isMultiPart ? null : Parse(node.Host, pars, isMultiPart: !IsTable(node.Name, node.Host.Name)); string name = parent == null ? decorate ? Database.DecorateName(node.Name) : node.Name : string.Format("{0}.{1}", parent, decorate ? Database.DecorateName(node.Name) : node.Name); - + columnSchema = GetColumnFromSchema(name); - + return name; } - - protected virtual string ParseSetMember(DynamicParser.Node.SetMember node, IDictionary pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null) + + 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) { @@ -6350,18 +6436,18 @@ namespace DynamORM else if (isMultiPart) parent = Parse(node.Host, pars, isMultiPart: isMultiPart); } - + ////string parent = node.Host == null || !isMultiPart ? null : Parse(node.Host, pars, isMultiPart: !IsTable(node.Name, node.Host.Name)); string name = parent == null ? decorate ? Database.DecorateName(node.Name) : node.Name : string.Format("{0}.{1}", parent, decorate ? Database.DecorateName(node.Name) : node.Name); - + columnSchema = GetColumnFromSchema(name); - - string value = Parse(node.Value, pars, nulls: true, columnSchema: columnSchema); + + 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) @@ -6369,19 +6455,19 @@ namespace DynamORM // 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... @@ -6391,35 +6477,35 @@ namespace DynamORM case ExpressionType.Divide: op = "/"; break; case ExpressionType.Modulo: op = "%"; break; case ExpressionType.Power: op = "^"; break; - + case ExpressionType.And: op = "AND"; break; case ExpressionType.Or: op = "OR"; break; - + // Logical comparisons... case ExpressionType.GreaterThan: op = ">"; break; case ExpressionType.GreaterThanOrEqual: op = ">="; break; case ExpressionType.LessThan: op = "<"; break; case ExpressionType.LessThanOrEqual: op = "<="; break; - + // Comparisons against 'NULL' require the 'IS' or 'IS NOT' operator instead the numeric ones... case ExpressionType.Equal: op = node.Right == null && !VirtualMode ? "IS" : "="; break; case ExpressionType.NotEqual: op = node.Right == null && !VirtualMode ? "IS NOT" : "<>"; break; - + default: throw new ArgumentException("Not supported operator: '" + node.Operation); } - + DynamicSchemaColumn? columnSchema = null; - string left = Parse(node.Left, pars, columnSchema: columnSchema); // Not nulls: left is assumed to be an object - string right = Parse(node.Right, pars, nulls: true, columnSchema: columnSchema); + 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, IDictionary pars = null) { string method = node.Name.ToUpper(); string parent = node.Host == null ? null : Parse(node.Host, pars: pars); string item = null; - + // Root-level methods... if (node.Host == null) { @@ -6431,7 +6517,7 @@ namespace DynamORM return string.Format("(NOT {0})", item); } } - + // Column-level methods... if (node.Host != null) { @@ -6441,47 +6527,47 @@ namespace DynamORM { if (node.Arguments == null || node.Arguments.Length == 0) throw new ArgumentException("BETWEEN method expects at least one argument: " + node.Arguments.Sketch()); - + if (node.Arguments.Length > 2) throw new ArgumentException("BETWEEN method expects at most two arguments: " + node.Arguments.Sketch()); - + var arguments = node.Arguments; - + if (arguments.Length == 1 && (arguments[0] is IEnumerable || arguments[0] is Array) && !(arguments[0] is byte[])) { var vals = arguments[0] as IEnumerable; - + if (vals == null && arguments[0] is Array) vals = ((Array)arguments[0]).Cast() as IEnumerable; - + if (vals != null) arguments = vals.ToArray(); else throw new ArgumentException("BETWEEN method expects single argument to be enumerable of exactly two elements: " + node.Arguments.Sketch()); } - + return string.Format("{0} BETWEEN {1} AND {2}", parent, Parse(arguments[0], pars: pars), Parse(arguments[1], pars: pars)); } - + case "IN": { if (node.Arguments == null || node.Arguments.Length == 0) throw new ArgumentException("IN method expects at least one argument: " + node.Arguments.Sketch()); - + bool firstParam = true; StringBuilder sbin = new StringBuilder(); foreach (var arg in node.Arguments) { if (!firstParam) sbin.Append(", "); - + if ((arg is IEnumerable || arg is Array) && !(arg is byte[])) { var vals = arg as IEnumerable; - + if (vals == null && arg is Array) vals = ((Array)arg).Cast() as IEnumerable; - + if (vals != null) foreach (var val in vals) { @@ -6489,7 +6575,7 @@ namespace DynamORM sbin.Append(", "); else firstParam = false; - + sbin.Append(Parse(val, pars: pars)); } else @@ -6497,86 +6583,91 @@ namespace DynamORM } else sbin.Append(Parse(arg, pars: pars)); - + firstParam = false; } - + return string.Format("{0} IN({1})", parent, sbin.ToString()); } - + case "LIKE": if (node.Arguments == null || node.Arguments.Length != 1) throw new ArgumentException("LIKE method expects one argument: " + node.Arguments.Sketch()); - + return string.Format("{0} LIKE {1}", parent, Parse(node.Arguments[0], pars: pars)); - + case "NOTLIKE": if (node.Arguments == null || node.Arguments.Length != 1) throw new ArgumentException("NOT LIKE method expects one argument: " + node.Arguments.Sketch()); - + return string.Format("{0} NOT LIKE {1}", parent, Parse(node.Arguments[0], pars: pars)); - + case "AS": if (node.Arguments == null || node.Arguments.Length != 1) throw new ArgumentException("AS method expects one argument: " + node.Arguments.Sketch()); - + item = Parse(node.Arguments[0], pars: null, rawstr: true, isMultiPart: false); // pars=null to avoid to parameterize aliases item = item.Validated("Alias"); // Intercepting null and empty aliases return string.Format("{0} AS {1}", parent, item); - + case "COUNT": if (node.Arguments != null && node.Arguments.Length > 1) throw new ArgumentException("COUNT method expects one or none argument: " + node.Arguments.Sketch()); - + if (node.Arguments == null || node.Arguments.Length == 0) return "COUNT(*)"; - + return string.Format("COUNT({0})", Parse(node.Arguments[0], pars: Parameters, nulls: true)); } } - + // Default case: parsing the method's name along with its arguments... method = parent == null ? node.Name : string.Format("{0}.{1}", parent, node.Name); StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0}(", method); - + if (node.Arguments != null && node.Arguments.Length != 0) { bool first = true; - + foreach (object argument in node.Arguments) { if (!first) sb.Append(", "); else first = false; - + sb.Append(Parse(argument, pars, nulls: true)); // We don't accept raw strings here!!! } } - + sb.Append(")"); return sb.ToString(); } - - protected virtual string ParseInvoke(DynamicParser.Node.Invoke node, IDictionary pars = null) + + 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, pars, rawstr: true, nulls: true)); + 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 @@ -6584,16 +6675,16 @@ namespace DynamORM 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 var par = new Parameter() { @@ -6603,60 +6694,60 @@ namespace DynamORM Virtual = VirtualMode, Schema = columnSchema, }; - + // If we are adding parameter we inform external sources about this. if (OnCreateTemporaryParameter != null) - OnCreateTemporaryParameter(par); - + 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) @@ -6664,67 +6755,66 @@ namespace DynamORM else return FixObjectNamePrivate(main, onlyColumn); } - + private string FixObjectNamePrivate(string f, bool onlyColumn = false) { var 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; - + ////if (colName.ContainsAny(StringExtensions.InvalidMultipartMemberChars)) + //// return null; + // First we need to get real column name and it's owner if exist. var parts = colName.Split('.') .Select(c => Database.StripName(c)) .ToArray(); - + var 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 var 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.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 } - - + /// Implementation of dynamic select query builder. internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere { @@ -6732,18 +6822,18 @@ namespace DynamORM 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; - + /// /// 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. /// @@ -6752,7 +6842,7 @@ namespace DynamORM : base(db) { } - + /// /// Initializes a new instance of the class. /// @@ -6762,7 +6852,7 @@ namespace DynamORM : 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() @@ -6778,14 +6868,14 @@ namespace DynamORM if (_orderby != null) sb.AppendFormat(" ORDER BY {0}", _orderby); if (_limit.HasValue) sb.AppendFormat(" LIMIT {0}", _limit); if (_offset.HasValue) sb.AppendFormat(" OFFSET {0}", _offset); - + return sb.ToString(); } - + #region Execution - + /*/// Execute this builder. - + /// Enumerator of objects expanded from query. public virtual IEnumerator GetEnumerator() { @@ -6798,7 +6888,7 @@ namespace DynamORM while (rdr.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 @@ -6809,23 +6899,23 @@ namespace DynamORM { var sb = new StringBuilder(); cmd.Dump(sb); - + throw new ArgumentException(string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb), argex.InnerException.NullOr(a => a, argex)); } - + yield return val; } } } - + /// Execute this builder. /// Enumerator of objects expanded from query. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }*/ - + /// Execute this builder. /// Enumerator of objects expanded from query. public virtual IEnumerable Execute() @@ -6838,7 +6928,7 @@ namespace DynamORM while (rdr.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 @@ -6849,25 +6939,25 @@ namespace DynamORM { var sb = new StringBuilder(); cmd.Dump(sb); - + throw new ArgumentException(string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb), argex.InnerException.NullOr(a => a, argex)); } - + yield return val; } } - + /// 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 { var mapper = DynamicMapperCache.GetMapper(); - + if (mapper == null) throw new InvalidOperationException("Type can't be mapped for unknown reason."); - + using (var con = Database.Open()) using (var cmd = con.CreateCommand()) { @@ -6877,7 +6967,7 @@ namespace DynamORM while (rdr.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 @@ -6888,16 +6978,16 @@ namespace DynamORM { var sb = new StringBuilder(); cmd.Dump(sb); - + throw new ArgumentException(string.Format("{0}{1}{2}", argex.Message, Environment.NewLine, sb), argex.InnerException.NullOr(a => a, argex)); } - + yield return mapper.Create(val) as T; } } } - + /// Execute this builder as a data reader. /// Action containing reader. public virtual void ExecuteDataReader(Action reader) @@ -6909,7 +6999,7 @@ namespace DynamORM .ExecuteReader()) reader(rdr); } - + /// Returns a single result. /// Result of a query. public virtual object Scalar() @@ -6922,11 +7012,11 @@ namespace DynamORM .ExecuteScalar(); } } - + #endregion Execution - + #region From/Join - + /// /// Adds to the 'From' clause the contents obtained by parsing the dynamic lambda expressions given. The supported /// formats are: @@ -6942,25 +7032,25 @@ namespace DynamORM { if (fn == null) throw new ArgumentNullException("Array of functions cannot be or contain null."); - + int index = FromFunc(-1, fn); foreach (var 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 (var parser = DynamicParser.Parse(f)) { var result = parser.Result; - + // If the expression result is string. if (result is string) { @@ -6977,24 +7067,24 @@ namespace DynamORM 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)); - + var 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 var node = (DynamicParser.Node)result; - + string owner = null; string main = null; string alias = null; Type type = null; - + while (true) { // Support for the AS() virtual method... @@ -7002,48 +7092,48 @@ namespace DynamORM { if (alias != null) throw new ArgumentException(string.Format("Alias '{0}' is already set when parsing '{1}'.", alias, result)); - + object[] args = ((DynamicParser.Node.Method)node).Arguments; - + if (args == null) throw new ArgumentNullException("arg", "AS() is not a parameterless method."); - + if (args.Length != 1) throw new ArgumentException("AS() requires one and only one parameter: " + args.Sketch()); - + alias = Parse(args[0], rawstr: true, decorate: false).Validated("Alias"); - + node = node.Host; continue; } - + /*if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "subquery") { main = Parse(this.SubQuery(((DynamicParser.Node.Method)node).Arguments.Where(p => p is Func).Cast>().ToArray()), Parameters); continue; }*/ - + // Support for table specifications... if (node is DynamicParser.Node.GetMember) { if (owner != null) throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); - + if (main != null) owner = ((DynamicParser.Node.GetMember)node).Name; else main = ((DynamicParser.Node.GetMember)node).Name; - + node = node.Host; continue; } - + // Support for generic sources... if (node is DynamicParser.Node.Invoke) { if (owner != null) throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); - + if (main != null) owner = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); else @@ -7054,66 +7144,66 @@ namespace DynamORM 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)); - + var mapper = DynamicMapperCache.GetMapper(type); - + if (mapper == null) throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}). Parsing {1}", type.FullName, result)); - + main = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? mapper.Type.Name : mapper.Table.Name; - + owner = (mapper.Table != null) ? mapper.Table.Owner : owner; } else main = string.Format("{0}", Parse(node, rawstr: true, pars: Parameters)); } - + node = node.Host; continue; } - + // Just finished the parsing... if (node is DynamicParser.Node.Argument) break; - + // All others are assumed to be part of the main element... if (main != null) main = Parse(node, pars: Parameters); else main = Parse(node, pars: Parameters); - + break; } - + if (!string.IsNullOrEmpty(main)) tableInfo = type == null ? new TableInfo(Database, main, alias, owner) : new TableInfo(Database, type, alias, owner); else throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); } - + // Or it is a not supported expression... if (tableInfo == null) throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); - + Tables.Add(tableInfo); - + // We finally add the contents... StringBuilder sb = new StringBuilder(); - + if (!string.IsNullOrEmpty(tableInfo.Owner)) sb.AppendFormat("{0}.", Database.DecorateName(tableInfo.Owner)); - + sb.Append(tableInfo.Name.ContainsAny(StringExtensions.InvalidMemberChars) ? tableInfo.Name : Database.DecorateName(tableInfo.Name)); - + if (!string.IsNullOrEmpty(tableInfo.Alias)) sb.AppendFormat(" AS {0}", tableInfo.Alias); - + _from = string.IsNullOrEmpty(_from) ? sb.ToString() : string.Format("{0}, {1}", _from, sb.ToString()); } - + return index; } - + /// /// Adds to the 'Join' clause the contents obtained by parsing the dynamic lambda expressions given. The supported /// formats are: @@ -7135,7 +7225,7 @@ namespace DynamORM // 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: @@ -7156,36 +7246,36 @@ namespace DynamORM 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 (var f in func) { index++; ITableInfo tableInfo = null; - + if (f == null) throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); - + using (var parser = DynamicParser.Parse(f)) { var result = parser.Result; if (result == null) throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); - + string type = null; string main = null; string owner = null; string alias = null; string condition = null; Type tableType = null; - + // If the expression resolves to a string... if (result is string) { var node = (string)result; - + int n = node.ToUpper().IndexOf("JOIN "); - + if (n < 0) main = node; else @@ -7194,15 +7284,15 @@ namespace DynamORM 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(); } - + var tuple = main.SplitSomethingAndAlias(); // In this case we split on the remaining 'main' var parts = tuple.Item1.Split('.'); main = Database.StripName(parts.Last()).Validated("Table"); @@ -7220,64 +7310,64 @@ namespace DynamORM { if (condition != null) throw new ArgumentException(string.Format("Condition '{0}' is already set when parsing '{1}'.", alias, result)); - + object[] args = ((DynamicParser.Node.Method)node).Arguments; if (args == null) throw new ArgumentNullException("arg", "ON() is not a parameterless method."); - + if (args.Length != 1) throw new ArgumentException("ON() requires one and only one parameter: " + args.Sketch()); - + condition = Parse(args[0], rawstr: true, pars: justAddTables ? null : Parameters); - + node = node.Host; continue; } - + // Support for the AS() virtual method... if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS") { if (alias != null) throw new ArgumentException(string.Format("Alias '{0}' is already set when parsing '{1}'.", alias, result)); - + object[] args = ((DynamicParser.Node.Method)node).Arguments; - + if (args == null) throw new ArgumentNullException("arg", "AS() is not a parameterless method."); - + if (args.Length != 1) throw new ArgumentException("AS() requires one and only one parameter: " + args.Sketch()); - + alias = Parse(args[0], rawstr: true, decorate: false, isMultiPart: false).Validated("Alias"); - + node = node.Host; continue; } - + // Support for table specifications... if (node is DynamicParser.Node.GetMember) { if (owner != null) throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result)); - + if (main != null) owner = ((DynamicParser.Node.GetMember)node).Name; else main = ((DynamicParser.Node.GetMember)node).Name; - + node = node.Host; continue; } - + // Support for Join Type specifications... if (node is DynamicParser.Node.Method && (node.Host is DynamicParser.Node.Argument || node.Host is DynamicParser.Node.Invoke)) { if (type != null) throw new ArgumentException(string.Format("Join type '{0}' is already set when parsing '{1}'.", main, result)); type = ((DynamicParser.Node.Method)node).Name; - + bool avoid = false; object[] args = ((DynamicParser.Node.Method)node).Arguments; - + if (args != null && args.Length > 0) { avoid = args[0] is bool && !((bool)args[0]); @@ -7285,7 +7375,7 @@ namespace DynamORM if (!string.IsNullOrEmpty(proposedType)) type = proposedType; } - + type = type.ToUpper(); // Normalizing, and stepping out the trivial case... if (type != "JOIN") { @@ -7294,13 +7384,13 @@ namespace DynamORM 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 { @@ -7311,17 +7401,17 @@ namespace DynamORM } } } - + 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 @@ -7331,23 +7421,23 @@ namespace DynamORM { tableType = (Type)invoke.Arguments[0]; var 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)); @@ -7358,17 +7448,17 @@ namespace DynamORM // Or it is a not supported expression... throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); } - + // We annotate the aliases being conservative... main = main.Validated("Main"); - + if (justAddTables) { if (!string.IsNullOrEmpty(main)) tableInfo = tableType == null ? new TableInfo(Database, main, alias, owner) : new TableInfo(Database, tableType, alias, owner); else throw new ArgumentException(string.Format("Specification #{0} is invalid: {1}", index, result)); - + Tables.Add(tableInfo); } else @@ -7377,37 +7467,37 @@ namespace DynamORM tableInfo = string.IsNullOrEmpty(alias) ? Tables.SingleOrDefault(t => t.Name == main && string.IsNullOrEmpty(t.Alias)) : Tables.SingleOrDefault(t => t.Alias == alias); - + // We finally add the contents if we can... StringBuilder sb = new StringBuilder(); if (string.IsNullOrEmpty(type)) type = "JOIN"; - + sb.AppendFormat("{0} ", type); - + if (!string.IsNullOrEmpty(tableInfo.Owner)) sb.AppendFormat("{0}.", Database.DecorateName(tableInfo.Owner)); - + sb.Append(tableInfo.Name.ContainsAny(StringExtensions.InvalidMemberChars) ? tableInfo.Name : Database.DecorateName(tableInfo.Name)); - + if (!string.IsNullOrEmpty(tableInfo.Alias)) sb.AppendFormat(" AS {0}", tableInfo.Alias); - + if (!string.IsNullOrEmpty(condition)) sb.AppendFormat(" ON {0}", condition); - + _join = string.IsNullOrEmpty(_join) ? sb.ToString() : string.Format("{0} {1}", _join, sb.ToString()); // No comma in this case } } } - + return this; } - + #endregion From/Join - + #region Where - + /// /// Adds to the 'Where' clause the contents obtained from parsing the dynamic lambda expression given. The condition /// is parsed to the appropriate syntax, where the specific customs virtual methods supported by the parser are used @@ -7422,7 +7512,7 @@ namespace DynamORM { return this.InternalWhere(func); } - + /// Add where condition. /// Condition column with operator and value. /// Builder instance. @@ -7430,7 +7520,7 @@ namespace DynamORM { return this.InternalWhere(column); } - + /// Add where condition. /// Condition column. /// Condition operator. @@ -7440,7 +7530,7 @@ namespace DynamORM { return this.InternalWhere(column, op, value); } - + /// Add where condition. /// Condition column. /// Condition value. @@ -7449,7 +7539,7 @@ namespace DynamORM { 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 @@ -7459,11 +7549,11 @@ namespace DynamORM { 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: @@ -7480,41 +7570,41 @@ namespace DynamORM { if (fn == null) throw new ArgumentNullException("Array of specifications cannot be null."); - + int index = SelectFunc(-1, fn); if (func != null) foreach (var 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 (var parser = DynamicParser.Parse(f)) { var result = parser.Result; if (result == null) throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); - + string main = null; string alias = null; bool all = false; bool anon = false; - + // If the expression resolves to a string... if (result is string) { var node = (string)result; var 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) @@ -7525,7 +7615,7 @@ namespace DynamORM else if (result.GetType().IsAnonymous()) { anon = true; - + foreach (var prop in result.ToDictionary()) { if (prop.Value is string) @@ -7533,7 +7623,7 @@ namespace DynamORM var node = (string)prop.Value; var 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) @@ -7546,7 +7636,7 @@ namespace DynamORM // 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); } @@ -7556,14 +7646,14 @@ namespace DynamORM // 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. @@ -7571,10 +7661,10 @@ namespace DynamORM { foreach (var 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 @@ -7584,11 +7674,11 @@ namespace DynamORM { return SelectColumn(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray()); } - + #endregion Select - + #region GroupBy - + /// /// Adds to the 'Group By' clause the contents obtained from from parsing the dynamic lambda expression given. /// @@ -7599,16 +7689,16 @@ namespace DynamORM { if (fn == null) throw new ArgumentNullException("Array of specifications cannot be null."); - + int index = GroupByFunc(-1, fn); - + if (func != null) foreach (var f in func) index = GroupByFunc(index, f); - + return this; } - + private int GroupByFunc(int index, Func f) { index++; @@ -7619,24 +7709,24 @@ namespace DynamORM var 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. @@ -7644,10 +7734,10 @@ namespace DynamORM { foreach (var col in columns) GroupBy(x => col.ToSQLGroupByColumn(Database)); - + return this; } - + /// Add select columns. /// Columns to group by. /// Column format consist of Column Name and @@ -7657,11 +7747,11 @@ namespace DynamORM { return GroupByColumn(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray()); } - + #endregion GroupBy - + #region OrderBy - + /// /// Adds to the 'Order By' clause the contents obtained from from parsing the dynamic lambda expression given. It /// accepts a multipart column specification followed by an optional Ascending() or Descending() virtual methods @@ -7675,41 +7765,41 @@ namespace DynamORM { if (fn == null) throw new ArgumentNullException("Array of specifications cannot be null."); - + int index = OrderByFunc(-1, fn); - + if (func != null) foreach (var f in func) 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 (var parser = DynamicParser.Parse(f)) { var result = parser.Result; if (result == null) throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); - + string main = null; bool ascending = true; - + if (result is int) main = result.ToString(); else if (result is string) { var 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 @@ -7726,9 +7816,9 @@ namespace DynamORM 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; @@ -7744,28 +7834,28 @@ namespace DynamORM 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. @@ -7773,10 +7863,10 @@ namespace DynamORM { foreach (var col in columns) OrderBy(x => col.ToSQLOrderByColumn(Database)); - + return this; } - + /// Add select columns. /// Columns to order by. /// Column format consist of Column Name and @@ -7786,11 +7876,11 @@ namespace DynamORM { 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. @@ -7798,11 +7888,11 @@ namespace DynamORM { if ((Database.Options & DynamicDatabaseOptions.SupportTop) != DynamicDatabaseOptions.SupportTop) throw new NotSupportedException("Database doesn't support TOP clause."); - + _top = top; return this; } - + /// Set top if database support it. /// How many objects select. /// Builder instance. @@ -7810,11 +7900,11 @@ namespace DynamORM { if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset) 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. @@ -7822,11 +7912,11 @@ namespace DynamORM { if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset) throw new NotSupportedException("Database doesn't support OFFSET clause."); - + _offset = offset; return this; } - + /// Set distinct mode. /// Distinct mode. /// Builder instance. @@ -7835,31 +7925,31 @@ namespace DynamORM _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; - + var node = (DynamicParser.Node)result; while (true) { @@ -7868,59 +7958,59 @@ namespace DynamORM { 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 var 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) @@ -7936,7 +8026,7 @@ namespace DynamORM else if (node.Host is DynamicParser.Node.Argument) { var table = ((DynamicParser.Node.Argument)node.Host).Name; - + if (IsTableAlias(table)) main = string.Format("{0}.{1}", table, Database.DecorateName(main)); else if (!IsTableAlias(main)) @@ -7944,62 +8034,61 @@ namespace DynamORM } 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 } - - + /// 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. @@ -8012,9 +8101,9 @@ namespace DynamORM string.IsNullOrEmpty(WhereCondition) ? string.Empty : " WHERE ", WhereCondition); } - + #region Update - + /// Add update value or where condition using schema. /// Update or where column name. /// Column value. @@ -8022,18 +8111,18 @@ namespace DynamORM 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. @@ -8042,58 +8131,58 @@ namespace DynamORM if (conditions is DynamicColumn) { var 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; } - + var dict = conditions.ToDictionary(); var mapper = DynamicMapperCache.GetMapper(conditions.GetType()); - + foreach (var 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; } } - + var 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: @@ -8106,7 +8195,7 @@ namespace DynamORM { if (func == null) throw new ArgumentNullException("Array of specifications cannot be null."); - + int index = -1; foreach (var f in func) { @@ -8114,23 +8203,23 @@ namespace DynamORM if (f == null) throw new ArgumentNullException(string.Format("Specification #{0} cannot be null.", index)); var result = DynamicParser.Parse(f).Result; - + if (result == null) throw new ArgumentException(string.Format("Specification #{0} resolves to null.", index)); - + string main = null; string value = null; string str = null; - + // When 'x => x.Table.Column = value' or 'x => x.Column = value'... if (result is DynamicParser.Node.SetMember) { var node = (DynamicParser.Node.SetMember)result; - + DynamicSchemaColumn? col = GetColumnFromSchema(node.Name); main = Database.DecorateName(node.Name); - value = Parse(node.Value, pars: Parameters, nulls: true, columnSchema: col); - + 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; @@ -8140,17 +8229,17 @@ namespace DynamORM Values(result); continue; } - + // Other specifications are considered invalid... var err = string.Format("Specification '{0}' is invalid.", result); str = Parse(result); if (str.Contains("=")) err += " May have you used a '==' instead of a '=' operator?"; throw new ArgumentException(err); } - + return this; } - + /// Add insert fields. /// Insert column. /// Insert value. @@ -8160,20 +8249,20 @@ namespace DynamORM if (value is DynamicColumn) { var 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. @@ -8183,19 +8272,19 @@ namespace DynamORM { var column = (DynamicColumn)o; DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName); - + string main = FixObjectName(column.ColumnName, onlyColumn: true); - string value = Parse(column.Value, pars: Parameters, nulls: true, columnSchema: col); - + string value = Parse(column.Value, ref col, pars: Parameters, nulls: true); + var str = string.Format("{0} = {1}", main, value); _columns = _columns == null ? str : string.Format("{0}, {1}", _columns, str); - + return this; } - + var dict = o.ToDictionary(); var mapper = DynamicMapperCache.GetMapper(o.GetType()); - + if (mapper != null) { foreach (var con in dict) @@ -8205,14 +8294,14 @@ namespace DynamORM else foreach (var 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 @@ -8227,7 +8316,7 @@ namespace DynamORM { return this.InternalWhere(func); } - + /// Add where condition. /// Condition column with operator and value. /// Builder instance. @@ -8235,7 +8324,7 @@ namespace DynamORM { return this.InternalWhere(column); } - + /// Add where condition. /// Condition column. /// Condition operator. @@ -8245,7 +8334,7 @@ namespace DynamORM { return this.InternalWhere(column, op, value); } - + /// Add where condition. /// Condition column. /// Condition value. @@ -8254,7 +8343,7 @@ namespace DynamORM { 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 @@ -8264,17 +8353,14 @@ namespace DynamORM { return this.InternalWhere(conditions, schema); } - + #endregion Where } - - } - } namespace Helpers - { + { /// Defines methods to support the comparison of collections for equality. /// The type of collection to compare. public class CollectionComparer : IEqualityComparer> @@ -8287,7 +8373,7 @@ namespace DynamORM { 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. @@ -8295,20 +8381,20 @@ namespace DynamORM { 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. @@ -8317,42 +8403,42 @@ namespace DynamORM { 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; - + var firstElementCounts = GetElementCounts(first, out firstCount); var secondElementCounts = GetElementCounts(second, out secondCount); - + if (firstCount != secondCount) return true; - + foreach (var kvp in firstElementCounts) if (kvp.Value != (secondElementCounts.TryGetNullable(kvp.Key) ?? 0)) return true; - + return false; } - + private static Dictionary GetElementCounts(IEnumerable enumerable, out int nullCount) { var dictionary = new Dictionary(); nullCount = 0; - + foreach (T element in enumerable) { if (element == null) @@ -8363,45 +8449,44 @@ namespace DynamORM dictionary[element] = ++count; } } - + return dictionary; } } - - + /// 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) { var binderType = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder"); - + if (binderType != null) { ParameterExpression param = Expression.Parameter(typeof(InvokeMemberBinder), "o"); - + return Expression.Lambda>>( Expression.TypeAs( Expression.Field( @@ -8412,16 +8497,16 @@ namespace DynamORM else { var inter = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); - + if (inter != null) { var prop = inter.GetProperty("TypeArguments"); - + if (!prop.CanRead) return null; - + var objParm = Expression.Parameter(typeof(InvokeMemberBinder), "o"); - + return Expression.Lambda>>( Expression.TypeAs( Expression.Property( @@ -8429,10 +8514,10 @@ namespace DynamORM typeof(IList)), objParm).Compile(); } } - + return null; } - + /// Extension method allowing to easily extract generic type /// arguments from assuming that it /// inherits from @@ -8449,16 +8534,16 @@ namespace DynamORM // 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. var 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; @@ -8467,29 +8552,28 @@ namespace DynamORM { // HACK: Using Reflection // In this case, we need more aerobic :D - + // First, get the interface var inter = binder.GetType().GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder"); - + if (inter != null) { // Now get property. var 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 { @@ -8501,8 +8585,7 @@ namespace DynamORM /// bool IsDisposed { get; } } - - + /// Class containing useful string extensions. internal static class StringExtensions { @@ -8511,20 +8594,20 @@ namespace DynamORM 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: @@ -8546,34 +8629,34 @@ namespace DynamORM { 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) @@ -8584,7 +8667,7 @@ namespace DynamORM if (method != null) ator = (IEnumerator)method.Invoke(obj, null); } - + if (ator != null) { if (brackets == null || brackets.Length < 2) brackets = "[]".ToCharArray(); @@ -8593,27 +8676,27 @@ namespace DynamORM 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 (var prop in props) @@ -8633,14 +8716,14 @@ namespace DynamORM } } } - + sb.AppendFormat("{0}", brackets[1]); } - + // And returning... return sb.ToString(); } - + /// /// Returns true if the target string contains any of the characters given. /// @@ -8651,12 +8734,12 @@ namespace DynamORM { 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. /// @@ -8684,14 +8767,14 @@ namespace DynamORM { // 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 @@ -8699,47 +8782,47 @@ namespace DynamORM 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 @@ -8750,11 +8833,11 @@ namespace DynamORM 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 @@ -8762,10 +8845,10 @@ namespace DynamORM 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. @@ -8777,30 +8860,29 @@ namespace DynamORM 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 { @@ -8822,7 +8904,7 @@ namespace DynamORM return obj != null && obj != DBNull.Value ? func(obj) : elseValue; } - + /// Easy way to use conditional value. /// Includes . /// Input object type to check. @@ -8843,17 +8925,16 @@ namespace DynamORM func(obj) : elseFunc != null ? elseFunc() : default(R); } } - 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. @@ -8862,7 +8943,7 @@ namespace DynamORM public class Node : IDynamicMetaObjectProvider, IExtendedDisposable, ISerializable { #region MetaNode - + /// /// Represents the dynamic binding and a binding logic of /// an object participating in the dynamic binding. @@ -8879,19 +8960,19 @@ namespace DynamORM : base(parameter, rest, value) { } - + private DynamicMetaObject GetBinder(Func newNodeFunc) { var o = (Node)this.Value; var node = newNodeFunc(o); o.Parser.Last = node; - + var p = Expression.Variable(typeof(Node), "ret"); var 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. /// @@ -8903,7 +8984,7 @@ namespace DynamORM { return GetBinder(x => new GetMember(x, binder.Name) { Parser = x.Parser }); } - + /// /// Performs the binding of the dynamic set member operation. /// @@ -8916,7 +8997,7 @@ namespace DynamORM { return GetBinder(x => new SetMember(x, binder.Name, value.Value) { Parser = x.Parser }); } - + /// /// Performs the binding of the dynamic get index operation. /// @@ -8929,7 +9010,7 @@ namespace DynamORM { return GetBinder(x => new GetIndex(x, indexes.Select(m => m.Value).ToArray()) { Parser = x.Parser }); } - + /// /// Performs the binding of the dynamic set index operation. /// @@ -8943,7 +9024,7 @@ namespace DynamORM { return GetBinder(x => new SetIndex(x, indexes.Select(m => m.Value).ToArray(), value.Value) { Parser = x.Parser }); } - + /// /// Performs the binding of the dynamic invoke operation. /// @@ -8956,7 +9037,7 @@ namespace DynamORM { return GetBinder(x => new Invoke(x, args.Select(m => m.Value).ToArray()) { Parser = x.Parser }); } - + /// /// Performs the binding of the dynamic invoke member operation. /// @@ -8969,7 +9050,7 @@ namespace DynamORM { return GetBinder(x => new Method(x, binder.Name, args.Select(m => m.Value).ToArray()) { Parser = x.Parser }); } - + /// /// Performs the binding of the dynamic binary operation. /// @@ -8982,7 +9063,7 @@ namespace DynamORM { return GetBinder(x => new Binary(x, binder.Operation, arg.Value) { Parser = x.Parser }); } - + /// /// Performs the binding of the dynamic unary operation. /// @@ -8995,20 +9076,20 @@ namespace DynamORM var o = (Node)this.Value; var 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; - + var p = Expression.Variable(ret.GetType(), "ret"); // the type is now obtained from "ret" var 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. /// @@ -9021,11 +9102,11 @@ namespace DynamORM var o = (Node)this.Value; var 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) @@ -9035,7 +9116,7 @@ namespace DynamORM else done = true; } - + // Creating an instance... if (binder.ReturnType == typeof(string)) ret = ret.ToString(); else @@ -9053,20 +9134,20 @@ namespace DynamORM ret = new object(); } } - + var p = Expression.Variable(binder.ReturnType, "ret"); var exp = Expression.Block( new ParameterExpression[] { p }, Expression.Assign(p, Expression.Constant(ret, binder.ReturnType))); // specifying binder.ReturnType - + return new MetaNode(exp, this.Restrictions, node); } } - + #endregion MetaNode - + #region Argument - + /// /// Describe a dynamic argument used in a dynamic lambda expression. /// @@ -9081,7 +9162,7 @@ namespace DynamORM : base(name) { } - + /// /// Initializes a new instance of the class. /// @@ -9091,7 +9172,7 @@ namespace DynamORM : base(info, context) { } - + /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() @@ -9101,11 +9182,11 @@ namespace DynamORM return Name; } } - + #endregion Argument - + #region GetMember - + /// /// Describe a 'get member' operation, as in 'x => x.Member'. /// @@ -9121,7 +9202,7 @@ namespace DynamORM : base(host, name) { } - + /// /// Initializes a new instance of the class. /// @@ -9131,7 +9212,7 @@ namespace DynamORM : base(info, context) { } - + /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() @@ -9141,11 +9222,11 @@ namespace DynamORM return string.Format("{0}.{1}", Host.Sketch(), Name.Sketch()); } } - + #endregion GetMember - + #region SetMember - + /// /// Describe a 'set member' operation, as in 'x => x.Member = y'. /// @@ -9157,7 +9238,7 @@ namespace DynamORM /// assigned to this instance, or if this instance is disposed. /// public object Value { get; private set; } - + /// /// Initializes a new instance of the class. /// @@ -9169,7 +9250,7 @@ namespace DynamORM { Value = value; } - + /// /// Initializes a new instance of the class. /// @@ -9181,7 +9262,7 @@ namespace DynamORM string type = info.GetString("MemberType"); Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); } - + /// /// Gets the object data. /// @@ -9192,10 +9273,10 @@ namespace DynamORM 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() @@ -9205,11 +9286,11 @@ namespace DynamORM return string.Format("({0}.{1} = {2})", Host.Sketch(), Name.Sketch(), Value.Sketch()); } } - + #endregion SetMember - + #region GetIndex - + /// /// Describe a 'get indexed' operation, as in 'x => x.Member[...]'. /// @@ -9218,7 +9299,7 @@ namespace DynamORM { /// Gets the indexes. public object[] Indexes { get; internal set; } - + /// /// Initializes a new instance of the class. /// @@ -9233,10 +9314,10 @@ namespace DynamORM 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. /// @@ -9246,7 +9327,7 @@ namespace DynamORM : 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++) @@ -9257,7 +9338,7 @@ namespace DynamORM } } } - + /// /// Gets the object data. /// @@ -9271,25 +9352,25 @@ namespace DynamORM 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()); } } - + #endregion GetIndex - + #region SetIndex - + /// /// Describe a 'set indexed' operation, as in 'x => x.Member[...] = Value'. /// @@ -9301,7 +9382,7 @@ namespace DynamORM /// assigned to this instance, or if this instance is disposed. /// public object Value { get; private set; } - + /// /// Initializes a new instance of the class. /// @@ -9313,7 +9394,7 @@ namespace DynamORM { Value = value; } - + /// /// Initializes a new instance of the class. /// @@ -9325,7 +9406,7 @@ namespace DynamORM string type = info.GetString("MemberType"); Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); } - + /// /// Gets the object data. /// @@ -9335,25 +9416,25 @@ namespace DynamORM { 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()); } } - + #endregion SetIndex - + #region Invoke - + /// /// Describe a method invocation operation, as in 'x => x.Method(...)". /// @@ -9362,7 +9443,7 @@ namespace DynamORM { /// Gets the arguments. public object[] Arguments { get; internal set; } - + /// /// Initializes a new instance of the class. /// @@ -9373,7 +9454,7 @@ namespace DynamORM { Arguments = arguments == null || arguments.Length == 0 ? null : arguments; } - + /// /// Initializes a new instance of the class. /// @@ -9383,7 +9464,7 @@ namespace DynamORM : 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++) @@ -9394,7 +9475,7 @@ namespace DynamORM } } } - + /// /// Gets the object data. /// @@ -9408,25 +9489,25 @@ namespace DynamORM 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::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(...)". /// @@ -9435,7 +9516,7 @@ namespace DynamORM { /// Gets the arguments. public object[] Arguments { get; internal set; } - + /// /// Initializes a new instance of the class. /// @@ -9447,7 +9528,7 @@ namespace DynamORM { Arguments = arguments == null || arguments.Length == 0 ? null : arguments; } - + /// /// Initializes a new instance of the class. /// @@ -9457,7 +9538,7 @@ namespace DynamORM : 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++) @@ -9468,7 +9549,7 @@ namespace DynamORM } } } - + /// /// Gets the object data. /// @@ -9482,25 +9563,25 @@ namespace DynamORM 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())); } } - + #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 @@ -9511,13 +9592,13 @@ namespace DynamORM { /// 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. /// @@ -9530,7 +9611,7 @@ namespace DynamORM Operation = operation; Right = right; } - + /// /// Initializes a new instance of the class. /// @@ -9540,11 +9621,11 @@ namespace DynamORM : 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. /// @@ -9553,29 +9634,29 @@ namespace DynamORM 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()); } } - + #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. @@ -9585,10 +9666,10 @@ namespace DynamORM { /// Gets the operation. public ExpressionType Operation { get; private set; } - + /// Gets host of the . public Node Target { get { return Host; } } - + /// /// Initializes a new instance of the class. /// @@ -9599,7 +9680,7 @@ namespace DynamORM { Operation = operation; } - + /// /// Initializes a new instance of the class. /// @@ -9610,7 +9691,7 @@ namespace DynamORM { Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); } - + /// /// Gets the object data. /// @@ -9619,25 +9700,25 @@ namespace DynamORM 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()); } } - + #endregion Unary - + #region Convert - + /// /// Represents a conversion operation, as in 'x => (string)x'. /// @@ -9646,10 +9727,10 @@ namespace DynamORM { /// 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. /// @@ -9660,7 +9741,7 @@ namespace DynamORM { NewType = newType; } - + /// /// Initializes a new instance of the class. /// @@ -9671,7 +9752,7 @@ namespace DynamORM { NewType = (Type)info.GetValue("NewType", typeof(Type)); } - + /// /// Gets the object data. /// @@ -9680,24 +9761,24 @@ namespace DynamORM 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; internal set; } - + /// /// Initializes a new instance of the class. /// @@ -9705,7 +9786,7 @@ namespace DynamORM { IsDisposed = false; } - + /// /// Initializes a new instance of the class. /// @@ -9715,10 +9796,10 @@ namespace DynamORM { if (host == null) throw new ArgumentNullException("host", "Host cannot be null."); - + Host = host; } - + /// /// Initializes a new instance of the class. /// @@ -9728,7 +9809,7 @@ namespace DynamORM { Name = name.Validated("Name"); } - + /// /// Initializes a new instance of the class. /// @@ -9740,7 +9821,7 @@ namespace DynamORM { Name = name.Validated("Name"); } - + /// /// Initializes a new instance of the class. /// @@ -9749,23 +9830,23 @@ namespace DynamORM 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 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. @@ -9775,20 +9856,20 @@ namespace DynamORM { if (IsDisposed) throw new ObjectDisposedException("DynamicParser.Node"); - + return new MetaNode( parameter, BindingRestrictions.GetInstanceRestriction(parameter, this), this); } - + #endregion Implementation of IDynamicMetaObjectProvider - + #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. /// @@ -9796,11 +9877,11 @@ namespace DynamORM { IsDisposed = true; } - + #endregion Implementation of IExtendedDisposable - + #region Implementation of ISerializable - + /// /// Populates a with the data needed to serialize the target object. /// @@ -9810,29 +9891,29 @@ namespace DynamORM { 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 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. /// @@ -9843,15 +9924,15 @@ namespace DynamORM List list = new List(); if (!IsDisposed && _arguments != null) list.AddRange(_arguments); - + foreach (var arg in list) yield return arg; - + list.Clear(); list = null; } } - + /// /// Gets the number of dynamic arguments used in the dynamic lambda expression parsed. /// @@ -9859,7 +9940,7 @@ namespace DynamORM { 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 @@ -9869,9 +9950,9 @@ namespace DynamORM { get { return _uncertainResult ?? Last; } } - + #endregion Properties - + private DynamicParser(Delegate f) { foreach (var p in f.Method.GetParameters()) @@ -9881,10 +9962,10 @@ namespace DynamORM else throw new ArgumentException(string.Format("Argument '{0}' must be dynamic.", p.Name)); } - + _uncertainResult = f.DynamicInvoke(_arguments.ToArray()); } - + /// /// 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 @@ -9896,19 +9977,19 @@ namespace DynamORM { 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 (var arg in _arguments) @@ -9917,19 +9998,19 @@ namespace DynamORM 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. /// @@ -9937,11 +10018,10 @@ namespace DynamORM { IsDisposed = true; } - + #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. @@ -9951,7 +10031,7 @@ namespace DynamORM private Type _type; private Dictionary _properties; private Dictionary _methods; - + /// /// Initializes a new instance of the class. /// @@ -9961,18 +10041,18 @@ namespace DynamORM { if (proxiedObject == null) throw new ArgumentNullException("proxiedObject"); - + _proxy = proxiedObject; _type = typeof(T); - + var members = GetAllMembers(_type); - + _properties = members .Where(x => x is PropertyInfo) .ToDictionary( k => k.Name, v => new DynamicPropertyInvoker((PropertyInfo)v, null)); - + _methods = members .Where(x => x is MethodInfo) .Cast() @@ -9992,7 +10072,7 @@ namespace DynamORM } }); } - + /// Provides implementation for type conversion operations. /// Classes derived from the /// class can override this method to specify dynamic behavior for @@ -10017,17 +10097,17 @@ namespace DynamORM 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 @@ -10052,9 +10132,9 @@ namespace DynamORM try { var 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) @@ -10062,7 +10142,7 @@ namespace DynamORM 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 @@ -10087,13 +10167,13 @@ namespace DynamORM try { var prop = _properties.TryGetValue(binder.Name); - + if (prop != null && prop.Set != null) { prop.Set(_proxy, value); return true; } - + return false; } catch (Exception ex) @@ -10101,7 +10181,7 @@ namespace DynamORM 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 @@ -10127,165 +10207,162 @@ namespace DynamORM { 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) { var members = new List(); - + var considered = new List(); var queue = new Queue(); - + considered.Add(type); queue.Enqueue(type); - + while (queue.Count > 0) { var subType = queue.Dequeue(); foreach (var subInterface in subType.GetInterfaces()) { if (considered.Contains(subInterface)) continue; - + considered.Add(subInterface); queue.Enqueue(subInterface); } - + var typeProperties = subType.GetMembers( BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance); - + var newPropertyInfos = typeProperties .Where(x => !members.Contains(x)); - + members.InsertRange(0, newPropertyInfos); } - + return members; } - + return type.GetMembers(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance); } } - - } - } 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 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() { } - + /// Initializes a new instance of the class. /// Name of column. public ColumnAttribute(string name) { Name = name; } - + /// Initializes a new instance of the class. /// Set column as a key column. public ColumnAttribute(bool isKey) { IsKey = isKey; } - + /// Initializes a new instance of the class. /// Name of column. /// Set column as a key column. @@ -10294,7 +10371,7 @@ namespace DynamORM { IsKey = isKey; } - + /// Initializes a new instance of the class. /// Set column as a key column. /// Set column type. @@ -10303,7 +10380,7 @@ namespace DynamORM { Type = type; } - + /// Initializes a new instance of the class. /// Name of column. /// Set column as a key column. @@ -10313,7 +10390,7 @@ namespace DynamORM { Type = type; } - + /// Initializes a new instance of the class. /// Name of column. /// Set column as a key column. @@ -10324,7 +10401,7 @@ namespace DynamORM { Size = size; } - + /// Initializes a new instance of the class. /// Name of column. /// Set column as a key column. @@ -10337,7 +10414,7 @@ namespace DynamORM Precision = precision; Scale = scale; } - + /// Initializes a new instance of the class. /// Name of column. /// Set column as a key column. @@ -10350,7 +10427,7 @@ namespace DynamORM { Size = size; } - + /// Initializes a new instance of the class. /// Name of column. /// Set column as a key column. @@ -10364,17 +10441,16 @@ namespace DynamORM { IsUnique = isUnique; } - + #endregion Constructors } - - + /// 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. @@ -10382,7 +10458,7 @@ namespace DynamORM { return GetMapper(typeof(T)); } - + /// Get type mapper. /// Type of mapper. /// Type mapper. @@ -10392,55 +10468,54 @@ namespace DynamORM 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; } } - - + /// 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 type of property. public Type Type { get; private set; } - + /// Gets value getter. public Func Get { get; private set; } - + /// Gets value setter. public Action Set { get; private set; } - + /// Gets name of property. public string Name { get; private set; } - + /// Gets type column description. public ColumnAttribute Column { get; private set; } - + /// Gets a value indicating whether this is ignored in some cases. public bool Ignore { get; private set; } - + /// Initializes a new instance of the class. /// Property info to be invoked in the future. /// Column attribute if exist. @@ -10448,27 +10523,27 @@ namespace DynamORM { Name = property.Name; Type = property.PropertyType; - + var ignore = property.GetCustomAttributes(typeof(IgnoreAttribute), false); - + Ignore = ignore != null && ignore.Length > 0; - + Column = attr; - + if (property.CanRead) Get = CreateGetter(property); - + if (property.CanWrite) Set = CreateSetter(property); } - + private Func CreateGetter(PropertyInfo property) { if (!property.CanRead) return null; - + var objParm = Expression.Parameter(typeof(object), "o"); - + return Expression.Lambda>( Expression.Convert( Expression.Property( @@ -10476,15 +10551,15 @@ namespace DynamORM 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( @@ -10493,97 +10568,96 @@ namespace DynamORM Expression.Convert(valueParm, property.PropertyType)), objParm, valueParm).Compile(); } - + #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; - + var attr = type.GetCustomAttributes(typeof(TableAttribute), false); - + if (attr != null && attr.Length > 0) Table = (TableAttribute)attr[0]; - + Creator = CreateCreator(); CreateColumnAndPropertyMap(); } - + private void CreateColumnAndPropertyMap() { var columnMap = new Dictionary(); var propertyMap = new Dictionary(); var ignored = new List(); - + foreach (var pi in Type.GetProperties()) { ColumnAttribute attr = null; - + var 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; - + var 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() { if (Type.GetConstructor(Type.EmptyTypes) != 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. @@ -10591,7 +10665,7 @@ namespace DynamORM { return Map(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. @@ -10599,56 +10673,50 @@ namespace DynamORM public object Map(object source, object destination) { DynamicPropertyInvoker dpi = null; - + foreach (var item in source.ToDictionary()) { if (ColumnsMap.TryGetValue(item.Key.ToLower(), out dpi) && item.Value != null) if (dpi.Set != null) dpi.Set(destination, item.Value); } - + return destination; } - + #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; } } - - } - -} - +} \ No newline at end of file diff --git a/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs b/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs index 89a0f46..d9adde3 100644 --- a/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs +++ b/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs @@ -113,6 +113,17 @@ namespace DynamORM.Builders.Extensions 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) @@ -145,6 +156,7 @@ namespace DynamORM.Builders.Extensions 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; diff --git a/DynamORM/Builders/IDynamicQueryBuilder.cs b/DynamORM/Builders/IDynamicQueryBuilder.cs index 5a5f179..1869142 100644 --- a/DynamORM/Builders/IDynamicQueryBuilder.cs +++ b/DynamORM/Builders/IDynamicQueryBuilder.cs @@ -63,12 +63,12 @@ namespace DynamORM.Builders /// This method must be override by derived classes. string CommandText(); - /// Gets or sets the on create temporary parameter action. + /// Gets or sets the on create temporary parameter actions. /// This is exposed to allow setting schema of column. - Action OnCreateTemporaryParameter { get; set; } + List> OnCreateTemporaryParameter { get; set; } - /// Gets or sets the on create real parameter action. + /// Gets or sets the on create real parameter actions. /// This is exposed to allow modification of parameter. - Action OnCreateParameter { get; set; } + List> OnCreateParameter { get; set; } } } \ No newline at end of file diff --git a/DynamORM/Builders/Implementation/DynamicInsertQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicInsertQueryBuilder.cs index 5b890d9..69b524e 100644 --- a/DynamORM/Builders/Implementation/DynamicInsertQueryBuilder.cs +++ b/DynamORM/Builders/Implementation/DynamicInsertQueryBuilder.cs @@ -121,7 +121,7 @@ namespace DynamORM.Builders.Implementation DynamicSchemaColumn? col = GetColumnFromSchema(node.Name); main = Database.DecorateName(node.Name); - value = Parse(node.Value, pars: Parameters, nulls: true, columnSchema: col); + value = Parse(node.Value, ref col, pars: Parameters, nulls: true); _columns = _columns == null ? main : string.Format("{0}, {1}", _columns, main); _values = _values == null ? value : string.Format("{0}, {1}", _values, value); @@ -175,7 +175,7 @@ namespace DynamORM.Builders.Implementation DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName); string main = FixObjectName(column.ColumnName, onlyColumn: true); - string value = Parse(column.Value, pars: Parameters, nulls: true, columnSchema: col); + 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); diff --git a/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs index 5e9875b..82071f0 100644 --- a/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs +++ b/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs @@ -217,13 +217,13 @@ namespace DynamORM.Builders.Implementation /// Gets or sets a value indicating whether add virtual parameters. public bool VirtualMode { get; set; } - /// Gets or sets the on create temporary parameter action. + /// Gets or sets the on create temporary parameter actions. /// This is exposed to allow setting schema of column. - public Action OnCreateTemporaryParameter { get; set; } + public List> OnCreateTemporaryParameter { get; set; } - /// Gets or sets the on create real parameter action. + /// Gets or sets the on create real parameter actions. /// This is exposed to allow modification of parameter. - public Action OnCreateParameter { get; set; } + public List> OnCreateParameter { get; set; } /// Gets a value indicating whether database supports standard schema. public bool SupportSchema { get; private set; } @@ -262,7 +262,7 @@ namespace DynamORM.Builders.Implementation (p as Parameter).Ordinal = command.Parameters.Count - 1; if (OnCreateParameter != null) - OnCreateParameter(p, param); + OnCreateParameter.ForEach(x => x(p, param)); return param.ParameterName; }, s); @@ -284,11 +284,32 @@ namespace DynamORM.Builders.Implementation /// database. If false and the value is null, then an exception is thrown. /// If set to true decorate element. /// If set parse argument as alias. This is workaround for AS method. - /// This parameter is used to determine type of parameter used in query. /// A string containing the result of the parsing, along with the parameters extracted in the /// instance if such is given. /// Null nodes are not accepted. - internal virtual string Parse(object node, IDictionary pars = null, bool rawstr = false, bool nulls = false, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null) + 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) @@ -296,38 +317,38 @@ namespace DynamORM.Builders.Implementation if (!nulls) throw new ArgumentNullException("node", "Null nodes are not accepted."); - return Dispatch(node, pars, decorate, columnSchema: columnSchema); + 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, pars, decorate, columnSchema: columnSchema); + else return Dispatch(node, ref columnSchema, pars, decorate); } // If node is a delegate, parse it to create the logical tree... if (node is Delegate) { node = DynamicParser.Parse((Delegate)node).Result; - return Parse(node, pars, rawstr, decorate: decorate, columnSchema: columnSchema); // Intercept containers as in (x => "string") + return Parse(node, ref columnSchema, pars, rawstr, decorate: decorate); // Intercept containers as in (x => "string") } - return Dispatch(node, pars, decorate, isMultiPart, columnSchema); + return Dispatch(node, ref columnSchema, pars, decorate, isMultiPart); } - private string Dispatch(object node, IDictionary pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null) + 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, pars, decorate, isMultiPart, columnSchema); - else if (node is DynamicParser.Node.SetMember) return ParseSetMember((DynamicParser.Node.SetMember)node, pars, decorate, isMultiPart, columnSchema); + else if (node is DynamicParser.Node.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, pars); - else if (node is DynamicParser.Node.Invoke) return ParseInvoke((DynamicParser.Node.Invoke)node, 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); } @@ -359,7 +380,7 @@ namespace DynamORM.Builders.Implementation return null; } - protected virtual string ParseGetMember(DynamicParser.Node.GetMember node, IDictionary pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = 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)) { @@ -394,7 +415,7 @@ namespace DynamORM.Builders.Implementation return name; } - protected virtual string ParseSetMember(DynamicParser.Node.SetMember node, IDictionary pars = null, bool decorate = true, bool isMultiPart = true, DynamicSchemaColumn? columnSchema = null) + 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)) { @@ -423,7 +444,7 @@ namespace DynamORM.Builders.Implementation columnSchema = GetColumnFromSchema(name); - string value = Parse(node.Value, pars, nulls: true, columnSchema: columnSchema); + string value = Parse(node.Value, ref columnSchema, pars, nulls: true); return string.Format("{0} = ({1})", name, value); } @@ -474,8 +495,8 @@ namespace DynamORM.Builders.Implementation } DynamicSchemaColumn? columnSchema = null; - string left = Parse(node.Left, pars, columnSchema: columnSchema); // Not nulls: left is assumed to be an object - string right = Parse(node.Right, pars, nulls: true, columnSchema: columnSchema); + 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); } @@ -624,7 +645,7 @@ namespace DynamORM.Builders.Implementation return sb.ToString(); } - protected virtual string ParseInvoke(DynamicParser.Node.Invoke node, IDictionary pars = null) + 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) @@ -634,9 +655,14 @@ namespace DynamORM.Builders.Implementation 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, pars, rawstr: true, nulls: true)); + sb.Append(Parse(arg, ref columnSchema, pars, rawstr: true, nulls: true)); } return sb.ToString(); @@ -671,7 +697,7 @@ namespace DynamORM.Builders.Implementation // If we are adding parameter we inform external sources about this. if (OnCreateTemporaryParameter != null) - OnCreateTemporaryParameter(par); + OnCreateTemporaryParameter.ForEach(x => x(par)); pars.Add(par.Name, par); @@ -748,8 +774,8 @@ namespace DynamORM.Builders.Implementation 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; + ////if (colName.ContainsAny(StringExtensions.InvalidMultipartMemberChars)) + //// return null; // First we need to get real column name and it's owner if exist. var parts = colName.Split('.') diff --git a/DynamORM/Builders/Implementation/DynamicUpdateQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicUpdateQueryBuilder.cs index 37924f6..aececfb 100644 --- a/DynamORM/Builders/Implementation/DynamicUpdateQueryBuilder.cs +++ b/DynamORM/Builders/Implementation/DynamicUpdateQueryBuilder.cs @@ -181,7 +181,7 @@ namespace DynamORM.Builders.Implementation DynamicSchemaColumn? col = GetColumnFromSchema(node.Name); main = Database.DecorateName(node.Name); - value = Parse(node.Value, pars: Parameters, nulls: true, columnSchema: col); + 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); @@ -237,7 +237,7 @@ namespace DynamORM.Builders.Implementation DynamicSchemaColumn? col = column.Schema ?? GetColumnFromSchema(column.ColumnName); string main = FixObjectName(column.ColumnName, onlyColumn: true); - string value = Parse(column.Value, pars: Parameters, nulls: true, columnSchema: col); + string value = Parse(column.Value, ref col, pars: Parameters, nulls: true); var str = string.Format("{0} = {1}", main, value); _columns = _columns == null ? str : string.Format("{0}, {1}", _columns, str); diff --git a/DynamORM/DynamicCommand.cs b/DynamORM/DynamicCommand.cs index 22f0198..2ac641a 100644 --- a/DynamORM/DynamicCommand.cs +++ b/DynamORM/DynamicCommand.cs @@ -89,7 +89,10 @@ namespace DynamORM ////_poolStamp = _db.PoolStamp; } - return _db.DumpCommands ? _command.Dump(Console.Out) : _command; + if (_db.DumpCommands) + _db.DumpCommand(_command); + + return _command; } #region IDbCommand Members diff --git a/DynamORM/DynamicDatabase.cs b/DynamORM/DynamicDatabase.cs index 2d76581..6d68da4 100644 --- a/DynamORM/DynamicDatabase.cs +++ b/DynamORM/DynamicDatabase.cs @@ -1187,6 +1187,13 @@ namespace DynamORM sb.AppendFormat(_parameterFormat, parameter.ToString().Replace(" ", "_")); } + /// Dumps the command into console output. + /// The command to dump. + public virtual void DumpCommand(IDbCommand cmd) + { + cmd.Dump(Console.Out); + } + #endregion Decorators #region Connection diff --git a/DynamORM/DynamicExtensions.cs b/DynamORM/DynamicExtensions.cs index e6b3c01..eda5b1c 100644 --- a/DynamORM/DynamicExtensions.cs +++ b/DynamORM/DynamicExtensions.cs @@ -816,7 +816,16 @@ namespace DynamORM /// Returns instance of builder on which action is set. public static T CreateTemporaryParameterAction(this T b, Action a) where T : IDynamicQueryBuilder { - b.OnCreateTemporaryParameter = a; + if (a == null) + b.OnCreateTemporaryParameter = null; + else + { + if (b.OnCreateTemporaryParameter == null) + b.OnCreateTemporaryParameter = new List>(); + + b.OnCreateTemporaryParameter.Add(a); + } + return b; } @@ -827,7 +836,16 @@ namespace DynamORM /// Returns instance of builder on which action is set. public static T CreateParameterAction(this T b, Action a) where T : IDynamicQueryBuilder { - b.OnCreateParameter = a; + if (a == null) + b.OnCreateParameter = null; + else + { + if (b.OnCreateParameter == null) + b.OnCreateParameter = new List>(); + + b.OnCreateParameter.Add(a); + } + return b; } diff --git a/DynamORM/DynamicTable.cs b/DynamORM/DynamicTable.cs index ab9824c..d6d34fb 100644 --- a/DynamORM/DynamicTable.cs +++ b/DynamORM/DynamicTable.cs @@ -351,7 +351,8 @@ namespace DynamORM using (var con = Database.Open()) using (var cmd = con.CreateCommand()) using (var rdr = cmd - .SetCommand(sql, args) + .SetCommand(sql) + .AddParameters(Database, args) .ExecuteReader()) while (rdr.Read()) {