diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs index e6ac6b5..dccfcbe 100644 --- a/AmalgamationTool/DynamORM.Amalgamation.cs +++ b/AmalgamationTool/DynamORM.Amalgamation.cs @@ -1895,14 +1895,15 @@ namespace DynamORM /// Owner of table from which extract column info. /// List of objects . /// If your database doesn't get those values in upper case (like most of the databases) you should override this method. - protected virtual IEnumerable ReadSchema(string table, string owner) + protected virtual IList ReadSchema(string table, string owner) { using (var con = Open()) using (var cmd = con.CreateCommand() .SetCommand(string.Format("SELECT * FROM {0}{1} WHERE 1 = 0", !string.IsNullOrEmpty(owner) ? string.Format("{0}.", DecorateName(owner)) : string.Empty, DecorateName(table)))) - return ReadSchema(cmd).ToList(); + return ReadSchema(cmd) + .ToList(); } /// Get schema describing objects from reader. @@ -1912,7 +1913,8 @@ namespace DynamORM protected virtual IEnumerable ReadSchema(IDbCommand cmd) { using (var rdr = cmd.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo)) - foreach (DataRow col in rdr.GetSchemaTable().Rows) + using (var st = rdr.GetSchemaTable()) + foreach (DataRow col in st.Rows) { var c = col.RowToDynamicUpper(); @@ -1950,7 +1952,8 @@ namespace DynamORM if (databaseSchemaSupport && !Schema.ContainsKey(tableName.ToLower())) { schema = ReadSchema(tableName, owner) - .Distinct() + .Where(x => x.Name != null) + .DistinctBy(x => x.Name) .ToDictionary(k => k.Name.ToLower(), k => k); Schema[tableName.ToLower()] = schema; @@ -2391,6 +2394,170 @@ namespace DynamORM DumpCommands = 0x01000000, } + /// Dynamic expando is a simple and temporary class to resolve memory leaks inside ExpandoObject. + public class DynamicExpando : DynamicObject, IDictionary, ICollection>, IEnumerable>, IEnumerable + { + /// Class containing information about last accessed property of dynamic object. + public class PropertyAccess + { + /// Enum describing type of access to object. + public enum TypeOfAccess + { + /// Get member. + Get, + + /// Set member. + Set, + } + + /// Gets the type of operation. + public TypeOfAccess Operation { get; internal set; } + + /// Gets the name of property. + public string Name { get; internal set; } + + /// Gets the type from binder. + public Type RequestedType { get; internal set; } + + /// Gets the type of value stored in object. + public Type Type { get; internal set; } + + /// Gets the value stored in object. + public object Value { get; internal set; } + } + + private Dictionary _data = new Dictionary(); + + private PropertyAccess _lastProp = new PropertyAccess(); + + /// Initializes a new instance of the class. + public DynamicExpando() + { + } + + /// Gets the last accesses property. + /// Description of last accessed property. + public PropertyAccess GetLastAccessesProperty() + { + return _lastProp; + } + + /// Tries to get member value. + /// Returns true, if get member was tried, false otherwise. + /// The context binder. + /// The invocation result. + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + result = _data.TryGetValue(binder.Name); + + _lastProp.Operation = PropertyAccess.TypeOfAccess.Get; + _lastProp.RequestedType = binder.ReturnType; + _lastProp.Name = binder.Name; + _lastProp.Value = result; + _lastProp.Type = result == null ? typeof(void) : result.GetType(); + + return true; + } + + /// Tries to set member. + /// Returns true, if set member was tried, false otherwise. + /// The context binder. + /// Value which will be set. + public override bool TrySetMember(SetMemberBinder binder, object value) + { + _data[binder.Name] = value; + + _lastProp.Operation = PropertyAccess.TypeOfAccess.Set; + _lastProp.RequestedType = binder.ReturnType; + _lastProp.Name = binder.Name; + _lastProp.Value = value; + _lastProp.Type = value == null ? typeof(void) : value.GetType(); + + return true; + } + + #region IDictionary implementation + + bool IDictionary.ContainsKey(string key) + { + return _data.ContainsKey(key); + } + + void IDictionary.Add(string key, object value) + { + _data.Add(key, value); + } + + bool IDictionary.Remove(string key) + { + return _data.Remove(key); + } + + bool IDictionary.TryGetValue(string key, out object value) + { + return _data.TryGetValue(key, out value); + } + + object IDictionary.this[string index] { get { return _data[index]; } set { _data[index] = value; } } + + ICollection IDictionary.Keys { get { return _data.Keys; } } + + ICollection IDictionary.Values { get { return _data.Values; } } + + #endregion IDictionary implementation + + #region ICollection implementation + + void ICollection>.Add(KeyValuePair item) + { + ((ICollection>)_data).Add(item); + } + + void ICollection>.Clear() + { + _data.Clear(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return ((ICollection>)_data).Contains(item); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((ICollection>)_data).CopyTo(array, arrayIndex); + } + + bool ICollection>.Remove(KeyValuePair item) + { + return ((ICollection>)_data).Remove(item); + } + + int ICollection>.Count { get { return _data.Count; } } + + bool ICollection>.IsReadOnly { get { return ((ICollection>)_data).IsReadOnly; } } + + #endregion ICollection implementation + + #region IEnumerable implementation + + IEnumerator> IEnumerable>.GetEnumerator() + { + return _data.GetEnumerator(); + } + + #endregion IEnumerable implementation + + #region IEnumerable implementation + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_data).GetEnumerator(); + } + + #endregion IEnumerable implementation + } + /// Extension to ORM objects. public static class DynamicExtensions { @@ -2577,6 +2744,20 @@ namespace DynamORM return cmd; } + /// Extension method for adding in a bunch of parameters. + /// Command to handle. + /// Database object required to get proper formatting. + /// Items to add in an expando object. + /// Returns edited instance. + public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, DynamicExpando args) + { + if (args != null && args.Count() > 0) + foreach (var item in args.ToDictionary()) + cmd.AddParameter(database, item.Key, item.Value); + + return cmd; + } + /// Extension for adding single parameter determining only type of object. /// Command to handle. /// Database object required to get proper formatting. @@ -2606,7 +2787,7 @@ namespace DynamORM p.DbType = TypeMap.TryGetNullable(type) ?? DbType.String; - if (type == typeof(ExpandoObject)) + if (type == typeof(DynamicExpando) || type == typeof(ExpandoObject)) p.Value = ((IDictionary)item).Values.FirstOrDefault(); else p.Value = item; @@ -3167,6 +3348,15 @@ namespace DynamORM return b.Execute().ToList(); } + /// Turns an to a Dynamic list of things with specified type. + /// Type of object to map on. + /// Ready to execute builder. + /// List of things. + public static List ToList(this IDynamicSelectQueryBuilder b) where T : class + { + return b.Execute().ToList(); + } + /// Sets the on create temporary parameter action. /// Class implementing interface. /// The builder on which set delegate. @@ -3289,22 +3479,27 @@ namespace DynamORM return result; } - /// Turns an to a Dynamic list of things with specified type. - /// Type of object to map on. - /// Ready to execute builder. - /// List of things. - public static List ToList(this IDynamicSelectQueryBuilder b) where T : class - { - return b.Execute().ToList(); - } - /// Turns the dictionary into an ExpandoObject. /// Dictionary to convert. /// Converted dictionary. public static dynamic ToDynamic(this IDictionary d) + { + var result = new DynamicExpando(); + var dict = (IDictionary)result; + + foreach (var prop in d) + dict.Add(prop.Key, prop.Value); + + return result; + } + + /// Turns the dictionary into an ExpandoObject. + /// Dictionary to convert. + /// Converted dictionary. + public static dynamic ToExpando(this IDictionary d) { var result = new ExpandoObject(); - var dict = result as IDictionary; + var dict = (IDictionary)result; foreach (var prop in d) dict.Add(prop.Key, prop.Value); @@ -3317,13 +3512,62 @@ namespace DynamORM /// Converted object. public static dynamic ToDynamic(this object o) { - var result = new ExpandoObject(); - var dict = result as IDictionary; var ot = o.GetType(); - if (ot == typeof(ExpandoObject)) + if (ot == typeof(DynamicExpando) || ot == typeof(ExpandoObject)) return o; + var result = new DynamicExpando(); + var dict = (IDictionary)result; + + if (o is IDictionary) + ((IDictionary)o) + .ToList() + .ForEach(kvp => dict.Add(kvp.Key, kvp.Value)); + else if (ot == typeof(NameValueCollection) || ot.IsSubclassOf(typeof(NameValueCollection))) + { + var nameValue = (NameValueCollection)o; + nameValue.Cast() + .Select(key => new KeyValuePair(key, nameValue[key])) + .ToList() + .ForEach(i => dict.Add(i)); + } + else + { + var mapper = DynamicMapperCache.GetMapper(ot); + + if (mapper != null) + { + foreach (var item in mapper.ColumnsMap.Values) + if (item.Get != null) + dict.Add(item.Name, item.Get(o)); + } + else + { + var props = ot.GetProperties(); + + foreach (var item in props) + if (item.CanRead) + dict.Add(item.Name, item.GetValue(o, null)); + } + } + + return result; + } + + /// Turns the object into an ExpandoObject. + /// Object to convert. + /// Converted object. + public static dynamic ToExpando(this object o) + { + var ot = o.GetType(); + + if (ot == typeof(ExpandoObject) || ot == typeof(DynamicExpando)) + return o; + + var result = new ExpandoObject(); + var dict = (IDictionary)result; + if (o is IDictionary) ((IDictionary)o) .ToList() @@ -3364,13 +3608,27 @@ namespace DynamORM /// Generated dynamic object. public static dynamic RowToDynamic(this DataRow r) { - dynamic e = new ExpandoObject(); - var d = e as IDictionary; + dynamic e = new DynamicExpando(); int len = r.Table.Columns.Count; for (int i = 0; i < len; i++) - d.Add(r.Table.Columns[i].ColumnName, r.IsNull(i) ? null : r[i]); + ((IDictionary)e).Add(r.Table.Columns[i].ColumnName, r.IsNull(i) ? null : r[i]); + + return e; + } + + /// Convert data row row into dynamic object. + /// DataRow from which read. + /// Generated dynamic object. + public static dynamic RowToExpando(this DataRow r) + { + dynamic e = new ExpandoObject(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + ((IDictionary)e).Add(r.Table.Columns[i].ColumnName, r.IsNull(i) ? null : r[i]); return e; } @@ -3380,13 +3638,40 @@ namespace DynamORM /// Generated dynamic object. public static dynamic RowToDynamicUpper(this DataRow r) { - dynamic e = new ExpandoObject(); - var d = e as IDictionary; + dynamic e = new DynamicExpando(); int len = r.Table.Columns.Count; for (int i = 0; i < len; i++) - d.Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); + ((IDictionary)e).Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); + + return e; + } + + /// Convert data row row into dynamic object (upper case key). + /// DataRow from which read. + /// Generated dynamic object. + public static dynamic RowToExpandoUpper(this DataRow r) + { + // ERROR: Memory leak + dynamic e = new ExpandoObject(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + ((IDictionary)e).Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); + + return e; + } + + internal static Dictionary RowToDynamicUpperDict(this DataRow r) + { + dynamic e = new Dictionary(); + + int len = r.Table.Columns.Count; + + for (int i = 0; i < len; i++) + e.Add(r.Table.Columns[i].ColumnName.ToUpper(), r.IsNull(i) ? null : r[i]); return e; } @@ -3395,6 +3680,29 @@ namespace DynamORM /// Reader from which read. /// Generated dynamic object. public static dynamic RowToDynamic(this IDataReader r) + { + dynamic e = new DynamicExpando(); + var d = e as IDictionary; + + int c = r.FieldCount; + for (int i = 0; i < c; i++) + try + { + d.Add(r.GetName(i), r.IsDBNull(i) ? null : r[i]); + } + catch (ArgumentException argex) + { + throw new ArgumentException( + string.Format("Field '{0}' is defined more than once in a query.", r.GetName(i)), "Column name or alias", argex); + } + + return e; + } + + /// Convert reader row into dynamic object. + /// Reader from which read. + /// Generated dynamic object. + public static dynamic RowToExpando(this IDataReader r) { dynamic e = new ExpandoObject(); var d = e as IDictionary; @@ -3422,6 +3730,14 @@ namespace DynamORM return (IDictionary)o; } + /// Turns the object into a Dictionary. + /// Object to convert. + /// Resulting dictionary. + public static IDictionary ToDictionary(this DynamicExpando o) + { + return (IDictionary)o; + } + /// Turns the object into a Dictionary. /// Object to convert. /// Resulting dictionary. @@ -3765,6 +4081,7 @@ namespace DynamORM } /// Dynamic query exception. + [Serializable] public class DynamicQueryException : Exception, ISerializable { /// Initializes a new instance of the @@ -4271,9 +4588,8 @@ namespace DynamORM /// 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 = null) + public virtual int Procedure(string procName) { if ((Database.Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) throw new InvalidOperationException("Database connection desn't support stored procedures."); @@ -4282,7 +4598,64 @@ namespace DynamORM using (var cmd = con.CreateCommand()) { return cmd - .SetCommand(CommandType.StoredProcedure, procName).AddParameters(Database, args) + .SetCommand(CommandType.StoredProcedure, procName) + .ExecuteNonQuery(); + } + } + + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Arguments (parameters) in form of expando object. + /// Number of affected rows. + public virtual int Procedure(string procName, params object[] args) + { + if ((Database.Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(Database, args) + .ExecuteNonQuery(); + } + } + + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Arguments (parameters) in form of expando object. + /// Number of affected rows. + public virtual int Procedure(string procName, DynamicExpando args) + { + if ((Database.Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(Database, args) + .ExecuteNonQuery(); + } + } + + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Arguments (parameters) in form of expando object. + /// Number of affected rows. + public virtual int Procedure(string procName, ExpandoObject args) + { + if ((Database.Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(Database, args) .ExecuteNonQuery(); } } @@ -6870,9 +7243,9 @@ namespace DynamORM //// 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 parts = colName.Split('.'); + for (int i = 0; i < parts.Length; i++) + parts[i] = Database.StripName(parts[i]); var columnName = parts.Last(); @@ -7802,7 +8175,11 @@ namespace DynamORM /// Builder instance. public virtual IDynamicSelectQueryBuilder SelectColumn(params string[] columns) { - return SelectColumn(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray()); + var cols = new DynamicColumn[columns.Length]; + for (int i = 0; i < columns.Length; i++) + cols[i] = DynamicColumn.ParseSelectColumn(columns[i]); + + return SelectColumn(cols); } #endregion Select @@ -7823,8 +8200,11 @@ namespace DynamORM int index = GroupByFunc(-1, fn); if (func != null) - foreach (var f in func) + for (int i = 0; i < func.Length; i++) + { + var f = func[i]; index = GroupByFunc(index, f); + } return this; } @@ -7862,8 +8242,11 @@ namespace DynamORM /// Builder instance. public virtual IDynamicSelectQueryBuilder GroupByColumn(params DynamicColumn[] columns) { - foreach (var col in columns) + for (int i = 0; i < columns.Length; i++) + { + var col = columns[i]; GroupBy(x => col.ToSQLGroupByColumn(Database)); + } return this; } @@ -7899,8 +8282,11 @@ namespace DynamORM int index = OrderByFunc(-1, fn); if (func != null) - foreach (var f in func) + for (int i = 0; i < func.Length; i++) + { + var f = func[i]; index = OrderByFunc(index, f); + } return this; } @@ -7991,8 +8377,11 @@ namespace DynamORM /// Builder instance. public virtual IDynamicSelectQueryBuilder OrderByColumn(params DynamicColumn[] columns) { - foreach (var col in columns) + for (int i = 0; i < columns.Length; i++) + { + var col = columns[i]; OrderBy(x => col.ToSQLOrderByColumn(Database)); + } return this; } @@ -8746,6 +9135,15 @@ namespace DynamORM bool IsDisposed { get; } } + /// Extends interface. + public interface IFinalizerDisposable : IExtendedDisposable + { + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + void Dispose(bool disposing); + } + /// Class containing useful string extensions. internal static class StringExtensions { @@ -9084,6 +9482,20 @@ namespace DynamORM return obj != null && obj != DBNull.Value ? func(obj) : elseFunc != null ? elseFunc() : default(R); } + + /// Simple distinct by selector extension. + /// The enumerator of elements distinct by specified selector. + /// Source collection. + /// Distinct key selector. + /// The enumerable element type parameter. + /// The selector type parameter. + public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) + { + HashSet seenKeys = new HashSet(); + foreach (R element in source) + if (seenKeys.Add(keySelector(element))) + yield return element; + } } namespace Dynamics @@ -9100,7 +9512,7 @@ namespace DynamORM /// a method of that argument. /// [Serializable] - public class Node : IDynamicMetaObjectProvider, IExtendedDisposable, ISerializable + public class Node : IDynamicMetaObjectProvider, IFinalizerDisposable, ISerializable { private DynamicParser _parser = null; @@ -9123,10 +9535,11 @@ namespace DynamORM { } - private DynamicMetaObject GetBinder(Func newNodeFunc) + // Func was cool but caused memory leaks + private DynamicMetaObject GetBinder(Node node) { var o = (Node)this.Value; - var node = newNodeFunc(o); + node.Parser = o.Parser; o.Parser.Last = node; var p = Expression.Variable(typeof(Node), "ret"); @@ -9144,7 +9557,7 @@ namespace DynamORM /// public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { - return GetBinder(x => new GetMember(x, binder.Name) { Parser = x.Parser }); + return GetBinder(new GetMember((Node)this.Value, binder.Name)); } /// @@ -9157,7 +9570,7 @@ namespace DynamORM /// public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { - return GetBinder(x => new SetMember(x, binder.Name, value.Value) { Parser = x.Parser }); + return GetBinder(new SetMember((Node)this.Value, binder.Name, value.Value)); } /// @@ -9170,7 +9583,7 @@ namespace DynamORM /// public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { - return GetBinder(x => new GetIndex(x, indexes.Select(m => m.Value).ToArray()) { Parser = x.Parser }); + return GetBinder(new GetIndex((Node)this.Value, MetaList2List(indexes))); } /// @@ -9184,7 +9597,7 @@ namespace DynamORM /// public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { - return GetBinder(x => new SetIndex(x, indexes.Select(m => m.Value).ToArray(), value.Value) { Parser = x.Parser }); + return GetBinder(new SetIndex((Node)this.Value, MetaList2List(indexes), value.Value)); } /// @@ -9197,7 +9610,7 @@ namespace DynamORM /// public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) { - return GetBinder(x => new Invoke(x, args.Select(m => m.Value).ToArray()) { Parser = x.Parser }); + return GetBinder(new Invoke((Node)this.Value, MetaList2List(args))); } /// @@ -9210,7 +9623,7 @@ namespace DynamORM /// public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { - return GetBinder(x => new Method(x, binder.Name, args.Select(m => m.Value).ToArray()) { Parser = x.Parser }); + return GetBinder(new Method((Node)this.Value, binder.Name, MetaList2List(args))); } /// @@ -9223,7 +9636,7 @@ namespace DynamORM /// public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) { - return GetBinder(x => new Binary(x, binder.Operation, arg.Value) { Parser = x.Parser }); + return GetBinder(new Binary((Node)this.Value, binder.Operation, arg.Value)); } /// @@ -9304,6 +9717,17 @@ namespace DynamORM return new MetaNode(exp, this.Restrictions, node); } + + private static object[] MetaList2List(DynamicMetaObject[] metaObjects) + { + if (metaObjects == null) return null; + + object[] list = new object[metaObjects.Length]; + for (int i = 0; i < metaObjects.Length; i++) + list[i] = metaObjects[i].Value; + + return list; + } } #endregion MetaNode @@ -9816,16 +10240,17 @@ namespace DynamORM /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. - public override void Dispose() + /// If set to true dispose object. + public override void Dispose(bool disposing) { - base.Dispose(); + base.Dispose(disposing); - if (Right != null && Right is Node) + if (disposing && Right != null && Right is Node) { Node n = (Node)Right; if (!n.IsDisposed) - n.Dispose(); + n.Dispose(disposing); Right = null; } @@ -10053,7 +10478,13 @@ namespace DynamORM #endregion Implementation of IDynamicMetaObjectProvider - #region Implementation of IExtendedDisposable + #region Implementation of IFinalizerDisposable + + /// Finalizes an instance of the class. + ~Node() + { + Dispose(false); + } /// Gets a value indicating whether this instance is disposed. public bool IsDisposed { get; private set; } @@ -10062,17 +10493,29 @@ namespace DynamORM /// freeing, releasing, or resetting unmanaged resources. public virtual void Dispose() { - IsDisposed = true; - - if (Host != null && !Host.IsDisposed) - Host.Dispose(); - - Host = null; - - Parser = null; + Dispose(true); + GC.SuppressFinalize(this); } - #endregion Implementation of IExtendedDisposable + /// Performs application-defined tasks associated with + /// freeing, releasing, or resetting unmanaged resources. + /// If set to true dispose object. + public virtual void Dispose(bool disposing) + { + if (disposing) + { + IsDisposed = true; + + if (Host != null && !Host.IsDisposed) + Host.Dispose(); + + Host = null; + + Parser = null; + } + } + + #endregion Implementation of IFinalizerDisposable #region Implementation of ISerializable @@ -10150,10 +10593,17 @@ namespace DynamORM private DynamicParser(Delegate f) { - foreach (var p in f.Method.GetParameters()) + // I know this can be almost a one liner + // but it causes memory leaks when so. + var pars = f.Method.GetParameters(); + foreach (var p in pars) { - if (p.GetCustomAttributes(typeof(DynamicAttribute), true).Any()) - this._arguments.Add(new Node.Argument(p.Name) { Parser = this }); + var attrs = p.GetCustomAttributes(typeof(DynamicAttribute), true).Length; + if (attrs != 0) + { + var par = new Node.Argument(p.Name) { Parser = this }; + this._arguments.Add(par); + } else throw new ArgumentException(string.Format("Argument '{0}' must be dynamic.", p.Name)); } @@ -10287,6 +10737,7 @@ namespace DynamORM { try { + // TODO: MAke this work... open instance delegate would be nice return Delegate.CreateDelegate(Expression.GetDelegateType(v.GetParameters().Select(t => t.ParameterType).Concat(new[] { v.ReflectedType }).ToArray()), _proxy, v.Name); } catch (ArgumentException) diff --git a/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs index 162a583..12eb8d5 100644 --- a/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs +++ b/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs @@ -898,7 +898,7 @@ namespace DynamORM.Builders.Implementation /// Builder instance. public virtual IDynamicSelectQueryBuilder SelectColumn(params string[] columns) { - var cols = new DynamicColumn[columns.Count]; + var cols = new DynamicColumn[columns.Length]; for (int i = 0; i < columns.Length; i++) cols[i] = DynamicColumn.ParseSelectColumn(columns[i]); diff --git a/DynamORM/DynamicExpando.cs b/DynamORM/DynamicExpando.cs index 411e5f1..99b069c 100644 --- a/DynamORM/DynamicExpando.cs +++ b/DynamORM/DynamicExpando.cs @@ -30,39 +30,88 @@ using System; using System.Collections; using System.Collections.Generic; using System.Dynamic; -using System.Collections.Concurrent; namespace DynamORM { /// Dynamic expando is a simple and temporary class to resolve memory leaks inside ExpandoObject. public class DynamicExpando : DynamicObject, IDictionary, ICollection>, IEnumerable>, IEnumerable { + /// Class containing information about last accessed property of dynamic object. + public class PropertyAccess + { + /// Enum describing type of access to object. + public enum TypeOfAccess + { + /// Get member. + Get, + + /// Set member. + Set, + } + + /// Gets the type of operation. + public TypeOfAccess Operation { get; internal set; } + + /// Gets the name of property. + public string Name { get; internal set; } + + /// Gets the type from binder. + public Type RequestedType { get; internal set; } + + /// Gets the type of value stored in object. + public Type Type { get; internal set; } + + /// Gets the value stored in object. + public object Value { get; internal set; } + } + private Dictionary _data = new Dictionary(); + private PropertyAccess _lastProp = new PropertyAccess(); + /// Initializes a new instance of the class. public DynamicExpando() { } + /// Gets the last accesses property. + /// Description of last accessed property. + public PropertyAccess GetLastAccessesProperty() + { + return _lastProp; + } + /// Tries to get member value. - /// Returns true, if get member was tryed, false otherwise. + /// Returns true, if get member was tried, false otherwise. /// The context binder. - /// The invokation result. + /// The invocation result. public override bool TryGetMember(GetMemberBinder binder, out object result) { result = _data.TryGetValue(binder.Name); + _lastProp.Operation = PropertyAccess.TypeOfAccess.Get; + _lastProp.RequestedType = binder.ReturnType; + _lastProp.Name = binder.Name; + _lastProp.Value = result; + _lastProp.Type = result == null ? typeof(void) : result.GetType(); + return true; } /// Tries to set member. - /// Returns true, if set member was tryed, false otherwise. + /// Returns true, if set member was tried, false otherwise. /// The context binder. /// Value which will be set. public override bool TrySetMember(SetMemberBinder binder, object value) { _data[binder.Name] = value; + _lastProp.Operation = PropertyAccess.TypeOfAccess.Set; + _lastProp.RequestedType = binder.ReturnType; + _lastProp.Name = binder.Name; + _lastProp.Value = value; + _lastProp.Type = value == null ? typeof(void) : value.GetType(); + return true; } @@ -88,13 +137,13 @@ namespace DynamORM return _data.TryGetValue(key, out value); } - object IDictionary.this [string index] { get { return _data[index]; } set { _data[index] = value; } } + object IDictionary.this[string index] { get { return _data[index]; } set { _data[index] = value; } } ICollection IDictionary.Keys { get { return _data.Keys; } } ICollection IDictionary.Values { get { return _data.Values; } } - #endregion + #endregion IDictionary implementation #region ICollection implementation @@ -123,11 +172,11 @@ namespace DynamORM return ((ICollection>)_data).Remove(item); } - int ICollection>.Count{ get { return _data.Count; } } + int ICollection>.Count { get { return _data.Count; } } bool ICollection>.IsReadOnly { get { return ((ICollection>)_data).IsReadOnly; } } - #endregion + #endregion ICollection implementation #region IEnumerable implementation @@ -136,7 +185,7 @@ namespace DynamORM return _data.GetEnumerator(); } - #endregion + #endregion IEnumerable implementation #region IEnumerable implementation @@ -145,7 +194,6 @@ namespace DynamORM return ((IEnumerable)_data).GetEnumerator(); } - #endregion + #endregion IEnumerable implementation } -} - +} \ No newline at end of file diff --git a/DynamORM/DynamicExtensions.cs b/DynamORM/DynamicExtensions.cs index b0a44f9..357096d 100644 --- a/DynamORM/DynamicExtensions.cs +++ b/DynamORM/DynamicExtensions.cs @@ -229,6 +229,20 @@ namespace DynamORM return cmd; } + /// Extension method for adding in a bunch of parameters. + /// Command to handle. + /// Database object required to get proper formatting. + /// Items to add in an expando object. + /// Returns edited instance. + public static IDbCommand AddParameters(this IDbCommand cmd, DynamicDatabase database, DynamicExpando args) + { + if (args != null && args.Count() > 0) + foreach (var item in args.ToDictionary()) + cmd.AddParameter(database, item.Key, item.Value); + + return cmd; + } + /// Extension for adding single parameter determining only type of object. /// Command to handle. /// Database object required to get proper formatting. @@ -258,7 +272,7 @@ namespace DynamORM p.DbType = TypeMap.TryGetNullable(type) ?? DbType.String; - if (type == typeof(ExpandoObject)) + if (type == typeof(DynamicExpando) || type == typeof(ExpandoObject)) p.Value = ((IDictionary)item).Values.FirstOrDefault(); else p.Value = item; @@ -985,7 +999,7 @@ namespace DynamORM { var ot = o.GetType(); - if (ot == typeof(ExpandoObject) || ot == typeof(DynamicExpando)) + if (ot == typeof(DynamicExpando) || ot == typeof(ExpandoObject)) return o; var result = new DynamicExpando(); @@ -1158,14 +1172,14 @@ namespace DynamORM int c = r.FieldCount; for (int i = 0; i < c; i++) try - { - d.Add(r.GetName(i), r.IsDBNull(i) ? null : r[i]); - } - catch (ArgumentException argex) - { - throw new ArgumentException( - string.Format("Field '{0}' is defined more than once in a query.", r.GetName(i)), "Column name or alias", argex); - } + { + d.Add(r.GetName(i), r.IsDBNull(i) ? null : r[i]); + } + catch (ArgumentException argex) + { + throw new ArgumentException( + string.Format("Field '{0}' is defined more than once in a query.", r.GetName(i)), "Column name or alias", argex); + } return e; } @@ -1181,14 +1195,14 @@ namespace DynamORM int c = r.FieldCount; for (int i = 0; i < c; i++) try - { - d.Add(r.GetName(i), r.IsDBNull(i) ? null : r[i]); - } - catch (ArgumentException argex) - { - throw new ArgumentException( - string.Format("Field '{0}' is defined more than once in a query.", r.GetName(i)), "Column name or alias", argex); - } + { + d.Add(r.GetName(i), r.IsDBNull(i) ? null : r[i]); + } + catch (ArgumentException argex) + { + throw new ArgumentException( + string.Format("Field '{0}' is defined more than once in a query.", r.GetName(i)), "Column name or alias", argex); + } return e; } @@ -1201,6 +1215,14 @@ namespace DynamORM return (IDictionary)o; } + /// Turns the object into a Dictionary. + /// Object to convert. + /// Resulting dictionary. + public static IDictionary ToDictionary(this DynamicExpando o) + { + return (IDictionary)o; + } + /// Turns the object into a Dictionary. /// Object to convert. /// Resulting dictionary. diff --git a/DynamORM/DynamicQueryException.cs b/DynamORM/DynamicQueryException.cs index 80b62c8..3ca0783 100644 --- a/DynamORM/DynamicQueryException.cs +++ b/DynamORM/DynamicQueryException.cs @@ -33,6 +33,7 @@ using System.Runtime.Serialization; namespace DynamORM { /// Dynamic query exception. + [Serializable] public class DynamicQueryException : Exception, ISerializable { /// Initializes a new instance of the diff --git a/DynamORM/DynamicTable.cs b/DynamORM/DynamicTable.cs index 8c9c39b..b8f9721 100644 --- a/DynamORM/DynamicTable.cs +++ b/DynamORM/DynamicTable.cs @@ -456,9 +456,8 @@ namespace DynamORM /// 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 = null) + public virtual int Procedure(string procName) { if ((Database.Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) throw new InvalidOperationException("Database connection desn't support stored procedures."); @@ -467,7 +466,64 @@ namespace DynamORM using (var cmd = con.CreateCommand()) { return cmd - .SetCommand(CommandType.StoredProcedure, procName).AddParameters(Database, args) + .SetCommand(CommandType.StoredProcedure, procName) + .ExecuteNonQuery(); + } + } + + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Arguments (parameters) in form of expando object. + /// Number of affected rows. + public virtual int Procedure(string procName, params object[] args) + { + if ((Database.Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(Database, args) + .ExecuteNonQuery(); + } + } + + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Arguments (parameters) in form of expando object. + /// Number of affected rows. + public virtual int Procedure(string procName, DynamicExpando args) + { + if ((Database.Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(Database, args) + .ExecuteNonQuery(); + } + } + + /// Execute stored procedure. + /// Name of stored procedure to execute. + /// Arguments (parameters) in form of expando object. + /// Number of affected rows. + public virtual int Procedure(string procName, ExpandoObject args) + { + if ((Database.Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures) + throw new InvalidOperationException("Database connection desn't support stored procedures."); + + using (var con = Database.Open()) + using (var cmd = con.CreateCommand()) + { + return cmd + .SetCommand(CommandType.StoredProcedure, procName) + .AddParameters(Database, args) .ExecuteNonQuery(); } } diff --git a/DynamORM/Helpers/Dynamics/DynamicParser.cs b/DynamORM/Helpers/Dynamics/DynamicParser.cs index b653d80..434bd96 100644 --- a/DynamORM/Helpers/Dynamics/DynamicParser.cs +++ b/DynamORM/Helpers/Dynamics/DynamicParser.cs @@ -32,7 +32,6 @@ using System; using System.Collections.Generic; using System.Dynamic; -using System.Linq; using System.Linq.Expressions; using System.Runtime.CompilerServices; using System.Runtime.Serialization; @@ -1020,9 +1019,7 @@ namespace DynamORM.Helpers.Dynamics #region Implementation of IFinalizerDisposable - /// Releases unmanaged resources and performs other cleanup operations before - /// the is reclaimed by - /// garbage collection. + /// Finalizes an instance of the class. ~Node() { Dispose(false); diff --git a/DynamORM/Helpers/IFinalizerDisposable.cs b/DynamORM/Helpers/IFinalizerDisposable.cs index 8986eba..5bf1ced 100644 --- a/DynamORM/Helpers/IFinalizerDisposable.cs +++ b/DynamORM/Helpers/IFinalizerDisposable.cs @@ -25,7 +25,6 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ -using System; namespace DynamORM.Helpers { @@ -37,5 +36,4 @@ namespace DynamORM.Helpers /// If set to true dispose object. void Dispose(bool disposing); } -} - +} \ No newline at end of file diff --git a/Tester/Tester.csproj b/Tester/Tester.csproj index 0466690..1577f28 100644 --- a/Tester/Tester.csproj +++ b/Tester/Tester.csproj @@ -26,7 +26,7 @@ true - x86 + AnyCPU pdbonly true bin\Release\ @@ -38,14 +38,14 @@ + + C:\Program Files\System.Data.SQLite\2010\bin\System.Data.SQLite.dll + - - ..\..\..\Libs\SQLite\x86\System.Data.SQLite.dll -