Skip to content

Commit

Permalink
Merge pull request #86 from abe545/add-no-connection-cloning
Browse files Browse the repository at this point in the history
Add no connection cloning
  • Loading branch information
abe545 authored Sep 2, 2016
2 parents 59bf323 + 38efe79 commit 7eaadbe
Show file tree
Hide file tree
Showing 16 changed files with 706 additions and 289 deletions.
12 changes: 9 additions & 3 deletions CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ internal class DynamicStoredProcedure : DynamicObject
private readonly CancellationToken token;
private readonly int timeout;
private readonly DynamicExecutionMode executionMode;
private readonly bool hasResults;

static DynamicStoredProcedure()
{
Expand All @@ -36,7 +37,8 @@ public DynamicStoredProcedure(IDbConnection connection,
IEnumerable<IDataTransformer> transformers,
CancellationToken token,
int timeout,
DynamicExecutionMode executionMode)
DynamicExecutionMode executionMode,
bool hasResults)
{
Contract.Requires(connection != null);
Contract.Requires(transformers != null);
Expand All @@ -46,14 +48,16 @@ public DynamicStoredProcedure(IDbConnection connection,
this.token = token;
this.timeout = timeout;
this.executionMode = executionMode;
this.hasResults = hasResults;
}

private DynamicStoredProcedure(IDbConnection connection,
IEnumerable<IDataTransformer> transformers,
CancellationToken token,
int timeout,
string schema,
DynamicExecutionMode executionMode)
DynamicExecutionMode executionMode,
bool hasResults)
{
Contract.Requires(connection != null);
Contract.Requires(transformers != null);
Expand All @@ -65,14 +69,15 @@ private DynamicStoredProcedure(IDbConnection connection,
this.token = token;
this.timeout = timeout;
this.executionMode = executionMode;
this.hasResults = hasResults;
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (!string.IsNullOrEmpty(schema))
throw new StoredProcedureException($"Schema already specified once. \n\tExisting schema: {schema}\n\tAdditional schema: {binder.Name}");

result = new DynamicStoredProcedure(connection, transformers, token, timeout, binder.Name, executionMode);
result = new DynamicStoredProcedure(connection, transformers, token, timeout, binder.Name, executionMode, hasResults);
return true;
}

Expand Down Expand Up @@ -166,6 +171,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o
parameters,
transformers,
executionMode,
hasResults,
token);

return true;
Expand Down
163 changes: 119 additions & 44 deletions CodeOnlyStoredProcedure/Dynamic/DynamicStoredProcedureResults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal class DynamicStoredProcedureResults : DynamicObject, IDisposable
});

