Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ Task("pack")
Configuration = configuration,
OutputDirectory = "./artifacts/",
NoBuild = true,
NoRestore = true
NoRestore = true,
IncludeSymbols = true
};

DotNetCorePack(solutionFile, settings);
Expand Down
4 changes: 4 additions & 0 deletions releasenotes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Releasenotes

## 2.0.3
- Enabling using Dapper.Oracle when running under a profiled DbCommand.
Basically, any database profiler will decorate an oracle connection, so Dapper.Oracle now looks for a public property of type DbCommand when it detects that it is not working against a OracleCommand, and will recursively search for the actual DbCommand in order to set the Oracle-specific properties.

## 2.0.2
- Bugfix of typeconversion for arrays

Expand Down
20 changes: 20 additions & 0 deletions src/Dapper.Oracle/CommandExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;

namespace Dapper.Oracle
{
internal static class CommandExtensions
{
public static bool IsWrapped(this IDbConnection connection)
{
return string.Compare(connection.GetType().Name, "OracleConnection", StringComparison.InvariantCulture) != 0;
}

public static bool IsWrapped(this IDbCommand connection)
{
return string.Compare(connection.GetType().Name, "OracleCommand", StringComparison.InvariantCulture) != 0;
}
}
}
4 changes: 1 addition & 3 deletions src/Dapper.Oracle/OracleDynamicParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ public partial class OracleDynamicParameters : SqlMapper.IDynamicParameters
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
public OracleDynamicParameters()
{
}
public OracleDynamicParameters() { }

/// <summary>
/// construct a dynamic parameter bag
Expand Down
15 changes: 12 additions & 3 deletions src/Dapper.Oracle/OracleMethodHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq.Expressions;
using System.Reflection;
using Dapper.Oracle.Expressions;
using Dapper.Oracle.Util;

