Browse Source

Merge pull request #2308 from nathana1/aspnetcore-db-scenarios

add aspnetcore database scenarios
A Shawn Bandy 8 years ago
parent
commit
6e5ed58d9e
33 changed files with 878 additions and 197 deletions
  1. 1 0
      frameworks/CSharp/aspnetcore/Benchmarks/Configuration/AppSettings.cs
  2. 12 0
      frameworks/CSharp/aspnetcore/Benchmarks/Configuration/DatabaseServer.cs
  3. 30 12
      frameworks/CSharp/aspnetcore/Benchmarks/Configuration/Scenarios.cs
  4. 11 51
      frameworks/CSharp/aspnetcore/Benchmarks/Controllers/FortunesController.cs
  5. 42 0
      frameworks/CSharp/aspnetcore/Benchmarks/Controllers/MultipleQueriesController.cs
  6. 42 0
      frameworks/CSharp/aspnetcore/Benchmarks/Controllers/MultipleUpdatesController.cs
  7. 41 0
      frameworks/CSharp/aspnetcore/Benchmarks/Controllers/SingleQueryController.cs
  8. 49 2
      frameworks/CSharp/aspnetcore/Benchmarks/Data/ApplicationDbContext.cs
  9. 24 0
      frameworks/CSharp/aspnetcore/Benchmarks/Data/BatchUpdateString.cs
  10. 50 12
      frameworks/CSharp/aspnetcore/Benchmarks/Data/DapperDb.cs
  11. 27 3
      frameworks/CSharp/aspnetcore/Benchmarks/Data/EfDb.cs
  12. 4 0
      frameworks/CSharp/aspnetcore/Benchmarks/Data/Fortune.cs
  13. 19 0
      frameworks/CSharp/aspnetcore/Benchmarks/Data/IDb.cs
  14. 43 0
      frameworks/CSharp/aspnetcore/Benchmarks/Data/NoTransactionSqlServerConnection.cs
  15. 6 3
      frameworks/CSharp/aspnetcore/Benchmarks/Data/Random.cs
  16. 83 39
      frameworks/CSharp/aspnetcore/Benchmarks/Data/RawDb.cs
  17. 5 0
      frameworks/CSharp/aspnetcore/Benchmarks/Data/World.cs
  18. 0 47
      frameworks/CSharp/aspnetcore/Benchmarks/Middleware/ErrorHandlerMiddleware.cs
  19. 5 5
      frameworks/CSharp/aspnetcore/Benchmarks/Middleware/FortunesEfMiddleware.cs
  20. 5 5
      frameworks/CSharp/aspnetcore/Benchmarks/Middleware/MultipleQueriesEfMiddleware.cs
  21. 61 0
      frameworks/CSharp/aspnetcore/Benchmarks/Middleware/MultipleUpdatesDapperMiddleware.cs
  22. 61 0
      frameworks/CSharp/aspnetcore/Benchmarks/Middleware/MultipleUpdatesEfMiddleware.cs
  23. 61 0
      frameworks/CSharp/aspnetcore/Benchmarks/Middleware/MultipleUpdatesRawMiddleware.cs
  24. 5 5
      frameworks/CSharp/aspnetcore/Benchmarks/Middleware/SingleQueryEfMiddleware.cs
  25. 41 8
      frameworks/CSharp/aspnetcore/Benchmarks/Startup.cs
  26. 0 3
      frameworks/CSharp/aspnetcore/Benchmarks/appsettings.json
  27. 4 0
      frameworks/CSharp/aspnetcore/Benchmarks/appsettings.postgresql.json
  28. 3 1
      frameworks/CSharp/aspnetcore/Benchmarks/project.json
  29. 132 1
      frameworks/CSharp/aspnetcore/benchmark_config.json
  30. 2 0
      frameworks/CSharp/aspnetcore/run-linux.sh
  31. 3 0
      frameworks/CSharp/aspnetcore/setup-dapper.sh
  32. 3 0
      frameworks/CSharp/aspnetcore/setup-ef.sh
  33. 3 0
      frameworks/CSharp/aspnetcore/setup-raw.sh

+ 1 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Configuration/AppSettings.cs

@@ -7,5 +7,6 @@ namespace Benchmarks.Configuration
     public class AppSettings
     {
         public string ConnectionString { get; set; }
+        public DatabaseServer Database { get; set; } = DatabaseServer.SqlServer;
     }
 }

+ 12 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Configuration/DatabaseServer.cs

@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+
+namespace Benchmarks.Configuration
+{
+    public enum DatabaseServer
+    {
+        SqlServer,
+        PostgreSql
+    }
+}

+ 30 - 12
frameworks/CSharp/aspnetcore/Benchmarks/Configuration/Scenarios.cs

@@ -44,6 +44,15 @@ namespace Benchmarks.Configuration
         [ScenarioPath("/queries/dapper")]
         public bool DbMultiQueryDapper { get; set; }
 
+        [ScenarioPath("/updates/raw")]
+        public bool DbMultiUpdateRaw { get; set; }
+
+        [ScenarioPath("/updates/ef")]
+        public bool DbMultiUpdateEf { get; set; }
+
+        [ScenarioPath("/updates/dapper")]
+        public bool DbMultiUpdateDapper { get; set; }
+
         [ScenarioPath("/fortunes/raw")]
         public bool DbFortunesRaw { get; set; }
 
@@ -62,23 +71,32 @@ namespace Benchmarks.Configuration
         [ScenarioPath("/mvc/view")]
         public bool MvcViews { get; set; }
 
-        //[ScenarioPath("/mvc/db/raw")]
-        //public bool MvcDbSingleQueryRaw { get; set; }
+        [ScenarioPath("/mvc/db/raw")]
+        public bool MvcDbSingleQueryRaw { get; set; }
+
+        [ScenarioPath("/mvc/db/dapper")]
+        public bool MvcDbSingleQueryDapper { get; set; }
+
+        [ScenarioPath("/mvc/db/ef")]
+        public bool MvcDbSingleQueryEf { get; set; }
+
+        [ScenarioPath("/mvc/queries/raw")]
+        public bool MvcDbMultiQueryRaw { get; set; }
 
-        //[ScenarioPath("/mvc/db/dapper")]
-        //public bool MvcDbSingleQueryDapper { get; set; }
+        [ScenarioPath("/mvc/queries/dapper")]
+        public bool MvcDbMultiQueryDapper { get; set; }
 
