/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012-2015, 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;
using System.Collections.Generic;
using System.Data;
using System.Dynamic;
using System.Linq;
using DynamORM.Helpers;
using DynamORM.Mapper;
namespace DynamORM
{
/// 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;
private List _prefixes;
internal DynamicProcedureInvoker(DynamicDatabase db, List prefixes = null)
{
_prefixes = prefixes;
_db = db;
}
/// This is where the magic begins.
/// Binder to create owner.
/// Binder invoke result.
/// Returns true if invoke was performed.
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
List pref = new List();
if (_prefixes != null)
pref.AddRange(_prefixes);
pref.Add(binder.Name);
result = new DynamicProcedureInvoker(_db, pref);
return true;
}
/// 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())
{
if (_prefixes == null || _prefixes.Count == 0)
cmd.SetCommand(CommandType.StoredProcedure, binder.Name);
else
cmd.SetCommand(CommandType.StoredProcedure, string.Format("{0}.{1}", string.Join(".", _prefixes), 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 (arg is DynamicColumn)
{
var dcv = (DynamicColumn)arg;
string paramName = dcv.Alias ?? dcv.ColumnName ??
(dcv.Schema.HasValue ? dcv.Schema.Value.Name : null) ??
(info.ArgumentNames.Count > i ? info.ArgumentNames[i] : i.ToString());
bool isOut = dcv.ParameterDirection == ParameterDirection.Output ||
dcv.ParameterDirection == ParameterDirection.ReturnValue;
if (isOut || dcv.ParameterDirection == ParameterDirection.InputOutput)
{
if (retParams == null)
retParams = new Dictionary();
retParams.Add(paramName, cmd.Parameters.Count);
}
if (dcv.Schema != null) {
var ds = dcv.Schema.Value;
cmd.AddParameter(
_db.GetParameterName(paramName), dcv.ParameterDirection,
ds.Type, ds.Size, ds.Precision, ds.Scale,
isOut ? DBNull.Value : dcv.Value);
} else
cmd.AddParameter(
_db.GetParameterName(paramName), dcv.ParameterDirection,
arg == null ? DbType.String : arg.GetType().ToDbType(), 0, isOut ? DBNull.Value : dcv.Value);
}
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))
{
using (IDataReader rdr = cmd.ExecuteReader())
mainResult = rdr.CachedReader();
}
else if (types[0] == typeof(DataTable))
{
using (IDataReader rdr = cmd.ExecuteReader())
mainResult = rdr.CachedReader().ToDataTable(binder.Name);
}
else if (types[0].IsGenericEnumerable())
{
Type argType = types[0].GetGenericArguments().First();
if (argType == typeof(object))
{
IDataReader cache = null;
using (IDataReader rdr = cmd.ExecuteReader())
cache = rdr.CachedReader();
mainResult = cache.EnumerateReader().ToList();
}
else if (argType.IsValueType || argType == typeof(string))
{
Type listType = typeof(List<>).MakeGenericType(new Type[] { argType });
IList listInstance = (IList)Activator.CreateInstance(listType);
object defVal = listType.GetDefaultValue();
IDataReader cache = null;
using (IDataReader rdr = cmd.ExecuteReader())
cache = rdr.CachedReader();
while (cache.Read())
listInstance.Add(cache[0] == DBNull.Value ? defVal : argType.CastObject(cache[0]));
mainResult = listInstance;
}
else if (argType == typeof(Guid))
{
Type listType = typeof(List<>).MakeGenericType(new Type[] { argType });
IList listInstance = (IList)Activator.CreateInstance(listType);
object defVal = listType.GetDefaultValue();
IDataReader cache = null;
using (IDataReader rdr = cmd.ExecuteReader())
cache = rdr.CachedReader();
while (cache.Read())
{
if (cache[0] == DBNull.Value && Guid.TryParse(cache[0].ToString(), out Guid g))
listInstance.Add(g);
}
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()));
IDataReader cache = null;
using (IDataReader rdr = cmd.ExecuteReader())
cache = rdr.CachedReader();
mainResult = cache.EnumerateReader().MapEnumerable(argType).ToList();
}
}
else if (types[0].IsValueType || types[0] == typeof(string))
{
mainResult = cmd.ExecuteScalar();
if (mainResult != DBNull.Value)
mainResult = types[0].CastObject(mainResult);
}
else if (types[0] == typeof(Guid))
{
mainResult = cmd.ExecuteScalar();
if (mainResult != DBNull.Value && Guid.TryParse(mainResult.ToString(), out Guid g))
mainResult = g;
}
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.RowToDynamic() 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()
{
}
}
}