private readonly Task<IDataReader> resultTask;
private readonly Task nonQueryTask;
private readonly IDbConnection connection;
private readonly IDbCommand command;
private readonly IEnumerable<IDataTransformer> transformers;
Expand All @@ -40,6 +41,7 @@ public DynamicStoredProcedureResults(
List<IStoredProcedureParameter> parameters,
IEnumerable<IDataTransformer> transformers,
DynamicExecutionMode executionMode,
bool hasResults,
CancellationToken token)
{
Contract.Requires(connection != null);
Expand All @@ -57,27 +59,48 @@ public DynamicStoredProcedureResults(
foreach (var p in parameters)
command.Parameters.Add(p.CreateDbDataParameter(command));

if (executionMode == DynamicExecutionMode.Synchronous)
if (!hasResults)
{
if (executionMode == DynamicExecutionMode.Synchronous)
{
command.ExecuteNonQuery();
TransferOutputParameters(parameters, token);
}
else
{
#if !NET40
var sqlCommand = command as SqlCommand;
if (sqlCommand != null)
nonQueryTask = sqlCommand.ExecuteNonQueryAsync(token);
else
#endif
nonQueryTask = Task.Factory.StartNew(() => command.ExecuteNonQuery(),
token,
TaskCreationOptions.None,
TaskScheduler.Default);

nonQueryTask = nonQueryTask.ContinueWith(_ => TransferOutputParameters(parameters, token), token);
}
}
else if (executionMode == DynamicExecutionMode.Synchronous)
{
var tcs = new TaskCompletionSource<IDataReader>();

token.ThrowIfCancellationRequested();
var res = command.ExecuteReader();
token.ThrowIfCancellationRequested();

foreach (IDbDataParameter p in command.Parameters)
TransferOutputParameters(parameters, token);

if (token.IsCancellationRequested)
{
if (p.Direction != ParameterDirection.Input)
{
parameters.OfType<IOutputStoredProcedureParameter>()
.FirstOrDefault(sp => sp.ParameterName == p.ParameterName)
?.TransferOutputValue(p.Value);
}
res.Dispose();
token.ThrowIfCancellationRequested();
}
else
{
tcs.SetResult(res);
resultTask = tcs.Task;
}

token.ThrowIfCancellationRequested();
tcs.SetResult(res);
resultTask = tcs.Task;
}
else
{
Expand All @@ -93,19 +116,10 @@ public DynamicStoredProcedureResults(
TaskScheduler.Default);

resultTask = resultTask.ContinueWith(r =>
{
foreach (IDbDataParameter p in command.Parameters)
{
if (p.Direction != ParameterDirection.Input)
{
parameters.OfType<IOutputStoredProcedureParameter>()
.FirstOrDefault(sp => sp.ParameterName == p.ParameterName)
?.TransferOutputValue(p.Value);
}
}

return r.Result;
}, token);
{
TransferOutputParameters(parameters, token);
return r.Result;
}, token);
}
}

Expand All @@ -117,29 +131,62 @@ public void Dispose()
command?.Dispose();
}

private IEnumerable<T> GetResults<T>(bool isSingle) => RowFactory<T>.Create(isSingle).ParseRows(resultTask.Result, transformers, token);
private IEnumerable<T> GetResults<T>(bool isSingle)
{
if (resultTask == null)
throw new NotSupportedException("When calling the dynamic syntax with a NonQuery variant, no results are returned, so the value can not be cast to a result set.");

try
{
return RowFactory<T>.Create(isSingle).ParseRows(resultTask.Result, transformers, token);
}
finally
{
if (isSingle)
resultTask.Result.Dispose();
}
}

private Task ContinueNoResults()
{
if (nonQueryTask != null)
{
return nonQueryTask.ContinueWith(r =>
{
Dispose();

if (r.Status == TaskStatus.Faulted)
throw r.Exception;
});
}

return resultTask.ContinueWith(r =>
{
r.Result.Dispose();
Dispose();

if (r.Status == TaskStatus.Faulted)
throw r.Exception;
}, token);
});
}

private Task<IEnumerable<T>> CreateSingleContinuation<T>()
{
if (resultTask == null)
throw new NotSupportedException("When calling the dynamic syntax with a NonQuery variant, no results are returned, so the value can not be cast to a result set.");

return resultTask.ContinueWith(_ =>
{
try { return GetResults<T>(true); }
finally { Dispose(); }
}, token);
});
}