-        //[ScenarioPath("/mvc/db/ef")]
-        //public bool MvcDbSingleQueryEf { get; set; }
+        [ScenarioPath("/mvc/queries/ef")]
+        public bool MvcDbMultiQueryEf { get; set; }
 
-        //[ScenarioPath("/mvc/queries/raw")]
-        //public bool MvcDbMultiQueryRaw { get; set; }
+        [ScenarioPath("/mvc/updates/raw")]
+        public bool MvcDbMultiUpdateRaw { get; set; }
 
-        //[ScenarioPath("/mvc/queries/dapper")]
-        //public bool MvcDbMultiQueryDapper { get; set; }
+        [ScenarioPath("/mvc/updates/dapper")]
+        public bool MvcDbMultiUpdateDapper { get; set; }
 
-        //[ScenarioPath("/mvc/queries/ef")]
-        //public bool MvcDbMultiQueryEf { get; set; }
+        [ScenarioPath("/mvc/updates/ef")]
+        public bool MvcDbMultiUpdateEf { get; set; }
 
         [ScenarioPath("/mvc/fortunes/raw")]
         public bool MvcDbFortunesRaw { get; set; }

+ 11 - 51
frameworks/CSharp/aspnetcore/Benchmarks/Controllers/FortunesController.cs

@@ -8,68 +8,28 @@ using Microsoft.Extensions.DependencyInjection;
 
 namespace Benchmarks.Controllers
 {
-    [Route("mvc")]
+    [Route("mvc/fortunes")]
     public class FortunesController : Controller
     {
-        private RawDb _rawDb;
-        private DapperDb _dapperDb;
-        private EfDb _efDb;
-
-        private RawDb RawDb
-        {
-            get
-            {
-                if (_rawDb == null)
-                {
-                    _rawDb = HttpContext.RequestServices.GetRequiredService<RawDb>();
-                }
-
-                return _rawDb;
-            }
-        }
-
-        private DapperDb DapperDb
-        {
-            get
-            {
-                if (_dapperDb == null)
-                {
-                    _dapperDb = HttpContext.RequestServices.GetRequiredService<DapperDb>();
-                }
-
-                return _dapperDb;
-            }
-        }
-
-        private EfDb EfDb
-        {
-            get
-            {
-                if (_efDb == null)
-                {
-                    _efDb = HttpContext.RequestServices.GetRequiredService<EfDb>();
-                }
-
-                return _efDb;
-            }
-        }
-
-        [HttpGet("fortunes/raw")]
+        [HttpGet("raw")]
         public async Task<IActionResult> Raw()
-        {
-            return View("Fortunes", await RawDb.LoadFortunesRows());
+        {   
+            var db = HttpContext.RequestServices.GetRequiredService<RawDb>();
+            return View("Fortunes", await db.LoadFortunesRows());
         }
 
-        [HttpGet("fortunes/dapper")]
+        [HttpGet("dapper")]
         public async Task<IActionResult> Dapper()
         {
-            return View("Fortunes", await DapperDb.LoadFortunesRows());
+            var db = HttpContext.RequestServices.GetRequiredService<DapperDb>();
+            return View("Fortunes", await db.LoadFortunesRows());
         }
 
-        [HttpGet("fortunes/ef")]
+        [HttpGet("ef")]
         public async Task<IActionResult> Ef()
         {
-            return View("Fortunes", await EfDb.LoadFortunesRows());
+            var db = HttpContext.RequestServices.GetRequiredService<EfDb>();
+            return View("Fortunes", await db.LoadFortunesRows());
         }
     }
 }

+ 42 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Controllers/MultipleQueriesController.cs

@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Benchmarks.Data;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Benchmarks.Controllers
+{
+    [Route("mvc/queries")]
+    public class MultipleQueriesController : Controller
+    {
+        [HttpGet("raw")]
+        [Produces("application/json")]
+        public Task<World[]> Raw(int queries = 1)
+        {
+            return ExecuteQuery<RawDb>(queries);
+        }
+
+        [HttpGet("dapper")]
+        [Produces("application/json")]
+        public Task<World[]> Dapper(int queries = 1)
+        {
+            return ExecuteQuery<DapperDb>(queries);
+        }
+
+        [HttpGet("ef")]
+        [Produces("application/json")]
+        public Task<World[]> Ef(int queries = 1)
+        {
+            return ExecuteQuery<EfDb>(queries);
+        }
+
+        private Task<World[]> ExecuteQuery<T>(int queries) where T : IDb
+        {
+            queries = queries < 1 ? 1 : queries > 500 ? 500 : queries;
+            var db = HttpContext.RequestServices.GetRequiredService<T>();
+            return db.LoadMultipleQueriesRows(queries);
+        }
+    }
+}

+ 42 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Controllers/MultipleUpdatesController.cs

@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Benchmarks.Data;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Benchmarks.Controllers
+{
+    [Route("mvc/updates")]
+    public class MultipleUpdatesController : Controller
+    {
+        [HttpGet("raw")]
+        [Produces("application/json")]
+        public Task<World[]> Raw(int queries = 1)
+        {
+            return ExecuteQuery<RawDb>(queries);
+        }
+
+        [HttpGet("dapper")]
+        [Produces("application/json")]
+        public Task<World[]> Dapper(int queries = 1)
+        {
+            return ExecuteQuery<DapperDb>(queries);
+        }
+
+        [HttpGet("ef")]
+        [Produces("application/json")]
+        public Task<World[]> Ef(int queries = 1)
+        {
+            return ExecuteQuery<EfDb>(queries);
+        }
+
+        private Task<World[]> ExecuteQuery<T>(int queries) where T : IDb
+        {
+            queries = queries < 1 ? 1 : queries > 500 ? 500 : queries;
+            var db = HttpContext.RequestServices.GetRequiredService<T>();
+            return db.LoadMultipleUpdatesRows(queries);
+        }
+    }
+}

+ 41 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Controllers/SingleQueryController.cs

@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Benchmarks.Data;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Benchmarks.Controllers
+{
+    [Route("mvc/db")]
+    public class SingleQueryController : Controller
+    {
+        [HttpGet("raw")]
+        [Produces("application/json")]
+        public Task<World> Raw()
+        {
+            return ExecuteQuery<RawDb>();
+        }
+
+        [HttpGet("dapper")]
+        [Produces("application/json")]
+        public Task<World> Dapper()
+        {
+            return ExecuteQuery<DapperDb>();
+        }
+
+        [HttpGet("ef")]
+        [Produces("application/json")]
+        public Task<World> Ef()
+        {
+            return ExecuteQuery<EfDb>();
+        }
+
+        private Task<World> ExecuteQuery<T>() where T : IDb
+        {
+            var db = HttpContext.RequestServices.GetRequiredService<T>();
+            return db.LoadSingleQueryRow();
+        }
+    }
+}

+ 49 - 2
frameworks/CSharp/aspnetcore/Benchmarks/Data/ApplicationDbContext.cs

@@ -1,9 +1,14 @@
 // Copyright (c) .NET Foundation. All rights reserved. 
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
 
+using System.Linq;
 using Benchmarks.Configuration;
 using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.Internal;
+using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
 using Microsoft.Extensions.Options;
+using Microsoft.Extensions.DependencyInjection;
 
 namespace Benchmarks.Data
 {
@@ -20,9 +25,51 @@ namespace Benchmarks.Data
 
         public DbSet<Fortune> Fortune { get; set; }
 
+        public bool UseBatchUpdate 
+        { 
+            get
+            {
+                return _appSettings.Database != DatabaseServer.PostgreSql;
+            }
+        } 
+
         protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
         {
-            optionsBuilder.UseSqlServer(_appSettings.ConnectionString);
+            if (_appSettings.Database == DatabaseServer.PostgreSql)
+            {
+                optionsBuilder.UseNpgsql(_appSettings.ConnectionString);
+            }
+            else
+            {
+                var extension = GetOrCreateExtension(optionsBuilder);
+                extension.ConnectionString = _appSettings.ConnectionString;
+                ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
+            }
+        }
+
+        private static SqlServerOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder optionsBuilder)
+        {
+            var existing = optionsBuilder.Options.FindExtension<NoTxSqlServerOptionsExtension>();
+            return existing != null
+                ? new NoTxSqlServerOptionsExtension(existing)
+                : new NoTxSqlServerOptionsExtension();
+        }
+
+        private class NoTxSqlServerOptionsExtension : SqlServerOptionsExtension
+        {
+            public NoTxSqlServerOptionsExtension()
+            {
+            }
+
+            public NoTxSqlServerOptionsExtension(NoTxSqlServerOptionsExtension copyFrom) : base(copyFrom)
+            {
+            }
+            public override void ApplyServices(IServiceCollection services)
+            {
+                base.ApplyServices(services);
+                services.Remove(services.First((sd) => sd.ServiceType == typeof(ISqlServerConnection)));
+                services.AddScoped<ISqlServerConnection, NoTransactionSqlServerConnection>();
+            }
         }
     }
