Skip to content
This repository was archived by the owner on Nov 9, 2022. It is now read-only.

Async support #8

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageTags>datadog tracing apm aspnetcore</PackageTags>
<Description>A C# client implementation for DataDog's APM solution for AspNetCore.</Description>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/DataDog.Tracing.Sql.Tests/TraceDbCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void ExecuteReader_is_traced(CommandBehavior commandBehavior)
}
}
customers.Count.Should().Be(2);
_root.Spans[1].Name.Should().Be("sql." + nameof(IDbCommand.ExecuteReader));
_root.Spans[1].Name.Should().Be("sql.ExecuteDbDataReader");
_root.Spans[1].Service.Should().Be("sql");
_root.Spans[1].Resource.Should().Be("main");
_root.Spans[1].Type.Should().Be("sql");
Expand Down
2 changes: 1 addition & 1 deletion src/DataDog.Tracing.Sql/DataDog.Tracing.Sql.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageTags>datadog tracing apm sql</PackageTags>
<Description>A C# client implementation for DataDog's APM solution for tracing ADO .NET implementations.</Description>
</PropertyGroup>
Expand Down
229 changes: 185 additions & 44 deletions src/DataDog.Tracing.Sql/TraceDbCommand.cs
Original file line number Diff line number Diff line change
@@ -1,51 +1,105 @@
using System;
using System.Data;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;

namespace DataDog.Tracing.Sql
{
public class TraceDbCommand : IDbCommand
public class TraceDbCommand : DbCommand
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the base classes rather than the interfaces is going to break downstream consumers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDbCommand and IDbConnection are pre .NET 2.0 interfaces, when async/await wasn't still a thing, and therefore lack support for asynchronous methods.

DbCommand and DbConnection were introduced after async/await support, and to avoid breaking previous implementations by adding new functionality Microsoft decided to create those abstract base classes to extend further the interfaces with async methods.

All connectors (MySql connector, SQLServer connector, PostgreSQL Npgsql connector, etc...) implement the abstract classes and not the interfaces nowadays, so this change shouldn't break any consumer (other than those using old .NET versions, but those don't support either .NET Standard so it doesn't even matter because they wouldn't be able to consume this library anyways).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It certainly breaks the one consumer I know about since they use the interfaces :)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you intend to do the work of fixing this in WB?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'll gladly work in the RepositoryBase implementation of the async overloads and all the Repositories that inherit from it!

It's probably a lot of work but I think it's worth the effort. Also we can leave the synchronous methods for backwards compatibility, maybe with the [ObsoleteAttribute].

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
private const string ServiceName = "sql";
private readonly IDbCommand _command;
private readonly DbCommand _command;
private readonly ISpanSource _spanSource;

public TraceDbCommand(IDbCommand command)
public TraceDbCommand(DbCommand command)
: this(command, TraceContextSpanSource.Instance)
{
}

public TraceDbCommand(IDbCommand command, ISpanSource spanSource)
public TraceDbCommand(DbCommand command, ISpanSource spanSource)
{
_command = command;
_spanSource = spanSource;
}

public IDbCommand InnerCommand => _command;

#region Overrides

public void Dispose() => _command.Dispose();
public override string CommandText
{
get => _command.CommandText;
set => _command.CommandText = value;
}

public void Cancel() => _command.Cancel();
public override int CommandTimeout
{
get => _command.CommandTimeout;
set => _command.CommandTimeout = value;
}

public IDbDataParameter CreateParameter() => _command.CreateParameter();
public override CommandType CommandType
{
get => _command.CommandType;
set => _command.CommandType = value;
}

private void SetMeta(ISpan span)
protected override DbConnection DbConnection
{
span.SetMeta("sql.CommandText", CommandText);
span.SetMeta("sql.CommandType", CommandType.ToString());
get => _command.Connection;
set => _command.Connection = value;
}

protected override DbParameterCollection DbParameterCollection
{
get => _command.Parameters;
}

protected override DbTransaction DbTransaction
{
get => _command.Transaction;
set => _command.Transaction = value;
}

public int ExecuteNonQuery()
public override bool DesignTimeVisible
{
get => _command.DesignTimeVisible;
set => _command.DesignTimeVisible = value;
}

public override UpdateRowSource UpdatedRowSource
{
get => _command.UpdatedRowSource;
set => _command.UpdatedRowSource = value;
}

public override void Cancel()
{
_command.Cancel();
}

protected override DbParameter CreateDbParameter()
{
return _command.CreateParameter();
}

