Add object-based stored procedure parameter binding
This commit is contained in:
@@ -5377,133 +5377,110 @@ namespace DynamORM
|
||||
|
||||
if (alen > 0)
|
||||
{
|
||||
for (int i = 0; i < alen; i++)
|
||||
if (alen == 1 && DynamicProcedureParameterBinder.CanBind(args[0]))
|
||||
{
|
||||
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)
|
||||
DynamicProcedureParameterBinder.BindingResult bindingResult = DynamicProcedureParameterBinder.Bind(_db, cmd, args[0]);
|
||||
retParams = bindingResult.ReturnParameters;
|
||||
retIsAdded = bindingResult.ReturnValueAdded;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < alen; i++)
|
||||
{
|
||||
var dcv = (DynamicColumn)arg;
|
||||
object arg = args[i];
|
||||
|
||||
string paramName = i.ToString();
|
||||
bool isOut = false;
|
||||
bool isRet = false;
|
||||
bool isBoth = false;
|
||||
|
||||
if (info.ArgumentNames.Count > 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)
|
||||
{
|
||||
isOut = info.ArgumentNames[i].StartsWith("out_");
|
||||
isRet = info.ArgumentNames[i].StartsWith("ret_");
|
||||
isBoth = info.ArgumentNames[i].StartsWith("both_");
|
||||
var dcv = (DynamicColumn)arg;
|
||||
|
||||
paramName = isOut || isRet ?
|
||||
info.ArgumentNames[i].Substring(4) :
|
||||
isBoth ? info.ArgumentNames[i].Substring(5) :
|
||||
info.ArgumentNames[i];
|
||||
string paramName = i.ToString();
|
||||
bool isOut = false;
|
||||
bool isRet = false;
|
||||
bool isBoth = false;
|
||||
|
||||
if (info.ArgumentNames.Count > i)
|
||||
{
|
||||
isOut = info.ArgumentNames[i].StartsWith("out_");
|
||||
isRet = info.ArgumentNames[i].StartsWith("ret_");
|
||||
isBoth = info.ArgumentNames[i].StartsWith("both_");
|
||||
|
||||
paramName = isOut || isRet ?
|
||||
info.ArgumentNames[i].Substring(4) :
|
||||
isBoth ? info.ArgumentNames[i].Substring(5) :
|
||||
info.ArgumentNames[i];
|
||||
}
|
||||
paramName = dcv.Alias ?? dcv.ColumnName ??
|
||||
(dcv.Schema.HasValue ? dcv.Schema.Value.Name : null) ??
|
||||
paramName;
|
||||
|
||||
if (!isOut && !isRet && !isBoth)
|
||||
{
|
||||
isOut = dcv.ParameterDirection == ParameterDirection.Output;
|
||||
isRet = dcv.ParameterDirection == ParameterDirection.ReturnValue;
|
||||
isBoth = dcv.ParameterDirection == ParameterDirection.InputOutput;
|
||||
}
|
||||
if (isRet)
|
||||
retIsAdded = true;
|
||||
|
||||
if (isOut || isRet || isBoth)
|
||||
{
|
||||
if (retParams == null)
|
||||
retParams = new Dictionary<string, int>();
|
||||
retParams.Add(paramName, cmd.Parameters.Count);
|
||||
}
|
||||
if (dcv.Schema != null)
|
||||
{
|
||||
var ds = dcv.Schema.Value;
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
ds.Type, ds.Size, ds.Precision, ds.Scale,
|
||||
(isOut || isRet) ? DBNull.Value : dcv.Value);
|
||||
}
|
||||
else
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
arg == null ? DbType.String : arg.GetType().ToDbType(),
|
||||
isRet ? 4 : 0,
|
||||
(isOut || isRet) ? DBNull.Value : dcv.Value);
|
||||
}
|
||||
paramName = dcv.Alias ?? dcv.ColumnName ??
|
||||
(dcv.Schema.HasValue ? dcv.Schema.Value.Name : null) ??
|
||||
paramName;
|
||||
|
||||
if (!isOut && !isRet && !isBoth)
|
||||
else if (arg is DynamicSchemaColumn)
|
||||
{
|
||||
isOut = dcv.ParameterDirection == ParameterDirection.Output;
|
||||
isRet = dcv.ParameterDirection == ParameterDirection.ReturnValue;
|
||||
isBoth = dcv.ParameterDirection == ParameterDirection.InputOutput;
|
||||
}
|
||||
if (isRet)
|
||||
retIsAdded = true;
|
||||
var dsc = (DynamicSchemaColumn)arg;
|
||||
|
||||
if (isOut || isRet || isBoth)
|
||||
{
|
||||
if (retParams == null)
|
||||
retParams = new Dictionary<string, int>();
|
||||
retParams.Add(paramName, cmd.Parameters.Count);
|
||||
}
|
||||
if (dcv.Schema != null)
|
||||
{
|
||||
var ds = dcv.Schema.Value;
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
ds.Type, ds.Size, ds.Precision, ds.Scale,
|
||||
(isOut || isRet) ? DBNull.Value : dcv.Value);
|
||||
}
|
||||
else
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
arg == null ? DbType.String : arg.GetType().ToDbType(),
|
||||
isRet ? 4 : 0,
|
||||
(isOut || isRet) ? DBNull.Value : dcv.Value);
|
||||
}
|
||||
else if (arg is DynamicSchemaColumn)
|
||||
{
|
||||
var dsc = (DynamicSchemaColumn)arg;
|
||||
string paramName = i.ToString();
|
||||
bool isOut = false;
|
||||
bool isRet = false;
|
||||
bool isBoth = false;
|
||||
|
||||
string paramName = i.ToString();
|
||||
bool isOut = false;
|
||||
bool isRet = false;
|
||||
bool isBoth = false;
|
||||
if (info.ArgumentNames.Count > i)
|
||||
{
|
||||
isOut = info.ArgumentNames[i].StartsWith("out_");
|
||||
isRet = info.ArgumentNames[i].StartsWith("ret_");
|
||||
isBoth = info.ArgumentNames[i].StartsWith("both_");
|
||||
|
||||
if (info.ArgumentNames.Count > i)
|
||||
{
|
||||
isOut = info.ArgumentNames[i].StartsWith("out_");
|
||||
isRet = info.ArgumentNames[i].StartsWith("ret_");
|
||||
isBoth = info.ArgumentNames[i].StartsWith("both_");
|
||||
|
||||
paramName = isOut || isRet ?
|
||||
info.ArgumentNames[i].Substring(4) :
|
||||
isBoth ? info.ArgumentNames[i].Substring(5) :
|
||||
info.ArgumentNames[i];
|
||||
}
|
||||
paramName = dsc.Name ?? paramName;
|
||||
|
||||
if (isRet)
|
||||
retIsAdded = true;
|
||||
|
||||
if (isOut || isRet || isBoth)
|
||||
{
|
||||
if (retParams == null)
|
||||
retParams = new Dictionary<string, int>();
|
||||
retParams.Add(paramName, cmd.Parameters.Count);
|
||||
}
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
dsc.Type, dsc.Size, dsc.Precision, dsc.Scale,
|
||||
DBNull.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_");
|
||||
paramName = isOut || isRet ?
|
||||
info.ArgumentNames[i].Substring(4) :
|
||||
isBoth ? info.ArgumentNames[i].Substring(5) :
|
||||
info.ArgumentNames[i];
|
||||
}
|
||||
paramName = dsc.Name ?? paramName;
|
||||
|
||||
if (isRet)
|
||||
retIsAdded = true;
|
||||
|
||||
string paramName = isOut || isRet ?
|
||||
info.ArgumentNames[i].Substring(4) :
|
||||
isBoth ? info.ArgumentNames[i].Substring(5) :
|
||||
info.ArgumentNames[i];
|
||||
|
||||
if (isOut || isBoth || isRet)
|
||||
if (isOut || isRet || isBoth)
|
||||
{
|
||||
if (retParams == null)
|
||||
retParams = new Dictionary<string, int>();
|
||||
@@ -5515,12 +5492,44 @@ namespace DynamORM
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
arg == null ? isRet ? DbType.Int32 : DbType.String : arg.GetType().ToDbType(),
|
||||
isRet ? 4 : 0,
|
||||
(isOut || isRet) ? DBNull.Value : arg);
|
||||
dsc.Type, dsc.Size, dsc.Precision, dsc.Scale,
|
||||
DBNull.Value);
|
||||
}
|
||||
else
|
||||
cmd.AddParameter(_db, arg);
|
||||
{
|
||||
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_");
|
||||
|
||||
if (isRet)
|
||||
retIsAdded = true;
|
||||
|
||||
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<string, int>();
|
||||
retParams.Add(paramName, cmd.Parameters.Count);
|
||||
}
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
arg == null ? isRet ? DbType.Int32 : DbType.String : arg.GetType().ToDbType(),
|
||||
isRet ? 4 : 0,
|
||||
(isOut || isRet) ? DBNull.Value : arg);
|
||||
}
|
||||
else
|
||||
cmd.AddParameter(_db, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6826,6 +6835,54 @@ namespace DynamORM
|
||||
|
||||
#endregion IExtendedDisposable Members
|
||||
}
|
||||
/// <summary>Declares metadata for object-based stored procedure parameters.</summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ProcedureParameterAttribute : ColumnAttribute
|
||||
{
|
||||
/// <summary>Sentinel used when database type was not provided.</summary>
|
||||
public const int UnspecifiedDbType = -1;
|
||||
|
||||
/// <summary>Sentinel used when size was not provided.</summary>
|
||||
public const int UnspecifiedSize = -1;
|
||||
|
||||
/// <summary>Sentinel used when precision or scale was not provided.</summary>
|
||||
public const byte UnspecifiedByte = byte.MaxValue;
|
||||
|
||||
/// <summary>Gets or sets parameter direction. Defaults to input.</summary>
|
||||
public ParameterDirection Direction { get; set; }
|
||||
|
||||
/// <summary>Gets or sets explicit parameter order. Lower values are emitted first.</summary>
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <summary>Gets or sets parameter database type.</summary>
|
||||
public DbType DbType { get; set; }
|
||||
|
||||
/// <summary>Gets or sets parameter size.</summary>
|
||||
public new int Size { get; set; }
|
||||
|
||||
/// <summary>Gets or sets parameter precision.</summary>
|
||||
public new byte Precision { get; set; }
|
||||
|
||||
/// <summary>Gets or sets parameter scale.</summary>
|
||||
public new byte Scale { get; set; }
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="ProcedureParameterAttribute"/> class.</summary>
|
||||
public ProcedureParameterAttribute()
|
||||
{
|
||||
Direction = ParameterDirection.Input;
|
||||
Order = int.MaxValue;
|
||||
DbType = (DbType)UnspecifiedDbType;
|
||||
Size = UnspecifiedSize;
|
||||
Precision = UnspecifiedByte;
|
||||
Scale = UnspecifiedByte;
|
||||
}
|
||||
/// <summary>Initializes a new instance of the <see cref="ProcedureParameterAttribute"/> class.</summary>
|
||||
public ProcedureParameterAttribute(string name)
|
||||
: this()
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
namespace Builders
|
||||
{
|
||||
/// <summary>Typed join kind used by typed fluent builder APIs.</summary>
|
||||
@@ -14416,6 +14473,133 @@ namespace DynamORM
|
||||
return resultTable;
|
||||
}
|
||||
}
|
||||
internal static class DynamicProcedureParameterBinder
|
||||
{
|
||||
internal sealed class BindingResult
|
||||
{
|
||||
public bool ReturnValueAdded { get; set; }
|
||||
public Dictionary<string, int> ReturnParameters { get; set; }
|
||||
}
|
||||
internal static bool CanBind(object item)
|
||||
{
|
||||
if (item == null)
|
||||
return false;
|
||||
|
||||
Type type = item.GetType();
|
||||
|
||||
if (type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime) || type == typeof(Guid) || type == typeof(TimeSpan))
|
||||
return false;
|
||||
|
||||
return GetBindableProperties(type).Any();
|
||||
}
|
||||
internal static BindingResult Bind(DynamicDatabase db, IDbCommand cmd, object item)
|
||||
{
|
||||
if (db == null)
|
||||
throw new ArgumentNullException("db");
|
||||
if (cmd == null)
|
||||
throw new ArgumentNullException("cmd");
|
||||
if (item == null)
|
||||
throw new ArgumentNullException("item");
|
||||
|
||||
BindingResult result = new BindingResult();
|
||||
|
||||
foreach (PropertyInfo property in GetBindableProperties(item.GetType()))
|
||||
{
|
||||
ProcedureParameterAttribute procAttr = property.GetCustomAttributes(typeof(ProcedureParameterAttribute), true).Cast<ProcedureParameterAttribute>().FirstOrDefault();
|
||||
ColumnAttribute colAttr = property.GetCustomAttributes(typeof(ColumnAttribute), true).Cast<ColumnAttribute>().FirstOrDefault();
|
||||
|
||||
string name = (procAttr != null && !string.IsNullOrEmpty(procAttr.Name) ? procAttr.Name : null)
|
||||
?? (colAttr != null && !string.IsNullOrEmpty(colAttr.Name) ? colAttr.Name : null)
|
||||
?? property.Name;
|
||||
|
||||
ParameterDirection direction = procAttr == null ? ParameterDirection.Input : procAttr.Direction;
|
||||
object value = property.GetValue(item, null);
|
||||
DbType dbType = ResolveDbType(property.PropertyType, value, procAttr, colAttr, direction);
|
||||
int size = ResolveSize(dbType, value, procAttr, colAttr, direction);
|
||||
byte precision = (procAttr != null && procAttr.Precision != ProcedureParameterAttribute.UnspecifiedByte ? procAttr.Precision : default(byte));
|
||||
byte scale = (procAttr != null && procAttr.Scale != ProcedureParameterAttribute.UnspecifiedByte ? procAttr.Scale : default(byte));
|
||||
|
||||
if (procAttr == null && colAttr != null)
|
||||
{
|
||||
precision = colAttr.Precision ?? precision;
|
||||
scale = colAttr.Scale ?? scale;
|
||||
}
|
||||
if (direction == ParameterDirection.ReturnValue)
|
||||
result.ReturnValueAdded = true;
|
||||
|
||||
if (direction == ParameterDirection.Output || direction == ParameterDirection.InputOutput || direction == ParameterDirection.ReturnValue)
|
||||
{
|
||||
if (result.ReturnParameters == null)
|
||||
result.ReturnParameters = new Dictionary<string, int>();
|
||||
result.ReturnParameters.Add(name, cmd.Parameters.Count);
|
||||
}
|
||||
cmd.AddParameter(
|
||||
db.GetParameterName(name),
|
||||
direction,
|
||||
dbType,
|
||||
size,
|
||||
precision,
|
||||
scale,
|
||||
direction == ParameterDirection.Output || direction == ParameterDirection.ReturnValue ? DBNull.Value : (value ?? DBNull.Value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private static IEnumerable<PropertyInfo> GetBindableProperties(Type type)
|
||||
{
|
||||
return type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(x => x.CanRead && x.GetIndexParameters().Length == 0)
|
||||
.Where(x => x.GetCustomAttributes(typeof(ProcedureParameterAttribute), true).Any() || x.GetCustomAttributes(typeof(ColumnAttribute), true).Any())
|
||||
.OrderBy(x =>
|
||||
{
|
||||
ProcedureParameterAttribute attr = x.GetCustomAttributes(typeof(ProcedureParameterAttribute), true).Cast<ProcedureParameterAttribute>().FirstOrDefault();
|
||||
return attr == null ? int.MaxValue : attr.Order;
|
||||
})
|
||||
.ThenBy(x => x.MetadataToken);
|
||||
}
|
||||
private static DbType ResolveDbType(Type propertyType, object value, ProcedureParameterAttribute procAttr, ColumnAttribute colAttr, ParameterDirection direction)
|
||||
{
|
||||
if (procAttr != null && (int)procAttr.DbType != ProcedureParameterAttribute.UnspecifiedDbType)
|
||||
return procAttr.DbType;
|
||||
|
||||
if (colAttr != null && colAttr.Type.HasValue)
|
||||
return colAttr.Type.Value;
|
||||
|
||||
Type targetType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
|
||||
|
||||
if (targetType == typeof(object) && value != null)
|
||||
targetType = value.GetType();
|
||||
|
||||
if (value == null && direction == ParameterDirection.ReturnValue)
|
||||
return DbType.Int32;
|
||||
|
||||
return targetType.ToDbType();
|
||||
}
|
||||
private static int ResolveSize(DbType dbType, object value, ProcedureParameterAttribute procAttr, ColumnAttribute colAttr, ParameterDirection direction)
|
||||
{
|
||||
if (procAttr != null && procAttr.Size != ProcedureParameterAttribute.UnspecifiedSize)
|
||||
return procAttr.Size;
|
||||
|
||||
if (colAttr != null && colAttr.Size.HasValue)
|
||||
return colAttr.Size.Value;
|
||||
|
||||
if (direction == ParameterDirection.ReturnValue)
|
||||
return 4;
|
||||
|
||||
if (dbType == DbType.AnsiString || dbType == DbType.AnsiStringFixedLength)
|
||||
{
|
||||
if (value != null)
|
||||
return value.ToString().Length > 8000 ? -1 : 8000;
|
||||
return 8000;
|
||||
}
|
||||
if (dbType == DbType.String || dbType == DbType.StringFixedLength)
|
||||
{
|
||||
if (value != null)
|
||||
return value.ToString().Length > 4000 ? -1 : 4000;
|
||||
return 4000;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/// <summary>Framework detection and specific implementations.</summary>
|
||||
public static class FrameworkTools
|
||||
{
|
||||
|
||||
135
DynamORM.Tests/Helpers/FakeDbCommand.cs
Normal file
135
DynamORM.Tests/Helpers/FakeDbCommand.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
|
||||
namespace DynamORM.Tests.Helpers
|
||||
{
|
||||
internal sealed class FakeDbCommand : IDbCommand
|
||||
{
|
||||
private readonly FakeParameterCollection _parameters = new FakeParameterCollection();
|
||||
|
||||
public string CommandText { get; set; }
|
||||
public int CommandTimeout { get; set; }
|
||||
public CommandType CommandType { get; set; }
|
||||
public IDbConnection Connection { get; set; }
|
||||
public IDataParameterCollection Parameters { get { return _parameters; } }
|
||||
public IDbTransaction Transaction { get; set; }
|
||||
public UpdateRowSource UpdatedRowSource { get; set; }
|
||||
|
||||
public void Cancel() { }
|
||||
public IDbDataParameter CreateParameter() { return new FakeDbParameter(); }
|
||||
public void Dispose() { }
|
||||
public int ExecuteNonQuery() { throw new NotSupportedException(); }
|
||||
public IDataReader ExecuteReader() { throw new NotSupportedException(); }
|
||||
public IDataReader ExecuteReader(CommandBehavior behavior) { throw new NotSupportedException(); }
|
||||
public object ExecuteScalar() { throw new NotSupportedException(); }
|
||||
public void Prepare() { }
|
||||
}
|
||||
|
||||
internal sealed class FakeDbParameter : IDbDataParameter
|
||||
{
|
||||
public byte Precision { get; set; }
|
||||
public byte Scale { get; set; }
|
||||
public int Size { get; set; }
|
||||
public DbType DbType { get; set; }
|
||||
public ParameterDirection Direction { get; set; }
|
||||
public bool IsNullable { get { return true; } }
|
||||
public string ParameterName { get; set; }
|
||||
public string SourceColumn { get; set; }
|
||||
public DataRowVersion SourceVersion { get; set; }
|
||||
public object Value { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class FakeParameterCollection : IDataParameterCollection
|
||||
{
|
||||
private readonly List<object> _items = new List<object>();
|
||||
|
||||
public object this[string parameterName]
|
||||
{
|
||||
get { return _items.Find(x => string.Equals(((IDbDataParameter)x).ParameterName, parameterName, StringComparison.Ordinal)); }
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public object this[int index]
|
||||
{
|
||||
get { return _items[index]; }
|
||||
set { _items[index] = value; }
|
||||
}
|
||||
|
||||
public bool IsFixedSize { get { return false; } }
|
||||
public bool IsReadOnly { get { return false; } }
|
||||
public int Count { get { return _items.Count; } }
|
||||
public bool IsSynchronized { get { return false; } }
|
||||
public object SyncRoot { get { return this; } }
|
||||
|
||||
public int Add(object value)
|
||||
{
|
||||
_items.Add(value);
|
||||
return _items.Count - 1;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_items.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(string parameterName)
|
||||
{
|
||||
return _items.Exists(x => string.Equals(((IDbDataParameter)x).ParameterName, parameterName, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
public bool Contains(object value)
|
||||
{
|
||||
return _items.Contains(value);
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
_items.ToArray().CopyTo(array, index);
|
||||
}
|
||||
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
return _items.GetEnumerator();
|
||||
}
|
||||
|
||||
public int IndexOf(string parameterName)
|
||||
{
|
||||
return _items.FindIndex(x => string.Equals(((IDbDataParameter)x).ParameterName, parameterName, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
public int IndexOf(object value)
|
||||
{
|
||||
return _items.IndexOf(value);
|
||||
}
|
||||
|
||||
public void Insert(int index, object value)
|
||||
{
|
||||
_items.Insert(index, value);
|
||||
}
|
||||
|
||||
public void Remove(object value)
|
||||
{
|
||||
_items.Remove(value);
|
||||
}
|
||||
|
||||
public void RemoveAt(string parameterName)
|
||||
{
|
||||
int index = IndexOf(parameterName);
|
||||
if (index >= 0)
|
||||
_items.RemoveAt(index);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
_items.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
DynamORM.Tests/Helpers/ProcedureParameterModels.cs
Normal file
31
DynamORM.Tests/Helpers/ProcedureParameterModels.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
using System.Data;
|
||||
|
||||
namespace DynamORM.Tests.Helpers
|
||||
{
|
||||
public class ProcedureParameterObject
|
||||
{
|
||||
[ProcedureParameter("code", Order = 2, DbType = DbType.String, Size = 32)]
|
||||
public string Code { get; set; }
|
||||
|
||||
[ProcedureParameter("result", Direction = ParameterDirection.Output, Order = 3, DbType = DbType.Int32)]
|
||||
public int Result { get; set; }
|
||||
|
||||
[ProcedureParameter("description", Direction = ParameterDirection.InputOutput, Order = 4, DbType = DbType.String, Size = 256)]
|
||||
public string Description { get; set; }
|
||||
|
||||
[ProcedureParameter("status", Direction = ParameterDirection.ReturnValue, Order = 1)]
|
||||
public int Status { get; set; }
|
||||
}
|
||||
|
||||
public class ProcedureParameterColumnFallbackObject
|
||||
{
|
||||
[DynamORM.Mapper.Column("code", false, DbType.String, 64)]
|
||||
public string Code { get; set; }
|
||||
}
|
||||
}
|
||||
117
DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs
Normal file
117
DynamORM.Tests/Procedure/ProcedureParameterBinderTests.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||
* Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com)
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
using System.Data;
|
||||
using DynamORM.Helpers;
|
||||
using DynamORM.Tests.Helpers;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace DynamORM.Tests.Procedure
|
||||
{
|
||||
[TestFixture]
|
||||
public class ProcedureParameterBinderTests : TestsBase
|
||||
{
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
CreateTestDatabase();
|
||||
CreateDynamicDatabase(
|
||||
DynamicDatabaseOptions.SingleConnection |
|
||||
DynamicDatabaseOptions.SingleTransaction |
|
||||
DynamicDatabaseOptions.SupportStoredProcedures |
|
||||
DynamicDatabaseOptions.SupportStoredProceduresResult |
|
||||
DynamicDatabaseOptions.SupportSchema);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
DestroyDynamicDatabase();
|
||||
DestroyTestDatabase();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCanBindProcedureParameterObject()
|
||||
{
|
||||
Assert.IsTrue(DynamicProcedureParameterBinder.CanBind(new ProcedureParameterObject()));
|
||||
Assert.IsTrue(DynamicProcedureParameterBinder.CanBind(new ProcedureParameterColumnFallbackObject()));
|
||||
Assert.IsFalse(DynamicProcedureParameterBinder.CanBind("x"));
|
||||
Assert.IsFalse(DynamicProcedureParameterBinder.CanBind(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBindUsesAttributeMetadataAndOrder()
|
||||
{
|
||||
using (IDbCommand cmd = new FakeDbCommand())
|
||||
{
|
||||
var result = DynamicProcedureParameterBinder.Bind(Database, cmd, new ProcedureParameterObject
|
||||
{
|
||||
Code = "ABC",
|
||||
Description = "seed"
|
||||
});
|
||||
|
||||
Assert.IsTrue(result.ReturnValueAdded);
|
||||
Assert.NotNull(result.ReturnParameters);
|
||||
Assert.AreEqual(3, result.ReturnParameters.Count);
|
||||
Assert.AreEqual(4, cmd.Parameters.Count);
|
||||
|
||||
var p0 = (IDbDataParameter)cmd.Parameters[0];
|
||||
var p1 = (IDbDataParameter)cmd.Parameters[1];
|
||||
var p2 = (IDbDataParameter)cmd.Parameters[2];
|
||||
var p3 = (IDbDataParameter)cmd.Parameters[3];
|
||||
|
||||
Assert.AreEqual(Database.GetParameterName("status"), p0.ParameterName);
|
||||
Assert.AreEqual(ParameterDirection.ReturnValue, p0.Direction);
|
||||
Assert.AreEqual(DbType.Int32, p0.DbType);
|
||||
Assert.AreEqual(4, p0.Size);
|
||||
|
||||
Assert.AreEqual(Database.GetParameterName("code"), p1.ParameterName);
|
||||
Assert.AreEqual(ParameterDirection.Input, p1.Direction);
|
||||
Assert.AreEqual(DbType.String, p1.DbType);
|
||||
Assert.AreEqual(32, p1.Size);
|
||||
Assert.AreEqual("ABC", p1.Value);
|
||||
|
||||
Assert.AreEqual(Database.GetParameterName("result"), p2.ParameterName);
|
||||
Assert.AreEqual(ParameterDirection.Output, p2.Direction);
|
||||
Assert.AreEqual(DbType.Int32, p2.DbType);
|
||||
Assert.AreEqual(DBNull.Value, p2.Value);
|
||||
|
||||
Assert.AreEqual(Database.GetParameterName("description"), p3.ParameterName);
|
||||
Assert.AreEqual(ParameterDirection.InputOutput, p3.Direction);
|
||||
Assert.AreEqual(DbType.String, p3.DbType);
|
||||
Assert.AreEqual(256, p3.Size);
|
||||
Assert.AreEqual("seed", p3.Value);
|
||||
|
||||
Assert.AreEqual(0, result.ReturnParameters["status"]);
|
||||
Assert.AreEqual(2, result.ReturnParameters["result"]);
|
||||
Assert.AreEqual(3, result.ReturnParameters["description"]);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBindFallsBackToColumnAttributeMetadata()
|
||||
{
|
||||
using (IDbCommand cmd = new FakeDbCommand())
|
||||
{
|
||||
var result = DynamicProcedureParameterBinder.Bind(Database, cmd, new ProcedureParameterColumnFallbackObject
|
||||
{
|
||||
Code = "XYZ"
|
||||
});
|
||||
|
||||
Assert.IsFalse(result.ReturnValueAdded);
|
||||
Assert.IsNull(result.ReturnParameters);
|
||||
Assert.AreEqual(1, cmd.Parameters.Count);
|
||||
|
||||
var p0 = (IDbDataParameter)cmd.Parameters[0];
|
||||
Assert.AreEqual(Database.GetParameterName("code"), p0.ParameterName);
|
||||
Assert.AreEqual(ParameterDirection.Input, p0.Direction);
|
||||
Assert.AreEqual(DbType.String, p0.DbType);
|
||||
Assert.AreEqual(64, p0.Size);
|
||||
Assert.AreEqual("XYZ", p0.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,13 +31,13 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using DynamORM.Helpers;
|
||||
using DynamORM.Mapper;
|
||||
|
||||
namespace DynamORM
|
||||
{
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using DynamORM.Helpers;
|
||||
using DynamORM.Mapper;
|
||||
|
||||
namespace DynamORM
|
||||
{
|
||||
/// <summary>Dynamic procedure invoker.</summary>
|
||||
/// <remarks>Unfortunately I can use <c>out</c> and <c>ref</c> to
|
||||
/// return parameters, <see href="http://stackoverflow.com/questions/2475310/c-sharp-4-0-dynamic-doesnt-set-ref-out-arguments"/>.
|
||||
@@ -106,164 +106,173 @@ namespace DynamORM
|
||||
|
||||
#region Prepare arguments
|
||||
|
||||
int alen = args.Length;
|
||||
bool retIsAdded = false;
|
||||
|
||||
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 = i.ToString();
|
||||
bool isOut = false;
|
||||
bool isRet = false;
|
||||
bool isBoth = false;
|
||||
|
||||
if (info.ArgumentNames.Count > i)
|
||||
{
|
||||
isOut = info.ArgumentNames[i].StartsWith("out_");
|
||||
isRet = info.ArgumentNames[i].StartsWith("ret_");
|
||||
isBoth = info.ArgumentNames[i].StartsWith("both_");
|
||||
|
||||
paramName = isOut || isRet ?
|
||||
info.ArgumentNames[i].Substring(4) :
|
||||
isBoth ? info.ArgumentNames[i].Substring(5) :
|
||||
info.ArgumentNames[i];
|
||||
}
|
||||
|
||||
paramName = dcv.Alias ?? dcv.ColumnName ??
|
||||
(dcv.Schema.HasValue ? dcv.Schema.Value.Name : null) ??
|
||||
paramName;
|
||||
|
||||
if (!isOut && !isRet && !isBoth)
|
||||
{
|
||||
isOut = dcv.ParameterDirection == ParameterDirection.Output;
|
||||
isRet = dcv.ParameterDirection == ParameterDirection.ReturnValue;
|
||||
isBoth = dcv.ParameterDirection == ParameterDirection.InputOutput;
|
||||
}
|
||||
|
||||
if (isRet)
|
||||
retIsAdded = true;
|
||||
|
||||
if (isOut || isRet || isBoth)
|
||||
{
|
||||
if (retParams == null)
|
||||
retParams = new Dictionary<string, int>();
|
||||
retParams.Add(paramName, cmd.Parameters.Count);
|
||||
}
|
||||
|
||||
if (dcv.Schema != null)
|
||||
{
|
||||
var ds = dcv.Schema.Value;
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
ds.Type, ds.Size, ds.Precision, ds.Scale,
|
||||
(isOut || isRet) ? DBNull.Value : dcv.Value);
|
||||
}
|
||||
else
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
arg == null ? DbType.String : arg.GetType().ToDbType(),
|
||||
isRet ? 4 : 0,
|
||||
(isOut || isRet) ? DBNull.Value : dcv.Value);
|
||||
}
|
||||
else if (arg is DynamicSchemaColumn)
|
||||
{
|
||||
var dsc = (DynamicSchemaColumn)arg;
|
||||
|
||||
string paramName = i.ToString();
|
||||
bool isOut = false;
|
||||
bool isRet = false;
|
||||
bool isBoth = false;
|
||||
|
||||
if (info.ArgumentNames.Count > i)
|
||||
{
|
||||
isOut = info.ArgumentNames[i].StartsWith("out_");
|
||||
isRet = info.ArgumentNames[i].StartsWith("ret_");
|
||||
isBoth = info.ArgumentNames[i].StartsWith("both_");
|
||||
|
||||
paramName = isOut || isRet ?
|
||||
info.ArgumentNames[i].Substring(4) :
|
||||
isBoth ? info.ArgumentNames[i].Substring(5) :
|
||||
info.ArgumentNames[i];
|
||||
}
|
||||
|
||||
paramName = dsc.Name ?? paramName;
|
||||
|
||||
if (isRet)
|
||||
retIsAdded = true;
|
||||
|
||||
if (isOut || isRet || isBoth)
|
||||
{
|
||||
if (retParams == null)
|
||||
retParams = new Dictionary<string, int>();
|
||||
retParams.Add(paramName, cmd.Parameters.Count);
|
||||
}
|
||||
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
dsc.Type, dsc.Size, dsc.Precision, dsc.Scale,
|
||||
DBNull.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_");
|
||||
|
||||
if (isRet)
|
||||
retIsAdded = true;
|
||||
|
||||
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<string, int>();
|
||||
retParams.Add(paramName, cmd.Parameters.Count);
|
||||
}
|
||||
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
arg == null ? isRet ? DbType.Int32 : DbType.String : arg.GetType().ToDbType(),
|
||||
isRet ? 4 : 0,
|
||||
(isOut || isRet) ? DBNull.Value : arg);
|
||||
}
|
||||
else
|
||||
cmd.AddParameter(_db, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
int alen = args.Length;
|
||||
bool retIsAdded = false;
|
||||
|
||||
if (alen > 0)
|
||||
{
|
||||
if (alen == 1 && DynamicProcedureParameterBinder.CanBind(args[0]))
|
||||
{
|
||||
DynamicProcedureParameterBinder.BindingResult bindingResult = DynamicProcedureParameterBinder.Bind(_db, cmd, args[0]);
|
||||
retParams = bindingResult.ReturnParameters;
|
||||
retIsAdded = bindingResult.ReturnValueAdded;
|
||||
}
|
||||
else
|
||||
{
|
||||
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 = i.ToString();
|
||||
bool isOut = false;
|
||||
bool isRet = false;
|
||||
bool isBoth = false;
|
||||
|
||||
if (info.ArgumentNames.Count > i)
|
||||
{
|
||||
isOut = info.ArgumentNames[i].StartsWith("out_");
|
||||
isRet = info.ArgumentNames[i].StartsWith("ret_");
|
||||
isBoth = info.ArgumentNames[i].StartsWith("both_");
|
||||
|
||||
paramName = isOut || isRet ?
|
||||
info.ArgumentNames[i].Substring(4) :
|
||||
isBoth ? info.ArgumentNames[i].Substring(5) :
|
||||
info.ArgumentNames[i];
|
||||
}
|
||||
|
||||
paramName = dcv.Alias ?? dcv.ColumnName ??
|
||||
(dcv.Schema.HasValue ? dcv.Schema.Value.Name : null) ??
|
||||
paramName;
|
||||
|
||||
if (!isOut && !isRet && !isBoth)
|
||||
{
|
||||
isOut = dcv.ParameterDirection == ParameterDirection.Output;
|
||||
isRet = dcv.ParameterDirection == ParameterDirection.ReturnValue;
|
||||
isBoth = dcv.ParameterDirection == ParameterDirection.InputOutput;
|
||||
}
|
||||
|
||||
if (isRet)
|
||||
retIsAdded = true;
|
||||
|
||||
if (isOut || isRet || isBoth)
|
||||
{
|
||||
if (retParams == null)
|
||||
retParams = new Dictionary<string, int>();
|
||||
retParams.Add(paramName, cmd.Parameters.Count);
|
||||
}
|
||||
|
||||
if (dcv.Schema != null)
|
||||
{
|
||||
var ds = dcv.Schema.Value;
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
ds.Type, ds.Size, ds.Precision, ds.Scale,
|
||||
(isOut || isRet) ? DBNull.Value : dcv.Value);
|
||||
}
|
||||
else
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
arg == null ? DbType.String : arg.GetType().ToDbType(),
|
||||
isRet ? 4 : 0,
|
||||
(isOut || isRet) ? DBNull.Value : dcv.Value);
|
||||
}
|
||||
else if (arg is DynamicSchemaColumn)
|
||||
{
|
||||
var dsc = (DynamicSchemaColumn)arg;
|
||||
|
||||
string paramName = i.ToString();
|
||||
bool isOut = false;
|
||||
bool isRet = false;
|
||||
bool isBoth = false;
|
||||
|
||||
if (info.ArgumentNames.Count > i)
|
||||
{
|
||||
isOut = info.ArgumentNames[i].StartsWith("out_");
|
||||
isRet = info.ArgumentNames[i].StartsWith("ret_");
|
||||
isBoth = info.ArgumentNames[i].StartsWith("both_");
|
||||
|
||||
paramName = isOut || isRet ?
|
||||
info.ArgumentNames[i].Substring(4) :
|
||||
isBoth ? info.ArgumentNames[i].Substring(5) :
|
||||
info.ArgumentNames[i];
|
||||
}
|
||||
|
||||
paramName = dsc.Name ?? paramName;
|
||||
|
||||
if (isRet)
|
||||
retIsAdded = true;
|
||||
|
||||
if (isOut || isRet || isBoth)
|
||||
{
|
||||
if (retParams == null)
|
||||
retParams = new Dictionary<string, int>();
|
||||
retParams.Add(paramName, cmd.Parameters.Count);
|
||||
}
|
||||
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
dsc.Type, dsc.Size, dsc.Precision, dsc.Scale,
|
||||
DBNull.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_");
|
||||
|
||||
if (isRet)
|
||||
retIsAdded = true;
|
||||
|
||||
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<string, int>();
|
||||
retParams.Add(paramName, cmd.Parameters.Count);
|
||||
}
|
||||
|
||||
cmd.AddParameter(
|
||||
_db.GetParameterName(paramName),
|
||||
isOut ? ParameterDirection.Output :
|
||||
isRet ? ParameterDirection.ReturnValue :
|
||||
isBoth ? ParameterDirection.InputOutput :
|
||||
ParameterDirection.Input,
|
||||
arg == null ? isRet ? DbType.Int32 : DbType.String : arg.GetType().ToDbType(),
|
||||
isRet ? 4 : 0,
|
||||
(isOut || isRet) ? DBNull.Value : arg);
|
||||
}
|
||||
else
|
||||
cmd.AddParameter(_db, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Prepare arguments
|
||||
|
||||
|
||||
175
DynamORM/Helpers/DynamicProcedureParameterBinder.cs
Normal file
175
DynamORM/Helpers/DynamicProcedureParameterBinder.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||
* Copyright (c) 2012-2026, 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.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using DynamORM.Mapper;
|
||||
|
||||
namespace DynamORM.Helpers
|
||||
{
|
||||
internal static class DynamicProcedureParameterBinder
|
||||
{
|
||||
internal sealed class BindingResult
|
||||
{
|
||||
public bool ReturnValueAdded { get; set; }
|
||||
public Dictionary<string, int> ReturnParameters { get; set; }
|
||||
}
|
||||
|
||||
internal static bool CanBind(object item)
|
||||
{
|
||||
if (item == null)
|
||||
return false;
|
||||
|
||||
Type type = item.GetType();
|
||||
|
||||
if (type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime) || type == typeof(Guid) || type == typeof(TimeSpan))
|
||||
return false;
|
||||
|
||||
return GetBindableProperties(type).Any();
|
||||
}
|
||||
|
||||
internal static BindingResult Bind(DynamicDatabase db, IDbCommand cmd, object item)
|
||||
{
|
||||
if (db == null)
|
||||
throw new ArgumentNullException("db");
|
||||
if (cmd == null)
|
||||
throw new ArgumentNullException("cmd");
|
||||
if (item == null)
|
||||
throw new ArgumentNullException("item");
|
||||
|
||||
BindingResult result = new BindingResult();
|
||||
|
||||
foreach (PropertyInfo property in GetBindableProperties(item.GetType()))
|
||||
{
|
||||
ProcedureParameterAttribute procAttr = property.GetCustomAttributes(typeof(ProcedureParameterAttribute), true).Cast<ProcedureParameterAttribute>().FirstOrDefault();
|
||||
ColumnAttribute colAttr = property.GetCustomAttributes(typeof(ColumnAttribute), true).Cast<ColumnAttribute>().FirstOrDefault();
|
||||
|
||||
string name = (procAttr != null && !string.IsNullOrEmpty(procAttr.Name) ? procAttr.Name : null)
|
||||
?? (colAttr != null && !string.IsNullOrEmpty(colAttr.Name) ? colAttr.Name : null)
|
||||
?? property.Name;
|
||||
|
||||
ParameterDirection direction = procAttr == null ? ParameterDirection.Input : procAttr.Direction;
|
||||
object value = property.GetValue(item, null);
|
||||
DbType dbType = ResolveDbType(property.PropertyType, value, procAttr, colAttr, direction);
|
||||
int size = ResolveSize(dbType, value, procAttr, colAttr, direction);
|
||||
byte precision = (procAttr != null && procAttr.Precision != ProcedureParameterAttribute.UnspecifiedByte ? procAttr.Precision : default(byte));
|
||||
byte scale = (procAttr != null && procAttr.Scale != ProcedureParameterAttribute.UnspecifiedByte ? procAttr.Scale : default(byte));
|
||||
|
||||
if (procAttr == null && colAttr != null)
|
||||
{
|
||||
precision = colAttr.Precision ?? precision;
|
||||
scale = colAttr.Scale ?? scale;
|
||||
}
|
||||
|
||||
if (direction == ParameterDirection.ReturnValue)
|
||||
result.ReturnValueAdded = true;
|
||||
|
||||
if (direction == ParameterDirection.Output || direction == ParameterDirection.InputOutput || direction == ParameterDirection.ReturnValue)
|
||||
{
|
||||
if (result.ReturnParameters == null)
|
||||
result.ReturnParameters = new Dictionary<string, int>();
|
||||
result.ReturnParameters.Add(name, cmd.Parameters.Count);
|
||||
}
|
||||
|
||||
cmd.AddParameter(
|
||||
db.GetParameterName(name),
|
||||
direction,
|
||||
dbType,
|
||||
size,
|
||||
precision,
|
||||
scale,
|
||||
direction == ParameterDirection.Output || direction == ParameterDirection.ReturnValue ? DBNull.Value : (value ?? DBNull.Value));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IEnumerable<PropertyInfo> GetBindableProperties(Type type)
|
||||
{
|
||||
return type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(x => x.CanRead && x.GetIndexParameters().Length == 0)
|
||||
.Where(x => x.GetCustomAttributes(typeof(ProcedureParameterAttribute), true).Any() || x.GetCustomAttributes(typeof(ColumnAttribute), true).Any())
|
||||
.OrderBy(x =>
|
||||
{
|
||||
ProcedureParameterAttribute attr = x.GetCustomAttributes(typeof(ProcedureParameterAttribute), true).Cast<ProcedureParameterAttribute>().FirstOrDefault();
|
||||
return attr == null ? int.MaxValue : attr.Order;
|
||||
})
|
||||
.ThenBy(x => x.MetadataToken);
|
||||
}
|
||||
|
||||
private static DbType ResolveDbType(Type propertyType, object value, ProcedureParameterAttribute procAttr, ColumnAttribute colAttr, ParameterDirection direction)
|
||||
{
|
||||
if (procAttr != null && (int)procAttr.DbType != ProcedureParameterAttribute.UnspecifiedDbType)
|
||||
return procAttr.DbType;
|
||||
|
||||
if (colAttr != null && colAttr.Type.HasValue)
|
||||
return colAttr.Type.Value;
|
||||
|
||||
Type targetType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
|
||||
|
||||
if (targetType == typeof(object) && value != null)
|
||||
targetType = value.GetType();
|
||||
|
||||
if (value == null && direction == ParameterDirection.ReturnValue)
|
||||
return DbType.Int32;
|
||||
|
||||
return targetType.ToDbType();
|
||||
}
|
||||
|
||||
private static int ResolveSize(DbType dbType, object value, ProcedureParameterAttribute procAttr, ColumnAttribute colAttr, ParameterDirection direction)
|
||||
{
|
||||
if (procAttr != null && procAttr.Size != ProcedureParameterAttribute.UnspecifiedSize)
|
||||
return procAttr.Size;
|
||||
|
||||
if (colAttr != null && colAttr.Size.HasValue)
|
||||
return colAttr.Size.Value;
|
||||
|
||||
if (direction == ParameterDirection.ReturnValue)
|
||||
return 4;
|
||||
|
||||
if (dbType == DbType.AnsiString || dbType == DbType.AnsiStringFixedLength)
|
||||
{
|
||||
if (value != null)
|
||||
return value.ToString().Length > 8000 ? -1 : 8000;
|
||||
return 8000;
|
||||
}
|
||||
|
||||
if (dbType == DbType.String || dbType == DbType.StringFixedLength)
|
||||
{
|
||||
if (value != null)
|
||||
return value.ToString().Length > 4000 ? -1 : 4000;
|
||||
return 4000;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
84
DynamORM/ProcedureParameterAttribute.cs
Normal file
84
DynamORM/ProcedureParameterAttribute.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* DynamORM - Dynamic Object-Relational Mapping library.
|
||||
* Copyright (c) 2012-2026, 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.Data;
|
||||
using DynamORM.Mapper;
|
||||
|
||||
namespace DynamORM
|
||||
{
|
||||
/// <summary>Declares metadata for object-based stored procedure parameters.</summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ProcedureParameterAttribute : ColumnAttribute
|
||||
{
|
||||
/// <summary>Sentinel used when database type was not provided.</summary>
|
||||
public const int UnspecifiedDbType = -1;
|
||||
|
||||
/// <summary>Sentinel used when size was not provided.</summary>
|
||||
public const int UnspecifiedSize = -1;
|
||||
|
||||
/// <summary>Sentinel used when precision or scale was not provided.</summary>
|
||||
public const byte UnspecifiedByte = byte.MaxValue;
|
||||
|
||||
/// <summary>Gets or sets parameter direction. Defaults to input.</summary>
|
||||
public ParameterDirection Direction { get; set; }
|
||||
|
||||
/// <summary>Gets or sets explicit parameter order. Lower values are emitted first.</summary>
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <summary>Gets or sets parameter database type.</summary>
|
||||
public DbType DbType { get; set; }
|
||||
|
||||
/// <summary>Gets or sets parameter size.</summary>
|
||||
public new int Size { get; set; }
|
||||
|
||||
/// <summary>Gets or sets parameter precision.</summary>
|
||||
public new byte Precision { get; set; }
|
||||
|
||||
/// <summary>Gets or sets parameter scale.</summary>
|
||||
public new byte Scale { get; set; }
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="ProcedureParameterAttribute"/> class.</summary>
|
||||
public ProcedureParameterAttribute()
|
||||
{
|
||||
Direction = ParameterDirection.Input;
|
||||
Order = int.MaxValue;
|
||||
DbType = (DbType)UnspecifiedDbType;
|
||||
Size = UnspecifiedSize;
|
||||
Precision = UnspecifiedByte;
|
||||
Scale = UnspecifiedByte;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="ProcedureParameterAttribute"/> class.</summary>
|
||||
public ProcedureParameterAttribute(string name)
|
||||
: this()
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user