private Task<T> CreateSingleRowContinuation<T>()
{
if (resultTask == null)
throw new NotSupportedException("When calling the dynamic syntax with a NonQuery variant, no results are returned, so the value can not be cast to a result set.");

return resultTask.ContinueWith(_ =>
{
try { return GetResults<T>(true).SingleOrDefault(); }
Expand All @@ -150,29 +197,43 @@ private Task<T> CreateSingleRowContinuation<T>()
private T GetMultipleResults<T>()
{
Contract.Ensures(Contract.Result<T>() != null);

var types = typeof(T).GetGenericArguments();
var res = types.Select((t, i) =>

if (resultTask == null)
throw new NotSupportedException("When calling the dynamic syntax with a NonQuery variant, no results are returned, so the value can not be cast to a result set.");

try
{
if (i > 0)
resultTask.Result.NextResult();
var types = typeof(T).GetGenericArguments();
var res = types.Select((t, i) =>
{
if (i > 0)
resultTask.Result.NextResult();

token.ThrowIfCancellationRequested();
token.ThrowIfCancellationRequested();

return getResultsMethod.Value
.MakeGenericMethod(t.GetEnumeratedType())
.Invoke(this, new object[] { false });
}).ToArray();
return getResultsMethod.Value
.MakeGenericMethod(t.GetEnumeratedType())
.Invoke(this, new object[] { false });
}).ToArray();

return (T)tupleCreates.Value[types.Length]
.MakeGenericMethod(types)
.Invoke(null, res);
return (T)tupleCreates.Value[types.Length]
.MakeGenericMethod(types)
.Invoke(null, res);
}
finally
{
resultTask.Result.Dispose();
}
}

private Task<T> CreateMultipleContinuation<T>()
{
Contract.Ensures(Contract.Result<Task<T>>() != null);

if (resultTask == null)
throw new NotSupportedException("When calling the dynamic syntax with a NonQuery variant, no results are returned, so the value can not be cast to a result set.");


return resultTask.ContinueWith(_ =>
{
try { return GetMultipleResults<T>(); }
Expand All @@ -191,7 +252,21 @@ private object InternalGetAwaiter()
if (executionMode == DynamicExecutionMode.Synchronous)
throw new NotSupportedException(DynamicStoredProcedure.asyncParameterDirectionError);

return DynamicStoredProcedureResultsAwaiter.Create(this, resultTask, continueOnCaller);
return DynamicStoredProcedureResultsAwaiter.Create(this, nonQueryTask ?? resultTask, continueOnCaller);
}

private void TransferOutputParameters(List<IStoredProcedureParameter> parameters, CancellationToken token)
{
foreach (IDbDataParameter p in command.Parameters)
{
token.ThrowIfCancellationRequested();
if (p.Direction != ParameterDirection.Input)
{
parameters.OfType<IOutputStoredProcedureParameter>()
.FirstOrDefault(sp => sp.ParameterName == p.ParameterName)
?.TransferOutputValue(p.Value);
}
}
}

private class Meta : DynamicMetaObject
Expand Down Expand Up @@ -308,7 +383,7 @@ public override DynamicMetaObject BindConvert(ConvertBinder binder)
e = Expression.Call(null, singleExtension.Value.MakeGenericMethod(retType), e);
}

// make sure to close the connection
// make sure to dispose the DynamicStoredProcedureResults
var res = Expression.Variable(retType);
e = Expression.Block(retType,
new[] { res },
Expand Down
9 changes: 4 additions & 5 deletions CodeOnlyStoredProcedure/GlobalSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ internal class GlobalSettings
private static GlobalSettings instance = new GlobalSettings();

public static GlobalSettings Instance { get { return instance; } }
public IList<IDataTransformer> DataTransformers { get; }
public ConcurrentDictionary<Type, Type> InterfaceMap { get; }
public IList<IDataTransformer> DataTransformers { get; } = new List<IDataTransformer>();
public ConcurrentDictionary<Type, Type> InterfaceMap { get; } = new ConcurrentDictionary<Type, Type>();
public bool IsTestInstance { get; }
public bool ConvertAllNumericValues { get; set; }
public bool CloneConnectionForEachCall { get; set; } = true;

private GlobalSettings(bool isTestInstance = false)
{
IsTestInstance = isTestInstance;
DataTransformers = new List<IDataTransformer>();
InterfaceMap = new ConcurrentDictionary<Type, Type>();
IsTestInstance = isTestInstance;
}

public static IDisposable UseTestInstance()
Expand Down
Loading

0 comments on commit 7eaadbe

Please sign in to comment.