Browse Source

Refactor postgresql module for new transaction mode

master
Savorboard 6 years ago
parent
commit
c69d1f6f98
11 changed files with 194 additions and 136 deletions
  1. +5
    -2
      src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs
  2. +1
    -0
      src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs
  3. +0
    -85
      src/DotNetCore.CAP.PostgreSql/CapPublisher.cs
  4. +93
    -0
      src/DotNetCore.CAP.PostgreSql/ICapPublisher.PostgreSql.cs
  5. +72
    -0
      src/DotNetCore.CAP.PostgreSql/ICapTransaction.PostgreSql.cs
  6. +0
    -0
      src/DotNetCore.CAP.PostgreSql/IMonitoringApi.PostgreSql.cs
  7. +2
    -4
      src/DotNetCore.CAP.PostgreSql/IStorage.PostgreSql.cs
  8. +7
    -7
      src/DotNetCore.CAP.PostgreSql/IStorageConnection.PostgreSql.cs
  9. +1
    -27
      src/DotNetCore.CAP.PostgreSql/IStorageTransaction.PostgreSql.cs
  10. +13
    -9
      test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs
  11. +0
    -2
      test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs

+ 5
- 2
src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlCapOptionsExtension.cs View File

@@ -24,9 +24,12 @@ namespace DotNetCore.CAP
services.AddSingleton<CapDatabaseStorageMarkerService>(); services.AddSingleton<CapDatabaseStorageMarkerService>();
services.AddSingleton<IStorage, PostgreSqlStorage>(); services.AddSingleton<IStorage, PostgreSqlStorage>();
services.AddSingleton<IStorageConnection, PostgreSqlStorageConnection>(); services.AddSingleton<IStorageConnection, PostgreSqlStorageConnection>();
services.AddScoped<ICapPublisher, CapPublisher>();
services.AddScoped<ICallbackPublisher, CapPublisher>();

services.AddScoped<ICapPublisher, PostgreSqlPublisher>();
services.AddScoped<ICallbackPublisher, PostgreSqlPublisher>();

services.AddTransient<ICollectProcessor, PostgreSqlCollectProcessor>(); services.AddTransient<ICollectProcessor, PostgreSqlCollectProcessor>();
services.AddTransient<CapTransactionBase, PostgreSqlCapTransaction>();


AddSingletonPostgreSqlOptions(services); AddSingletonPostgreSqlOptions(services);
} }


+ 1
- 0
src/DotNetCore.CAP.PostgreSql/CAP.PostgreSqlOptions.cs View File

@@ -1,6 +1,7 @@
// Copyright (c) .NET Core Community. All rights reserved. // Copyright (c) .NET Core Community. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information. // Licensed under the MIT License. See License.txt in the project root for license information.


// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP namespace DotNetCore.CAP
{ {
public class PostgreSqlOptions : EFOptions public class PostgreSqlOptions : EFOptions


+ 0
- 85
src/DotNetCore.CAP.PostgreSql/CapPublisher.cs View File

@@ -1,85 +0,0 @@
// Copyright (c) .NET Core Community. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Data;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Abstractions;
using DotNetCore.CAP.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;
using Npgsql;

namespace DotNetCore.CAP.PostgreSql
{
public class CapPublisher : CapPublisherBase, ICallbackPublisher
{
private readonly DbContext _dbContext;
private readonly PostgreSqlOptions _options;

public CapPublisher(ILogger<CapPublisher> logger, IDispatcher dispatcher,
IServiceProvider provider, PostgreSqlOptions options)
: base(logger, dispatcher)
{
ServiceProvider = provider;
_options = options;

if (_options.DbContextType != null)
{
IsUsingEF = true;
_dbContext = (DbContext) ServiceProvider.GetService(_options.DbContextType);
}
}

public async Task PublishCallbackAsync(CapPublishedMessage message)
{
using (var conn = new NpgsqlConnection(_options.ConnectionString))
{
var id = await conn.ExecuteScalarAsync<int>(PrepareSql(), message);
message.Id = id;
Enqueue(message);
}
}

protected override void PrepareConnectionForEF()
{
DbConnection = _dbContext.Database.GetDbConnection();
var dbContextTransaction = _dbContext.Database.CurrentTransaction;
var dbTrans = dbContextTransaction?.GetDbTransaction();
//DbTransaction is dispose in original
if (dbTrans?.Connection == null)
{
IsCapOpenedTrans = true;
dbContextTransaction?.Dispose();
dbContextTransaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted);
dbTrans = dbContextTransaction.GetDbTransaction();
}

DbTransaction = dbTrans;
}

protected override int Execute(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message)
{
return dbConnection.ExecuteScalar<int>(PrepareSql(), message, dbTransaction);
}

protected override Task<int> ExecuteAsync(IDbConnection dbConnection, IDbTransaction dbTransaction,
CapPublishedMessage message)
{
return dbConnection.ExecuteScalarAsync<int>(PrepareSql(), message, dbTransaction);
}

#region private methods

private string PrepareSql()
{
return
$"INSERT INTO \"{_options.Schema}\".\"published\" (\"Name\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName) RETURNING \"Id\";";
}

#endregion private methods
}
}

+ 93
- 0
src/DotNetCore.CAP.PostgreSql/ICapPublisher.PostgreSql.cs View File

@@ -0,0 +1,93 @@
// Copyright (c) .NET Core Community. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP.Abstractions;
using DotNetCore.CAP.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Npgsql;

namespace DotNetCore.CAP.PostgreSql
{
public class PostgreSqlPublisher : CapPublisherBase, ICallbackPublisher
{
private readonly DbContext _dbContext;
private readonly PostgreSqlOptions _options;
private readonly bool _isUsingEF;

private NpgsqlConnection _connection;

public PostgreSqlPublisher(IServiceProvider provider, PostgreSqlOptions options): base(provider)
{
_options = options;

if (_options.DbContextType == null)
{
return;
}

_isUsingEF = true;
_dbContext = (DbContext)ServiceProvider.GetService(_options.DbContextType);
}

public async Task PublishCallbackAsync(CapPublishedMessage message)
{
await PublishAsyncInternal(message);
}

protected override Task ExecuteAsync(CapPublishedMessage message, ICapTransaction transaction,
CancellationToken cancel = default(CancellationToken))
{
var dbTrans = transaction.DbTransaction as IDbTransaction;
if (dbTrans == null && transaction.DbTransaction is IDbContextTransaction dbContextTrans)
{
dbTrans = dbContextTrans.GetDbTransaction();
}
var conn = dbTrans?.Connection;
return conn.ExecuteAsync(PrepareSql(), message, dbTrans);
}

protected override object GetDbTransaction()
{
if (_isUsingEF)
{
var dbContextTransaction = _dbContext.Database.CurrentTransaction;
if (dbContextTransaction == null)
{
return InitDbConnection();
}

return dbContextTransaction;
}

return InitDbConnection();
}

#region private methods

private string PrepareSql()
{
return
$"INSERT INTO \"{_options.Schema}\".\"published\" (\"Id\",\"Name\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Id,@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
}

private IDbTransaction InitDbConnection()
{
_connection = new NpgsqlConnection(_options.ConnectionString);
_connection.Open();
return _connection.BeginTransaction(IsolationLevel.ReadCommitted);
}
#endregion private methods

public void Dispose()
{
_dbContext?.Dispose();
_connection?.Dispose();
}
}
}

+ 72
- 0
src/DotNetCore.CAP.PostgreSql/ICapTransaction.PostgreSql.cs View File

@@ -0,0 +1,72 @@
using System.Data;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Storage;

// ReSharper disable once CheckNamespace
namespace DotNetCore.CAP
{
public class PostgreSqlCapTransaction : CapTransactionBase
{
public PostgreSqlCapTransaction(IDispatcher dispatcher) : base(dispatcher) { }

public override void Commit()
{
Debug.Assert(DbTransaction != null);

switch (DbTransaction)
{
case IDbTransaction dbTransaction:
dbTransaction.Commit();
break;
case IDbContextTransaction dbContextTransaction:
dbContextTransaction.Commit();
break;
}

Flush();
}

public override void Rollback()
{
Debug.Assert(DbTransaction != null);

switch (DbTransaction)
{
case IDbTransaction dbTransaction:
dbTransaction.Rollback();
break;
case IDbContextTransaction dbContextTransaction:
dbContextTransaction.Rollback();
break;
}
}

public override void Dispose()
{
(DbTransaction as IDbTransaction)?.Dispose();
}
}

public static class CapTransactionExtensions
{
public static ICapTransaction Begin(this ICapTransaction transaction,
IDbTransaction dbTransaction, bool autoCommit = false)
{
transaction.DbTransaction = dbTransaction;
transaction.AutoCommit = autoCommit;

return transaction;
}

public static ICapTransaction Begin(this ICapTransaction transaction,
IDbContextTransaction dbTransaction, bool autoCommit = false)
{

transaction.DbTransaction = dbTransaction;
transaction.AutoCommit = autoCommit;

return transaction;
}
}

}

src/DotNetCore.CAP.PostgreSql/PostgreSqlMonitoringApi.cs → src/DotNetCore.CAP.PostgreSql/IMonitoringApi.PostgreSql.cs View File


src/DotNetCore.CAP.PostgreSql/PostgreSqlStorage.cs → src/DotNetCore.CAP.PostgreSql/IStorage.PostgreSql.cs View File

@@ -100,10 +100,8 @@ namespace DotNetCore.CAP.PostgreSql
var batchSql = $@" var batchSql = $@"
CREATE SCHEMA IF NOT EXISTS ""{schema}""; CREATE SCHEMA IF NOT EXISTS ""{schema}"";


DROP TABLE IF EXISTS ""{schema}"".""queue"";

CREATE TABLE IF NOT EXISTS ""{schema}"".""received""( CREATE TABLE IF NOT EXISTS ""{schema}"".""received""(
""Id"" SERIAL PRIMARY KEY NOT NULL,
""Id"" BIGINT PRIMARY KEY NOT NULL,
""Name"" VARCHAR(200) NOT NULL, ""Name"" VARCHAR(200) NOT NULL,
""Group"" VARCHAR(200) NULL, ""Group"" VARCHAR(200) NULL,
""Content"" TEXT NULL, ""Content"" TEXT NULL,
@@ -114,7 +112,7 @@ CREATE TABLE IF NOT EXISTS ""{schema}"".""received""(
); );


CREATE TABLE IF NOT EXISTS ""{schema}"".""published""( CREATE TABLE IF NOT EXISTS ""{schema}"".""published""(
""Id"" SERIAL PRIMARY KEY NOT NULL,
""Id"" BIGINT PRIMARY KEY NOT NULL,
""Name"" VARCHAR(200) NOT NULL, ""Name"" VARCHAR(200) NOT NULL,
""Content"" TEXT NULL, ""Content"" TEXT NULL,
""Retries"" INT NOT NULL, ""Retries"" INT NOT NULL,

src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageConnection.cs → src/DotNetCore.CAP.PostgreSql/IStorageConnection.PostgreSql.cs View File

@@ -28,7 +28,7 @@ namespace DotNetCore.CAP.PostgreSql
return new PostgreSqlStorageTransaction(this); return new PostgreSqlStorageTransaction(this);
} }


public async Task<CapPublishedMessage> GetPublishedMessageAsync(int id)
public async Task<CapPublishedMessage> GetPublishedMessageAsync(long id)
{ {
var sql = $"SELECT * FROM \"{Options.Schema}\".\"published\" WHERE \"Id\"={id} FOR UPDATE SKIP LOCKED"; var sql = $"SELECT * FROM \"{Options.Schema}\".\"published\" WHERE \"Id\"={id} FOR UPDATE SKIP LOCKED";


@@ -50,7 +50,7 @@ namespace DotNetCore.CAP.PostgreSql
} }
} }


public async Task<int> StoreReceivedMessageAsync(CapReceivedMessage message)
public void StoreReceivedMessage(CapReceivedMessage message)
{ {
if (message == null) if (message == null)
{ {
@@ -58,15 +58,15 @@ namespace DotNetCore.CAP.PostgreSql
} }


var sql = var sql =
$"INSERT INTO \"{Options.Schema}\".\"received\"(\"Name\",\"Group\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName) RETURNING \"Id\";";
$"INSERT INTO \"{Options.Schema}\".\"received\"(\"Id\",\"Name\",\"Group\",\"Content\",\"Retries\",\"Added\",\"ExpiresAt\",\"StatusName\")VALUES(@Id,@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName) RETURNING \"Id\";";


using (var connection = new NpgsqlConnection(Options.ConnectionString)) using (var connection = new NpgsqlConnection(Options.ConnectionString))
{ {
return await connection.ExecuteScalarAsync<int>(sql, message);
connection.Execute(sql, message);
} }
} }


public async Task<CapReceivedMessage> GetReceivedMessageAsync(int id)
public async Task<CapReceivedMessage> GetReceivedMessageAsync(long id)
{ {
var sql = $"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"Id\"={id} FOR UPDATE SKIP LOCKED"; var sql = $"SELECT * FROM \"{Options.Schema}\".\"received\" WHERE \"Id\"={id} FOR UPDATE SKIP LOCKED";
using (var connection = new NpgsqlConnection(Options.ConnectionString)) using (var connection = new NpgsqlConnection(Options.ConnectionString))
@@ -90,7 +90,7 @@ namespace DotNetCore.CAP.PostgreSql
{ {
} }


public bool ChangePublishedState(int messageId, string state)
public bool ChangePublishedState(long messageId, string state)
{ {
var sql = var sql =
$"UPDATE \"{Options.Schema}\".\"published\" SET \"Retries\"=\"Retries\"+1,\"ExpiresAt\"=NULL,\"StatusName\" = '{state}' WHERE \"Id\"={messageId}"; $"UPDATE \"{Options.Schema}\".\"published\" SET \"Retries\"=\"Retries\"+1,\"ExpiresAt\"=NULL,\"StatusName\" = '{state}' WHERE \"Id\"={messageId}";
@@ -101,7 +101,7 @@ namespace DotNetCore.CAP.PostgreSql
} }
} }