-}
+}

+ 24 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Data/BatchUpdateString.cs

@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Benchmarks.Data
+{
+    internal class BatchUpdateString
+    {
+        public static IList<BatchUpdateString> Strings { get;} = 
+            Enumerable.Range(0, 500)
+                      .Select(i => new BatchUpdateString
+                      {
+                          Id = $"Id_{i}",
+                          Random = $"Random_{i}",
+                          UpdateQuery = $"UPDATE world SET randomnumber = @Random_{i} WHERE id = @Id_{i};"
+                      }).ToArray();
+                        
+        public string Id { get; set; }
+        public string Random { get; set; }
+        public string UpdateQuery { get; set; }
+    }
+}

+ 50 - 12
frameworks/CSharp/aspnetcore/Benchmarks/Data/DapperDb.cs

@@ -1,8 +1,11 @@
 // Copyright (c) .NET Foundation. All rights reserved. 
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
 
+using System;
 using System.Collections.Generic;
 using System.Data.Common;
+using System.Dynamic;
+using System.Text;
 using System.Threading.Tasks;
 using Benchmarks.Configuration;
 using Dapper;
@@ -10,7 +13,7 @@ using Microsoft.Extensions.Options;
 
 namespace Benchmarks.Data
 {
-    public class DapperDb
+    public class DapperDb : IDb
     {
         private readonly IRandom _random;
         private readonly DbProviderFactory _dbProviderFactory;
@@ -30,15 +33,39 @@ namespace Benchmarks.Data
                 db.ConnectionString = _connectionString;
 
                 // Note: Don't need to open connection if only doing one thing; let dapper do it
-                return await db.QueryFirstOrDefaultAsync<World>(
-                    "SELECT [Id], [RandomNumber] FROM [World] WHERE [Id] = @Id",
-                    new { Id = _random.Next(1, 10001) });
+                return await ReadSingleRow(db);
             }
         }
 
+        async Task<World> ReadSingleRow(DbConnection db)
+        {
+            return await db.QueryFirstOrDefaultAsync<World>(
+                    "SELECT id, randomnumber FROM world WHERE id = @Id",
+                    new { Id = _random.Next(1, 10001) });
+        }
+
         public async Task<World[]> LoadMultipleQueriesRows(int count)
         {
-            var result = new World[count];
+            var results = new World[count];
+            using (var db = _dbProviderFactory.CreateConnection())
+            {
+                db.ConnectionString = _connectionString;
+                await db.OpenAsync();
+
+                for (int i = 0; i < count; i++)
+                {
+                    results[i] = await ReadSingleRow(db);
+                }
+            }
+
+            return results;
+        }
+
+        public async Task<World[]> LoadMultipleUpdatesRows(int count)
+        {
+            var results = new World[count];
+            IDictionary<string, object> parameters = new ExpandoObject();
+            var updateCommand = new StringBuilder(count);
 
             using (var db = _dbProviderFactory.CreateConnection())
             {
@@ -47,15 +74,26 @@ namespace Benchmarks.Data
 
                 for (int i = 0; i < count; i++)
                 {
-                    result[i] = await db.QueryFirstOrDefaultAsync<World>(
-                        "SELECT [Id], [RandomNumber] FROM [World] WHERE [Id] = @Id",
-                        new { Id = _random.Next(1, 10001) });
+                    results[i] = await ReadSingleRow(db);
+                }
+
+                // postgres has problems with deadlocks when these aren't sorted
+                Array.Sort<World>(results, (a, b) => a.Id.CompareTo(b.Id));
+
+                for (int i = 0; i < count; i++)
+                {
+                    var randomNumber = _random.Next(1, 10001);
+                    parameters[BatchUpdateString.Strings[i].Random] = randomNumber;
+                    parameters[BatchUpdateString.Strings[i].Id] = results[i].Id;
+
+                    results[i].RandomNumber = randomNumber;
+                    updateCommand.Append(BatchUpdateString.Strings[i].UpdateQuery);
                 }
 
-                db.Close();
+                await db.ExecuteAsync(updateCommand.ToString(), parameters);
             }
 
-            return result;
+            return results;
         }
 
         public async Task<IEnumerable<Fortune>> LoadFortunesRows()