namespace Dapper.Oracle
{
Expand All @@ -17,17 +18,20 @@ internal class OracleMethodHelper

public static void SetArrayBindCount(IDbCommand command, int arrayBindCount)
{
Get(command).ArrayBindCount.SetValue(command, arrayBindCount);
IDbCommand actual = command.IsWrapped() ? command.DownCastCommand() : command;
Get(actual).ArrayBindCount.SetValue(actual, arrayBindCount);
}

public static void SetInitialLOBFetchSize(IDbCommand command, int arrayBindCount)
{
Get(command).InitialLOBFetchSize.SetValue(command, arrayBindCount);
IDbCommand actual = command.IsWrapped() ? command.DownCastCommand() : command;
Get(actual).InitialLOBFetchSize.SetValue(actual, arrayBindCount);
}

public static void SetBindByName(IDbCommand command, bool bindByName)
{
Get(command).BindByName.SetValue(command, bindByName);
IDbCommand actual = command.IsWrapped() ? command.DownCastCommand() : command;
Get(actual).BindByName.SetValue(actual, bindByName);
}

public static void SetOracleParameters(IDbDataParameter parameter, OracleDynamicParameters.OracleParameterInfo oracleParameterInfo)
Expand Down Expand Up @@ -148,6 +152,11 @@ private class CommandExpressions

public CommandExpressions(Type commandType)
{
if (!commandType.Namespace.StartsWith("Oracle"))
{
throw new NotSupportedException($"Whoopsies! This library will only work with Oracle types, you are attempting to use type {commandType.FullName}, which is not supported.");
}

BindByName = new ObjectWrapper<IDbCommand, bool>("BindByName", commandType);
InitialLOBFetchSize = new ObjectWrapper<IDbCommand, int>("InitialLOBFetchSize", commandType);
ArrayBindCount = new ObjectWrapper<IDbCommand, int>("ArrayBindCount", commandType);
Expand Down
121 changes: 121 additions & 0 deletions src/Dapper.Oracle/Util/Downcaster.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Dapper.Oracle.Util
{
internal static class ConnectionExtensions
{
private static Dictionary<Type, Action<IDbConnection, string>> setters =
new Dictionary<Type, Action<IDbConnection, string>>();

public static T DownCastConnection<T>(this T connection) where T : IDbConnection
{
return DownCaster<T>.DownCast(connection);
}

public static T DownCastCommand<T>(this T command) where T : IDbCommand
{
return DownCaster<T>.DownCast(command);
}


}

internal static class DownCaster<T>
{
private static Dictionary<Type, Tuple<bool, Func<T, T>>> dictionary =
new Dictionary<Type, Tuple<bool, Func<T, T>>>();

internal static T DownCast(T obj)
{
return GetInner(obj);
}

private static T GetInner(T obj)
{
if (obj == null)
{
return default(T);
}

var type = obj.GetType();
if (dictionary.TryGetValue(type, out var info))
{
if (info.Item1 == false)
{
return obj;
}

return GetInner(info.Item2(obj));
}

var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(pi => pi.CanRead && typeof(T).IsAssignableFrom(pi.PropertyType)).ToArray();

if (properties.Length > 1)
{
throw new Exception($"More than one inner property of type {typeof(T)}");
}

if (properties.Length == 0)
{
dictionary[type] = new Tuple<bool, Func<T, T>>(false, null);
return obj;
}

var property = properties[0];

dictionary[type] =
new Tuple<bool, Func<T, T>>(true, new PropertyWrapper<T, T>(property.Name, type).CreateGetter());

return GetInner(dictionary[type].Item2(obj));
}

}

internal class PropertyWrapper<TObject, TValue>
{
private readonly string _propertyName;
private readonly Type _objectType;

public PropertyWrapper(string propertyName, Type objectType)
{
_propertyName = propertyName;
_objectType = objectType;
}

public Func<TObject, TValue> CreateGetter()
{
var inputVariable = Expression.Parameter(typeof(TObject));
if (typeof(TObject) != _objectType)
{
var converted = Expression.Convert(inputVariable, _objectType);
var retreiver = Expression.Property(converted, _objectType.GetProperty(_propertyName));
return Expression.Lambda<Func<TObject, TValue>>(retreiver, inputVariable).Compile();
}
else
{
var retreiver = Expression.Property(inputVariable, _objectType.GetProperty(_propertyName));
return Expression.Lambda<Func<TObject, TValue>>(retreiver, inputVariable).Compile();
}
}

public Action<TObject, TValue> CreateSetter()
{
var inputVariable = Expression.Parameter(typeof(TObject));
var inputVariable2 = Expression.Parameter(typeof(TValue));

var convertExpression = Expression.Convert(inputVariable, _objectType);

var expression = Expression.Assign(
Expression.PropertyOrField(convertExpression, _propertyName),
inputVariable2);

return Expression.Lambda<Action<TObject, TValue>>(expression, inputVariable, inputVariable2).Compile();
}
}
}
51 changes: 51 additions & 0 deletions src/Tests.Dapper.Oracle/DecoratedCommandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Data.SqlClient;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using Dapper;
using Dapper.Oracle;
using FluentAssertions;
using Oracle.ManagedDataAccess.Client;
using Xunit;

namespace Tests.Dapper.Oracle
{
public class DecoratedCommandTests
{


private static OracleCommand CreateCommand => new OracleCommand();

public static IEnumerable<object[]> Commands()
{
yield return new object[] { CreateCommand, new OracleManagedParameterRetretreiver() };
yield return new object[] { new DecoratedDbCommand(CreateCommand), new OracleManagedParameterRetretreiver() };
yield return new object[] { new DecoratedDbCommand(new DecoratedDbCommand(CreateCommand)), new OracleManagedParameterRetretreiver() };
}

[Theory, MemberData(nameof(Commands))]
public void Works_On_Decorated_Commands(IDbCommand command, IOracleParameterRetretreiver retreiver)
{
var parameters = new TestableOracleDynamicParameters();
parameters.Add("Foo", dbType: OracleMappingType.RefCursor, direction: ParameterDirection.ReturnValue);
parameters.AddParam(command);
var oracleParam = retreiver.GetParameter(command.Parameters[0]);
oracleParam.OracleDbType.Should().Be("RefCursor");
oracleParam.Direction.Should().Be(ParameterDirection.ReturnValue);
}

[Theory, MemberData(nameof(Commands))]
public void Set_BindByName_On_Decorated_Commands(IDbCommand command, IOracleParameterRetretreiver retreiver)
{
var parameters = new TestableOracleDynamicParameters();
parameters.BindByName = true;
parameters.Add("Foo", dbType: OracleMappingType.RefCursor, direction: ParameterDirection.ReturnValue);
parameters.AddParam(command);
var oracleParam = retreiver.GetParameter(command.Parameters[0]);
oracleParam.OracleDbType.Should().Be("RefCursor");
oracleParam.Direction.Should().Be(ParameterDirection.ReturnValue);
}
}
}
52 changes: 52 additions & 0 deletions src/Tests.Dapper.Oracle/Decorators/DecoratedConnection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Data;
using System.Data.Common;

namespace Tests.Dapper.Oracle
{
internal class DecoratedConnection : DbConnection
{
private DbConnection Decorated;

public DecoratedConnection(DbConnection decorated)
{
Decorated = decorated;
}

protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
{
return new DecoratedTransaction(Decorated.BeginTransaction(isolationLevel), this);
}

public override void ChangeDatabase(string databaseName)
{
Decorated.ChangeDatabase(databaseName);
}

public override void Close()
{
Decorated.Close();
}

public override void Open()
{
Decorated.Open();
}

public override string ConnectionString
{
get => Decorated.ConnectionString;
set => Decorated.ConnectionString = value;
}

public override string Database => Decorated.Database;
public override ConnectionState State => Decorated.State;
public override string DataSource => Decorated.DataSource;
public override string ServerVersion => Decorated.ServerVersion;


protected override DbCommand CreateDbCommand()
{
return new DecoratedDbCommand(Decorated.CreateCommand());
}
}
}
Loading