public bool ChangeReceivedState(int messageId, string state)
public bool ChangeReceivedState(long messageId, string state)
{ {
var sql = var sql =
$"UPDATE \"{Options.Schema}\".\"received\" SET \"Retries\"=\"Retries\"+1,\"ExpiresAt\"=NULL,\"StatusName\" = '{state}' WHERE \"Id\"={messageId}"; $"UPDATE \"{Options.Schema}\".\"received\" SET \"Retries\"=\"Retries\"+1,\"ExpiresAt\"=NULL,\"StatusName\" = '{state}' WHERE \"Id\"={messageId}";

src/DotNetCore.CAP.PostgreSql/PostgreSqlStorageTransaction.cs → src/DotNetCore.CAP.PostgreSql/IStorageTransaction.PostgreSql.cs View File

@@ -35,9 +35,7 @@ namespace DotNetCore.CAP.PostgreSql
} }


var sql = var sql =
$@"UPDATE ""{
_schema
}"".""published"" SET ""Retries""=@Retries,""Content""= @Content,""ExpiresAt""=@ExpiresAt,""StatusName""=@StatusName WHERE ""Id""=@Id;";
$@"UPDATE ""{_schema}"".""published"" SET ""Retries""=@Retries,""Content""= @Content,""ExpiresAt""=@ExpiresAt,""StatusName""=@StatusName WHERE ""Id""=@Id;";
_dbConnection.Execute(sql, message, _dbTransaction); _dbConnection.Execute(sql, message, _dbTransaction);
} }


@@ -66,29 +64,5 @@ namespace DotNetCore.CAP.PostgreSql
_dbTransaction.Dispose(); _dbTransaction.Dispose();
_dbConnection.Dispose(); _dbConnection.Dispose();
} }

public void EnqueueMessage(CapPublishedMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}

var sql = $@"INSERT INTO ""{_schema}"".""queue"" values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Publish},
_dbTransaction);
}

public void EnqueueMessage(CapReceivedMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}

var sql = $@"INSERT INTO ""{_schema}"".""queue"" values(@MessageId,@MessageType);";
_dbConnection.Execute(sql, new CapQueue {MessageId = message.Id, MessageType = MessageType.Subscribe},
_dbTransaction);
}
} }
} }

+ 13
- 9
test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageConnectionTest.cs View File