@@ -67,7 +105,7 @@ namespace Benchmarks.Data
                 db.ConnectionString = _connectionString;
 
                 // Note: don't need to open connection if only doing one thing; let dapper do it
-                result = (await db.QueryAsync<Fortune>("SELECT [Id], [Message] FROM [Fortune]")).AsList();
+                result = (await db.QueryAsync<Fortune>("SELECT id, message FROM fortune")).AsList();
             }
 
             result.Add(new Fortune { Message = "Additional fortune added at request time." });
@@ -76,4 +114,4 @@ namespace Benchmarks.Data
             return result;
         }
     }
-}
+}

+ 27 - 3
frameworks/CSharp/aspnetcore/Benchmarks/Data/EfDb.cs

@@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore;
 
 namespace Benchmarks.Data
 {
-    public class EfDb
+    public class EfDb : IDb
     {
         private readonly IRandom _random;
         private readonly ApplicationDbContext _dbContext;
@@ -22,7 +22,6 @@ namespace Benchmarks.Data
         public Task<World> LoadSingleQueryRow()
         {
             var id = _random.Next(1, 10001);
-            
             return _dbContext.World.FirstAsync(w => w.Id == id);
         }
 
@@ -39,6 +38,31 @@ namespace Benchmarks.Data
             return result;
         }
 
+        public async Task<World[]> LoadMultipleUpdatesRows(int count)
+        {
+            var results = new World[count];
+
+            for (int i = 0; i < count; i++)
+            {
+                var id = _random.Next(1, 10001);
+                var result = await _dbContext.World.AsTracking().FirstAsync(w => w.Id == id);
+
+                result.RandomNumber = _random.Next(1, 10001);
+                results[i] = result;
+                if(!_dbContext.UseBatchUpdate)
+                {
+                    await _dbContext.SaveChangesAsync();
+                }
+            }
+            
+            if(_dbContext.UseBatchUpdate)
+            {
+                await _dbContext.SaveChangesAsync();
+            }
+
+            return results;
+        }
+
         public async Task<IEnumerable<Fortune>> LoadFortunesRows()
         {
             var result = await _dbContext.Fortune.ToListAsync();
@@ -49,4 +73,4 @@ namespace Benchmarks.Data
             return result;
         }
     }
-}
+}

+ 4 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Data/Fortune.cs

@@ -3,13 +3,17 @@
 
 using System;
 using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
 
 namespace Benchmarks.Data
 {
+    [Table("fortune")]
     public class Fortune : IComparable<Fortune>, IComparable
     {
+        [Column("id")]
         public int Id { get; set; }
 
+        [Column("message")]
         [StringLength(2048)]
         public string Message { get; set; }
         

+ 19 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Data/IDb.cs

@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Benchmarks.Data
+{
+    public interface IDb
+    {
+        Task<World> LoadSingleQueryRow();
+
+        Task<World[]> LoadMultipleQueriesRows(int count);
+
+        Task<World[]> LoadMultipleUpdatesRows(int count);
+
+        Task<IEnumerable<Fortune>> LoadFortunesRows();
+    }
+}

+ 43 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Data/NoTransactionSqlServerConnection.cs

@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System.Data;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Internal;
+using Microsoft.Extensions.Logging;
+
+namespace Benchmarks.Data
+{
+    class NoTransactionSqlServerConnection : SqlServerConnection
+    {
+        public NoTransactionSqlServerConnection(IDbContextOptions options, ILogger<SqlServerConnection> logger)
+            : base(options, logger)
+        {
+        }
+
+        public override Task<IDbContextTransaction> BeginTransactionAsync(
+            IsolationLevel isolationLevel, CancellationToken cancellationToken = new CancellationToken())
+            => Task.FromResult<IDbContextTransaction>(new FakeTransaction());
+
+        public override IDbContextTransaction BeginTransaction(IsolationLevel isolationLevel) 
+            => new FakeTransaction();
+
+        private class FakeTransaction : IDbContextTransaction
+        {
+            public void Dispose()
+            {
+            }
+
+            public void Commit()
+            {
+            }
+
+            public void Rollback()
+            {
+            }
+        }
+    }
+}

+ 6 - 3
frameworks/CSharp/aspnetcore/Benchmarks/Data/Random.cs

@@ -2,16 +2,19 @@
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
 
 using System;
+using System.Threading;
 
 namespace Benchmarks.Data
 {
     public class DefaultRandom : IRandom
     {
-        private readonly Random _random = new Random();
+        private static int nextSeed = 0;
+        // Random isn't thread safe
+        private static readonly ThreadLocal<Random> _random = new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref nextSeed)));
 
         public int Next(int minValue, int maxValue)
         {
-            return _random.Next(minValue, maxValue);
+            return _random.Value.Next(minValue, maxValue);
         }
     }
-}
+}

+ 83 - 39
frameworks/CSharp/aspnetcore/Benchmarks/Data/RawDb.cs

@@ -1,21 +1,23 @@
 // Copyright (c) .NET Foundation. All rights reserved. 
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
 
