174 lines
7.6 KiB
C#
174 lines
7.6 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|
||
}
|