@@ -22,17 +22,18 @@ namespace DotNetCore.CAP.PostgreSql.Test
[Fact] [Fact]
public async Task GetPublishedMessageAsync_Test() public async Task GetPublishedMessageAsync_Test()
{ {
var sql = @"INSERT INTO ""cap"".""published""(""Name"",""Content"",""Retries"",""Added"",""ExpiresAt"",""StatusName"") VALUES(@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName) RETURNING ""Id"";";
var sql = @"INSERT INTO ""cap"".""published""(""Id"",""Name"",""Content"",""Retries"",""Added"",""ExpiresAt"",""StatusName"") VALUES(@Id,@Name,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
var insertedId = SnowflakeId.Default().NextId();
var publishMessage = new CapPublishedMessage var publishMessage = new CapPublishedMessage
{ {
Id = insertedId,
Name = "PostgreSqlStorageConnectionTest", Name = "PostgreSqlStorageConnectionTest",
Content = "", Content = "",
StatusName = StatusName.Scheduled StatusName = StatusName.Scheduled
}; };
var insertedId = default(int);
using (var connection = ConnectionUtil.CreateConnection()) using (var connection = ConnectionUtil.CreateConnection())
{ {
insertedId = connection.QueryFirst<int>(sql, publishMessage);
await connection.ExecuteAsync(sql, publishMessage);
} }
var message = await _storage.GetPublishedMessageAsync(insertedId); var message = await _storage.GetPublishedMessageAsync(insertedId);
Assert.NotNull(message); Assert.NotNull(message);
@@ -41,10 +42,11 @@ namespace DotNetCore.CAP.PostgreSql.Test
} }


[Fact] [Fact]
public async Task StoreReceivedMessageAsync_Test()
public void StoreReceivedMessageAsync_Test()
{ {
var receivedMessage = new CapReceivedMessage var receivedMessage = new CapReceivedMessage
{ {
Id = SnowflakeId.Default().NextId(),
Name = "PostgreSqlStorageConnectionTest", Name = "PostgreSqlStorageConnectionTest",
Content = "", Content = "",
Group = "mygroup", Group = "mygroup",
@@ -54,7 +56,7 @@ namespace DotNetCore.CAP.PostgreSql.Test
Exception exception = null; Exception exception = null;
try try
{ {
await _storage.StoreReceivedMessageAsync(receivedMessage);
_storage.StoreReceivedMessage(receivedMessage);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -67,19 +69,21 @@ namespace DotNetCore.CAP.PostgreSql.Test
public async Task GetReceivedMessageAsync_Test() public async Task GetReceivedMessageAsync_Test()
{ {
var sql = $@" var sql = $@"
INSERT INTO ""cap"".""received""(""Name"",""Group"",""Content"",""Retries"",""Added"",""ExpiresAt"",""StatusName"")
VALUES(@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName) RETURNING ""Id"";";
INSERT INTO ""cap"".""received""(""Id"",""Name"",""Group"",""Content"",""Retries"",""Added"",""ExpiresAt"",""StatusName"")
VALUES(@Id,@Name,@Group,@Content,@Retries,@Added,@ExpiresAt,@StatusName);";
var insertedId = SnowflakeId.Default().NextId();
var receivedMessage = new CapReceivedMessage var receivedMessage = new CapReceivedMessage
{ {
Id= insertedId,
Name = "PostgreSqlStorageConnectionTest", Name = "PostgreSqlStorageConnectionTest",
Content = "", Content = "",
Group = "mygroup", Group = "mygroup",
StatusName = StatusName.Scheduled StatusName = StatusName.Scheduled
}; };
var insertedId = default(int);
using (var connection = ConnectionUtil.CreateConnection()) using (var connection = ConnectionUtil.CreateConnection())
{ {
insertedId = connection.QueryFirst<int>(sql, receivedMessage);
await connection.ExecuteAsync(sql, receivedMessage);
} }


var message = await _storage.GetReceivedMessageAsync(insertedId); var message = await _storage.GetReceivedMessageAsync(insertedId);


+ 0
- 2
test/DotNetCore.CAP.PostgreSql.Test/PostgreSqlStorageTest.cs View File

@@ -6,13 +6,11 @@ namespace DotNetCore.CAP.PostgreSql.Test
[Collection("postgresql")] [Collection("postgresql")]
public class SqlServerStorageTest : DatabaseTestHost public class SqlServerStorageTest : DatabaseTestHost
{ {
private readonly string _dbName;
private readonly string _masterDbConnectionString; private readonly string _masterDbConnectionString;
private readonly string _dbConnectionString; private readonly string _dbConnectionString;


public SqlServerStorageTest() public SqlServerStorageTest()
{ {
_dbName = ConnectionUtil.GetDatabaseName();
_masterDbConnectionString = ConnectionUtil.GetMasterConnectionString(); _masterDbConnectionString = ConnectionUtil.GetMasterConnectionString();
_dbConnectionString = ConnectionUtil.GetConnectionString(); _dbConnectionString = ConnectionUtil.GetConnectionString();
} }


Loading…
Cancel
Save