+using System;
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Text;
 using System.Threading.Tasks;
 using Benchmarks.Configuration;
 using Microsoft.Extensions.Options;
 
 namespace Benchmarks.Data
 {
-    public class RawDb
+    public class RawDb : IDb
     {
         private readonly IRandom _random;
         private readonly DbProviderFactory _dbProviderFactory;
         private readonly string _connectionString;
-
+        
         public RawDb(IRandom random, DbProviderFactory dbProviderFactory, IOptions<AppSettings> appSettings)
         {
             _random = random;
@@ -26,66 +28,108 @@ namespace Benchmarks.Data
         public async Task<World> LoadSingleQueryRow()
         {
             using (var db = _dbProviderFactory.CreateConnection())
-            using (var cmd = db.CreateCommand())
+            using (var cmd = CreateReadCommand(db))
             {
-                cmd.CommandText = "SELECT [Id], [RandomNumber] FROM [World] WHERE [Id] = @Id";
-                var id = cmd.CreateParameter();
-                id.ParameterName = "@Id";
-                id.DbType = DbType.Int32;
-                id.Value = _random.Next(1, 10001);
-                cmd.Parameters.Add(id);
-
                 db.ConnectionString = _connectionString;
                 await db.OpenAsync();
 
-                using (var rdr = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection))
-                {
-                    await rdr.ReadAsync();
+                return await ReadSingleRow(db, cmd);
+            }
+        }
+        
+        async Task<World> ReadSingleRow(DbConnection connection, DbCommand cmd)
+        {
+            using (var rdr = await cmd.ExecuteReaderAsync(CommandBehavior.SingleRow))
+            {
+                await rdr.ReadAsync();
 
-                    return new World
-                    {
-                        Id = rdr.GetInt32(0),
-                        RandomNumber = rdr.GetInt32(1)
-                    };
-                }
+                return new World
+                {
+                    Id = rdr.GetInt32(0),
+                    RandomNumber = rdr.GetInt32(1)
+                };
             }
         }
 
+        DbCommand CreateReadCommand(DbConnection connection)
+        {
+            var cmd = connection.CreateCommand();
+            cmd.CommandText = "SELECT id, randomnumber FROM world WHERE id = @Id";
+            var id = cmd.CreateParameter();
+            id.ParameterName = "@Id";
+            id.DbType = DbType.Int32;
+            id.Value = _random.Next(1, 10001);
+            cmd.Parameters.Add(id);
+
+            return cmd;
+        }
+
         public async Task<World[]> LoadMultipleQueriesRows(int count)
         {
             var result = new World[count];
 
             using (var db = _dbProviderFactory.CreateConnection())
-            using (var cmd = db.CreateCommand())
+            using (var cmd = CreateReadCommand(db))
             {
                 db.ConnectionString = _connectionString;
                 await db.OpenAsync();
+                for (int i = 0; i < count; i++)
+                {
+                    result[i] = await ReadSingleRow(db, cmd);
+                    cmd.Parameters["@Id"].Value = _random.Next(1, 10001);
+                }
+            }
 
-                cmd.CommandText = "SELECT [Id], [RandomNumber] FROM [World] WHERE [Id] = @Id";
-                var id = cmd.CreateParameter();
-                id.ParameterName = "@Id";
-                id.DbType = DbType.Int32;
-                cmd.Parameters.Add(id);
+            return result;
+        }
+
+        public async Task<World[]> LoadMultipleUpdatesRows(int count)
+        {
+            var results = new World[count];
+           
+            var updateCommand = new StringBuilder(count);
+
+            using (var db = _dbProviderFactory.CreateConnection())
+            using (var updateCmd = db.CreateCommand())
+            using (var queryCmd = CreateReadCommand(db))
+            {
+                db.ConnectionString = _connectionString;
+                await db.OpenAsync();
 
                 for (int i = 0; i < count; i++)
                 {
-                    id.Value = _random.Next(1, 10001);
-                    using (var rdr = await cmd.ExecuteReaderAsync(CommandBehavior.SingleRow))
-                    {
-                        await rdr.ReadAsync();
+                    results[i] = await ReadSingleRow(db, queryCmd);
+                    queryCmd.Parameters["@Id"].Value = _random.Next(1, 10001);
+                }
 
-                        result[i] = new World
-                        {
-                            Id = rdr.GetInt32(0),
-                            RandomNumber = rdr.GetInt32(1)
-                        };
-                    }
+                // postgres has problems with deadlocks when these aren't sorted
+                Array.Sort<World>(results, (a, b) => a.Id.CompareTo(b.Id));
+
+                for(int i = 0; i < count; i++)
+                {
+                    var id = updateCmd.CreateParameter();
+                    id.ParameterName = BatchUpdateString.Strings[i].Id;
+                    id.DbType = DbType.Int32;
+                    updateCmd.Parameters.Add(id);
+
+                    var random = updateCmd.CreateParameter();
+                    random.ParameterName = BatchUpdateString.Strings[i].Random;
+                    id.DbType = DbType.Int32;
+                    updateCmd.Parameters.Add(random);
+
+                    var randomNumber = _random.Next(1, 10001);
+                    id.Value = results[i].Id;
+                    random.Value = randomNumber;
+                    results[i].RandomNumber = randomNumber;
+
+                    updateCommand.Append(BatchUpdateString.Strings[i].UpdateQuery);
                 }
 
-                db.Close();
+                updateCmd.CommandText = updateCommand.ToString();
+                await updateCmd.ExecuteNonQueryAsync();
             }
 
-            return result;
+            return results;
         }
 
         public async Task<IEnumerable<Fortune>> LoadFortunesRows()
@@ -95,7 +139,7 @@ namespace Benchmarks.Data
             using (var db = _dbProviderFactory.CreateConnection())
             using (var cmd = db.CreateCommand())
             {
-                cmd.CommandText = "SELECT [Id], [Message] FROM [Fortune]";
+                cmd.CommandText = "SELECT id, message FROM fortune";
 
                 db.ConnectionString = _connectionString;
                 await db.OpenAsync();
@@ -119,4 +163,4 @@ namespace Benchmarks.Data
             return result;
         }
     }
-}
+}

+ 5 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Data/World.cs

@@ -1,12 +1,17 @@
 // Copyright (c) .NET Foundation. All rights reserved. 
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
 
+using System.ComponentModel.DataAnnotations.Schema;
+
 namespace Benchmarks.Data
 {
+    [Table("world")]
     public class World
     {
+        [Column("id")] 
         public int Id { get; set; }
 
+        [Column("randomnumber")] 
         public int RandomNumber { get; set; }
     }
 }

+ 0 - 47
frameworks/CSharp/aspnetcore/Benchmarks/Middleware/ErrorHandlerMiddleware.cs

@@ -1,47 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved. 
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
-
-using System;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
-
-namespace Benchmarks.Middleware
-{
-    public class ErrorHandlerMiddleware
-    {
-        private readonly RequestDelegate _next;
-
-        public ErrorHandlerMiddleware(RequestDelegate next)
-        {
-            _next = next;
-        }
-
-        public async Task Invoke(HttpContext httpContext)
-        {
-            try
-            {
-                await _next(httpContext);
-            }
-            catch (Exception ex)
-            {
-                if (!httpContext.Response.HasStarted)
-                {
-                    httpContext.Response.Clear();
-                    httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
-                    httpContext.Response.ContentType = "text/html";
-                }
-                
-                await httpContext.Response.WriteAsync($"<pre style='color:red'>{ex.ToString()}</pre>");
-            }
-        }
-    }
-
-    public static class ErrorHandlerMiddlewareExtensions
-    {
-        public static IApplicationBuilder UseErrorHandler(this IApplicationBuilder builder)
-        {
-            return builder.UseMiddleware<ErrorHandlerMiddleware>();
-        }
-    }
-}

