diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs index 4a90825..16c35e6 100644 --- a/AmalgamationTool/DynamORM.Amalgamation.cs +++ b/AmalgamationTool/DynamORM.Amalgamation.cs @@ -6674,6 +6674,47 @@ namespace DynamORM #endregion GroupBy + #region Having + + /// + /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Having() methods are chained their contents are, by default, concatenated with an 'AND' operator. + /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: + /// 'Having( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicSelectQueryBuilder Having(Func func); + + /// Add Having condition. + /// Condition column with operator and value. + /// Builder instance. + IDynamicSelectQueryBuilder Having(DynamicColumn column); + + /// Add Having condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value); + + /// Add Having condition. + /// Condition column. + /// Condition value. + /// Builder instance. + IDynamicSelectQueryBuilder Having(string column, object value); + + /// Add Having condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + IDynamicSelectQueryBuilder Having(object conditions, bool schema = false); + + #endregion Having + #region OrderBy /// @@ -6857,6 +6898,211 @@ namespace DynamORM namespace Extensions { + internal static class DynamicHavingQueryExtensions + { + #region Where + + internal static T InternalHaving(this T builder, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + return builder.InternalHaving(false, false, func); + } + + internal static T InternalHaving(this T builder, bool addBeginBrace, bool addEndBrace, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + if (func == null) throw new ArgumentNullException("Array of functions cannot be null."); + + using (DynamicParser parser = DynamicParser.Parse(func)) + { + string condition = null; + bool and = true; + + object result = parser.Result; + if (result is string) + { + condition = (string)result; + + if (condition.ToUpper().IndexOf("OR") == 0) + { + and = false; + condition = condition.Substring(3); + } + else if (condition.ToUpper().IndexOf("AND") == 0) + condition = condition.Substring(4); + } + else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType) + return builder.InternalHaving(result); + else + { + // Intercepting the 'x => x.And()' and 'x => x.Or()' virtual methods... + if (result is DynamicParser.Node.Method && ((DynamicParser.Node.Method)result).Host is DynamicParser.Node.Argument) + { + DynamicParser.Node.Method node = (DynamicParser.Node.Method)result; + string name = node.Name.ToUpper(); + if (name == "AND" || name == "OR") + { + object[] args = ((DynamicParser.Node.Method)node).Arguments; + if (args == null) throw new ArgumentNullException("arg", string.Format("{0} is not a parameterless method.", name)); + if (args.Length != 1) throw new ArgumentException(string.Format("{0} requires one and only one parameter: {1}.", name, args.Sketch())); + + and = name == "AND" ? true : false; + result = args[0]; + } + } + + // Just parsing the contents now... + condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition"); + } + + if (addBeginBrace) builder.HavingOpenBracketsCount++; + if (addEndBrace) builder.HavingOpenBracketsCount--; + + if (builder.HavingCondition == null) + builder.HavingCondition = string.Format("{0}{1}{2}", + addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); + else + builder.HavingCondition = string.Format("{0} {1} {2}{3}{4}", builder.HavingCondition, and ? "AND" : "OR", + addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); + } + + return builder; + } + + internal static T InternalHaving(this T builder, DynamicColumn column) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + bool virt = builder.VirtualMode; + if (column.VirtualColumn.HasValue) + builder.VirtualMode = column.VirtualColumn.Value; + + Action modParam = (p) => + { + if (column.Schema.HasValue) + p.Schema = column.Schema; + + if (!p.Schema.HasValue) + p.Schema = column.Schema ?? builder.GetColumnFromSchema(column.ColumnName); + }; + + builder.CreateTemporaryParameterAction(modParam); + + // It's kind of uglu, but... well it works. + if (column.Or) + switch (column.Operator) + { + default: + case DynamicColumn.CompareOperator.Eq: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break; + case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break; + case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break; + case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); break; + case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break; + case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break; + case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break; + case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break; + case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break; + case DynamicColumn.CompareOperator.Between: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Between(column.Value))); break; + } + else + switch (column.Operator) + { + default: + case DynamicColumn.CompareOperator.Eq: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break; + case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break; + case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break; + case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break; + case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break; + case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break; + case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break; + case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break; + case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break; + case DynamicColumn.CompareOperator.Between: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break; + } + + builder.OnCreateTemporaryParameter.Remove(modParam); + builder.VirtualMode = virt; + + return builder; + } + + internal static T InternalHaving(this T builder, string column, DynamicColumn.CompareOperator op, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + if (value is DynamicColumn) + { + DynamicColumn v = (DynamicColumn)value; + + if (string.IsNullOrEmpty(v.ColumnName)) + v.ColumnName = column; + + return builder.InternalHaving(v); + } + else if (value is IEnumerable) + { + foreach (DynamicColumn v in (IEnumerable)value) + builder.InternalHaving(v); + + return builder; + } + + return builder.InternalHaving(new DynamicColumn + { + ColumnName = column, + Operator = op, + Value = value + }); + } + + internal static T InternalHaving(this T builder, string column, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + return builder.InternalHaving(column, DynamicColumn.CompareOperator.Eq, value); + } + + internal static T InternalHaving(this T builder, object conditions, bool schema = false) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + if (conditions is DynamicColumn) + return builder.InternalHaving((DynamicColumn)conditions); + else if (conditions is IEnumerable) + { + foreach (DynamicColumn v in (IEnumerable)conditions) + builder.InternalHaving(v); + + return builder; + } + + IDictionary dict = conditions.ToDictionary(); + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType()); + string table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty); + + foreach (KeyValuePair condition in dict) + { + if (mapper.Ignored.Contains(condition.Key) || condition.Key == "_table") + continue; + + string colName = mapper != null ? mapper.PropertyMap.TryGetValue(condition.Key) ?? condition.Key : condition.Key; + + DynamicSchemaColumn? col = null; + + // This should be used on typed queries or update/delete steatements, which usualy operate on a single table. + if (schema) + { + col = builder.GetColumnFromSchema(colName, mapper, table); + + if ((!col.HasValue || !col.Value.IsKey) && + (mapper == null || mapper.ColumnsMap.TryGetValue(colName).NullOr(m => m.Ignore || m.Column.NullOr(c => !c.IsKey, true), true))) + continue; + + colName = col.HasValue ? col.Value.Name : colName; + } + + if (!string.IsNullOrEmpty(table)) + builder.InternalHaving(x => x(builder.FixObjectName(string.Format("{0}.{1}", table, colName))) == condition.Value); + else + builder.InternalHaving(x => x(builder.FixObjectName(colName)) == condition.Value); + } + + return builder; + } + + #endregion Where + } + internal static class DynamicModifyBuilderExtensions { internal static T Table(this T builder, Func func) where T : DynamicModifyBuilder @@ -7033,8 +7279,8 @@ namespace DynamORM condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition"); } - if (addBeginBrace) builder.OpenBracketsCount++; - if (addEndBrace) builder.OpenBracketsCount--; + if (addBeginBrace) builder.WhereOpenBracketsCount++; + if (addEndBrace) builder.WhereOpenBracketsCount--; if (builder.WhereCondition == null) builder.WhereCondition = string.Format("{0}{1}{2}", @@ -7512,7 +7758,17 @@ namespace DynamORM string WhereCondition { get; set; } /// Gets or sets the amount of not closed brackets in where statement. - int OpenBracketsCount { get; set; } + int WhereOpenBracketsCount { get; set; } + } + + /// Empty interface to allow having query builder implementation use universal approach. + internal interface IQueryWithHaving + { + /// Gets or sets the having condition. + string HavingCondition { get; set; } + + /// Gets or sets the amount of not closed brackets in having statement. + int HavingOpenBracketsCount { get; set; } } private DynamicQueryBuilder _parent = null; @@ -7679,7 +7935,7 @@ namespace DynamORM OnCreateParameter = new List>(); WhereCondition = null; - OpenBracketsCount = 0; + WhereOpenBracketsCount = 0; Database = db; if (Database != null) @@ -7705,7 +7961,7 @@ namespace DynamORM public string WhereCondition { get; set; } /// Gets or sets the amount of not closed brackets in where statement. - public int OpenBracketsCount { get; set; } + public int WhereOpenBracketsCount { get; set; } #endregion IQueryWithWhere @@ -7749,10 +8005,22 @@ namespace DynamORM // End not ended where statement if (this is IQueryWithWhere) { - while (OpenBracketsCount > 0) + while (WhereOpenBracketsCount > 0) { WhereCondition += ")"; - OpenBracketsCount--; + WhereOpenBracketsCount--; + } + } + + // End not ended having statement + if (this is IQueryWithHaving) + { + IQueryWithHaving h = this as IQueryWithHaving; + + while (h.HavingOpenBracketsCount > 0) + { + h.HavingCondition += ")"; + h.HavingOpenBracketsCount--; } } @@ -8170,6 +8438,12 @@ namespace DynamORM return "COUNT(*)"; return string.Format("COUNT({0})", Parse(node.Arguments[0], ref columnSchema, pars: Parameters, nulls: true)); + + case "COUNT0": + if (node.Arguments != null && node.Arguments.Length > 0) + throw new ArgumentException("COUNT0 method doesn't expect arguments"); + + return "COUNT(0)"; } } @@ -8407,7 +8681,7 @@ namespace DynamORM } /// Implementation of dynamic select query builder. - internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere + internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere, DynamicQueryBuilder.IQueryWithHaving { private int? _limit = null; private int? _offset = null; @@ -8419,6 +8693,16 @@ namespace DynamORM private string _groupby; private string _orderby; + #region IQueryWithHaving + + /// Gets or sets the having condition. + public string HavingCondition { get; set; } + + /// Gets or sets the amount of not closed brackets in having statement. + public int HavingOpenBracketsCount { get; set; } + + #endregion IQueryWithHaving + /// /// Gets a value indicating whether this instance has select columns. /// @@ -8478,6 +8762,7 @@ namespace DynamORM if (_join != null) sb.AppendFormat(" {0}", _join); if (WhereCondition != null) sb.AppendFormat(" WHERE {0}", WhereCondition); if (_groupby != null) sb.AppendFormat(" GROUP BY {0}", _groupby); + if (HavingCondition != null) sb.AppendFormat(" HAVING {0}", HavingCondition); if (_orderby != null) sb.AppendFormat(" ORDER BY {0}", _orderby); if (_limit.HasValue && !lused && (Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset) sb.AppendFormat(" LIMIT {0}", _limit); @@ -9377,6 +9662,62 @@ namespace DynamORM #endregion GroupBy + #region Having + + /// + /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Having() methods are chained their contents are, by default, concatenated with an 'AND' operator. + /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: + /// 'Having( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder Having(Func func) + { + return this.InternalHaving(func); + } + + /// Add Having condition. + /// Condition column with operator and value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(DynamicColumn column) + { + return this.InternalHaving(column); + } + + /// Add Having condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value) + { + return this.InternalHaving(column, op, value); + } + + /// Add Having condition. + /// Condition column. + /// Condition value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(string column, object value) + { + return this.InternalHaving(column, value); + } + + /// Add Having condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(object conditions, bool schema = false) + { + return this.InternalHaving(conditions, schema); + } + + #endregion Having + #region OrderBy /// diff --git a/DynamORM.Tests/Select/ParserTests.cs b/DynamORM.Tests/Select/ParserTests.cs index 198010c..e353b2c 100644 --- a/DynamORM.Tests/Select/ParserTests.cs +++ b/DynamORM.Tests/Select/ParserTests.cs @@ -26,10 +26,10 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ +using System.Linq; using DynamORM.Builders; using DynamORM.Builders.Implementation; using NUnit.Framework; -using System.Linq; namespace DynamORM.Tests.Select { @@ -225,6 +225,20 @@ namespace DynamORM.Tests.Select Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE (c.\"UserName\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); } + /// + /// Tests where method with alias. + /// + [Test] + public void TestHavingAlias() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Having(u => u.Sum(u.c.ClientsCount) > 10); + + Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c HAVING (Sum(c.\"ClientsCount\") > [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText()); + } + /// /// Tests complex where method with alias. /// @@ -735,6 +749,25 @@ namespace DynamORM.Tests.Select cmd.Parameters.Keys.ToArray()[0]), cmd.CommandText()); } + /// + /// Tests select escaped case. + /// + [Test] + public void TestCoalesceCalculatedArgs() + { + IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database); + + cmd.From(u => u.dbo.Users.As(u.c)) + .Select(u => u.Coalesce(u.c.Test1 + "_", u.c.Test2 + "_", u.c.Test3 + "_").As(u.Hash)); + + Assert.AreEqual(string.Format("SELECT Coalesce((c.\"Test1\" + [${0}]), (c.\"Test2\" + [${1}]), (c.\"Test3\" + [${2}])) AS \"Hash\" FROM \"dbo\".\"Users\" AS c", + cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText()); + + //var c = Database.Open().CreateCommand(); + //cmd.FillCommand(c); + //c.Dispose(); + } + /// /// Tests select escaped case. /// diff --git a/DynamORM/Builders/Extensions/DynamicHavingQueryExtensions.cs b/DynamORM/Builders/Extensions/DynamicHavingQueryExtensions.cs new file mode 100644 index 0000000..3193bd5 --- /dev/null +++ b/DynamORM/Builders/Extensions/DynamicHavingQueryExtensions.cs @@ -0,0 +1,245 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2015, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + * + * Some of methods in this code file is based on Kerosene ORM solution + * for parsing dynamic lambda expressions by Moisés Barba Cebeira + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using DynamORM.Builders.Implementation; +using DynamORM.Helpers; +using DynamORM.Helpers.Dynamics; +using DynamORM.Mapper; + +namespace DynamORM.Builders.Extensions +{ + internal static class DynamicHavingQueryExtensions + { + #region Where + + internal static T InternalHaving(this T builder, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + return builder.InternalHaving(false, false, func); + } + + internal static T InternalHaving(this T builder, bool addBeginBrace, bool addEndBrace, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + if (func == null) throw new ArgumentNullException("Array of functions cannot be null."); + + using (DynamicParser parser = DynamicParser.Parse(func)) + { + string condition = null; + bool and = true; + + object result = parser.Result; + if (result is string) + { + condition = (string)result; + + if (condition.ToUpper().IndexOf("OR") == 0) + { + and = false; + condition = condition.Substring(3); + } + else if (condition.ToUpper().IndexOf("AND") == 0) + condition = condition.Substring(4); + } + else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType) + return builder.InternalHaving(result); + else + { + // Intercepting the 'x => x.And()' and 'x => x.Or()' virtual methods... + if (result is DynamicParser.Node.Method && ((DynamicParser.Node.Method)result).Host is DynamicParser.Node.Argument) + { + DynamicParser.Node.Method node = (DynamicParser.Node.Method)result; + string name = node.Name.ToUpper(); + if (name == "AND" || name == "OR") + { + object[] args = ((DynamicParser.Node.Method)node).Arguments; + if (args == null) throw new ArgumentNullException("arg", string.Format("{0} is not a parameterless method.", name)); + if (args.Length != 1) throw new ArgumentException(string.Format("{0} requires one and only one parameter: {1}.", name, args.Sketch())); + + and = name == "AND" ? true : false; + result = args[0]; + } + } + + // Just parsing the contents now... + condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition"); + } + + if (addBeginBrace) builder.HavingOpenBracketsCount++; + if (addEndBrace) builder.HavingOpenBracketsCount--; + + if (builder.HavingCondition == null) + builder.HavingCondition = string.Format("{0}{1}{2}", + addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); + else + builder.HavingCondition = string.Format("{0} {1} {2}{3}{4}", builder.HavingCondition, and ? "AND" : "OR", + addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty); + } + + return builder; + } + + internal static T InternalHaving(this T builder, DynamicColumn column) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + bool virt = builder.VirtualMode; + if (column.VirtualColumn.HasValue) + builder.VirtualMode = column.VirtualColumn.Value; + + Action modParam = (p) => + { + if (column.Schema.HasValue) + p.Schema = column.Schema; + + if (!p.Schema.HasValue) + p.Schema = column.Schema ?? builder.GetColumnFromSchema(column.ColumnName); + }; + + builder.CreateTemporaryParameterAction(modParam); + + // It's kind of uglu, but... well it works. + if (column.Or) + switch (column.Operator) + { + default: + case DynamicColumn.CompareOperator.Eq: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break; + case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break; + case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break; + case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); break; + case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break; + case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break; + case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break; + case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break; + case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break; + case DynamicColumn.CompareOperator.Between: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Between(column.Value))); break; + } + else + switch (column.Operator) + { + default: + case DynamicColumn.CompareOperator.Eq: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break; + case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break; + case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break; + case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break; + case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break; + case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break; + case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break; + case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break; + case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break; + case DynamicColumn.CompareOperator.Between: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break; + } + + builder.OnCreateTemporaryParameter.Remove(modParam); + builder.VirtualMode = virt; + + return builder; + } + + internal static T InternalHaving(this T builder, string column, DynamicColumn.CompareOperator op, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + if (value is DynamicColumn) + { + DynamicColumn v = (DynamicColumn)value; + + if (string.IsNullOrEmpty(v.ColumnName)) + v.ColumnName = column; + + return builder.InternalHaving(v); + } + else if (value is IEnumerable) + { + foreach (DynamicColumn v in (IEnumerable)value) + builder.InternalHaving(v); + + return builder; + } + + return builder.InternalHaving(new DynamicColumn + { + ColumnName = column, + Operator = op, + Value = value + }); + } + + internal static T InternalHaving(this T builder, string column, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + return builder.InternalHaving(column, DynamicColumn.CompareOperator.Eq, value); + } + + internal static T InternalHaving(this T builder, object conditions, bool schema = false) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving + { + if (conditions is DynamicColumn) + return builder.InternalHaving((DynamicColumn)conditions); + else if (conditions is IEnumerable) + { + foreach (DynamicColumn v in (IEnumerable)conditions) + builder.InternalHaving(v); + + return builder; + } + + IDictionary dict = conditions.ToDictionary(); + DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType()); + string table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty); + + foreach (KeyValuePair condition in dict) + { + if (mapper.Ignored.Contains(condition.Key) || condition.Key == "_table") + continue; + + string colName = mapper != null ? mapper.PropertyMap.TryGetValue(condition.Key) ?? condition.Key : condition.Key; + + DynamicSchemaColumn? col = null; + + // This should be used on typed queries or update/delete steatements, which usualy operate on a single table. + if (schema) + { + col = builder.GetColumnFromSchema(colName, mapper, table); + + if ((!col.HasValue || !col.Value.IsKey) && + (mapper == null || mapper.ColumnsMap.TryGetValue(colName).NullOr(m => m.Ignore || m.Column.NullOr(c => !c.IsKey, true), true))) + continue; + + colName = col.HasValue ? col.Value.Name : colName; + } + + if (!string.IsNullOrEmpty(table)) + builder.InternalHaving(x => x(builder.FixObjectName(string.Format("{0}.{1}", table, colName))) == condition.Value); + else + builder.InternalHaving(x => x(builder.FixObjectName(colName)) == condition.Value); + } + + return builder; + } + + #endregion Where + } +} \ No newline at end of file diff --git a/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs b/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs index 5dbbed9..96e76f9 100644 --- a/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs +++ b/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs @@ -93,8 +93,8 @@ namespace DynamORM.Builders.Extensions condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition"); } - if (addBeginBrace) builder.OpenBracketsCount++; - if (addEndBrace) builder.OpenBracketsCount--; + if (addBeginBrace) builder.WhereOpenBracketsCount++; + if (addEndBrace) builder.WhereOpenBracketsCount--; if (builder.WhereCondition == null) builder.WhereCondition = string.Format("{0}{1}{2}", diff --git a/DynamORM/Builders/IDynamicSelectQueryBuilder.cs b/DynamORM/Builders/IDynamicSelectQueryBuilder.cs index 9e97307..faabac2 100644 --- a/DynamORM/Builders/IDynamicSelectQueryBuilder.cs +++ b/DynamORM/Builders/IDynamicSelectQueryBuilder.cs @@ -193,6 +193,47 @@ namespace DynamORM.Builders #endregion GroupBy + #region Having + + /// + /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Having() methods are chained their contents are, by default, concatenated with an 'AND' operator. + /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: + /// 'Having( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + IDynamicSelectQueryBuilder Having(Func func); + + /// Add Having condition. + /// Condition column with operator and value. + /// Builder instance. + IDynamicSelectQueryBuilder Having(DynamicColumn column); + + /// Add Having condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value); + + /// Add Having condition. + /// Condition column. + /// Condition value. + /// Builder instance. + IDynamicSelectQueryBuilder Having(string column, object value); + + /// Add Having condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + IDynamicSelectQueryBuilder Having(object conditions, bool schema = false); + + #endregion Having + #region OrderBy /// diff --git a/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs index 28258ff..9372834 100644 --- a/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs +++ b/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs @@ -51,7 +51,17 @@ namespace DynamORM.Builders.Implementation string WhereCondition { get; set; } /// Gets or sets the amount of not closed brackets in where statement. - int OpenBracketsCount { get; set; } + int WhereOpenBracketsCount { get; set; } + } + + /// Empty interface to allow having query builder implementation use universal approach. + internal interface IQueryWithHaving + { + /// Gets or sets the having condition. + string HavingCondition { get; set; } + + /// Gets or sets the amount of not closed brackets in having statement. + int HavingOpenBracketsCount { get; set; } } private DynamicQueryBuilder _parent = null; @@ -218,7 +228,7 @@ namespace DynamORM.Builders.Implementation OnCreateParameter = new List>(); WhereCondition = null; - OpenBracketsCount = 0; + WhereOpenBracketsCount = 0; Database = db; if (Database != null) @@ -244,7 +254,7 @@ namespace DynamORM.Builders.Implementation public string WhereCondition { get; set; } /// Gets or sets the amount of not closed brackets in where statement. - public int OpenBracketsCount { get; set; } + public int WhereOpenBracketsCount { get; set; } #endregion IQueryWithWhere @@ -288,10 +298,22 @@ namespace DynamORM.Builders.Implementation // End not ended where statement if (this is IQueryWithWhere) { - while (OpenBracketsCount > 0) + while (WhereOpenBracketsCount > 0) { WhereCondition += ")"; - OpenBracketsCount--; + WhereOpenBracketsCount--; + } + } + + // End not ended having statement + if (this is IQueryWithHaving) + { + IQueryWithHaving h = this as IQueryWithHaving; + + while (h.HavingOpenBracketsCount > 0) + { + h.HavingCondition += ")"; + h.HavingOpenBracketsCount--; } } @@ -639,7 +661,7 @@ namespace DynamORM.Builders.Implementation return string.Format("{0} IN({1})", parent, sbin.ToString()); } - + case "NOTIN": { if (node.Arguments == null || node.Arguments.Length == 0) @@ -709,6 +731,12 @@ namespace DynamORM.Builders.Implementation return "COUNT(*)"; return string.Format("COUNT({0})", Parse(node.Arguments[0], ref columnSchema, pars: Parameters, nulls: true)); + + case "COUNT0": + if (node.Arguments != null && node.Arguments.Length > 0) + throw new ArgumentException("COUNT0 method doesn't expect arguments"); + + return "COUNT(0)"; } } diff --git a/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs index 28af8ba..8b7072f 100644 --- a/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs +++ b/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs @@ -42,7 +42,7 @@ using DynamORM.Mapper; namespace DynamORM.Builders.Implementation { /// Implementation of dynamic select query builder. - internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere + internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere, DynamicQueryBuilder.IQueryWithHaving { private int? _limit = null; private int? _offset = null; @@ -54,6 +54,16 @@ namespace DynamORM.Builders.Implementation private string _groupby; private string _orderby; + #region IQueryWithHaving + + /// Gets or sets the having condition. + public string HavingCondition { get; set; } + + /// Gets or sets the amount of not closed brackets in having statement. + public int HavingOpenBracketsCount { get; set; } + + #endregion IQueryWithHaving + /// /// Gets a value indicating whether this instance has select columns. /// @@ -113,6 +123,7 @@ namespace DynamORM.Builders.Implementation if (_join != null) sb.AppendFormat(" {0}", _join); if (WhereCondition != null) sb.AppendFormat(" WHERE {0}", WhereCondition); if (_groupby != null) sb.AppendFormat(" GROUP BY {0}", _groupby); + if (HavingCondition != null) sb.AppendFormat(" HAVING {0}", HavingCondition); if (_orderby != null) sb.AppendFormat(" ORDER BY {0}", _orderby); if (_limit.HasValue && !lused && (Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset) sb.AppendFormat(" LIMIT {0}", _limit); @@ -1012,6 +1023,62 @@ namespace DynamORM.Builders.Implementation #endregion GroupBy + #region Having + + /// + /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition + /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used + /// as needed. + /// - If several Having() methods are chained their contents are, by default, concatenated with an 'AND' operator. + /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in: + /// 'Having( x => x.Or( condition ) )'. + /// + /// The specification. + /// This instance to permit chaining. + public virtual IDynamicSelectQueryBuilder Having(Func func) + { + return this.InternalHaving(func); + } + + /// Add Having condition. + /// Condition column with operator and value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(DynamicColumn column) + { + return this.InternalHaving(column); + } + + /// Add Having condition. + /// Condition column. + /// Condition operator. + /// Condition value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value) + { + return this.InternalHaving(column, op, value); + } + + /// Add Having condition. + /// Condition column. + /// Condition value. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(string column, object value) + { + return this.InternalHaving(column, value); + } + + /// Add Having condition. + /// Set conditions as properties and values of an object. + /// If true use schema to determine key columns and ignore those which + /// aren't keys. + /// Builder instance. + public virtual IDynamicSelectQueryBuilder Having(object conditions, bool schema = false) + { + return this.InternalHaving(conditions, schema); + } + + #endregion Having + #region OrderBy /// diff --git a/DynamORM/DynamORM.csproj b/DynamORM/DynamORM.csproj index c91f9d0..cf107bc 100644 --- a/DynamORM/DynamORM.csproj +++ b/DynamORM/DynamORM.csproj @@ -64,6 +64,7 @@ + diff --git a/DynamORM/DynamicCachedReader.cs b/DynamORM/DynamicCachedReader.cs new file mode 100644 index 0000000..0fc6644 --- /dev/null +++ b/DynamORM/DynamicCachedReader.cs @@ -0,0 +1,496 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Dynamic; +using System.IO; +using DynamORM.Helpers; +using DynamORM.Mapper; + +namespace DynamORM +{ + /// Cache data reader in memory. + internal class DynamicCachedReader : DynamicObject, IDataReader + { + #region Constructor and Data + + private DataTable _schema; + private int _fields; + private int _rows; + private int _position; + private int _cachePos; + + private IList _names; + private IDictionary _ordinals; + private IList _types; + private IList _cache; + + private DynamicCachedReader() + { + } + + /// Initializes a new instance of the class. + /// Reader to cache. + public DynamicCachedReader(IDataReader reader) + { + InitDataReader(reader); + } + + #endregion Constructor and Data + + #region Helpers + + /// Create data reader from enumerable. + /// Type of enumerated objects. + /// List of objects. + /// Instance of containing objects data. + public static DynamicCachedReader FromEnumerable(IEnumerable objects) + { + var mapper = DynamicMapperCache.GetMapper(); + + if (mapper == null) + throw new InvalidCastException(string.Format("Object type '{0}' can't be mapped.", typeof(T).FullName)); + + var r = new DynamicCachedReader(); + r.Init(mapper.ColumnsMap.Count + 1); + r.CreateSchemaTable(mapper); + r.FillFromEnumerable(objects, mapper); + + return r; + } + + private void InitDataReader(IDataReader reader) + { + _schema = reader.GetSchemaTable(); + + Init(reader.FieldCount); + + int i = 0; + + for (i = 0; i < _fields; i++) + { + _names.Add(reader.GetName(i)); + _types.Add(reader.GetFieldType(i)); + + if (!_ordinals.ContainsKey(reader.GetName(i).ToUpper())) + _ordinals.Add(reader.GetName(i).ToUpper(), i); + } + + while (reader.Read()) + { + for (i = 0; i < _fields; i++) + _cache.Add(reader[i]); + + _rows++; + } + + IsClosed = false; + _position = -1; + _cachePos = -1; + + reader.Close(); + } + + private void FillFromEnumerable(IEnumerable objects, DynamicTypeMap mapper) + { + foreach (var elem in objects) + { + foreach (var col in mapper.ColumnsMap) + { + object val = null; + + if (col.Value.Get != null) + val = col.Value.Get(elem); + + _cache.Add(val); + } + + _cache.Add(elem); + + _rows++; + } + } + + private void CreateSchemaTable(DynamicTypeMap mapper) + { + _schema = new DataTable("DYNAMIC"); + _schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); + _schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); + _schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); + _schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); + _schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); + _schema.Columns.Add(new DataColumn("DataType", typeof(Type))); + _schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); + _schema.Columns.Add(new DataColumn("NativeType", typeof(int))); + _schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); + _schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); + _schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); + _schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); + + int ordinal = 0; + DataRow dr = null; + + foreach (var column in mapper.ColumnsMap) + { + dr = _schema.NewRow(); + + dr[0] = column.Value.Column.NullOr(x => x.Name ?? column.Value.Name, column.Value.Name); + dr[1] = ordinal; + dr[2] = column.Value.Column.NullOr(x => x.Size ?? int.MaxValue, int.MaxValue); + dr[3] = column.Value.Column.NullOr(x => x.Precision ?? 0, 0); + dr[4] = column.Value.Column.NullOr(x => x.Scale ?? 0, 0); + dr[5] = column.Value.Column.NullOr(x => x.Type.HasValue ? x.Type.Value.ToType() : column.Value.Type, column.Value.Type); + dr[6] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), column.Value.Type.ToDbType()); + dr[7] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), column.Value.Type.ToDbType()); + dr[8] = !column.Value.Column.NullOr(x => x.IsKey, false); + dr[9] = false; + dr[10] = column.Value.Column.NullOr(x => x.IsKey, false); + dr[11] = false; + + _schema.Rows.Add(dr); + + _names.Add(dr[0].ToString()); + _ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); + _types.Add((Type)dr[5]); + + dr.AcceptChanges(); + } + + dr = _schema.NewRow(); + + dr[0] = "#O"; + dr[1] = ordinal; + dr[2] = int.MaxValue; + dr[3] = 0; + dr[4] = 0; + dr[5] = mapper.Type; + dr[6] = DbType.Object; + dr[7] = DbType.Object; + dr[8] = true; + dr[9] = false; + dr[10] = false; + dr[11] = false; + + _schema.Rows.Add(dr); + + _names.Add("#O"); + _ordinals.Add("#O".ToUpper(), ordinal++); + _types.Add(mapper.Type); + + dr.AcceptChanges(); + } + + private void Init(int fieldCount) + { + _rows = 0; + _fields = fieldCount; + _names = new List(_fields); + _ordinals = new Dictionary(_fields); + _types = new List(_fields); + _cache = new List(_fields * 100); + } + + #endregion Helpers + + #region IDataReader Members + + /// Closes the System.Data.IDataReader Object. + public void Close() + { + IsClosed = true; + _position = _rows; + _cachePos = -1; + } + + /// Gets a value indicating the depth of nesting for the current row. + /// This implementation use this field to indicate row count. + public int Depth + { + get { return _rows; } + } + + /// Returns a System.Data.DataTable that describes the column metadata of the + /// System.Data.IDataReader.A System.Data.DataTable that describes + /// the column metadata. + /// The System.Data.IDataReader is closed. + public DataTable GetSchemaTable() + { + return _schema; + } + + /// Gets a value indicating whether the data reader is closed. + public bool IsClosed { get; private set; } + + /// Advances the data reader to the next result, when reading the results of batch SQL statements. + /// Returns true if there are more rows; otherwise, false. + public bool NextResult() + { + _cachePos = (++_position) * _fields; + + return _position < _rows; + } + + /// Advances the System.Data.IDataReader to the next record. + /// Returns true if there are more rows; otherwise, false. + public bool Read() + { + _cachePos = (++_position) * _fields; + + return _position < _rows; + } + + /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. + /// The number of rows changed, inserted, or deleted; 0 if no rows were affected or the statement + /// failed; and -1 for SELECT statements. + public int RecordsAffected { get { return 0; } } + + #endregion IDataReader Members + + #region IDisposable Members + + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + _names.Clear(); + _types.Clear(); + _cache.Clear(); + _schema.Dispose(); + } + + #endregion IDisposable Members + + #region IDataRecord Members + + /// Gets the number of columns in the current row. + /// When not positioned in a valid record set, 0; otherwise, the number of columns in the current record. The default is -1. + public int FieldCount { get { return _fields; } } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public bool GetBoolean(int i) + { + return (bool)_cache[_cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public byte GetByte(int i) + { + return (byte)_cache[_cachePos + i]; + } + + /// Reads a stream of bytes from the specified column offset into the buffer + /// as an array, starting at the given buffer offset. + /// The index of the field to find. + /// The index within the field from which to start the read operation. + /// The buffer into which to read the stream of bytes. + /// The index for buffer to start the read operation. + /// The number of bytes to read. + /// The actual number of bytes read. + public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + { + using (MemoryStream ms = new MemoryStream((byte[])_cache[_cachePos + i])) + return ms.Read(buffer, bufferoffset, length); + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public char GetChar(int i) + { + return (char)_cache[_cachePos + i]; + } + + /// Reads a stream of characters from the specified column offset into the buffer + /// as an array, starting at the given buffer offset. + /// The zero-based column ordinal. + /// The index within the row from which to start the read operation. + /// The buffer into which to read the stream of bytes. + /// The index for buffer to start the read operation. + /// The number of bytes to read. + /// The actual number of characters read. + public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) + { + using (MemoryStream ms = new MemoryStream((byte[])_cache[_cachePos + i])) + { + byte[] buff = new byte[buffer.Length]; + long ret = ms.Read(buff, bufferoffset, length); + + for (int n = bufferoffset; n < ret; n++) + buffer[n] = (char)buff[n]; + + return ret; + } + } + + /// Returns an System.Data.IDataReader for the specified column ordinal. + /// The index of the field to find. + /// An System.Data.IDataReader. + public IDataReader GetData(int i) + { + return null; + } + + /// Gets the data type information for the specified field. + /// The index of the field to find. + /// The data type information for the specified field. + public string GetDataTypeName(int i) + { + return _types[i].Name; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public DateTime GetDateTime(int i) + { + return (DateTime)_cache[_cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public decimal GetDecimal(int i) + { + return (decimal)_cache[_cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public double GetDouble(int i) + { + return (double)_cache[_cachePos + i]; + } + + /// Gets the System.Type information corresponding to the type of System.Object + /// that would be returned from System.Data.IDataRecord.GetValue(System.Int32). + /// The index of the field to find. + /// The System.Type information corresponding to the type of System.Object that + /// would be returned from System.Data.IDataRecord.GetValue(System.Int32). + public Type GetFieldType(int i) + { + return _types[i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public float GetFloat(int i) + { + return (float)_cache[_cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public Guid GetGuid(int i) + { + return (Guid)_cache[_cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public short GetInt16(int i) + { + return (short)_cache[_cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public int GetInt32(int i) + { + return (int)_cache[_cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public long GetInt64(int i) + { + return (long)_cache[_cachePos + i]; + } + + /// Gets the name for the field to find. + /// The index of the field to find. + /// The name of the field or the empty string (""), if there is no value to return. + public string GetName(int i) + { + return _names[i]; + } + + /// Return the index of the named field. + /// The name of the field to find. + /// The index of the named field. + public int GetOrdinal(string name) + { + if (_ordinals.ContainsKey(name.ToUpper())) + return _ordinals[name.ToUpper()]; + + return -1; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public string GetString(int i) + { + return (string)_cache[_cachePos + i]; + } + + /// Return the value of the specified field. + /// The index of the field to find. + /// Field value upon return. + public object GetValue(int i) + { + return _cache[_cachePos + i]; + } + + /// Gets all the attribute fields in the collection for the current record. + /// An array of System.Object to copy the attribute fields into. + /// The number of instances of System.Object in the array. + public int GetValues(object[] values) + { + for (int i = 0; i < _fields; i++) + values[i] = _cache[_cachePos + i]; + + return _fields; + } + + /// Return whether the specified field is set to null. + /// The index of the field to find. + /// Returns true if the specified field is set to null; otherwise, false. + public bool IsDBNull(int i) + { + return _cache[_cachePos + i] == null || _cache[_cachePos + i] == DBNull.Value; + } + + /// Gets or sets specified value in current record. + /// Name of column. + /// Value of specified column. + public object this[string name] + { + get + { + if (_ordinals.ContainsKey(name.ToUpper())) + return _cache[_cachePos + _ordinals[name.ToUpper()]]; + + throw new IndexOutOfRangeException(String.Format("Field '{0}' not found.", name)); + } + } + + /// Gets or sets specified value in current record. + /// The index of the field to find. + /// Value of specified column. + public object this[int i] + { + get { return _cache[_cachePos + i]; } + } + + #endregion IDataRecord Members + } +} \ No newline at end of file