diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs
index 1b53e1b..8045f48 100644
--- a/AmalgamationTool/DynamORM.Amalgamation.cs
+++ b/AmalgamationTool/DynamORM.Amalgamation.cs
@@ -1,6 +1,6 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
- * Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
+ * Copyright (c) 2012-2015, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -35,6 +35,7 @@
using System;
using System.Collections;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
@@ -935,6 +936,7 @@ namespace DynamORM
#region Internal fields and properties
private DbProviderFactory _provider;
+ private DynamicProcedureInvoker _proc;
private string _connectionString;
private bool _singleConnection;
private bool _singleTransaction;
@@ -1006,6 +1008,23 @@ namespace DynamORM
/// Gets the database provider.
public DbProviderFactory Provider { get { return _provider; } }
+ /// Gets the procedures invoker.
+ public dynamic Procedures
+ {
+ get
+ {
+ if (_proc == null)
+ {
+ if ((Options & DynamicDatabaseOptions.SupportStoredProcedures) != DynamicDatabaseOptions.SupportStoredProcedures)
+ throw new InvalidOperationException("Database connection desn't support stored procedures.");
+
+ _proc = new DynamicProcedureInvoker(this);
+ }
+
+ return _proc;
+ }
+ }
+
/// Gets or sets a value indicating whether
/// dump commands to console or not.
public bool DumpCommands { get; set; }
@@ -2636,6 +2655,8 @@ namespace DynamORM
}
ClearSchema();
+ if (_proc != null)
+ _proc.Dispose();
IsDisposed = true;
}
@@ -3007,7 +3028,14 @@ namespace DynamORM
{
if (args != null && args.Count() > 0)
foreach (object item in args)
- cmd.AddParameter(database, item);
+ {
+ if (item is DynamicExpando)
+ cmd.AddParameters(database, (DynamicExpando)item);
+ else if (item is ExpandoObject)
+ cmd.AddParameters(database, (ExpandoObject)item);
+ else
+ cmd.AddParameter(database, item);
+ }
return cmd;
}
@@ -4210,6 +4238,12 @@ namespace DynamORM
return TypeMap.TryGetNullable(r.GetFieldType(i)) ?? DbType.String;
}
+ internal static IEnumerable EnumerateReader(this IDataReader r)
+ {
+ while (r.Read())
+ yield return r.RowToDynamic();
+ }
+
#endregion IDataReader extensions
#region Mapper extensions
@@ -4383,6 +4417,203 @@ namespace DynamORM
#endregion Coalesce - besicaly not an extensions
}
+ /// Dynamic procedure invoker.
+ /// Unfortunately I can use out and ref to
+ /// return parameters, .
+ /// But see example for workaround. If there aren't any return parameters execution will return scalar value.
+ /// Scalar result is not converted to provided generic type (if any). For output results there is possibility to map to provided class.
+ /// You still can use out, return and both way parameters by providing variable prefix:
+ /// dynamic res = db.Procedures.sp_Test_Scalar_In_Out(inp: Guid.NewGuid(), out_outp: Guid.Empty);
+ /// Console.Out.WriteLine(res.outp);
+ /// Prefixes: out_, ret_, both_. Result will contain field without prefix.
+ /// Here is an example with result class:
+ /// public class ProcResult { [Column("outp")] public Guid Output { get; set; } }
+ /// ProcResult res4 = db.Procedures.sp_Test_Scalar_In_Out<ProcResult>(inp: Guid.NewGuid(), out_outp: Guid.Empty) as ProcResult;
+ /// As you can se, you can use mapper to do job for you.
+ public class DynamicProcedureInvoker : DynamicObject, IDisposable
+ {
+ private DynamicDatabase _db;
+
+ internal DynamicProcedureInvoker(DynamicDatabase db)
+ {
+ _db = db;
+ }
+
+ /// This is where the magic begins.
+ /// Binder to invoke.
+ /// Binder arguments.
+ /// Binder invoke result.
+ /// Returns true if invoke was performed.
+ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
+ {
+ // parse the method
+ CallInfo info = binder.CallInfo;
+
+ // Get generic types
+ IList types = binder.GetGenericTypeArguments();
+
+ Dictionary retParams = null;
+
+ using (IDbConnection con = _db.Open())
+ using (IDbCommand cmd = con.CreateCommand())
+ {
+ cmd.SetCommand(CommandType.StoredProcedure, binder.Name);
+
+ #region Prepare arguments
+
+ int alen = args.Length;
+ if (alen > 0)
+ {
+ for (int i = 0; i < alen; i++)
+ {
+ object arg = args[i];
+
+ if (arg is DynamicExpando)
+ cmd.AddParameters(_db, (DynamicExpando)arg);
+ else if (arg is ExpandoObject)
+ cmd.AddParameters(_db, (ExpandoObject)arg);
+ else
+ {
+ if (info.ArgumentNames.Count > i && !string.IsNullOrEmpty(info.ArgumentNames[i]))
+ {
+ bool isOut = info.ArgumentNames[i].StartsWith("out_");
+ bool isRet = info.ArgumentNames[i].StartsWith("ret_");
+ bool isBoth = info.ArgumentNames[i].StartsWith("both_");
+ string paramName = isOut || isRet ?
+ info.ArgumentNames[i].Substring(4) :
+ isBoth ? info.ArgumentNames[i].Substring(5) :
+ info.ArgumentNames[i];
+
+ if (isOut || isBoth || isRet)
+ {
+ if (retParams == null)
+ retParams = new Dictionary();
+ retParams.Add(paramName, cmd.Parameters.Count);
+ }
+
+ cmd.AddParameter(
+ _db.GetParameterName(paramName),
+ isOut ? ParameterDirection.Output :
+ isRet ? ParameterDirection.ReturnValue :
+ isBoth ? ParameterDirection.InputOutput : ParameterDirection.Input,
+ arg == null ? DbType.String : arg.GetType().ToDbType(), 0, isOut ? DBNull.Value : arg);
+ }
+ else
+ cmd.AddParameter(_db, arg);
+ }
+ }
+ }
+
+ #endregion Prepare arguments
+
+ #region Get main result
+
+ object mainResult = null;
+
+ if (types.Count > 0)
+ {
+ mainResult = types[0].GetDefaultValue();
+
+ if (types[0] == typeof(IDataReader))
+ mainResult = cmd.ExecuteReader();
+ else if (types[0].IsGenericEnumerable())
+ {
+ Type argType = types[0].GetGenericArguments().First();
+ if (argType == typeof(object))
+ using (IDataReader rdr = cmd.ExecuteReader())
+ mainResult = rdr.EnumerateReader().ToList();
+ else if (argType.IsValueType)
+ {
+ Type listType = typeof(List<>).MakeGenericType(new Type[] { argType });
+ IList listInstance = (IList)Activator.CreateInstance(listType);
+
+ object defVal = listType.GetDefaultValue();
+
+ using (IDataReader rdr = cmd.ExecuteReader())
+ while (rdr.Read())
+ listInstance.Add(rdr[0] == DBNull.Value ? defVal : argType.CastObject(rdr[0]));
+
+ mainResult = listInstance;
+ }
+ else
+ {
+ DynamicTypeMap mapper = DynamicMapperCache.GetMapper(argType);
+ if (mapper == null)
+ throw new InvalidCastException(string.Format("Don't konw what to do with this type: '{0}'.", argType.ToString()));
+
+ using (IDataReader rdr = cmd.ExecuteReader())
+ mainResult = rdr.EnumerateReader().MapEnumerable(argType).ToList();
+ }
+ }
+ else if (types[0].IsValueType)
+ {
+ mainResult = cmd.ExecuteScalar();
+ if (mainResult != DBNull.Value)
+ mainResult = types[0].CastObject(mainResult);
+ }
+ else
+ {
+ DynamicTypeMap mapper = DynamicMapperCache.GetMapper(types[0]);
+ if (mapper == null)
+ throw new InvalidCastException(string.Format("Don't konw what to do with this type: '{0}'.", types[0].ToString()));
+
+ using (IDataReader rdr = cmd.ExecuteReader())
+ if (rdr.Read())
+ mainResult = (rdr.ToDynamic() as object).Map(types[0]);
+ else
+ mainResult = null;
+ }
+ }
+ else
+ mainResult = cmd.ExecuteNonQuery();
+
+ #endregion Get main result
+
+ #region Handle out params
+
+ if (retParams != null)
+ {
+ Dictionary res = new Dictionary();
+
+ if (mainResult != null)
+ {
+ if (mainResult == DBNull.Value)
+ res.Add(binder.Name, null);
+ else
+ res.Add(binder.Name, mainResult);
+ }
+
+ foreach (KeyValuePair pos in retParams)
+ res.Add(pos.Key, ((IDbDataParameter)cmd.Parameters[pos.Value]).Value);
+
+ if (types.Count > 1)
+ {
+ DynamicTypeMap mapper = DynamicMapperCache.GetMapper(types[1]);
+
+ if (mapper != null)
+ result = mapper.Create(res.ToDynamic());
+ else
+ result = res.ToDynamic();
+ }
+ else
+ result = res.ToDynamic();
+ }
+ else
+ result = mainResult;
+
+ #endregion Handle out params
+ }
+
+ return true;
+ }
+
+ /// Performs application-defined tasks associated with
+ /// freeing, releasing, or resetting unmanaged resources.
+ public void Dispose()
+ {
+ }
+ }
+
/// Dynamic query exception.
[Serializable]
public class DynamicQueryException : Exception, ISerializable
@@ -11307,6 +11538,91 @@ namespace DynamORM
#endregion Constructors
}
+ /// Type cast helper.
+ public static class DynamicCast
+ {
+ /// Gets the default value.
+ /// The type.
+ /// Default instance.
+ public static object GetDefaultValue(this Type type)
+ {
+ return type.IsValueType ? TypeDefaults.GetOrAdd(type, t => Activator.CreateInstance(t)) : null;
+ }
+
+ /// Casts the object to this type.
+ /// The type to which cast value.
+ /// The value to cast.
+ /// Value casted to new type.
+ public static object CastObject(this Type type, object val)
+ {
+ return GetConverter(type, val)(val);
+ }
+
+ private static readonly ConcurrentDictionary TypeDefaults = new ConcurrentDictionary();
+ private static readonly ConcurrentDictionary> TypeAsCasts = new ConcurrentDictionary>();
+ private static readonly ConcurrentDictionary> TypeConvert = new ConcurrentDictionary>();
+ private static readonly ParameterExpression ConvParameter = Expression.Parameter(typeof(object), "val");
+
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ private static Func