+ 5 - 5
frameworks/CSharp/aspnetcore/Benchmarks/Middleware/FortunesEfMiddleware.cs

@@ -8,6 +8,7 @@ using Benchmarks.Configuration;
 using Benchmarks.Data;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
 
 namespace Benchmarks.Middleware
 {
@@ -16,13 +17,11 @@ namespace Benchmarks.Middleware
         private static readonly PathString _path = new PathString(Scenarios.GetPath(s => s.DbFortunesEf));
 
         private readonly RequestDelegate _next;
-        private readonly EfDb _db;
         private readonly HtmlEncoder _htmlEncoder;
 
-        public FortunesEfMiddleware(RequestDelegate next, EfDb db, HtmlEncoder htmlEncoder)
+        public FortunesEfMiddleware(RequestDelegate next, HtmlEncoder htmlEncoder)
         {
             _next = next;
-            _db = db;
             _htmlEncoder = htmlEncoder;
         }
 
@@ -30,7 +29,8 @@ namespace Benchmarks.Middleware
         {
             if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal))
             {
-                var rows = await _db.LoadFortunesRows();
+                var db = httpContext.RequestServices.GetService<EfDb>();
+                var rows = await db.LoadFortunesRows();
 
                 await MiddlewareHelpers.RenderFortunesHtml(rows, httpContext, _htmlEncoder);
 
@@ -48,4 +48,4 @@ namespace Benchmarks.Middleware
             return builder.UseMiddleware<FortunesEfMiddleware>();
         }
     }
-}
+}

+ 5 - 5
frameworks/CSharp/aspnetcore/Benchmarks/Middleware/MultipleQueriesEfMiddleware.cs

@@ -7,6 +7,7 @@ using Benchmarks.Configuration;
 using Benchmarks.Data;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
 using Newtonsoft.Json;
 using Newtonsoft.Json.Serialization;
 
@@ -21,20 +22,19 @@ namespace Benchmarks.Middleware
         };
 
         private readonly RequestDelegate _next;
-        private readonly EfDb _db;
 
-        public MultipleQueriesEfMiddleware(RequestDelegate next, EfDb db)
+        public MultipleQueriesEfMiddleware(RequestDelegate next)
         {
             _next = next;
-            _db = db;
         }
 
         public async Task Invoke(HttpContext httpContext)
         {
             if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal))
             {
+                var db = httpContext.RequestServices.GetService<EfDb>();
                 var count = MiddlewareHelpers.GetMultipleQueriesQueryCount(httpContext);
-                var rows = await _db.LoadMultipleQueriesRows(count);
+                var rows = await db.LoadMultipleQueriesRows(count);
 
                 var result = JsonConvert.SerializeObject(rows, _jsonSettings);
 
@@ -58,4 +58,4 @@ namespace Benchmarks.Middleware
             return builder.UseMiddleware<MultipleQueriesEfMiddleware>();
         }
     }
-}
+}

+ 61 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Middleware/MultipleUpdatesDapperMiddleware.cs

@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System;
+using System.Threading.Tasks;
+using Benchmarks.Configuration;
+using Benchmarks.Data;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Benchmarks.Middleware
+{
+    public class MultipleUpdatesDapperMiddleware
+    {
+        private static readonly PathString _path = new PathString(Scenarios.GetPath(s => s.DbMultiUpdateDapper));
+        private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
+        {
+            ContractResolver = new CamelCasePropertyNamesContractResolver()
+        };
+
+        private readonly RequestDelegate _next;
+        private readonly DapperDb _db;
+
+        public MultipleUpdatesDapperMiddleware(RequestDelegate next, DapperDb db)
+        {
+            _next = next;
+            _db = db;
+        }
+
+        public async Task Invoke(HttpContext httpContext)
+        {
+            if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal))
+            {
+                var count = MiddlewareHelpers.GetMultipleQueriesQueryCount(httpContext);
+                var rows = await _db.LoadMultipleUpdatesRows(count);
+
+                var result = JsonConvert.SerializeObject(rows, _jsonSettings);
+
+                httpContext.Response.StatusCode = StatusCodes.Status200OK;
+                httpContext.Response.ContentType = "application/json";
+                httpContext.Response.ContentLength = result.Length;
+
+                await httpContext.Response.WriteAsync(result);
+
+                return;
+            }
+
+            await _next(httpContext);
+        }
+    }
+
+    public static class MultipleUpdatesDapperMiddlewareExtensions
+    {
+        public static IApplicationBuilder UseMultipleUpdatesDapper(this IApplicationBuilder builder)
+        {
+            return builder.UseMiddleware<MultipleUpdatesDapperMiddleware>();
+        }
+    }
+}

+ 61 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Middleware/MultipleUpdatesEfMiddleware.cs

@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System;
+using System.Threading.Tasks;
+using Benchmarks.Configuration;
+using Benchmarks.Data;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Benchmarks.Middleware
+{
+    public class MultipleUpdatesEfMiddleware
+    {
+        private static readonly PathString _path = new PathString(Scenarios.GetPath(s => s.DbMultiUpdateEf));
+        private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
+        {
+            ContractResolver = new CamelCasePropertyNamesContractResolver()
+        };
+
+        private readonly RequestDelegate _next;
+
+        public MultipleUpdatesEfMiddleware(RequestDelegate next)
+        {
+            _next = next;
+        }
+
+        public async Task Invoke(HttpContext httpContext)
+        {
+            if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal))
+            {
+                var db = httpContext.RequestServices.GetService<EfDb>();
+                var count = MiddlewareHelpers.GetMultipleQueriesQueryCount(httpContext);
+                var rows = await db.LoadMultipleUpdatesRows(count);
+
+                var result = JsonConvert.SerializeObject(rows, _jsonSettings);
+
+                httpContext.Response.StatusCode = StatusCodes.Status200OK;
+                httpContext.Response.ContentType = "application/json";
+                httpContext.Response.ContentLength = result.Length;
+
+                await httpContext.Response.WriteAsync(result);
+
+                return;
+            }
+
+            await _next(httpContext);
+        }
+    }
+
+    public static class MultipleUpdatesEfMiddlewareExtensions
+    {
+        public static IApplicationBuilder UseMultipleUpdatesEf(this IApplicationBuilder builder)
+        {
+            return builder.UseMiddleware<MultipleUpdatesEfMiddleware>();
+        }
+    }
+}

