/* * DynamORM - Dynamic Object-Relational Mapping library. * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using DynamORM.Builders; using DynamORM.Builders.Extensions; using DynamORM.Builders.Implementation; using DynamORM.Helpers; using DynamORM.Helpers.Dynamics; using DynamORM.Mapper; namespace DynamORM { #if !DYNAMORM_OMMIT_OLDSYNTAX /// Dynamic table is a simple ORM using dynamic objects. /// /// Assume that we have a table representing Users class. /// /// Let's take a look at Query possibilities. Assume we want /// to get enumerator for all records in database, mapped to our class /// instead of dynamic type we can use following syntax. /// Approach first. Use dynamic Query method and just set type /// then just cast it to user class. Remember that you must cast result /// of Queryto IEnumerable<object>. because from /// point of view of runtime you are operating on object type. /// (db.Table<User>().Query(type: typeof(User)) as IEnumerable<object>).Cast<User>(); /// Second approach is similar. We ask database using dynamic /// Query method. The difference is that we use extension method of /// IEnumerable<object> (to which we must cast to) to map /// object. /// (db.Table<User>().Query(columns: "*") as IEnumerable<object>).MapEnumerable<User>(); /// You can also use generic approach. But be careful this method is currently available thanks to framework hack. /// (db.Table<User>().Query<User>() as IEnumerable<object>).Cast<User>() /// Another approach uses existing methods, but still requires a /// cast, because Query also returns dynamic object enumerator. /// (db.Table<User>().Query().Execute() as IEnumerable<object>).MapEnumerable<User>(); /// /// Below you can find various invocations of dynamic and non dynamic /// methods of this class. x variable is a class instance. /// First various selects: /// x.Count(columns: "id"); /// x.Count(last: new DynamicColumn /// { /// Operator = DynamicColumn.CompareOperator.In, /// Value = new object[] { "Hendricks", "Goodwin", "Freeman" }.Take(3) /// }); /// x.Count(last: new DynamicColumn /// { /// Operator = DynamicColumn.CompareOperator.In, /// Value = new object[] { "Hendricks", "Goodwin", "Freeman" } /// }); /// x.First(columns: "id").id; /// x.Last(columns: "id").id; /// x.Count(first: "Ori"); /// x.Min(columns: "id"); /// x.Max(columns: "id"); /// x.Avg(columns: "id"); /// x.Sum(columns: "id"); /// x.Scalar(columns: "first", id: 19); /// x.Scalar(columns: "first:first:group_concat", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 }); /// x.Scalar(columns: "group_concat(first):first", id: new DynamicColumn { Operator = DynamicColumn.CompareOperator.Lt, Value = 20 }); /// var v = (x.Query(columns: "first,first:occurs:count", group: "first", order: ":desc:2") as IEnumerable<dynamic>).ToList(); /// x.Scalar(columns: @"length(""login""):len:avg"); /// x.Avg(columns: @"length(""email""):len"); /// x.Count(condition1: /// new DynamicColumn() /// { /// ColumnName = "email", /// Aggregate = "length", /// Operator = DynamicColumn.CompareOperator.Gt, /// Value = 27 /// }); /// var o = x.Single(columns: "id,first,last", id: 19); /// x.Single(where: new DynamicColumn("id").Eq(100)).login; /// x.Count(where: new DynamicColumn("id").Not(100)); /// x.Single(where: new DynamicColumn("login").Like("Hoyt.%")).id; /// x.Count(where: new DynamicColumn("login").NotLike("Hoyt.%")); /// x.Count(where: new DynamicColumn("id").Greater(100)); /// x.Count(where: new DynamicColumn("id").GreaterOrEqual(100)); /// x.Count(where: new DynamicColumn("id").Less(100)); /// x.Count(where: new DynamicColumn("id").LessOrEqual(100)); /// x.Count(where: new DynamicColumn("id").Between(75, 100)); /// x.Count(where: new DynamicColumn("id").In(75, 99, 100)); /// x.Count(where: new DynamicColumn("id").In(new[] { 75, 99, 100 })); /// Inserts: /// x.Insert(code: 201, first: "Juri", last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla"); /// x.Insert(values: new { code = 202, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" }); /// x.Insert(values: new Users /// { /// Id = u.Max(columns: "id") + 1, /// Code = "203", /// First = "Juri", /// Last = "Gagarin", /// Email = "juri.gagarin@megacorp.com", /// Quote = "bla, bla, bla" /// }); /// x.Insert(values: new users /// { /// id = u.Max(columns: "id") + 1, /// code = "204", /// first = "Juri", /// last = "Gagarin", /// email = "juri.gagarin@megacorp.com", /// quote = "bla, bla, bla" /// }); /// x.Update(id: 1, code: 201, first: "Juri", last: "Gagarin", email: "juri.gagarin@megacorp.com", quote: "bla, bla, bla"); /// x.Update(update: new { id = 2, code = 202, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" }); /// Updates: /// x.Update(update: new Users /// { /// Id = 3, /// Code = "203", /// First = "Juri", /// Last = "Gagarin", /// Email = "juri.gagarin@megacorp.com", /// Quote = "bla, bla, bla" /// }); /// x.Update(update: new users /// { /// id = 4, /// code = "204", /// first = "Juri", /// last = "Gagarin", /// email = "juri.gagarin@megacorp.com", /// quote = "bla, bla, bla" /// }); /// x.Update(values: new { code = 205, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" }, where: new { id = 5 }); /// x.Update(values: new Users /// { /// Id = 6, /// Code = "206", /// First = "Juri", /// Last = "Gagarin", /// Email = "juri.gagarin@megacorp.com", /// Quote = "bla, bla, bla" /// }, id: 6); /// x.Update(values: new users /// { /// id = 7, /// code = "207", /// first = "Juri", /// last = "Gagarin", /// email = "juri.gagarin@megacorp.com", /// quote = "bla, bla, bla" /// }, id: 7); /// Delete: /// x.Delete(code: 10); /// x.Delete(delete: new { id = 11, code = 11, first = "Juri", last = "Gagarin", email = "juri.gagarin@megacorp.com", quote = "bla, bla, bla" }); /// x.Delete(delete: new Users /// { /// Id = 12, /// Code = "12", /// First = "Juri", /// Last = "Gagarin", /// Email = "juri.gagarin@megacorp.com", /// Quote = "bla, bla, bla" /// }); /// x.Delete(delete: new users /// { /// id = 13, /// code = "13", /// first = "Juri", /// last = "Gagarin", /// email = "juri.gagarin@megacorp.com", /// quote = "bla, bla, bla" /// }); /// x.Delete(where: new { id = 14, code = 14 }); /// public class DynamicTable : DynamicObject, IExtendedDisposable, ICloneable { private static HashSet _allowedCommands = new HashSet { "Insert", "Update", "Delete", "Query", "Single", "Where", "First", "Last", "Get", "Count", "Sum", "Avg", "Min", "Max", "Scalar" }; /// Gets dynamic database. internal DynamicDatabase Database { get; private set; } /// Gets type of table (for coning and schema building). internal Type TableType { get; private set; } /// Gets name of table. public virtual string TableName { get; private set; } /// Gets name of owner. public virtual string OwnerName { get; private set; } /// Gets full name of table containing owner and table name. public virtual string FullName { get { return string.IsNullOrEmpty(TableName) ? null : string.IsNullOrEmpty(OwnerName) ? Database.DecorateName(TableName) : string.Format("{0}.{1}", Database.DecorateName(OwnerName), Database.DecorateName(TableName)); } } /// Gets table schema. /// If database doesn't support schema, only key columns are listed here. public virtual Dictionary Schema { get; private set; } private DynamicTable() { } /// Initializes a new instance of the class. /// Database and connection management. /// Table name. /// Owner of the table. /// Override keys in schema. public DynamicTable(DynamicDatabase database, string table = "", string owner = "", string[] keys = null) { IsDisposed = false; Database = database; TableName = Database.StripName(table); OwnerName = owner != null ? Database.StripName(owner) : string.Empty; TableType = null; BuildAndCacheSchema(keys); } /// Initializes a new instance of the class. /// Database and connection management. /// Type describing table. /// Override keys in schema. public DynamicTable(DynamicDatabase database, Type type, string[] keys = null) { if (type == null) throw new ArgumentNullException("type", "Type can't be null."); IsDisposed = false; Database = database; TableType = type; DynamicTypeMap mapper = DynamicMapperCache.GetMapper(type); if (mapper != null) { TableName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? type.Name : mapper.Table.Name; OwnerName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Owner) ? null : mapper.Table.Owner; } BuildAndCacheSchema(keys); } #region Schema private void BuildAndCacheSchema(string[] keys) { Dictionary schema = null; schema = Database.GetSchema(TableType) ?? Database.GetSchema(TableName, OwnerName); #region Fill currrent table schema if (keys == null && TableType != null) { DynamicTypeMap mapper = DynamicMapperCache.GetMapper(TableType); if (mapper != null) { IEnumerable k = mapper.ColumnsMap.Where(p => p.Value.Column != null && p.Value.Column.IsKey).Select(p => p.Key); if (k.Count() > 0) keys = k.ToArray(); } } if (schema != null) { if (keys == null) Schema = new Dictionary(schema); else { // TODO: Make this.... nicer List ks = keys.Select(k => k.ToLower()).ToList(); Schema = schema.ToDictionary(k => k.Key, (v) => { DynamicSchemaColumn dsc = v.Value; dsc.IsKey = ks.Contains(v.Key); return dsc; }); } } #endregion Fill currrent table schema #region Build ad-hock schema if (keys != null && Schema == null) { Schema = keys.Select(k => k.ToLower()).ToList() .ToDictionary(k => k, k => new DynamicSchemaColumn { Name = k, IsKey = true }); } #endregion Build ad-hock schema } #endregion Schema #region Basic Queries /// Enumerate the reader and yield the result. /// SQL query containing numbered parameters in format provided by /// methods. Also names should be formatted with /// method. /// Arguments (parameters). /// Enumerator of objects expanded from query. public virtual IEnumerable Query(string sql, params object[] args) { return Database.Query(sql, args); } /// Enumerate the reader and yield the result. /// Command builder. /// Enumerator of objects expanded from query. public virtual IEnumerable Query(IDynamicQueryBuilder builder) { return Database.Query(builder); } /// Create new . /// New instance. public virtual IDynamicSelectQueryBuilder Query() { IDynamicSelectQueryBuilder builder = new DynamicSelectQueryBuilder(this.Database); string name = this.FullName; if (!string.IsNullOrEmpty(name)) builder.From(x => name); return builder; } /// Returns a single result. /// SQL query containing numbered parameters in format provided by /// methods. Also names should be formatted with /// method. /// Arguments (parameters). /// Result of a query. public virtual object Scalar(string sql, params object[] args) { return Database.Scalar(sql, args); } /// Returns a single result. /// Command builder. /// Result of a query. public virtual object Scalar(IDynamicQueryBuilder builder) { return Database.Scalar(builder); } #if !DYNAMORM_OMMIT_GENERICEXECUTION && !DYNAMORM_OMMIT_TRYPARSE /// Returns a single result. /// What kind of result is expected. /// SQL query containing numbered parameters in format provided by /// methods. Also names should be formatted with /// method. /// Arguments (parameters). /// Result of a query. public virtual T ScalarAs(string sql, params object[] args) { return Database.ScalarAs(sql, args); } /// Returns a single result. /// What kind of result is expected. /// Command builder. /// Default value. /// Result of a query. public virtual T ScalarAs(IDynamicQueryBuilder builder, T defaultValue = default(T)) { return Database.ScalarAs(builder, defaultValue); } #endif /// Execute stored procedure. /// Name of stored procedure to execute. /// Number of affected rows. public virtual int Procedure(string procName) { return Database.Procedure(procName); } /// Execute stored procedure. /// Name of stored procedure to execute. /// Arguments (parameters) in form of expando object. /// Number of affected rows. public virtual int Procedure(string procName, params object[] args) { return Database.Procedure(procName, args); } /// Execute stored procedure. /// Name of stored procedure to execute. /// Arguments (parameters) in form of expando object. /// Number of affected rows. public virtual int Procedure(string procName, DynamicExpando args) { return Database.Procedure(procName, args); } /// Execute stored procedure. /// Name of stored procedure to execute. /// Arguments (parameters) in form of expando object. /// Number of affected rows. public virtual int Procedure(string procName, ExpandoObject args) { return Database.Procedure(procName, args); } /// Execute non query. /// SQL query containing numbered parameters in format provided by /// methods. Also names should be formatted with /// method. /// Arguments (parameters). /// Number of affected rows. public virtual int Execute(string sql, params object[] args) { return Database.Execute(sql, args); } /// Execute non query. /// Command builder. /// Number of affected rows. public virtual int Execute(IDynamicQueryBuilder builder) { return Database.Execute(builder); } /// Execute non query. /// Command builders. /// Number of affected rows. public virtual int Execute(IDynamicQueryBuilder[] builders) { return Database.Execute(builders); } #endregion Basic Queries #region Insert /// Create new . /// New instance. public dynamic Insert() { return new DynamicProxy(new DynamicInsertQueryBuilder(this.Database, this.FullName)); } /// Adds a record to the database. You can pass in an Anonymous object, an , /// A regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString. /// Anonymous object, an , a regular old POCO, or a NameValueCollection /// from a Request.Form or Request.QueryString, containing fields to update. /// Number of updated rows. public virtual int Insert(object o) { return Insert() .Insert(o) .Execute(); } #endregion Insert #region Update /// Create new . /// New instance. public dynamic Update() { return new DynamicProxy(new DynamicUpdateQueryBuilder(this.Database, this.FullName)); } /// Updates a record in the database. You can pass in an Anonymous object, an ExpandoObject, /// a regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString. /// Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection /// from a Request.Form or Request.QueryString, containing fields to update. /// Anonymous object, an , a regular old POCO, or a NameValueCollection /// from a Request.Form or Request.QueryString, containing fields with conditions. /// Number of updated rows. public virtual int Update(object o, object key) { return Update() .Values(o) .Where(key) .Execute(); } /// Updates a record in the database using schema. You can pass in an Anonymous object, an ExpandoObject, /// a regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString. /// Anonymous object, an , a regular old POCO, or a NameValueCollection /// from a Request.Form or Request.QueryString, containing fields to update and conditions. /// Number of updated rows. public virtual int Update(object o) { return Update() .Update(o) .Execute(); } #endregion Update #region Delete /// Create new . /// New instance. public dynamic Delete() { return new DynamicProxy(new DynamicDeleteQueryBuilder(this.Database, this.FullName)); } /// Removes a record from the database. You can pass in an Anonymous object, an , /// A regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString. /// Anonymous object, an , a regular old POCO, or a NameValueCollection /// from a Request.Form or Request.QueryString, containing fields with where conditions. /// If true use schema to determine key columns and ignore those which /// aren't keys. /// Number of updated rows. public virtual int Delete(object o, bool schema = true) { return Delete() .Where(o, schema) .Execute(); } #endregion Delete #region Universal Dynamic Invoker /// This is where the magic begins. /// Binder to invoke. /// Binder arguments. /// Binder invoke result. /// Returns true if invoke was performed. public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { // parse the method CallInfo info = binder.CallInfo; // Get generic types IList types = binder.GetGenericTypeArguments(); // accepting named args only... SKEET! if (info.ArgumentNames.Count != args.Length) throw new InvalidOperationException("Please use named arguments for this type of query - the column name, orderby, columns, etc"); string op = binder.Name; // Avoid strange things if (!_allowedCommands.Contains(op)) throw new InvalidOperationException(string.Format("Dynamic method '{0}' is not supported.", op)); switch (op) { case "Insert": result = DynamicInsert(args, info, types); break; case "Update": result = DynamicUpdate(args, info, types); break; case "Delete": result = DynamicDelete(args, info, types); break; default: result = DynamicQuery(args, info, op, types); break; } return true; } private object DynamicInsert(object[] args, CallInfo info, IList types) { DynamicInsertQueryBuilder builder = new DynamicInsertQueryBuilder(this.Database); if (types != null && types.Count == 1) HandleTypeArgument(null, info, ref types, builder, 0); if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0) builder.Table(string.IsNullOrEmpty(this.OwnerName) ? this.TableName : string.Format("{0}.{1}", this.OwnerName, this.TableName), this.Schema); // loop the named args - see if we have order, columns and constraints if (info.ArgumentNames.Count > 0) { for (int i = 0; i < args.Length; i++) { string fullName = info.ArgumentNames[i]; string name = fullName.ToLower(); switch (name) { case "table": if (args[i] is string) builder.Table(args[i].ToString()); else goto default; break; case "values": builder.Insert(args[i]); break; case "type": if (types == null || types.Count == 0) HandleTypeArgument(args, info, ref types, builder, i); else goto default; break; default: builder.Insert(fullName, args[i]); break; } } } // Execute return Execute(builder); } private object DynamicUpdate(object[] args, CallInfo info, IList types) { DynamicUpdateQueryBuilder builder = new DynamicUpdateQueryBuilder(this.Database); if (types != null && types.Count == 1) HandleTypeArgument(null, info, ref types, builder, 0); if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0) builder.Table(string.IsNullOrEmpty(this.OwnerName) ? this.TableName : string.Format("{0}.{1}", this.OwnerName, this.TableName), this.Schema); // loop the named args - see if we have order, columns and constraints if (info.ArgumentNames.Count > 0) { for (int i = 0; i < args.Length; i++) { string fullName = info.ArgumentNames[i]; string name = fullName.ToLower(); switch (name) { case "table": if (args[i] is string) builder.Table(args[i].ToString()); else goto default; break; case "update": builder.Update(args[i]); break; case "values": builder.Values(args[i]); break; case "where": builder.Where(args[i]); break; case "type": if (types == null || types.Count == 0) HandleTypeArgument(args, info, ref types, builder, i); else goto default; break; default: builder.Update(fullName, args[i]); break; } } } // Execute return Execute(builder); } private object DynamicDelete(object[] args, CallInfo info, IList types) { DynamicDeleteQueryBuilder builder = new DynamicDeleteQueryBuilder(this.Database); if (types != null && types.Count == 1) HandleTypeArgument(null, info, ref types, builder, 0); if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0) builder.Table(string.IsNullOrEmpty(this.OwnerName) ? this.TableName : string.Format("{0}.{1}", this.OwnerName, this.TableName), this.Schema); // loop the named args - see if we have order, columns and constraints if (info.ArgumentNames.Count > 0) { for (int i = 0; i < args.Length; i++) { string fullName = info.ArgumentNames[i]; string name = fullName.ToLower(); switch (name) { case "table": if (args[i] is string) builder.Table(args[i].ToString()); else goto default; break; case "where": builder.Where(args[i], false); break; case "delete": builder.Where(args[i], true); break; case "type": if (types == null || types.Count == 0) HandleTypeArgument(args, info, ref types, builder, i); else goto default; break; default: builder.Where(fullName, args[i]); break; } } } // Execute return Execute(builder); } private object DynamicQuery(object[] args, CallInfo info, string op, IList types) { object result; DynamicSelectQueryBuilder builder = new DynamicSelectQueryBuilder(this.Database); if (types != null && types.Count == 1) HandleTypeArgument(null, info, ref types, builder, 0); if (!string.IsNullOrEmpty(this.TableName) && builder.Tables.Count == 0) builder.From(x => string.IsNullOrEmpty(this.OwnerName) ? this.TableName : string.Format("{0}.{1}", this.OwnerName, this.TableName)); // loop the named args - see if we have order, columns and constraints if (info.ArgumentNames.Count > 0) { for (int i = 0; i < args.Length; i++) { string fullName = info.ArgumentNames[i]; string name = fullName.ToLower(); // TODO: Make this nicer switch (name) { case "order": if (args[i] is string) builder.OrderByColumn(((string)args[i]).Split(',')); else if (args[i] is string[]) builder.OrderByColumn(args[i] as string); else if (args[i] is DynamicColumn[]) builder.OrderByColumn((DynamicColumn[])args[i]); else if (args[i] is DynamicColumn) builder.OrderByColumn((DynamicColumn)args[i]); else goto default; break; case "group": if (args[i] is string) builder.GroupByColumn(((string)args[i]).Split(',')); else if (args[i] is string[]) builder.GroupByColumn(args[i] as string); else if (args[i] is DynamicColumn[]) builder.GroupByColumn((DynamicColumn[])args[i]); else if (args[i] is DynamicColumn) builder.GroupByColumn((DynamicColumn)args[i]); else goto default; break; case "columns": { string agregate = (op == "Sum" || op == "Max" || op == "Min" || op == "Avg" || op == "Count") ? op.ToUpper() : null; if (args[i] is string || args[i] is string[]) builder.SelectColumn((args[i] as String).NullOr(s => s.Split(','), args[i] as String[]) .Select(c => { DynamicColumn col = DynamicColumn.ParseSelectColumn(c); if (string.IsNullOrEmpty(col.Aggregate)) col.Aggregate = agregate; return col; }).ToArray()); else if (args[i] is DynamicColumn || args[i] is DynamicColumn[]) builder.SelectColumn((args[i] as DynamicColumn).NullOr(c => new DynamicColumn[] { c }, args[i] as DynamicColumn[]) .Select(c => { if (string.IsNullOrEmpty(c.Aggregate)) c.Aggregate = agregate; return c; }).ToArray()); else goto default; } break; case "where": builder.Where(args[i]); break; case "table": if (args[i] is string) builder.From(x => args[i].ToString()); else goto default; break; case "type": if (types == null || types.Count == 0) HandleTypeArgument(args, info, ref types, builder, i); else goto default; break; default: builder.Where(fullName, args[i]); break; } } } if (op == "Count" && !builder.HasSelectColumns) { result = Scalar(builder.Select(x => x.Count())); if (result is long) result = (int)(long)result; } else if (op == "Sum" || op == "Max" || op == "Min" || op == "Avg" || op == "Count") { if (!builder.HasSelectColumns) throw new InvalidOperationException("You must select one column to agregate."); result = Scalar(builder); if (op == "Count" && result is long) result = (int)(long)result; else if (result == DBNull.Value) result = null; } else { // build the SQL bool justOne = op == "First" || op == "Last" || op == "Get" || op == "Single"; // Be sure to sort by DESC on selected columns if (justOne && !(op == "Last")) { if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset) builder.Limit(1); else if ((Database.Options & DynamicDatabaseOptions.SupportTop) == DynamicDatabaseOptions.SupportTop) builder.Top(1); } if (op == "Scalar") { if (!builder.HasSelectColumns) throw new InvalidOperationException("You must select one column in scalar statement."); result = Scalar(builder); } else { if (justOne) { if (op == "Last") result = Query(builder).LastOrDefault(); // Last record fallback else result = Query(builder).FirstOrDefault(); // return a single record } else result = Query(builder); // return lots // MapEnumerable to specified result (still needs to be casted after that) if (types != null) { if (types.Count == 1) result = justOne ? result.Map(types[0]) : ((IEnumerable)result).MapEnumerable(types[0]); // TODO: Dictionaries } } } return result; } private void HandleTypeArgument(object[] args, CallInfo info, ref IList types, T builder, int i) where T : DynamicQueryBuilder { if (args != null) { if (args[i] is Type[]) types = new List((Type[])args[i]); else if (args[i] is Type) types = new List(new Type[] { (Type)args[i] }); } if (types != null && types.Count == 1 && !info.ArgumentNames.Any(a => a.ToLower() == "table")) builder.Table(types[0]); } #endregion Universal Dynamic Invoker #region IExtendedDisposable Members /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. public void Dispose() { if (IsDisposed) return; // Lose reference but don't kill it. if (Database != null) { Database.RemoveFromCache(this); Database = null; } IsDisposed = true; } /// Gets a value indicating whether this instance is disposed. public bool IsDisposed { get; private set; } #endregion IExtendedDisposable Members #region ICloneable Members /// Creates a new object that is a copy of the current /// instance. /// A new object that is a copy of this instance. public object Clone() { return new DynamicTable() { Database = this.Database, Schema = this.Schema, TableName = this.TableName, TableType = this.TableType }; } #endregion ICloneable Members } #endif }