diff --git a/bulky/.gitignore b/bulky/.gitignore
new file mode 100644
index 0000000..bdc3535
--- /dev/null
+++ b/bulky/.gitignore
@@ -0,0 +1,108 @@
+# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
+[Bb]in/
+[Oo]bj/
+
+# mstest test results
+TestResults
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Rr]elease/
+x64/
+*_i.c
+*_p.c
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.log
+*.vspscc
+*.vssscc
+.builds
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*
+
+# NCrunch
+*.ncrunch*
+.*crunch*.local.xml
+
+# Installshield output folder
+[Ee]xpress
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish
+
+# Publish Web Output
+*.Publish.xml
+
+# NuGet Packages Directory
+packages
+
+# Windows Azure Build Output
+csx
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+[Bb]in
+[Oo]bj
+sql
+TestResults
+[Tt]est[Rr]esult*
+*.Cache
+ClientBin
+[Ss]tyle[Cc]op.*
+~$*
+*.dbmdl
+Generated_Code #added for RIA/Silverlight projects
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
diff --git a/bulky/.nuget/NuGet.Config b/bulky/.nuget/NuGet.Config
new file mode 100644
index 0000000..5e74378
--- /dev/null
+++ b/bulky/.nuget/NuGet.Config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bulky/.nuget/NuGet.targets b/bulky/.nuget/NuGet.targets
new file mode 100644
index 0000000..bda5bea
--- /dev/null
+++ b/bulky/.nuget/NuGet.targets
@@ -0,0 +1,143 @@
+
+
+
+ $(MSBuildProjectDirectory)\..\
+
+
+ false
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+
+
+
+
+
+
+
+ $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
+ $([System.IO.Path]::Combine($(ProjectDir), "packages.config"))
+ $([System.IO.Path]::Combine($(SolutionDir), "packages"))
+
+
+
+
+ $(SolutionDir).nuget
+ packages.config
+ $(SolutionDir)packages
+
+
+
+
+ $(NuGetToolsPath)\nuget.exe
+ @(PackageSource)
+
+ "$(NuGetExePath)"
+ mono --runtime=v4.0.30319 $(NuGetExePath)
+
+ $(TargetDir.Trim('\\'))
+
+ -RequireConsent
+
+ $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -o "$(PackagesDir)"
+ $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols
+
+
+
+ RestorePackages;
+ $(BuildDependsOn);
+
+
+
+
+ $(BuildDependsOn);
+ BuildPackage;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bulky/LICENSE.md b/bulky/LICENSE.md
new file mode 100644
index 0000000..d6329b6
--- /dev/null
+++ b/bulky/LICENSE.md
@@ -0,0 +1,16 @@
+### bulky
+#### http://github.com/danielcrenna/copper
+
+Copyright (c) 2012 Conatus Creative Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/bulky/README.md b/bulky/README.md
new file mode 100644
index 0000000..0fad8b4
--- /dev/null
+++ b/bulky/README.md
@@ -0,0 +1,102 @@
+bulky
+=====
+
+```powershell
+PM> Install-Package bulky
+```
+
+Bulky is an idiomatic bulk insert interface for databases. It gives you the ability to use the
+native bulk copy mechanism of your database simply, using plain C# objects and an extension method on `IDbConnection`, much like how [Dapper](https://github.com/SamSaffron/dapper-dot-net) functions.
+
+### Why would I use this?
+- You want simple, high speed bulk inserts of large collections of objects
+- You need support for multiple databases (SQL Server, MySQL, and SQLite are supported today)
+- You use [copper](http://github.com/danielcrenna/copper), and you want to hook up a `BulkCopyConsumer` for high performance, periodic batching inserts
+- It works great with [tophat](http://github.com/danielcrenna/tophat)
+
+
+### Usage
+
+The hands-free usage is simple:
+
+```csharp
+using bulky;
+
+// Get your objects from somewhere
+IEnumerable users = ReallyLargeCollectionOfUsers();
+
+// Get your connection from somewhere else
+IDbConnection connection = GetMyDatabaseConnectionFromSomewhere();
+
+// Profit!
+connection.BulkCopy(users);
+```
+
+Behind the scenes, Bulky is using the provided bulk copy implementation.
+This is how you can change the underlying strategy:
+
+```csharp
+using bulky;
+
+// Change to MySQL multi-value inserts
+Bulky.BulkCopier = new MySqlBulkCopy();
+
+// Change to SQLite's transactional flush
+Bulky.BulkCopier = new SqliteBulkCopy();
+
+// Change to SQL Server's SqlBulkCopy (the default)
+Bulky.BulkCopier = new SqlServerBulkCopy();
+```
+
+### How does bulky map objects to database columns?
+
+Under the hood, bulky relies on a [TableDescriptor](http://github.com/danielcrenna/TableDescriptor) definition
+of a class in order to map it to database columns. By default, it uses TableDescriptor's built-in `SimpleDescriptor`
+to perform that mapping, but you can always pass in any implementation of `Descriptor`. The default conventions
+are simple enough, but if you have more advanced mapping needs you'll want to look at that project directly.
+For example, this `User` object will map to the database below:
+
+```csharp
+public class User
+{
+ public int Id { get; set; }
+ public string Email { get; set; }
+}
+```
+
+```sql
+CREATE TABLE [dbo].[User]
+(
+ [Id] [int] IDENTITY(1,1) NOT NULL,
+ [Email] [varchar](255) NOT NULL,
+ CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC)
+)
+```
+
+### How do I use this with [copper](http://github.com/danielcrenna/copper)?
+
+Bulky includes a `BulkCopyConsumer` (in source, not in the distro) that will batch events handled by it and
+bulk insert them to the underlying database. By default, it uses [tophat](http://github.com/danielcrenna/tophat) for connection scoping
+(but you can provide your own `ConnectionBuilder` function), and [TableDescriptor](http://github.com/danielcrenna/TableDescriptor) for object mapping
+(but you can provide your own `Descriptor` for custom mapping).
+
+```csharp
+using tophat;
+using TableDescriptor;
+using bulky;
+
+// Consumer will bulk copy every 100 records, or every five seconds, whichever comes first
+var consumer = new BulkCopyConsumer(100, TimeSpan.FromSeconds(5);
+
+// By default, the connection used by the consumer is tophat's current unit of work
+consumer.ConnectionBuilder = () => UnitOfWork.Current;
+
+// By default, the mapper used by the consumer is TableDescriptor's SimpleDescriptor
+consumer.Descriptor = SimpleDescriptor.Create();
+
+// These users could come from anywhere...
+var users = MyBigBagOfUsers()
+
+// Some producer is off obtaining users from somewhere, and bulk copying them in batches
+var producer = new CollectionProducer(users).Consumes(consumer).Start();
+```
\ No newline at end of file
diff --git a/bulky/bulky.1.0.5.nupkg b/bulky/bulky.1.0.5.nupkg
new file mode 100644
index 0000000..c4ed86b
Binary files /dev/null and b/bulky/bulky.1.0.5.nupkg differ
diff --git a/bulky/bulky.nuspec b/bulky/bulky.nuspec
new file mode 100644
index 0000000..632e3f9
--- /dev/null
+++ b/bulky/bulky.nuspec
@@ -0,0 +1,20 @@
+
+
+
+ bulky
+ 1.0.5
+ Daniel Crenna
+ Daniel Crenna
+ An idiomatic bulk insert interface for databases
+ An idiomatic bulk insert interface for databases. Allows you to bulk insert arbitrary types to an underlying database using a fast insert mechanism specific for that database. Currently supports SQL Server, MySQL, and SQLite. Works on the IDbConnection with extension methods, like Dapper.
+ en-US
+ http://github.com/danielcrenna/bulky
+ https://github.com/danielcrenna/bulky/blob/master/LICENSE
+ http://apitize.com.s3.amazonaws.com/logo_generic.png
+ bulk insert sqlserver sqlite mysql dapper
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bulky/pack-nuget.bat b/bulky/pack-nuget.bat
new file mode 100644
index 0000000..48b3708
--- /dev/null
+++ b/bulky/pack-nuget.bat
@@ -0,0 +1,3 @@
+copy LICENSE.md bin
+copy README.md bin
+".nuget\NuGet.exe" pack bulky.nuspec -BasePath bin
\ No newline at end of file
diff --git a/bulky/push-nuget.bat b/bulky/push-nuget.bat
new file mode 100644
index 0000000..c359e0b
--- /dev/null
+++ b/bulky/push-nuget.bat
@@ -0,0 +1 @@
+".nuget\NuGet.exe" push bulky.1.0.5.nupkg
\ No newline at end of file
diff --git a/bulky/src/.nuget/packages.config b/bulky/src/.nuget/packages.config
new file mode 100644
index 0000000..c811286
--- /dev/null
+++ b/bulky/src/.nuget/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/App.config b/bulky/src/Bulky.Tests/App.config
new file mode 100644
index 0000000..c2a413a
--- /dev/null
+++ b/bulky/src/Bulky.Tests/App.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/Baseline.cs b/bulky/src/Bulky.Tests/Baseline.cs
new file mode 100644
index 0000000..3d65a7c
--- /dev/null
+++ b/bulky/src/Bulky.Tests/Baseline.cs
@@ -0,0 +1,19 @@
+using FluentMigrator;
+
+namespace bulky.Tests
+{
+ [Migration(1)]
+ public class Baseline : AutoReversingMigration
+ {
+ public override void Up()
+ {
+ Create.Table("User")
+ .WithColumn("Id").AsInt32().PrimaryKey().Identity()
+ .WithColumn("Email").AsAnsiString();
+
+ Create.Table("UserRole")
+ .WithColumn("UserId").AsInt32().PrimaryKey()
+ .WithColumn("RoleId").AsInt32().PrimaryKey();
+ }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/Fixtures/BulkCopyFixture.cs b/bulky/src/Bulky.Tests/Fixtures/BulkCopyFixture.cs
new file mode 100644
index 0000000..b475ec9
--- /dev/null
+++ b/bulky/src/Bulky.Tests/Fixtures/BulkCopyFixture.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using Dapper;
+using NUnit.Framework;
+using copper;
+using tophat;
+using tuxedo;
+
+namespace bulky.Tests.Fixtures
+{
+ public class BulkCopyFixture
+ {
+ public static Dialect Dialect { get; set; }
+
+ public static void BulkCopyUsers(int trials, bool trace = false)
+ {
+ var users = ResetUsers(trials);
+ var sw = Stopwatch.StartNew();
+ UnitOfWork.Current.BulkCopy(users);
+ var elapsed = sw.Elapsed;
+ var count = AssertInsertCount(users.Count, elapsed, trace);
+ if (trace)
+ {
+ Console.WriteLine("Inserting {0} records took {1}", count, elapsed);
+ }
+ }
+
+ public static void BulkCopyUsersWithConsumer(int trials, bool trace = false)
+ {
+ var users = ResetUsers(trials);
+ var consumer = new BulkCopyConsumer(100);
+
+ var block = new ManualResetEvent(false);
+ var producer = new ObservingProducer().Produces(users, onCompleted: () =>
+ {
+ while (consumer.Consumed < trials)
+ {
+ Thread.Sleep(100);
+ }
+ block.Set();
+ });
+ producer.Consumes(consumer);
+ var sw = Stopwatch.StartNew();
+ producer.Start();
+ block.WaitOne();
+ var elapsed = sw.Elapsed;
+
+ var count = AssertInsertCount(users.Count, elapsed, trace);
+ if (trace)
+ {
+ Console.WriteLine("Inserting {0} records took {1}", count, elapsed);
+ }
+ }
+
+ public static List ResetUsers(int trials)
+ {
+ var sql = "DELETE FROM " + QualifiedUser();
+ UnitOfWork.Current.Execute(sql);
+ var users = GetInsertCollection(trials).ToList();
+ return users;
+ }
+
+ public static IEnumerable GetInsertCollection(int number)
+ {
+ for (var i = 1; i < number + 1; i++)
+ {
+ yield return new User { Email = String.Format("user{0}@email.com", i) };
+ }
+ }
+
+ public static int AssertInsertCount(int expected, TimeSpan elapsed, bool trace = false)
+ {
+ int actual;
+ try
+ {
+ actual = UnitOfWork.Current.Query("SELECT COUNT(1) FROM " + QualifiedUser()).Single();
+ }
+ catch
+ {
+ // SQlite...
+ actual = (int)UnitOfWork.Current.Query("SELECT COUNT(1) FROM " + QualifiedUser()).Single();
+ }
+
+ Assert.AreEqual(expected, actual);
+ return actual;
+ }
+
+ private static string QualifiedUser()
+ {
+ return Dialect.StartIdentifier + "User" + Dialect.EndIdentifier;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/Fixtures/MySqlFixture.cs b/bulky/src/Bulky.Tests/Fixtures/MySqlFixture.cs
new file mode 100644
index 0000000..b76fd93
--- /dev/null
+++ b/bulky/src/Bulky.Tests/Fixtures/MySqlFixture.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Configuration;
+using System.Linq;
+using Dapper;
+using NUnit.Framework;
+using bulky.Tests.MySql;
+using tophat;
+using tuxedo.Dialects;
+
+namespace bulky.Tests.Fixtures
+{
+ public class MySqlFixture
+ {
+ [TestFixtureSetUp]
+ public void TestFixtureSetUp()
+ {
+ Bulky.BulkCopier = new MySqlBulkCopy();
+ BulkCopyFixture.Dialect = new MySqlDialect();
+ }
+
+ [SetUp]
+ public void SetUp()
+ {
+ UnitOfWork.Purge();
+ ConfigureTestDatabase();
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ UnitOfWork.Current.Execute(string.Format("DROP DATABASE `{0}`", UnitOfWork.Current.Database));
+ UnitOfWork.Purge();
+ }
+
+ private static void ConfigureTestDatabase()
+ {
+ var database = CreateDatabase();
+ var connectionString = string.Format("Server=localhost;Uid={0};Pwd={1};Database={2};", ConfigurationManager.AppSettings["MySQLUser"], ConfigurationManager.AppSettings["MySQLPassword"], database);
+ Database.Install(connectionString, ConnectionScope.ByThread);
+ new MigrationService().MigrateToLatest("mysql", connectionString);
+ UnitOfWork.Current.Query("SELECT COUNT(*) AS `C` FROM `User`").Single();
+ Assert.AreEqual(UnitOfWork.Current.Database, database.ToString());
+ }
+
+ private static Guid CreateDatabase()
+ {
+ var database = Guid.NewGuid();
+ var connectionString = string.Format("Server=localhost;Uid={0};Pwd={1};", ConfigurationManager.AppSettings["MySQLUser"], ConfigurationManager.AppSettings["MySQLPassword"]);
+ var factory = new MySqlConnectionFactory { ConnectionString = connectionString };
+ using (var connection = factory.CreateConnection())
+ {
+ connection.Open();
+ var sql = string.Format("CREATE DATABASE `{0}`", database);
+ connection.Execute(sql);
+ sql = string.Format("USE `{0}`", database);
+ connection.Execute(sql);
+ }
+ return database;
+ }
+ }
+}
diff --git a/bulky/src/Bulky.Tests/Fixtures/SqlServerFixture.cs b/bulky/src/Bulky.Tests/Fixtures/SqlServerFixture.cs
new file mode 100644
index 0000000..3f29de5
--- /dev/null
+++ b/bulky/src/Bulky.Tests/Fixtures/SqlServerFixture.cs
@@ -0,0 +1,59 @@
+using System;
+using Dapper;
+using NUnit.Framework;
+using tophat;
+using tuxedo.Dialects;
+
+namespace bulky.Tests.Fixtures
+{
+ public class SqlServerFixture
+ {
+ private string _database;
+
+ [TestFixtureSetUp]
+ public void TestFixtureSetUp()
+ {
+ Bulky.BulkCopier = new SqlServerBulkCopy();
+ BulkCopyFixture.Dialect = new SqlServerDialect();
+ }
+
+ [SetUp]
+ public void SetUp()
+ {
+ UnitOfWork.Purge();
+ _database = ConfigureTestDatabase();
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ UnitOfWork.Current.Execute("USE master");
+ UnitOfWork.Current.Execute(string.Format("ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE", _database));
+ UnitOfWork.Current.Execute(string.Format("DROP DATABASE [{0}]", _database));
+ UnitOfWork.Purge();
+ }
+
+ private static string ConfigureTestDatabase()
+ {
+ var database = CreateDatabase();
+ var connectionString = string.Format("Data Source=localhost;Initial Catalog={0};Integrated Security=true", database);
+ Database.Install(connectionString, ConnectionScope.ByThread);
+ new MigrationService().MigrateToLatest("sqlserver", connectionString);
+ return database.ToString();
+ }
+
+ private static Guid CreateDatabase()
+ {
+ var database = Guid.NewGuid();
+ var connectionString = string.Format("Data Source=localhost;Integrated Security=true;");
+ var factory = new SqlServerConnectionFactory { ConnectionString = connectionString };
+ using (var connection = factory.CreateConnection())
+ {
+ connection.Open();
+ var sql = string.Format("CREATE DATABASE [{0}]", database);
+ connection.Execute(sql);
+ }
+ return database;
+ }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/Fixtures/SqliteFixture.cs b/bulky/src/Bulky.Tests/Fixtures/SqliteFixture.cs
new file mode 100644
index 0000000..81eda2b
--- /dev/null
+++ b/bulky/src/Bulky.Tests/Fixtures/SqliteFixture.cs
@@ -0,0 +1,84 @@
+using System;
+using System.IO;
+using System.Threading;
+using NUnit.Framework;
+using bulky.Tests.Sqlite;
+using tophat;
+using tuxedo.Dialects;
+
+namespace bulky.Tests.Fixtures
+{
+ public class SqliteFixture
+ {
+ [TestFixtureSetUp]
+ public void TestFixtureSetUp()
+ {
+ Bulky.BulkCopier = new SqliteBulkCopy();
+ BulkCopyFixture.Dialect = new SqliteDialect();
+ foreach (var database in Directory.GetFiles(Utils.WhereAmI(), "*.db"))
+ {
+ DeleteDatabase(database);
+ }
+ }
+
+ [SetUp]
+ public void SetUp()
+ {
+ CreateTestDatabase();
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ UnitOfWork.Purge();
+ }
+
+ private static void DeleteDatabase(string databaseName)
+ {
+ if (!File.Exists(databaseName))
+ {
+ return;
+ }
+ var i = 10;
+ while (IsDatabaseInUse(databaseName) && i > 0)
+ {
+ i--;
+ Thread.Sleep(1000);
+ }
+ if (i > 0)
+ {
+ File.Delete(databaseName);
+ }
+ }
+
+ private static bool IsDatabaseInUse(string databaseName)
+ {
+ FileStream fs = null;
+ try
+ {
+ var fi = new FileInfo(databaseName);
+ fs = fi.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
+ return false;
+ }
+ catch (Exception)
+ {
+ return true;
+ }
+ finally
+ {
+ if (fs != null)
+ {
+ fs.Close();
+ }
+ }
+ }
+
+ private static void CreateTestDatabase()
+ {
+ var database = string.Format("{0}.db", Guid.NewGuid());
+ var connectionString = string.Format("Data Source={0};Version=3;New=True;", database);
+ Database.Install(connectionString, ConnectionScope.ByThread);
+ new MigrationService().MigrateToLatest("sqlite", connectionString);
+ }
+ }
+}
diff --git a/bulky/src/Bulky.Tests/Fixtures/Utils.cs b/bulky/src/Bulky.Tests/Fixtures/Utils.cs
new file mode 100644
index 0000000..0239cba
--- /dev/null
+++ b/bulky/src/Bulky.Tests/Fixtures/Utils.cs
@@ -0,0 +1,16 @@
+using System;
+using System.IO;
+using System.Reflection;
+
+namespace bulky.Tests.Fixtures
+{
+ public class Utils
+ {
+ public static string WhereAmI()
+ {
+ var dir = new Uri(Assembly.GetExecutingAssembly().CodeBase);
+ var fi = new FileInfo(dir.AbsolutePath);
+ return fi.Directory != null ? fi.Directory.FullName : null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/MigrationExtensions.cs b/bulky/src/Bulky.Tests/MigrationExtensions.cs
new file mode 100644
index 0000000..6e2ad13
--- /dev/null
+++ b/bulky/src/Bulky.Tests/MigrationExtensions.cs
@@ -0,0 +1,118 @@
+using System;
+using System.IO;
+using System.Reflection;
+using FluentMigrator;
+using FluentMigrator.Builders.Create.Table;
+using FluentMigrator.Runner;
+using FluentMigrator.Runner.Announcers;
+using FluentMigrator.Runner.Initialization;
+
+namespace bulky.Tests
+{
+ internal static class Migrator
+ {
+ static Migrator()
+ {
+ Builder = CreateMigratorMethod();
+ }
+
+ private static Lazy Builder { get; set; }
+ private static Lazy CreateMigratorMethod()
+ {
+ return new Lazy(() => new MigrationService());
+ }
+
+ public static void MigrateToLatest(string databaseType, string connectionString, string profile = null, Assembly assembly = null, bool trace = false)
+ {
+ Builder.Value.MigrateToLatest(databaseType, connectionString, profile, assembly, trace);
+ }
+
+ public static void MigrateToVersion(string databaseType, string connectionString, long version, string profile = null, Assembly assembly = null, bool trace = false)
+ {
+ Builder.Value.MigrateToVersion(databaseType, connectionString, version, profile, assembly, trace);
+ }
+ }
+
+ public static class DatabaseType
+ {
+ public static readonly string SqlServer = "sqlserver";
+ public static readonly string SqlServerCe = "sqlserver";
+ public static readonly string Sqlite = "sqlite";
+ public static readonly string MySql = "mysql";
+ public static readonly string Oracle = "oracle";
+
+ // Add all the others FluentMigrator supports...
+ }
+
+ public interface IMigrationService
+ {
+ void MigrateToLatest(string databaseType, string connectionString, string profile = null, Assembly assembly = null, bool trace = false);
+ void MigrateToVersion(string databaseType, string connectionString, long version, string profile = null, Assembly assembly = null, bool trace = false);
+ }
+
+ public class MigrationService : IMigrationService
+ {
+ public void MigrateToLatest(string databaseType, string connectionString, string profile = null, Assembly assembly = null, bool trace = false)
+ {
+ MigrateToVersion(databaseType, connectionString, 0, profile, assembly);
+ }
+
+ public void MigrateToVersion(string databaseType, string connectionString, long version, string profile = null, Assembly assembly = null, bool trace = false)
+ {
+ if (databaseType == "sqlite")
+ {
+ CopyInteropAssemblyByPlatform();
+ }
+ assembly = assembly ?? Assembly.GetExecutingAssembly();
+ var announcer = trace ? new TextWriterAnnouncer(Console.Out) : (IAnnouncer)new NullAnnouncer();
+ var context = new RunnerContext(announcer)
+ {
+ Connection = connectionString,
+ Database = databaseType,
+ Target = assembly.FullName,
+ Version = version,
+ Profile = profile
+ };
+ var executor = new TaskExecutor(context);
+ executor.Execute();
+ }
+
+ private const string SQLiteAssembly = "SQLite.Interop.dll";
+ public static void CopyInteropAssemblyByPlatform()
+ {
+ var baseDir = WhereAmI();
+ var destination = Path.Combine(baseDir, SQLiteAssembly);
+ if (File.Exists(destination))
+ {
+ return;
+ }
+ var arch = Environment.Is64BitProcess ? "x64" : "x86";
+ var path = Path.Combine(arch, SQLiteAssembly);
+ var source = Path.Combine(baseDir, path);
+ File.Copy(source, destination, true);
+ }
+ internal static string WhereAmI()
+ {
+ var dir = new Uri(Assembly.GetExecutingAssembly().CodeBase);
+ var fi = new FileInfo(dir.AbsolutePath);
+ return fi.Directory != null ? fi.Directory.FullName : null;
+ }
+ }
+
+ internal static class MigrationExtensions
+ {
+ public static ICreateTableWithColumnSyntax Timestamps(this ICreateTableWithColumnSyntax migration)
+ {
+ return migration
+ .WithColumn("CreatedAt").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime)
+ .WithColumn("UpdatedAt").AsDateTime().Nullable();
+ }
+
+ public static ICreateTableWithColumnSyntax EffectiveDates(this ICreateTableWithColumnSyntax migration)
+ {
+ return migration
+ .WithColumn("StartDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime)
+ .WithColumn("EndDate").AsDateTime().Nullable();
+ }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/MySql/MySqlConnectionFactory.cs b/bulky/src/Bulky.Tests/MySql/MySqlConnectionFactory.cs
new file mode 100644
index 0000000..0dbde1e
--- /dev/null
+++ b/bulky/src/Bulky.Tests/MySql/MySqlConnectionFactory.cs
@@ -0,0 +1,14 @@
+using System.Data;
+using MySql.Data.MySqlClient;
+using tophat;
+
+namespace bulky.Tests.MySql
+{
+ public class MySqlConnectionFactory : ConnectionFactory
+ {
+ public override IDbConnection CreateConnection()
+ {
+ return new MySqlConnection(ConnectionString);
+ }
+ }
+}
diff --git a/bulky/src/Bulky.Tests/MySql/MySqlDataContext.cs b/bulky/src/Bulky.Tests/MySql/MySqlDataContext.cs
new file mode 100644
index 0000000..d8c2781
--- /dev/null
+++ b/bulky/src/Bulky.Tests/MySql/MySqlDataContext.cs
@@ -0,0 +1,12 @@
+using tophat;
+
+namespace bulky.Tests.MySql
+{
+ public class MySqlDataContext : DataContext
+ {
+ public MySqlDataContext(string connectionString) : base(connectionString)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/MySqlBulkCopyTests.cs b/bulky/src/Bulky.Tests/MySqlBulkCopyTests.cs
new file mode 100644
index 0000000..4d46127
--- /dev/null
+++ b/bulky/src/Bulky.Tests/MySqlBulkCopyTests.cs
@@ -0,0 +1,27 @@
+using NUnit.Framework;
+using bulky.Tests.Fixtures;
+
+namespace bulky.Tests
+{
+ [TestFixture]
+ public class MySqlBulkCopyTests : MySqlFixture
+ {
+ [TestCase(100)]
+ [TestCase(1000)]
+ [TestCase(10000)]
+ [TestCase(100000)]
+ public void Bulk_copy_n_records_directly(int trials)
+ {
+ BulkCopyFixture.BulkCopyUsers(trials);
+ BulkCopyFixture.BulkCopyUsers(trials, trace: true);
+ }
+
+ [TestCase(100)]
+ [TestCase(1000)]
+ public void Bulk_copy_n_records_with_consumer(int trials)
+ {
+ BulkCopyFixture.BulkCopyUsersWithConsumer(trials);
+ BulkCopyFixture.BulkCopyUsersWithConsumer(trials, trace: true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/Properties/AssemblyInfo.cs b/bulky/src/Bulky.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..332e23e
--- /dev/null
+++ b/bulky/src/Bulky.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("bulky.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("bulky.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("ff405887-e680-4b97-ae98-4559c39f7cdb")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/bulky/src/Bulky.Tests/SqlServerBulkCopyTests.cs b/bulky/src/Bulky.Tests/SqlServerBulkCopyTests.cs
new file mode 100644
index 0000000..718e392
--- /dev/null
+++ b/bulky/src/Bulky.Tests/SqlServerBulkCopyTests.cs
@@ -0,0 +1,27 @@
+using NUnit.Framework;
+using bulky.Tests.Fixtures;
+
+namespace bulky.Tests
+{
+ [TestFixture]
+ public class SqlServerBulkCopyTests : SqlServerFixture
+ {
+ [TestCase(100)]
+ [TestCase(1000)]
+ [TestCase(10000)]
+ [TestCase(100000)]
+ public void Bulk_copy_n_records_directly(int trials)
+ {
+ BulkCopyFixture.BulkCopyUsers(trials);
+ BulkCopyFixture.BulkCopyUsers(trials, trace: true);
+ }
+
+ [TestCase(100)]
+ [TestCase(1000)]
+ public void Bulk_copy_n_records_with_consumer(int trials)
+ {
+ BulkCopyFixture.BulkCopyUsersWithConsumer(trials);
+ BulkCopyFixture.BulkCopyUsersWithConsumer(trials, trace: true);
+ }
+ }
+}
diff --git a/bulky/src/Bulky.Tests/Sqlite/SqliteConnectionFactory.cs b/bulky/src/Bulky.Tests/Sqlite/SqliteConnectionFactory.cs
new file mode 100644
index 0000000..1da91ed
--- /dev/null
+++ b/bulky/src/Bulky.Tests/Sqlite/SqliteConnectionFactory.cs
@@ -0,0 +1,14 @@
+using System.Data;
+using System.Data.SQLite;
+using tophat;
+
+namespace bulky.Tests.Sqlite
+{
+ public class SqliteConnectionFactory : ConnectionFactory
+ {
+ public override IDbConnection CreateConnection()
+ {
+ return new SQLiteConnection(ConnectionString);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/Sqlite/SqliteDataContext.cs b/bulky/src/Bulky.Tests/Sqlite/SqliteDataContext.cs
new file mode 100644
index 0000000..aee3ae6
--- /dev/null
+++ b/bulky/src/Bulky.Tests/Sqlite/SqliteDataContext.cs
@@ -0,0 +1,12 @@
+using tophat;
+
+namespace bulky.Tests.Sqlite
+{
+ public class SqliteDataContext : DataContext
+ {
+ public SqliteDataContext(string connectionString) : base(connectionString)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/SqliteBulkCopyTests.cs b/bulky/src/Bulky.Tests/SqliteBulkCopyTests.cs
new file mode 100644
index 0000000..6af6a19
--- /dev/null
+++ b/bulky/src/Bulky.Tests/SqliteBulkCopyTests.cs
@@ -0,0 +1,27 @@
+using NUnit.Framework;
+using bulky.Tests.Fixtures;
+
+namespace bulky.Tests
+{
+ [TestFixture]
+ public class SqliteBulkCopyTests : SqliteFixture
+ {
+ [TestCase(100)]
+ [TestCase(1000)]
+ [TestCase(10000)]
+ [TestCase(100000)]
+ public void Bulk_copy_n_records_directly(int trials)
+ {
+ BulkCopyFixture.BulkCopyUsers(trials);
+ BulkCopyFixture.BulkCopyUsers(trials, trace: true);
+ }
+
+ [TestCase(100)]
+ [TestCase(1000)]
+ public void Bulk_copy_n_records_with_consumer(int trials)
+ {
+ BulkCopyFixture.BulkCopyUsersWithConsumer(trials);
+ BulkCopyFixture.BulkCopyUsersWithConsumer(trials, trace: true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/User.cs b/bulky/src/Bulky.Tests/User.cs
new file mode 100644
index 0000000..781b0e5
--- /dev/null
+++ b/bulky/src/Bulky.Tests/User.cs
@@ -0,0 +1,8 @@
+namespace bulky.Tests
+{
+ public class User
+ {
+ public int Id { get; set; }
+ public string Email { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/bulky.Tests.csproj b/bulky/src/Bulky.Tests/bulky.Tests.csproj
new file mode 100644
index 0000000..45712c3
--- /dev/null
+++ b/bulky/src/Bulky.Tests/bulky.Tests.csproj
@@ -0,0 +1,130 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {F556E6C7-9500-4BF0-A746-D8ABF9A7B7D0}
+ Library
+ Properties
+ bulky.Tests
+ bulky.Tests
+ v4.0
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\tophat.1.3.0\lib\net40\container.dll
+
+
+ False
+ ..\..\..\copper\bin\net40\copper.dll
+
+
+ ..\packages\tuxedo.1.0.1\lib\net40\Dapper.dll
+
+
+ ..\packages\FluentMigrator.1.0.6.0\lib\40\FluentMigrator.dll
+
+
+ ..\packages\FluentMigrator.Tools.1.0.6.0\tools\AnyCPU\40\FluentMigrator.Runner.dll
+
+
+ ..\packages\MySql.Data.6.6.4\lib\net40\MySql.Data.dll
+
+
+ ..\packages\NUnit.2.6.2\lib\nunit.framework.dll
+
+
+
+
+
+ ..\packages\System.Data.SQLite.1.0.84.0\lib\net40\System.Data.SQLite.dll
+
+
+ ..\packages\System.Data.SQLite.1.0.84.0\lib\net40\System.Data.SQLite.Linq.dll
+
+
+
+
+
+
+
+ ..\packages\tuxedo.1.0.1\lib\net40\TableDescriptor.dll
+
+
+ ..\packages\tophat.1.3.0\lib\net40\tophat.dll
+
+
+ ..\packages\tuxedo.1.0.1\lib\net40\tuxedo.dll
+
+
+ ..\packages\tuxedo.1.0.1\lib\net40\tuxedo.Dapper.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {B8A26190-F414-4BF6-943B-AA786411637C}
+ bulky.Copper
+
+
+ {b9424b16-efdd-497a-b698-3abc1dac51e6}
+ bulky
+
+
+
+
+ Always
+
+
+ Always
+
+
+
+
+
\ No newline at end of file
diff --git a/bulky/src/Bulky.Tests/packages.config b/bulky/src/Bulky.Tests/packages.config
new file mode 100644
index 0000000..f20f7b9
--- /dev/null
+++ b/bulky/src/Bulky.Tests/packages.config
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bulky/src/Bulky.sln b/bulky/src/Bulky.sln
new file mode 100644
index 0000000..4ac4e7a
--- /dev/null
+++ b/bulky/src/Bulky.sln
@@ -0,0 +1,41 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "bulky", "bulky\bulky.csproj", "{B9424B16-EFDD-497A-B698-3ABC1DAC51E6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "bulky.Tests", "Bulky.Tests\bulky.Tests.csproj", "{F556E6C7-9500-4BF0-A746-D8ABF9A7B7D0}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{13D473E5-192F-438C-B9EC-6AF89A1DB793}"
+ ProjectSection(SolutionItems) = preProject
+ ..\bulky.nuspec = ..\bulky.nuspec
+ ..\LICENSE.md = ..\LICENSE.md
+ ..\pack-nuget.bat = ..\pack-nuget.bat
+ ..\push-nuget.bat = ..\push-nuget.bat
+ ..\README.md = ..\README.md
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "bulky.Copper", "bulky.Copper\bulky.Copper.csproj", "{B8A26190-F414-4BF6-943B-AA786411637C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B9424B16-EFDD-497A-B698-3ABC1DAC51E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B9424B16-EFDD-497A-B698-3ABC1DAC51E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B9424B16-EFDD-497A-B698-3ABC1DAC51E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B9424B16-EFDD-497A-B698-3ABC1DAC51E6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F556E6C7-9500-4BF0-A746-D8ABF9A7B7D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F556E6C7-9500-4BF0-A746-D8ABF9A7B7D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F556E6C7-9500-4BF0-A746-D8ABF9A7B7D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F556E6C7-9500-4BF0-A746-D8ABF9A7B7D0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B8A26190-F414-4BF6-943B-AA786411637C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B8A26190-F414-4BF6-943B-AA786411637C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B8A26190-F414-4BF6-943B-AA786411637C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B8A26190-F414-4BF6-943B-AA786411637C}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/bulky/src/Bulky/BulkCopyMapping.cs b/bulky/src/Bulky/BulkCopyMapping.cs
new file mode 100644
index 0000000..ad8867e
--- /dev/null
+++ b/bulky/src/Bulky/BulkCopyMapping.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Data;
+
+namespace bulky
+{
+ public class BulkCopyMapping
+ {
+ public DataTable DataReaderTable { get; set; }
+ public IEnumerable SchemaTableColumns { get; set; }
+ public IEnumerable DatabaseTableColumns { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky/Bulky.cs b/bulky/src/Bulky/Bulky.cs
new file mode 100644
index 0000000..c07b98c
--- /dev/null
+++ b/bulky/src/Bulky/Bulky.cs
@@ -0,0 +1,11 @@
+namespace bulky
+{
+ public static class Bulky
+ {
+ public static IBulkCopy BulkCopier { get; set; }
+ static Bulky()
+ {
+ BulkCopier = new SqlServerBulkCopy();
+ }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky/BulkyExtensions.cs b/bulky/src/Bulky/BulkyExtensions.cs
new file mode 100644
index 0000000..44ca436
--- /dev/null
+++ b/bulky/src/Bulky/BulkyExtensions.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using System.Data;
+using TableDescriptor;
+
+namespace bulky
+{
+ public static class BulkyExtensions
+ {
+ public static void BulkCopy(this IDbConnection connection, IEnumerable entities, IDbTransaction transaction = null, int? commandTimeout = null)
+ {
+ Bulky.BulkCopier.Copy(SimpleDescriptor.Create(), connection, entities, transaction, commandTimeout);
+ }
+
+ public static void BulkCopy(this IDbConnection connection, Descriptor descriptor, IEnumerable entities, IDbTransaction transaction = null, int? commandTimeout = null)
+ {
+ Bulky.BulkCopier.Copy(SimpleDescriptor.Create(), connection, entities, transaction, commandTimeout);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky/EnumerableDataReader.cs b/bulky/src/Bulky/EnumerableDataReader.cs
new file mode 100644
index 0000000..6969685
--- /dev/null
+++ b/bulky/src/Bulky/EnumerableDataReader.cs
@@ -0,0 +1,297 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using TableDescriptor;
+
+namespace bulky
+{
+ ///
+ /// A data reader that wraps an sequence.
+ ///
+ /// - Since bulk copying sometimes requires the total record count to optimize batch sizes, it's up to the enumerable to avoid re-enumerating when Count() is requested.
+ ///
+ ///
+ ///
+ public class EnumerableDataReader : IDataReader
+ {
+ private IEnumerable _enumerable;
+ private object[] _record;
+ private readonly PropertyToColumn[] _properties;
+ private readonly string _tableName;
+ private readonly IEnumerator _enumerator;
+
+ public int Count
+ {
+ get { return _enumerable.Count(); }
+ }
+
+ public EnumerableDataReader(IEnumerable entities) : this()
+ {
+ _enumerable = new ConcurrentQueue(entities);
+ _enumerator = _enumerable.GetEnumerator();
+ }
+
+ public EnumerableDataReader() : this(SimpleDescriptor.Create())
+ {
+
+ }
+
+ public EnumerableDataReader(Descriptor descriptor)
+ {
+ _tableName = descriptor.Table;
+ _properties = descriptor.Insertable.ToArray();
+ FieldCount = _properties.Length;
+ }
+
+ public DataTable GetSchemaTable()
+ {
+ var table = new DataTable();
+ foreach (var property in _properties)
+ {
+ var columnName = property.ColumnName;
+ var columnType = property.Property.Type;
+ var fieldType = columnType;
+ var isNullable = IsNullable(columnType);
+ if (isNullable)
+ {
+ fieldType = Nullable.GetUnderlyingType(columnType);
+ }
+ var column = table.Columns.Add(columnName, fieldType);
+ column.AllowDBNull = isNullable;
+ column.ReadOnly = true;
+ }
+ table.TableName = _tableName;
+ return table;
+ }
+
+ public static bool IsNullable(Type type)
+ {
+ return type.IsValueType &&
+ type.IsGenericType &&
+ type.GetGenericTypeDefinition() == typeof(Nullable<>);
+ }
+
+ public bool Read()
+ {
+ if (!_enumerator.MoveNext())
+ {
+ return false;
+ }
+ var next = _enumerator.Current;
+ var properties = _properties;
+ _record = new object[properties.Length];
+ for (var i = 0; i < _record.Length; i++)
+ {
+ var propertyMap = properties[i];
+ _record[i] = propertyMap.Property.Get(next);
+ }
+ return true;
+ }
+
+ private TValue GetValue(int i)
+ {
+ var value = GetValue(i);
+ if (value == DBNull.Value && !IsNullable(typeof(TValue)))
+ {
+ throw new InvalidOperationException("Value is not nullable");
+ }
+ return (TValue)value;
+ }
+
+ public int GetOrdinal(string name)
+ {
+ var map = _properties.First(p => p.ColumnName == name);
+ if (map != null)
+ {
+ for (var i = 0; i < _properties.Length; i++)
+ {
+ if (_properties[i] == map)
+ {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ public int GetValues(object[] values)
+ {
+ var columns = 0;
+ for (var i = 0; i < values.Length && i < FieldCount; i++)
+ {
+ columns += 1;
+ values[i] = GetValue(i);
+ }
+ return columns;
+ }
+
+ public int FieldCount { get; private set; }
+
+ public void Close()
+ {
+ _enumerable = null;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public virtual void Dispose(bool disposing)
+ {
+ if(disposing)
+ {
+ Close();
+ }
+ }
+
+ #region Not Supported
+ private static void NotUsedByBulkCopy()
+ {
+ throw new NotSupportedException("This method does not apply to in-memory collections, which is all this class supports");
+ }
+
+ public int RecordsAffected
+ {
+ get
+ {
+ NotUsedByBulkCopy();
+ return 0;
+ }
+ }
+
+ public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
+ {
+ NotUsedByBulkCopy();
+ return 0;
+ }
+
+ public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
+ {
+ NotUsedByBulkCopy();
+ return 0;
+ }
+ public IDataReader GetData(int i)
+ {
+ NotUsedByBulkCopy();
+ return null;
+ }
+ #endregion
+
+ #region Passthrough
+
+ public object this[string name]
+ {
+ get { return GetValue(GetOrdinal(name)); }
+ }
+
+ public object this[int i]
+ {
+ get { return GetValue(i); }
+ }
+
+ public bool NextResult()
+ {
+ return false;
+ }
+
+ public int Depth
+ {
+ get { return 1; }
+ }
+
+ public bool IsClosed
+ {
+ get { return _enumerable == null; }
+ }
+
+ public object GetValue(int i)
+ {
+ return _record[i];
+ }
+
+ public string GetName(int i)
+ {
+ return _properties[i].ColumnName;
+ }
+
+ public string GetDataTypeName(int i)
+ {
+ return GetFieldType(i).Name;
+ }
+
+ public Type GetFieldType(int i)
+ {
+ return _properties[i].Property.Type;
+ }
+
+ public bool GetBoolean(int i)
+ {
+ return GetValue(i);
+ }
+
+ public byte GetByte(int i)
+ {
+ return GetValue(i);
+ }
+
+ public char GetChar(int i)
+ {
+ return GetValue(i);
+ }
+
+ public Guid GetGuid(int i)
+ {
+ return GetValue(i);
+ }
+
+ public short GetInt16(int i)
+ {
+ return GetValue(i);
+ }
+
+ public int GetInt32(int i)
+ {
+ return GetValue(i);
+ }
+
+ public long GetInt64(int i)
+ {
+ return GetValue(i);
+ }
+
+ public float GetFloat(int i)
+ {
+ return GetValue(i);
+ }
+
+ public double GetDouble(int i)
+ {
+ return GetValue(i);
+ }
+
+ public string GetString(int i)
+ {
+ return GetValue(i);
+ }
+
+ public decimal GetDecimal(int i)
+ {
+ return GetValue(i);
+ }
+
+ public DateTime GetDateTime(int i)
+ {
+ return GetValue(i);
+ }
+
+ public bool IsDBNull(int i)
+ {
+ return (GetValue(i) == DBNull.Value);
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/bulky/src/Bulky/IBulkCopy.cs b/bulky/src/Bulky/IBulkCopy.cs
new file mode 100644
index 0000000..63b8b6b
--- /dev/null
+++ b/bulky/src/Bulky/IBulkCopy.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using System.Data;
+using TableDescriptor;
+
+namespace bulky
+{
+ public interface IBulkCopy
+ {
+ void Copy(Descriptor descriptor, IDbConnection connection, IEnumerable entities, IDbTransaction transaction = null, int? commandTimeout = null);
+ }
+}
diff --git a/bulky/src/Bulky/MySqlBulkCopy.cs b/bulky/src/Bulky/MySqlBulkCopy.cs
new file mode 100644
index 0000000..6ab6a00
--- /dev/null
+++ b/bulky/src/Bulky/MySqlBulkCopy.cs
@@ -0,0 +1,165 @@
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using TableDescriptor;
+using tuxedo;
+using tuxedo.Dialects;
+
+namespace bulky
+{
+ ///
+ /// Performs high speed bulk inserts against a MySQL database.
+ ///
+ /// - Note that prepared statements bypass the query cache in MySQL; Flint caches statements by type and batch size
+ /// - This currently doesn't intelligently set the batch size based on packets; the value is arbitrary
+ ///
+ ///
+ ///
+ ///
+ ///
+ public class MySqlBulkCopy : IBulkCopy
+ {
+ public const int BatchSize = 100;
+
+ private static readonly MySqlDialect Dialect;
+
+ static MySqlBulkCopy()
+ {
+ Dialect = new MySqlDialect();
+ }
+
+ public void Copy(Descriptor descriptor, IDbConnection connection, IEnumerable entities, IDbTransaction transaction = null, int? commandTimeout = null)
+ {
+ descriptor = descriptor ?? SimpleDescriptor.Create();
+ transaction = transaction ?? connection.BeginTransaction();
+ var reader = new EnumerableDataReader(entities);
+ var total = reader.Count;
+ var batchSize = total < BatchSize ? total : BatchSize;
+ var pending = new List