+ 61 - 0
frameworks/CSharp/aspnetcore/Benchmarks/Middleware/MultipleUpdatesRawMiddleware.cs

@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System;
+using System.Threading.Tasks;
+using Benchmarks.Configuration;
+using Benchmarks.Data;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Benchmarks.Middleware
+{
+    public class MultipleUpdatesRawMiddleware
+    {
+        private static readonly PathString _path = new PathString(Scenarios.GetPath(s => s.DbMultiUpdateRaw));
+        private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
+        {
+            ContractResolver = new CamelCasePropertyNamesContractResolver()
+        };
+
+        private readonly RequestDelegate _next;
+        private readonly RawDb _db;
+
+        public MultipleUpdatesRawMiddleware(RequestDelegate next, RawDb db)
+        {
+            _next = next;
+            _db = db;
+        }
+
+        public async Task Invoke(HttpContext httpContext)
+        {
+            if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal))
+            {
+                var count = MiddlewareHelpers.GetMultipleQueriesQueryCount(httpContext);
+                var rows = await _db.LoadMultipleUpdatesRows(count);
+
+                var result = JsonConvert.SerializeObject(rows, _jsonSettings);
+
+                httpContext.Response.StatusCode = StatusCodes.Status200OK;
+                httpContext.Response.ContentType = "application/json";
+                httpContext.Response.ContentLength = result.Length;
+
+                await httpContext.Response.WriteAsync(result);
+
+                return;
+            }
+
+            await _next(httpContext);
+        }
+    }
+
+    public static class MultipleUpdatesRawMiddlewareExtensions
+    {
+        public static IApplicationBuilder UseMultipleUpdatesRaw(this IApplicationBuilder builder)
+        {
+            return builder.UseMiddleware<MultipleUpdatesRawMiddleware>();
+        }
+    }
+}

+ 5 - 5
frameworks/CSharp/aspnetcore/Benchmarks/Middleware/SingleQueryEfMiddleware.cs

@@ -7,6 +7,7 @@ using Benchmarks.Configuration;
 using Benchmarks.Data;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
 using Newtonsoft.Json;
 using Newtonsoft.Json.Serialization;
 
@@ -21,19 +22,18 @@ namespace Benchmarks.Middleware
         };
 
         private readonly RequestDelegate _next;
-        private readonly EfDb _db;
 
-        public SingleQueryEfMiddleware(RequestDelegate next, EfDb db)
+        public SingleQueryEfMiddleware(RequestDelegate next)
         {
             _next = next;
-            _db = db;
         }
 
         public async Task Invoke(HttpContext httpContext)
         {
             if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal))
             {
-                var row = await _db.LoadSingleQueryRow();
+                var db = httpContext.RequestServices.GetService<EfDb>();
+                var row = await db.LoadSingleQueryRow();
                 var result = JsonConvert.SerializeObject(row, _jsonSettings);
 
                 httpContext.Response.StatusCode = StatusCodes.Status200OK;
@@ -56,4 +56,4 @@ namespace Benchmarks.Middleware
             return builder.UseMiddleware<SingleQueryEfMiddleware>();
         }
     }
-}
+}

+ 41 - 8
frameworks/CSharp/aspnetcore/Benchmarks/Startup.cs

@@ -4,6 +4,8 @@
 using System;
 using System.Data.Common;
 using System.Data.SqlClient;
+using System.Text.Encodings.Web;
+using System.Text.Unicode;
 using Benchmarks.Configuration;
 using Benchmarks.Data;
 using Benchmarks.Middleware;
@@ -11,7 +13,9 @@ using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
 using Microsoft.Extensions.PlatformAbstractions;
+using Npgsql;
 
 namespace Benchmarks
 {
@@ -49,11 +53,21 @@ namespace Benchmarks
             services.AddSingleton<ApplicationDbSeeder>();
             services.AddEntityFrameworkSqlServer()
                 .AddDbContext<ApplicationDbContext>();
-
+            
             if (Scenarios.Any("Raw") || Scenarios.Any("Dapper"))
             {
-                // TODO: Add support for plugging in different DbProviderFactory implementations via configuration
-                services.AddSingleton<DbProviderFactory>(SqlClientFactory.Instance);
+                services.AddSingleton<DbProviderFactory>((provider) => {
+                    var settings = provider.GetRequiredService<IOptions<AppSettings>>().Value;
+
+                    if (settings.Database == DatabaseServer.PostgreSql)
+                    {
+                        return NpgsqlFactory.Instance;
+                    }
+                    else
+                    {
+                        return SqlClientFactory.Instance;
+                    }
+                });
             }
 
             if (Scenarios.Any("Ef"))
@@ -73,7 +87,12 @@ namespace Benchmarks
 
             if (Scenarios.Any("Fortunes"))
             {
-                services.AddWebEncoders();
+                var settings = new TextEncoderSettings(UnicodeRanges.BasicLatin, UnicodeRanges.Katakana, UnicodeRanges.Hiragana);
+                settings.AllowCharacter('\u2014');  // allow EM DASH through
+                services.AddWebEncoders((options) =>
+                {
+                    options.TextEncoderSettings = settings;
+                });
             }
 
             if (Scenarios.Any("Mvc"))
@@ -83,7 +102,7 @@ namespace Benchmarks
                     //.AddApplicationPart(typeof(Startup).GetTypeInfo().Assembly)
                     .AddControllersAsServices();
 
-                if (Scenarios.MvcJson)
+                if (Scenarios.MvcJson || Scenarios.Any("MvcDbSingle") || Scenarios.Any("MvcDbMulti"))
                 {
                     mvcBuilder.AddJsonFormatters();
                 }
@@ -99,8 +118,6 @@ namespace Benchmarks
 
         public void Configure(IApplicationBuilder app, ApplicationDbSeeder dbSeeder, ApplicationDbContext dbContext)
         {
-            app.UseErrorHandler();
-
             if (Scenarios.Plaintext)
             {
                 app.UsePlainText();
@@ -143,6 +160,22 @@ namespace Benchmarks
                 app.UseMultipleQueriesEf();
             }
 
+            // Multiple update endpoints
+            if (Scenarios.DbMultiUpdateRaw)
+            {
+                app.UseMultipleUpdatesRaw();
+            }
+
+            if (Scenarios.DbMultiUpdateDapper)
+            {
+                app.UseMultipleUpdatesDapper();
+            }
+
+            if (Scenarios.DbMultiUpdateEf)
+            {
+                app.UseMultipleUpdatesEf();
+            }
+
             // Fortunes endpoints
             if (Scenarios.DbFortunesRaw)
             {
@@ -182,4 +215,4 @@ namespace Benchmarks
             app.RunDebugInfoPage();
         }
     }
-}
+}

