Knocks/BackEnd/Knoks.Framework/DataAccess/ProcExecutor.cs

174 lines
7.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, string> _dbConnections;
private readonly Dictionary<string, Dictionary<string, List<ParamMetaData>>> _metaData;
private readonly ILogger<ProcExecutor> _logger;
public ProcExecutor(IDictionary<string, string> dbConnections, ILogger<ProcExecutor> logger)
{
_dbConnections = new Dictionary<string, string>(dbConnections, StringComparer.OrdinalIgnoreCase);
_metaData = new Dictionary<string, Dictionary<string, List<ParamMetaData>>>(StringComparer.OrdinalIgnoreCase);
_logger = logger;
foreach (var conn in _dbConnections.Keys)
{
_metaData[conn] = DbMetaData.LoadParamMetaDataSet(_dbConnections[conn]);
}
}
public async Task<DataResult> Go(string procName, params object[] procParams)
{
return await Go(DatabaseNames.Operate, procName, procParams);
}
public async Task<DataResult> 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<string, object>(StringComparer.OrdinalIgnoreCase);
try
{
foreach (var prm in procParams)
{
if (prm != null)
{
if (prm is IEnumerable<KeyValuePair<string, object>>)
{
foreach (var param in prm as IEnumerable<KeyValuePair<string, object>>)
AddParam(tmpProcParams, param);
}
else if (prm is KeyValuePair<string, object>)
{
AddParam(tmpProcParams, (KeyValuePair<string, object>)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<KeyValuePair<string, object>> 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<string, object> dic, KeyValuePair<string, object> kv)
{
if (!kv.Value.GetType().GetTypeInfo().IsDefined(typeOfIgnoreProcParamAttribute))
AddParam(dic, kv.Key, kv.Value);
}
private static void AddParam(Dictionary<string, object> dic, string key, object value)
{
dic.Add("@" + key, value);
}
private async Task<DataResult> Execute(string databaseName, string procName, Dictionary<string, object> 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;
}
}
}
}