/* * 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() { } } }