+ 0 - 3
frameworks/CSharp/aspnetcore/Benchmarks/appsettings.json

@@ -1,3 +0,0 @@
-{
-  "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-Benchmarks;Trusted_Connection=True;MultipleActiveResultSets=true"
-}

+ 4 - 0
frameworks/CSharp/aspnetcore/Benchmarks/appsettings.postgresql.json

@@ -0,0 +1,4 @@
+{
+  "ConnectionString": "Server={db_server_placeholder};Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;Maximum Pool Size=1024",
+  "Database": "postgresql"
+}

+ 3 - 1
frameworks/CSharp/aspnetcore/Benchmarks/project.json

@@ -25,7 +25,9 @@
     "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0-*",
     "Microsoft.Extensions.Configuration.Json": "1.0.0-*",
     "Microsoft.Extensions.Configuration.CommandLine": "1.0.0-*",
-    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-*"
+    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-*",
+    "Npgsql": "3.1.8-*",
+    "Npgsql.EntityFrameworkCore.PostgreSQL": "1.0.2-*" 
   },
   "frameworks": {
     "netcoreapp1.0": {

+ 132 - 1
frameworks/CSharp/aspnetcore/benchmark_config.json

@@ -39,6 +39,72 @@
       "notes": "",
       "versus": ""
     },
+    "middleware-raw": {
+      "setup_file": "setup-raw",
+      "db_url": "/db/raw",
+      "query_url": "/queries/raw?queries=",
+      "update_url": "/updates/raw?queries=",
+      "fortune_url": "/fortunes/raw",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "aspnetcore",
+      "language": "C#",
+      "orm": "Raw",
+      "platform": "NET",
+      "flavor": "CoreCLR",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "aspnetcore-linux",
+      "notes": "",
+      "versus": ""
+    },
+    "middleware-ef": {
+      "setup_file": "setup-ef",
+      "db_url": "/db/ef",
+      "query_url": "/queries/ef?queries=",
+      "update_url": "/updates/ef?queries=",
+      "fortune_url": "/fortunes/ef",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "aspnetcore",
+      "language": "C#",
+      "orm": "Full",
+      "platform": "NET",
+      "flavor": "CoreCLR",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "aspnetcore-linux",
+      "notes": "",
+      "versus": ""
+    },
+    "middleware-dapper": {
+      "setup_file": "setup-dapper",
+      "db_url": "/db/dapper",
+      "query_url": "/queries/dapper?queries=",
+      "update_url": "/updates/dapper?queries=",
+      "fortune_url": "/fortunes/dapper",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "aspnetcore",
+      "language": "C#",
+      "orm": "Micro",
+      "platform": "NET",
+      "flavor": "CoreCLR",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "aspnetcore-linux",
+      "notes": "",
+      "versus": ""
+    },
     "mvc-linux": {
       "setup_file": "setup-plaintext",
       "plaintext_url": "/mvc/plaintext",
@@ -77,7 +143,72 @@
       "notes": "",
       "versus": ""
     },
-
+    "mvc-raw": {
+      "setup_file": "setup-raw",
+      "db_url": "/mvc/db/raw",
+      "query_url": "/mvc/queries/raw?queries=",
+      "update_url": "/mvc/updates/raw?queries=",
+      "fortune_url": "/mvc/fortunes/raw",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "database": "Postgres",
+      "framework": "aspnetcore",
+      "language": "C#",
+      "orm": "Raw",
+      "platform": "NET",
+      "flavor": "CoreCLR",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "aspnetcore-linux",
+      "notes": "",
+      "versus": ""
+    },
+    "mvc-ef": {
+      "setup_file": "setup-ef",
+      "db_url": "/mvc/db/ef",
+      "query_url": "/mvc/queries/ef?queries=",
+      "update_url": "/mvc/updates/ef?queries=",
+      "fortune_url": "/mvc/fortunes/ef",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "database": "Postgres",
+      "framework": "aspnetcore",
+      "language": "C#",
+      "orm": "Full",
+      "platform": "NET",
+      "flavor": "CoreCLR",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "aspnetcore-linux",
+      "notes": "",
+      "versus": ""
+    },
+    "mvc-dapper": {
+      "setup_file": "setup-dapper",
+      "db_url": "/mvc/db/dapper",
+      "query_url": "/mvc/queries/dapper?queries=",
+      "update_url": "/mvc/updates/dapper?queries=",
+      "fortune_url": "/mvc/fortunes/dapper",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "database": "Postgres",
+      "framework": "aspnetcore",
+      "language": "C#",
+      "orm": "Micro",
+      "platform": "NET",
+      "flavor": "CoreCLR",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "aspnetcore-linux",
+      "notes": "",
+      "versus": ""
+    },
     "win": {
       "setup_file": "setup-windows",
       "json_url": "/json",

+ 2 - 0
frameworks/CSharp/aspnetcore/run-linux.sh

@@ -8,6 +8,8 @@ then
 fi
 
 cd Benchmarks
+cp appsettings.postgresql.json appsettings.json
+sed -i 's|{db_server_placeholder}|'"${DBHOST}"'|g' appsettings.json
 dotnet restore
 dotnet build -c Release -f netcoreapp1.0
 

+ 3 - 0
frameworks/CSharp/aspnetcore/setup-dapper.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+source run-linux.sh dapper $(($(nproc)/2))

+ 3 - 0
frameworks/CSharp/aspnetcore/setup-ef.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+source run-linux.sh ef $(($(nproc)/2))

+ 3 - 0
frameworks/CSharp/aspnetcore/setup-raw.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+source run-linux.sh raw $(($(nproc)/2))