public override int ExecuteNonQuery()
{
const string name = "sql." + nameof(ExecuteNonQuery);

var span = _spanSource.Begin(name, ServiceName, _command.Connection.Database, ServiceName);

try
{
var result = _command.ExecuteNonQuery();
int result = _command.ExecuteNonQuery();

if (span != null)
{
span.SetMeta("sql.RowsAffected", result.ToString());
SetMeta(span);
}

return result;
}
catch (Exception ex)
Expand All @@ -59,21 +113,23 @@ public int ExecuteNonQuery()
}
}

public IDataReader ExecuteReader() => ExecuteReader(CommandBehavior.Default);

public IDataReader ExecuteReader(CommandBehavior behavior)
public override async Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken)
{
const string name = "sql." + nameof(ExecuteReader);
const string name = "sql." + nameof(ExecuteNonQueryAsync);

var span = _spanSource.Begin(name, ServiceName, _command.Connection.Database, ServiceName);

try
{
int result = await _command.ExecuteNonQueryAsync(cancellationToken);

if (span != null)
{
const string metaKey = "sql." + nameof(CommandBehavior);
span.SetMeta(metaKey, behavior.ToString("x"));
span.SetMeta("sql.RowsAffected", result.ToString());
SetMeta(span);
}
return _command.ExecuteReader(behavior);

return result;
}
catch (Exception ex)
{
Expand All @@ -86,13 +142,19 @@ public IDataReader ExecuteReader(CommandBehavior behavior)
}
}

public object ExecuteScalar()
public override object ExecuteScalar()
{
const string name = "sql." + nameof(ExecuteScalar);
var span = _spanSource.Begin(name, ServiceName, _command.Connection.Database, ServiceName);

ISpan span = _spanSource.Begin(name, ServiceName, _command.Connection.Database, ServiceName);

try
{
if (span != null) SetMeta(span);
if (span != null)
{
SetMeta(span);
}

return _command.ExecuteScalar();
}
catch (Exception ex)
Expand All @@ -106,44 +168,123 @@ public object ExecuteScalar()
}
}

public void Prepare() => _command.Prepare();

public string CommandText
public override async Task<object> ExecuteScalarAsync(CancellationToken cancellationToken)
{
get => _command.CommandText;
set => _command.CommandText = value;
}
const string name = "sql." + nameof(ExecuteScalarAsync);

ISpan span = _spanSource.Begin(name, ServiceName, _command.Connection.Database, ServiceName);

public int CommandTimeout
try
{
if (span != null)
{
SetMeta(span);
}

return await _command.ExecuteScalarAsync(cancellationToken);
}
catch (Exception ex)
{
span?.SetError(ex);
throw;
}
finally
{
span?.Dispose();
}
}

protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
{
get => _command.CommandTimeout;
set => _command.CommandTimeout = value;
const string name = "sql." + nameof(ExecuteDbDataReader);

ISpan span = _spanSource.Begin(name, ServiceName, _command.Connection.Database, ServiceName);

try
{
if (span != null)
{
const string metaKey = "sql." + nameof(CommandBehavior);

span.SetMeta(metaKey, behavior.ToString("x"));
SetMeta(span);
}

return _command.ExecuteReader(behavior);
}
catch (Exception ex)
{
span?.SetError(ex);
throw;
}
finally
{
span?.Dispose();
}
}

public CommandType CommandType
protected override async Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
{
get => _command.CommandType;
set => _command.CommandType = value;
const string name = "sql." + nameof(ExecuteDbDataReaderAsync);

ISpan span = _spanSource.Begin(name, ServiceName, _command.Connection.Database, ServiceName);

try
{
if (span != null)
{
const string metaKey = "sql." + nameof(CommandBehavior);

span.SetMeta(metaKey, behavior.ToString("x"));
SetMeta(span);
}

return await _command.ExecuteReaderAsync(behavior, cancellationToken);
}
catch (Exception ex)
{
span?.SetError(ex);
throw;
}
finally
{
span?.Dispose();
}
}

public IDbConnection Connection
public override void Prepare()
{
get => _command.Connection;
set => _command.Connection = value;
_command.Prepare();
}

public IDataParameterCollection Parameters => _command.Parameters;
#endregion

public IDbTransaction Transaction
#region Private Methods

private void SetMeta(ISpan span)
{
get => _command.Transaction;
set => _command.Transaction = value;
span.SetMeta("sql.CommandText", CommandText);
span.SetMeta("sql.CommandType", CommandType.ToString());
}

public UpdateRowSource UpdatedRowSource
#endregion

#region Dispose Pattern

private bool _disposed;

protected override void Dispose(bool disposing)
{
get => _command.UpdatedRowSource;
set => _command.UpdatedRowSource = value;
if (disposing && !_disposed)
{
_command?.Dispose();
}

_disposed = true;

base.Dispose(disposing);
}

#endregion
}
}
Loading