using System; using System.Reflection; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using FastMember; using Microsoft.Extensions.Logging; using System.Threading.Tasks; using System.Linq; namespace Knoks.Framework.DataAccess { public class ProcExecutor : IProcExecutor { private readonly Dictionary _dbConnections; private readonly Dictionary>> _metaData; private readonly ILogger _logger; public ProcExecutor(IDictionary dbConnections, ILogger logger) { _dbConnections = new Dictionary(dbConnections, StringComparer.OrdinalIgnoreCase); _metaData = new Dictionary>>(StringComparer.OrdinalIgnoreCase); _logger = logger; foreach (var conn in _dbConnections.Keys) { _metaData[conn] = DbMetaData.LoadParamMetaDataSet(_dbConnections[conn]); } } public async Task Go(string procName, params object[] procParams) { return await Go(DatabaseNames.Operate, procName, procParams); } public async Task Go(string databaseName, string procName, params object[] procParams) { if (string.IsNullOrWhiteSpace(databaseName)) throw new ArgumentException("Undefined database name.", "databaseName"); if (string.IsNullOrWhiteSpace(procName)) throw new ArgumentException("Undefined stored procedure name.", "procName"); var tmpProcParams = new Dictionary(StringComparer.OrdinalIgnoreCase); try { foreach (var prm in procParams) { if (prm != null) { if (prm is IEnumerable>) { foreach (var param in prm as IEnumerable>) AddParam(tmpProcParams, param); } else if (prm is KeyValuePair) { AddParam(tmpProcParams, (KeyValuePair)prm); } else { if (prm is string) throw new ArgumentException("Stored procedure parameters cannot be represented with string type.", nameof(procParams)); var type = prm.GetType(); var typeInfo = type.GetTypeInfo(); if (typeInfo.IsDefined(typeOfIgnoreProcParamAttribute)) throw new ArgumentException("The type is marked with ignore attribute.", "procParams"); if (!typeInfo.IsClass) throw new ArgumentException("Stored procedure parameters should be represented with class type.", nameof(procParams)); if (typeInfo.IsArray) throw new ArgumentException("Stored procedure parameters cannot be represented with non IEnumerable> array.", nameof(procParams)); var accessor = TypeAccessor.Create(type); foreach (var member in accessor.GetMembers().Where(m => !m.IsDefined(typeOfIgnoreProcParamAttribute))) AddParam(tmpProcParams, member.Name, accessor[prm, member.Name]); } } } } catch(Exception ex) { _logger.LogError(ex.ToString()); throw ex; } return await Execute(databaseName, procName, tmpProcParams); } private static readonly Type typeOfIgnoreProcParamAttribute = typeof(ProcParamIgnoreAttribute); private static void AddParam(Dictionary dic, KeyValuePair kv) { if (!kv.Value.GetType().GetTypeInfo().IsDefined(typeOfIgnoreProcParamAttribute)) AddParam(dic, kv.Key, kv.Value); } private static void AddParam(Dictionary dic, string key, object value) { dic.Add("@" + key, value); } private async Task Execute(string databaseName, string procName, Dictionary procParams) { try { using (var con = new SqlConnection(_dbConnections[databaseName])) using (var cmd = new SqlCommand(procName, con) { CommandType = CommandType.StoredProcedure }) { if (procParams.Count > 0 && !_metaData[databaseName].ContainsKey(procName)) { throw new InvalidOperationException($"The procedure '{procName}' does not have the following parameter(s): [{string.Join(", ", procParams.Keys)}]."); } else if (procParams.Count > 0) { foreach (var md in _metaData[databaseName][procName]) { if (procParams.TryGetValue(md.ParamName, out object value)) { procParams.Remove(md.ParamName); cmd.Parameters.Add(md.GetSqlParameter(value)); } else if (md.Direction == ParameterDirection.InputOutput || md.Direction == ParameterDirection.Output) { cmd.Parameters.Add(md.GetSqlParameter(value)); } } } if (procParams.Count > 0) throw new InvalidOperationException($"The procedure '{procName}' does not have the following parameter(s): [{string.Join(", ", procParams.Keys)}]."); var returnValue = new SqlParameter() { Direction = ParameterDirection.ReturnValue }; cmd.Parameters.Add(returnValue); con.Open(); using (var reader = await cmd.ExecuteReaderAsync()) { var result = new DataResult(); short tableId = 0; do { result.Тables.Add(tableId++, new Table(reader)); } while (reader.NextResult()); foreach (SqlParameter param in cmd.Parameters) { if (param.Direction == ParameterDirection.Output || param.Direction == ParameterDirection.InputOutput) { result.OutputParams.Add(param.ParameterName.Substring(1), param.Value == DBNull.Value ? null : param.Value); } } if (returnValue.Value != null) result.ReturnValue = Convert.ToInt32(returnValue.Value); return result; } } } catch (Exception ex) { _logger.LogError(ex.ToString()); throw ex; } } } }