Commit 3fe88fc1 authored by mgravell's avatar mgravell

add Dapper.ProviderTools lib

parent b5352775
using System;
using System.Collections.Concurrent;
using System.Data;
using System.Data.Common;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Dapper.ProviderTools.Internal;
namespace Dapper.ProviderTools
{
/// <summary>
/// Provides provider-agnostic access to bulk-copy services
/// </summary>
public abstract class BulkCopy : IDisposable
{
/// <summary>
/// Attempt to create a BulkCopy instance for the connection provided
/// </summary>
public static BulkCopy? TryCreate(DbConnection connection)
{
if (connection == null) return null;
var type = connection.GetType();
if (!s_bcpFactory.TryGetValue(type, out var func))
{
s_bcpFactory[type] = func = CreateBcpFactory(type);
}
var obj = func?.Invoke(connection);
return DynamicBulkCopy.Create(obj);
}
/// <summary>
/// Provide an external registration for a given connection type
/// </summary>
public static void Register(Type type, Func<DbConnection, object>? factory)
=> s_bcpFactory[type] = factory;
private static readonly ConcurrentDictionary<Type, Func<DbConnection, object>?> s_bcpFactory
= new ConcurrentDictionary<Type, Func<DbConnection, object>?>();
internal static Func<DbConnection, object>? CreateBcpFactory(Type connectionType)
{
try
{
var match = Regex.Match(connectionType.Name, "^(.+)Connection$");
if (match.Success)
{
var prefix = match.Groups[1].Value;
var bcpType = connectionType.Assembly.GetType($"{connectionType.Namespace}.{prefix}BulkCopy");
if (bcpType != null)
{
var ctor = bcpType.GetConstructor(new[] { connectionType });
if (ctor == null) return null;
var p = Expression.Parameter(typeof(DbConnection), "conn");
var body = Expression.New(ctor, Expression.Convert(p, connectionType));
return Expression.Lambda<Func<DbConnection, object>>(body, p).Compile();
}
}
}
catch { }
return null;
}
/// <summary>
/// Name of the destination table on the server.
/// </summary>
public abstract string DestinationTableName { get; set; }
/// <summary>
/// Write a set of data to the server
/// </summary>
public abstract void WriteToServer(DataTable source);
/// <summary>
/// Write a set of data to the server
/// </summary>
public abstract void WriteToServer(IDataReader source);
/// <summary>
/// Write a set of data to the server
/// </summary>
public abstract Task WriteToServerAsync(DbDataReader source, CancellationToken cancellationToken);
/// <summary>
/// Write a set of data to the server
/// </summary>
public abstract Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken);
/// <summary>
/// Add a mapping between two columns by name
/// </summary>
public abstract void AddColumnMapping(string sourceColumn, string destinationColumn);
/// <summary>
/// Add a mapping between two columns by position
/// </summary>
public abstract void AddColumnMapping(int sourceColumn, int destinationColumn);
/// <summary>
/// The underlying untyped object providing the bulk-copy service
/// </summary>
public abstract object Wrapped { get; }
/// <summary>
/// Enables or disables streaming from a data-reader
/// </summary>
public bool EnableStreaming { get; set; }
/// <summary>
/// Number of rows in each batch
/// </summary>
public int BatchSize { get; set; }
/// <summary>
/// Number of seconds for the operation to complete before it times out.
/// </summary>
public int BulkCopyTimeout { get; set; }
/// <summary>
/// Release any resources associated with this instance
/// </summary>
public void Dispose() => Dispose(true);
/// <summary>
/// Release any resources associated with this instance
/// </summary>
protected abstract void Dispose(bool disposing);
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Dapper.ProviderTools</AssemblyName>
<PackageTags>orm;sql;micro-orm</PackageTags>
<Title>Dapper Provider Tools</Title>
<Description>Provider-agnostic ADO.NET helper utilities</Description>
<Authors>Marc Gravell</Authors>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<SignAssembly>true</SignAssembly>
<Nullable>enable</Nullable>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
</ItemGroup>
</Project>
using System;
using System.Collections.Concurrent;
using System.Data.Common;
using System.Linq.Expressions;
using System.Reflection;
namespace Dapper.ProviderTools
{
/// <summary>
/// Helper utilties for working with database exceptions
/// </summary>
public static class DbExceptionExtensions
{
/// <summary>
/// Indicates whether the provided exception has an integer Number property with the supplied value
/// </summary>
public static bool IsNumber(this DbException exception, int number)
=> exception != null && ByTypeHelpers.Get(exception.GetType()).IsNumber(exception, number);
private sealed class ByTypeHelpers
{
private static readonly ConcurrentDictionary<Type, ByTypeHelpers> s_byType
= new ConcurrentDictionary<Type, ByTypeHelpers>();
private readonly Func<DbException, int>? _getNumber;
public bool IsNumber(DbException exception, int number)
=> _getNumber != null && _getNumber(exception) == number;
public static ByTypeHelpers Get(Type type)
{
if (!s_byType.TryGetValue(type, out var value))
{
s_byType[type] = value = new ByTypeHelpers(type);
}
return value;
}
private ByTypeHelpers(Type type)
{
_getNumber = TryGetInstanceProperty<int>("Number", type);
}
private static Func<DbException, T>? TryGetInstanceProperty<T>(string name, Type type)
{
try
{
var prop = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
if (prop == null || !prop.CanRead) return null;
if (prop.PropertyType != typeof(T)) return null;
var p = Expression.Parameter(typeof(DbException), "exception");
var body = Expression.Property(Expression.Convert(p, type), prop);
var lambda = Expression.Lambda<Func<DbException, T>>(body, p);
return lambda.Compile();
}
catch
{
return null;
}
}
}
}
}
using System;
using System.Data;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
namespace Dapper.ProviderTools.Internal
{
internal sealed class DynamicBulkCopy : BulkCopy
{
internal static BulkCopy? Create(object? wrapped)
=> wrapped == null ? null : new DynamicBulkCopy(wrapped);
private DynamicBulkCopy(object wrapped)
=> _wrapped = wrapped;
private readonly dynamic _wrapped;
public override string DestinationTableName
{
get => _wrapped.DestinationTableName;
set => _wrapped.DestinationTableName = value;
}
public override object Wrapped => _wrapped;
public override void AddColumnMapping(string sourceColumn, string destinationColumn)
=> _wrapped.ColumnMappings.Add(sourceColumn, destinationColumn);
public override void AddColumnMapping(int sourceColumn, int destinationColumn)
=> _wrapped.ColumnMappings.Add(sourceColumn, destinationColumn);
public override void WriteToServer(DataTable source)
=> _wrapped.WriteToServer(source);
public override void WriteToServer(IDataReader source)
=> _wrapped.WriteToServer(source);
public override Task WriteToServerAsync(DbDataReader source, CancellationToken cancellationToken)
=> _wrapped.WriteToServer(source, cancellationToken);
public override Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken)
=> _wrapped.WriteToServer(source, cancellationToken);
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_wrapped is IDisposable d)
{
try { d.Dispose(); } catch { }
}
}
}
}
}
......@@ -23,25 +23,23 @@
<ItemGroup>
<PackageReference Include="System.Data.SqlClient" Version="4.6.1" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="1.0.19239.1" />
<PackageReference Include="Microsoft.SqlServer.Types" Version="14.0.1016.290"
Condition="'$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net472'" />
<PackageReference Include="Microsoft.SqlServer.Types" Version="14.0.1016.290" Condition="'$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net472'" />
</ItemGroup>
<ItemGroup Condition="'$(Platform)'=='x64'">
<Content Include="$(USERPROFILE)\.nuget\packages\microsoft.sqlserver.types\14.0.1016.290\nativeBinaries\x64\*.dll"
Condition="'$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net472'">
<Content Include="$(USERPROFILE)\.nuget\packages\microsoft.sqlserver.types\14.0.1016.290\nativeBinaries\x64\*.dll" Condition="'$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net472'">
<Link>%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(Platform)'=='x86'">
<Content Include="$(USERPROFILE)\.nuget\packages\microsoft.sqlserver.types\14.0.1016.290\nativeBinaries\x86\*.dll"
Condition="'$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net472'">
<Content Include="$(USERPROFILE)\.nuget\packages\microsoft.sqlserver.types\14.0.1016.290\nativeBinaries\x86\*.dll" Condition="'$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net472'">
<Link>%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dapper.ProviderTools\Dapper.ProviderTools.csproj" />
<ProjectReference Include="..\Dapper\Dapper.csproj" />
<ProjectReference Include="..\Dapper.Contrib\Dapper.Contrib.csproj" />
<PackageReference Include="FirebirdSql.Data.FirebirdClient" Version="7.0.0" />
......@@ -54,6 +52,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Oracle.ManagedDataAccess" Version="19.3.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net472'">
......
using System.Data.Common;
using Dapper.ProviderTools;
using Xunit;
namespace Dapper.Tests
{
public class ProviderTests
{
[Fact]
public void BulkCopy_SystemDataSqlClient()
{
using (var conn = new System.Data.SqlClient.SqlConnection())
{
Test<System.Data.SqlClient.SqlBulkCopy>(conn);
}
}
[Fact]
public void BulkCopy_MicrosoftDataSqlClient()
{
using (var conn = new Microsoft.Data.SqlClient.SqlConnection())
{
Test<Microsoft.Data.SqlClient.SqlBulkCopy>(conn);
}
}
private static void Test<T>(DbConnection connection)
{
using (var bcp = BulkCopy.TryCreate(connection))
{
Assert.NotNull(bcp);
Assert.IsType<T>(bcp.Wrapped);
bcp.EnableStreaming = true;
}
}
[Theory]
[InlineData(51000, 51000, true)]
[InlineData(51000, 43, false)]
public void DbNumber_SystemData(int create, int test, bool result)
=> Test<SystemSqlClientProvider>(create, test, result);
[Theory]
[InlineData(51000, 51000, true)]
[InlineData(51000, 43, false)]
public void DbNumber_MicrosoftData(int create, int test, bool result)
=> Test<MicrosoftSqlClientProvider>(create, test, result);
private void Test<T>(int create, int test, bool result)
where T : SqlServerDatabaseProvider, new()
{
var provider = new T();
using (var conn = provider.GetOpenConnection())
{
try
{
conn.Execute("throw @create, 'boom', 1;", new { create });
Assert.False(true);
}
catch(DbException err)
{
Assert.Equal(result, err.IsNumber(test));
}
}
}
}
}
......@@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.Stro
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -91,6 +93,10 @@ Global
{F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.Build.0 = Release|Any CPU
{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -106,6 +112,7 @@ Global
{8A74F0B6-188F-45D2-8A4B-51E4F211805A} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}
{39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}
{F017075A-2969-4A8E-8971-26F154EB420F} = {568BD46C-1C65-4D44-870C-12CD72563262}
{B06DB435-0C74-4BD3-BC97-52AF7CF9916B} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment