diff --git a/Beeline.sln b/Beeline.sln
index 9edd6aa..c55a1d2 100644
--- a/Beeline.sln
+++ b/Beeline.sln
@@ -13,6 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Beeline.Tests", "test\Beeli
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Beeline.Benchmarks", "test\Beeline.Benchmarks\Beeline.Benchmarks.csproj", "{0045104E-E9BC-4566-8324-3D1E4099CD6E}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{0A575C00-110D-46B7-A30D-E69A25961CCE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Beeline.AspNetCoreBenchmarks", "samples\Beeline.AspNetCoreBenchmarks\Beeline.AspNetCoreBenchmarks.csproj", "{DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -62,10 +66,23 @@ Global
{0045104E-E9BC-4566-8324-3D1E4099CD6E}.Release|x64.Build.0 = Release|x64
{0045104E-E9BC-4566-8324-3D1E4099CD6E}.Release|x86.ActiveCfg = Release|x86
{0045104E-E9BC-4566-8324-3D1E4099CD6E}.Release|x86.Build.0 = Release|x86
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Debug|x64.ActiveCfg = Debug|x64
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Debug|x64.Build.0 = Debug|x64
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Debug|x86.ActiveCfg = Debug|x86
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Debug|x86.Build.0 = Debug|x86
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Release|x64.ActiveCfg = Release|x64
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Release|x64.Build.0 = Release|x64
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Release|x86.ActiveCfg = Release|x86
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B7F309C8-75FC-41FE-BF10-32B727AF9EB4} = {FD1C9140-EAE4-4C8B-8B93-985C07992FF7}
{EBD6AEB9-1E0B-4651-83CD-5D4956C19EA3} = {D20967AB-999E-44C6-9F01-BF0638F12D1E}
{0045104E-E9BC-4566-8324-3D1E4099CD6E} = {D20967AB-999E-44C6-9F01-BF0638F12D1E}
+ {DF8CE83B-0BF0-412D-A5B6-BBE27F58E675} = {0A575C00-110D-46B7-A30D-E69A25961CCE}
EndGlobalSection
EndGlobal
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 0000000..6f666e0
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Beeline.AspNetCoreBenchmarks/Beeline.AspNetCoreBenchmarks.csproj b/samples/Beeline.AspNetCoreBenchmarks/Beeline.AspNetCoreBenchmarks.csproj
new file mode 100644
index 0000000..e949be1
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/Beeline.AspNetCoreBenchmarks.csproj
@@ -0,0 +1,19 @@
+
+
+ netcoreapp2.1
+ 2.1.0-preview1-26103-03
+ 2.1.0-preview1-26103-03
+ 7.2
+
+
+ Microsoft.NETCore.App
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/Beeline.AspNetCoreBenchmarks/Configuration/AppSettings.cs b/samples/Beeline.AspNetCoreBenchmarks/Configuration/AppSettings.cs
new file mode 100644
index 0000000..80079a6
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/Configuration/AppSettings.cs
@@ -0,0 +1,7 @@
+namespace Beeline.AspNetCoreBenchmarks.Configuration
+{
+ public class AppSettings
+ {
+ public string ConnectionString { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/samples/Beeline.AspNetCoreBenchmarks/Controllers/ValuesController.cs b/samples/Beeline.AspNetCoreBenchmarks/Controllers/ValuesController.cs
new file mode 100644
index 0000000..632ca7d
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/Controllers/ValuesController.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Beeline.AspNetCoreBenchmarks.Controllers
+{
+ [Route("api/[controller]")]
+ public class ValuesController : Controller
+ {
+ // GET api/values
+ [HttpGet]
+ public IEnumerable Get()
+ {
+ return new string[] { "value1", "value2" };
+ }
+
+ // GET api/values/5
+ [HttpGet("{id}")]
+ public string Get(int id)
+ {
+ return "value";
+ }
+
+ // POST api/values
+ [HttpPost]
+ public void Post([FromBody]string value)
+ {
+ }
+
+ // PUT api/values/5
+ [HttpPut("{id}")]
+ public void Put(int id, [FromBody]string value)
+ {
+ }
+
+ // DELETE api/values/5
+ [HttpDelete("{id}")]
+ public void Delete(int id)
+ {
+ }
+ }
+}
diff --git a/samples/Beeline.AspNetCoreBenchmarks/Data/BeelineDb.cs b/samples/Beeline.AspNetCoreBenchmarks/Data/BeelineDb.cs
new file mode 100644
index 0000000..188e8cd
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/Data/BeelineDb.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Buffers;
+using System.Data;
+using System.Data.Common;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using Beeline.AspNetCoreBenchmarks.Configuration;
+using Microsoft.Extensions.Options;
+
+namespace Beeline.AspNetCoreBenchmarks.Data
+{
+ public class BeelineDb
+ {
+ private readonly IRandom _random;
+ private readonly DbProviderFactory _dbProvider;
+ private readonly string _connectionString;
+ private ObjectWriter _objectWriter;
+
+ public BeelineDb(IRandom random, DbProviderFactory dbProvider, IOptions appSettings)
+ {
+ _random = random;
+ _dbProvider = dbProvider;
+ _connectionString = appSettings.Value.ConnectionString;
+ }
+
+ public async Task LoadSingleQueryRow(byte[] buffer, CancellationToken ct = default)
+ {
+ using (var db = _dbProvider.CreateConnection())
+ {
+ Debug.Assert(db != null);
+ db.ConnectionString = _connectionString;
+ using (var cmd = CreateSingleQueryCommand(db, _random.Next(1, 10001)))
+ {
+ await db.OpenAsync(ct);
+ using (var reader = await cmd.ExecuteReaderAsync(ct))
+ {
+ if (_objectWriter is null)
+ {
+ _objectWriter = new ObjectWriter(RowSerializer.For(reader, true), 1024);
+ }
+
+ return await _objectWriter.WriteSingle(reader, buffer, ct);
+ }
+ }
+ }
+ }
+
+ private static DbCommand CreateSingleQueryCommand(DbConnection db, int id)
+ {
+ var cmd = db.CreateCommand();
+ cmd.CommandText = "SELECT id, randomnumber FROM world WHERE id = @id";
+ var idParameter = cmd.CreateParameter();
+ idParameter.ParameterName = "id";
+ idParameter.DbType = DbType.Int32;
+ idParameter.Value = id;
+ cmd.Parameters.Add(idParameter);
+ return cmd;
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/Beeline.AspNetCoreBenchmarks/Data/DatabaseInitializer.cs b/samples/Beeline.AspNetCoreBenchmarks/Data/DatabaseInitializer.cs
new file mode 100644
index 0000000..68156b6
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/Data/DatabaseInitializer.cs
@@ -0,0 +1,44 @@
+using System;
+using Npgsql;
+using NpgsqlTypes;
+
+namespace Beeline.AspNetCoreBenchmarks.Data
+{
+ public static class DatabaseInitializer
+ {
+ private const string DropTable = "drop table if exists world";
+ private const string CreateTable = @"create table world
+(
+ id integer not null
+ constraint world_pkey
+ primary key,
+ randomnumber integer
+)";
+
+ private const string Insert = "INSERT INTO world (id, randomnumber) VALUES (@id, @randomnumber)";
+
+ public static void Initialize(string connectionString)
+ {
+ var random = new Random(42);
+ using (var connection = new NpgsqlConnection(connectionString))
+ using (var cmd = connection.CreateCommand())
+ {
+ connection.Open();
+ cmd.CommandText = DropTable;
+ cmd.ExecuteNonQuery();
+ cmd.CommandText = CreateTable;
+ cmd.ExecuteNonQuery();
+ cmd.CommandText = Insert;
+ var @id = cmd.Parameters.Add("id", NpgsqlDbType.Integer);
+ var @randomnumber = cmd.Parameters.Add("randomnumber", NpgsqlDbType.Integer);
+ cmd.Prepare();
+ for (int i = 1; i < 10001; i++)
+ {
+ @id.Value = i;
+ @randomnumber.Value = random.Next(1, 999_999_999);
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/Beeline.AspNetCoreBenchmarks/Data/DefaultRandom.cs b/samples/Beeline.AspNetCoreBenchmarks/Data/DefaultRandom.cs
new file mode 100644
index 0000000..c4e2e4f
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/Data/DefaultRandom.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Threading;
+
+namespace Beeline.AspNetCoreBenchmarks.Data
+{
+ public class DefaultRandom : IRandom
+ {
+ private static int _nextSeed = 0;
+ // Random isn't thread safe
+ private static readonly ThreadLocal Random = new ThreadLocal(() => new Random(Interlocked.Increment(ref _nextSeed)));
+
+ public int Next(int minValue, int maxValue)
+ {
+ return Random.Value.Next(minValue, maxValue);
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/Beeline.AspNetCoreBenchmarks/Data/IRandom.cs b/samples/Beeline.AspNetCoreBenchmarks/Data/IRandom.cs
new file mode 100644
index 0000000..a614650
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/Data/IRandom.cs
@@ -0,0 +1,7 @@
+namespace Beeline.AspNetCoreBenchmarks.Data
+{
+ public interface IRandom
+ {
+ int Next(int minValue, int maxValue);
+ }
+}
\ No newline at end of file
diff --git a/samples/Beeline.AspNetCoreBenchmarks/Middleware/SingleQueryBeelineMiddleware.cs b/samples/Beeline.AspNetCoreBenchmarks/Middleware/SingleQueryBeelineMiddleware.cs
new file mode 100644
index 0000000..ff93ea3
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/Middleware/SingleQueryBeelineMiddleware.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Buffers;
+using System.Threading.Tasks;
+using Beeline.AspNetCoreBenchmarks.Data;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Beeline.AspNetCoreBenchmarks.Middleware
+{
+ public class SingleQueryBeelineMiddleware
+ {
+ private static readonly PathString Path = new PathString("/db/beeline");
+
+ private readonly RequestDelegate _next;
+
+ public SingleQueryBeelineMiddleware(RequestDelegate next)
+ {
+ _next = next;
+ }
+
+ public async Task Invoke(HttpContext httpContext)
+ {
+ if (httpContext.Request.Path.StartsWithSegments(Path, StringComparison.Ordinal))
+ {
+ var db = httpContext.RequestServices.GetService();
+ var buffer = ArrayPool.Shared.Rent(128);
+ try
+ {
+ var length = await db.LoadSingleQueryRow(buffer, httpContext.RequestAborted);
+
+ httpContext.Response.StatusCode = StatusCodes.Status200OK;
+ httpContext.Response.ContentType = "application/json";
+ httpContext.Response.ContentLength = length;
+ await httpContext.Response.Body.WriteAsync(buffer, 0, length, httpContext.RequestAborted);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
+ }
+
+ return;
+ }
+
+ await _next(httpContext);
+ }
+ }
+
+ public static class SingleQueryDapperMiddlewareExtensions
+ {
+ public static IApplicationBuilder UseSingleQueryBeeline(this IApplicationBuilder builder)
+ {
+ return builder.UseMiddleware();
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/Beeline.AspNetCoreBenchmarks/Program.cs b/samples/Beeline.AspNetCoreBenchmarks/Program.cs
new file mode 100644
index 0000000..6b91ef6
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/Program.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+
+namespace Beeline.AspNetCoreBenchmarks
+{
+ [UsedImplicitly]
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ BuildWebHost(args).Run();
+ }
+
+ private static IWebHost BuildWebHost(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
+ .UseStartup()
+ .Build();
+ }
+}
diff --git a/samples/Beeline.AspNetCoreBenchmarks/Startup.cs b/samples/Beeline.AspNetCoreBenchmarks/Startup.cs
new file mode 100644
index 0000000..121184d
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/Startup.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Linq;
+using System.Threading.Tasks;
+using Beeline.AspNetCoreBenchmarks.Configuration;
+using Beeline.AspNetCoreBenchmarks.Data;
+using Beeline.AspNetCoreBenchmarks.Middleware;
+using JetBrains.Annotations;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Npgsql;
+
+namespace Beeline.AspNetCoreBenchmarks
+{
+ [UsedImplicitly]
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ private IConfiguration Configuration { get; }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ DatabaseInitializer.Initialize(Configuration.GetValue("ConnectionString"));
+ services.Configure(Configuration);
+ services.AddSingleton();
+ services.AddSingleton(NpgsqlFactory.Instance);
+ services.AddSingleton();
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ app.UseSingleQueryBeeline();
+ }
+ }
+}
diff --git a/samples/Beeline.AspNetCoreBenchmarks/appsettings.Development.json b/samples/Beeline.AspNetCoreBenchmarks/appsettings.Development.json
new file mode 100644
index 0000000..3bb1924
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/appsettings.Development.json
@@ -0,0 +1,11 @@
+{
+ "Logging": {
+ "IncludeScopes": false,
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ },
+ "ConnectionString": "Host=localhost;Database=beeline;Username=beeline;Password=S3cr3tSqu1rr3l"
+}
diff --git a/samples/Beeline.AspNetCoreBenchmarks/appsettings.json b/samples/Beeline.AspNetCoreBenchmarks/appsettings.json
new file mode 100644
index 0000000..d8379f9
--- /dev/null
+++ b/samples/Beeline.AspNetCoreBenchmarks/appsettings.json
@@ -0,0 +1,16 @@
+{
+ "Logging": {
+ "IncludeScopes": false,
+ "Debug": {
+ "LogLevel": {
+ "Default": "Warning"
+ }
+ },
+ "Console": {
+ "LogLevel": {
+ "Default": "Warning"
+ }
+ }
+ },
+ "ConnectionString": "Host=localhost;Database=beeline;Username=beeline;Password=S3cr3tSqu1rr3l"
+}
diff --git a/src/Beeline/ArrayWriter.cs b/src/Beeline/ArrayWriter.cs
index 9e7b947..47870d7 100644
--- a/src/Beeline/ArrayWriter.cs
+++ b/src/Beeline/ArrayWriter.cs
@@ -1,5 +1,6 @@
using System;
using System.Buffers;
+using System.ComponentModel;
using System.Data.Common;
using System.IO;
using System.Threading;
@@ -27,7 +28,6 @@ public async Task Write(DbDataReader reader, Memory buffer, Cancellat
{
int rowCount = 0;
int pos = 0;
- bool first = true;
buffer.Span[pos++] = OpenBracket;
if (!await reader.ReadAsync(ct).ConfigureAwait(false)) return 0;
diff --git a/src/Beeline/ObjectWriter.cs b/src/Beeline/ObjectWriter.cs
index 787d50d..f771c22 100644
--- a/src/Beeline/ObjectWriter.cs
+++ b/src/Beeline/ObjectWriter.cs
@@ -22,8 +22,7 @@ public async Task WriteSingle(DbDataReader reader, Memory buffer, Can
{
if (await reader.ReadAsync(ct).ConfigureAwait(false))
{
- int bytes = _serializer.Write(reader, buffer.Span);
- return 1;
+ return _serializer.Write(reader, buffer.Span);
}
return 0;
diff --git a/test/Beeline.Benchmarks/Beeline.Benchmarks.csproj b/test/Beeline.Benchmarks/Beeline.Benchmarks.csproj
index 84723d8..1cf341a 100644
--- a/test/Beeline.Benchmarks/Beeline.Benchmarks.csproj
+++ b/test/Beeline.Benchmarks/Beeline.Benchmarks.csproj
@@ -4,6 +4,7 @@
netcoreapp2.1
2.1.0-preview1-26016-05
2.1.0-preview1-26016-05
+ 7.2