public async Task OnDomainEventAsync(IDomainEventingContext context, TransferTransactionStartedDomainEvent @event, CancellationToken token = default)
        {
            var insertWithdrawSql = $"INSERT INTO `{Tables.AccountOutTransactions}` (`Id`, `Type`, `AccountId`, `AccountName`, `Bank`, `Amount`, `Status`, `Creator`, `CreatedTimestamp`) VALUES (@TransactionId, @Type, @AccountId, @AccountName, @Bank, @amount, @Status, @Creator, @CreatedTimestamp);";
            var insertDepositSql  = $"INSERT INTO `{Tables.AccountInTransactions}` (`Id`, `Type`, `AccountId`, `AccountName`, `Bank`, `Amount`, `Status`, `Creator`, `CreatedTimestamp`) VALUES (@TransactionId, @Type, @AccountId, @AccountName, @Bank, @amount, @Status, @Creator, @CreatedTimestamp);";

            await _db.ExecuteAsync(insertWithdrawSql, new
            {
                TransactionId    = @event.AggregateRootId.Id,
                Type             = AccountTransactionType.Transfer.ToString(),
                AccountId        = @event.SourceAccount.Id.Id,
                AccountName      = @event.SourceAccount.Name.Name,
                Bank             = @event.SourceAccount.Bank.Name,
                Amount           = @event.Money.Amount,
                Status           = TransferTransactionStatus.Started.ToString(),
                Creator          = "justmine",
                CreatedTimestamp = @event.Timestamp
            }, token);

            await _db.ExecuteAsync(insertDepositSql, new
            {
                TransactionId    = @event.AggregateRootId.Id,
                Type             = AccountTransactionType.Transfer.ToString(),
                AccountId        = @event.SinkAccount.Id.Id,
                AccountName      = @event.SinkAccount.Name.Name,
                Bank             = @event.SinkAccount.Bank.Name,
                Amount           = @event.Money.Amount,
                Status           = TransferTransactionStatus.Started.ToString(),
                Creator          = "justmine",
                CreatedTimestamp = @event.Timestamp
            }, token);
        }
Ejemplo n.º 2
0
        public async Task OnDomainEventAsync(IDomainEventingContext context, AccountOpenedDomainEvent @event, CancellationToken token = default)
        {
            var sql = $"INSERT INTO `{Tables.BankAccounts}` (`Id`, `Name`, `Bank`, `Balance`, `Creator`, `CreatedTimestamp`) VALUES (@Id, @Name, @Bank, @Balance, @Creator, @CreatedTimestamp);";

            var parameter = new
            {
                Id               = @event.AggregateRootId.Id,
                Name             = @event.AccountName.Name,
                Bank             = @event.Bank.Name,
                Balance          = @event.InitialBalance.Amount,
                Creator          = "justmine",
                CreatedTimestamp = @event.Timestamp
            };

            await _db.ExecuteAsync(sql, parameter, token);
        }
        public async Task <AggregateRootCheckpointResult> AppendAsync(
            AggregateRootCheckpoint <TPayload> checkpoint,
            CancellationToken token = default)
        {
            PreConditions.NotNull(checkpoint, nameof(checkpoint));
            PreConditions.NotNullOrEmpty(checkpoint.AggregateRootId, nameof(checkpoint.AggregateRootId));
            PreConditions.NotNull(checkpoint.AggregateRootType, nameof(checkpoint.AggregateRootType));
            PreConditions.Nonnegative(checkpoint.AggregateRootGeneration, nameof(checkpoint.AggregateRootGeneration));
            PreConditions.Nonnegative(checkpoint.AggregateRootVersion, nameof(checkpoint.AggregateRootVersion));

            var record     = AggregateRootCheckpointRecordPortAdapter.ToRecord(checkpoint, _binarySerializer);
            var parameters = DbParameterProvider.ReflectionParameters(record);

            var tables = _options.Tables.DomainModelOptions;

            var insertCheckpointIndexSql = $"INSERT INTO `{tables.AggregateRootCheckpointIndices}`(`AggregateRootId`,`AggregateRootType`,`AggregateRootVersion`,`AggregateRootGeneration`,`CreatedTimestamp`) VALUES(@AggregateRootId,@AggregateRootType,@AggregateRootVersion,@AggregateRootGeneration,@CreatedTimestamp)";
            var insertCheckpointSql      = $"INSERT INTO `{tables.AggregateRootCheckpoints}`(`AggregateRootId`,`Payload`) VALUES (@AggregateRootId,@Payload)";

            var maxNumErrorTries = 3;
            var maxExecutionTime = TimeSpan.FromSeconds(3);
            var expectedRows     = 2;

            bool ErrorFilter(Exception exc, int attempt)
            {
                if (exc is MySqlException inner &&
                    inner.HasDuplicateEntry())
                {
                    _logger.LogWarning($"{record.AggregateRootType}: [Ignored]find duplicated aggregate root checkpoint from mysql state backend:{inner.Message}.");

                    return(false);
                }

                _logger.LogError($"Append aggregate root checkpoint has unknown exception: {LogFormatter.PrintException(exc)}.");

                return(true);
            }

            var affectedRows = await AsyncExecutorWithRetries.ExecuteWithRetriesAsync(async attempt =>
            {
                return(await _db.ExecuteAsync(
                           $"{insertCheckpointIndexSql};{insertCheckpointSql};",
                           command => command.Parameters.AddRange(parameters),
                           token));
            }, maxNumErrorTries, ErrorFilter, maxExecutionTime).ConfigureAwait(false);

            if (affectedRows != expectedRows)
            {
                return(AggregateRootCheckpointResult.StorageFailed(record.AggregateRootId,
                                                                   $"The affected rows returned MySql state backend is incorrect when append aggregate root checkpoint, expected: {expectedRows}, actual: {affectedRows}."));
            }

            return(AggregateRootCheckpointResult.StorageSucceed(record.AggregateRootId));
        }
Ejemplo n.º 4
0
        public async Task <DomainEventResult> AppendAsync(
            IDomainEvent @event,
            CancellationToken token = default)
        {
            if ([email protected]())
            {
                return(DomainEventResult.StorageFailed(@event.Id, $"The domain event: [{@event.Print()}] cannot be stored mysql state backend."));
            }

            var domainEventRecord = DomainEventRecordPortAdapter.ToDomainEventRecord(@event, _binarySerializer);
            var parameters        = DbParameterProvider.ReflectionParameters(domainEventRecord);

            var tables = _options.Tables.DomainEventOptions;
            var insertDomainEventIndexSql = $"INSERT INTO `{tables.DomainEventIndices}`(`DomainCommandId`,`DomainCommandType`,`DomainCommandVersion`,`AggregateRootId`,`AggregateRootType`,`AggregateRootVersion`,`AggregateRootGeneration`,`DomainEventId`,`DomainEventType`,`DomainEventVersion`,`DomainEventPayloadBytes`,`CreatedTimestamp`) VALUES(@DomainCommandId,@DomainCommandType,@DomainCommandVersion,@AggregateRootId,@AggregateRootType,@AggregateRootVersion,@AggregateRootGeneration,@DomainEventId,@DomainEventType,@DomainEventVersion,@DomainEventPayloadBytes,@CreatedTimestamp)";
            var insertDomainEventSql      = $"INSERT INTO `{tables.DomainEvents}`(`DomainEventId`,`Payload`) VALUES (@DomainEventId,@Payload)";

            var maxNumErrorTries = 3;
            var maxExecutionTime = TimeSpan.FromSeconds(3);
            var expectedRows     = 2;

            bool ErrorFilter(Exception exc, int attempt)
            {
                if (exc is MySqlException inner &&
                    inner.HasDuplicateEntry())
                {
                    _logger.LogWarning($"{domainEventRecord.AggregateRootType}: [Ignored]find duplicated domain event from mysql state backend:{inner.Message}.");

                    return(false);
                }

                _logger.LogError($"Append domain event has unknown exception: {LogFormatter.PrintException(exc)}.");

                return(true);
            }

            var affectedRows = await AsyncExecutorWithRetries.ExecuteWithRetriesAsync(async attempt =>
            {
                return(await _db.ExecuteAsync(
                           $"{insertDomainEventIndexSql};{insertDomainEventSql};",
                           command => command.Parameters.AddRange(parameters),
                           token));
            }, maxNumErrorTries, ErrorFilter, maxExecutionTime).ConfigureAwait(false);

            if (affectedRows != expectedRows)
            {
                return(DomainEventResult.StorageFailed(@event.Id,
                                                       $"The affected rows returned MySql state backend is incorrect, expected: {expectedRows}, actual: {affectedRows}."));
            }

            return(DomainEventResult.StorageSucceed(@event.Id));
        }
Ejemplo n.º 5
0
 /// <summary>
 /// Uses <see cref="IRelationalDbStorage"/> with <see cref="DbExtensions.ReflectionSelector{TResult}(System.Data.IDataRecord)"/>.
 /// </summary>
 /// <param name="storage">The storage to use.</param>
 /// <param name="query">Executes a given statement. Especially intended to use with <em>INSERT</em>, <em>UPDATE</em>, <em>DELETE</em> or <em>DDL</em> queries.</param>
 /// <param name="parameters">Adds parameters to the query. Parameter names must match those defined in the query.</param>
 /// <param name="cancellationToken">The cancellation token. Defaults to <see cref="CancellationToken.None"/>.</param>
 /// <returns>Affected rows count.</returns>
 /// <example>This uses reflection to provide parameters to an execute
 /// query that reads only affected rows count if available.
 /// <code>
 /// //Here reflection (<seealso cref="DbExtensions.ReflectionParameterProvider{T}(IDbCommand, T, IReadOnlyDictionary{string, string})"/>)
 /// is used to match parameter names as well as to read back the results (<seealso cref="DbExtensions.ReflectionSelector{TResult}(IDataRecord)"/>).
 /// var query = "IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @tname) CREATE TABLE Test(Id INT PRIMARY KEY IDENTITY(1, 1) NOT NULL);"
 /// await db.ExecuteAsync(query, new { tname = "test_table" });
 /// </code>
 /// </example>
 public static Task <int> ExecuteAsync(
     this IRelationalDbStorage storage,
     string query,
     object parameters,
     CancellationToken cancellationToken = default)
 {
     return(storage.ExecuteAsync(query, command =>
     {
         if (parameters != null)
         {
             command.ReflectionParameterProvider(parameters);
         }
     }, cancellationToken));
 }
Ejemplo n.º 6
0
        /// <summary>
        /// Executes a multi-record insert query clause with <em>SELECT UNION ALL</em>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="storage">The storage to use.</param>
        /// <param name="tableName">The table name to against which to execute the query.</param>
        /// <param name="parameters">The parameters to insert.</param>
        /// <param name="cancellationToken">The cancellation token. Defaults to <see cref="CancellationToken.None"/>.</param>
        /// <param name="nameMap">If provided, maps property names from <typeparamref name="T"/> to ones provided in the map.</param>
        /// <param name="onlyOnceColumns">If given, SQL parameter values for the given <typeparamref name="T"/> property types are generated only once. Effective only when <paramref name="useSqlParams"/> is <em>TRUE</em>.</param>
        /// <param name="useSqlParams"><em>TRUE</em> if the query should be in parameterized form. <em>FALSE</em> otherwise.</param>
        /// <returns>The rows affected.</returns>
        public static Task <int> ExecuteMultipleInsertIntoAsync <T>(
            this IRelationalDbStorage storage,
            string tableName,
            IEnumerable <T> parameters,
            CancellationToken cancellationToken          = default,
            IReadOnlyDictionary <string, string> nameMap = null,
            IEnumerable <string> onlyOnceColumns         = null,
            bool useSqlParams = true)
        {
            if (string.IsNullOrWhiteSpace(tableName))
            {
                throw new ArgumentException("The name must be a legal SQL table name", nameof(tableName));
            }

            if (parameters == null)
            {
                throw new ArgumentNullException(nameof(parameters));
            }

            var storageConstants = DbConstantsStore.GetDbConstants(storage.InvariantName);

            var startEscapeIndicator = storageConstants.StartEscapeIndicator;
            var endEscapeIndicator   = storageConstants.EndEscapeIndicator;

            //SqlParameters map is needed in case the query needs to be parameterized in order to avoid two
            //reflection passes as first a query needs to be constructed and after that when a database
            //command object has been created, parameters need to be provided to them.
            var          sqlParameters            = new Dictionary <string, object>();
            const string insertIntoValuesTemplate = "INSERT INTO {0} ({1}) SELECT {2};";
            var          columns = string.Empty;
            var          values  = new List <string>();

            if (parameters.Any())
            {
                //Type and property information are the same for all of the objects.
                //The following assumes the property names will be retrieved in the same
                //order as is the index iteration done.
                var onlyOnceRow = new List <string>();
                var properties  = parameters.First().GetType().GetProperties();

                columns = string.Join(",", nameMap == null
                    ? properties.Select(pn =>
                                        $"{startEscapeIndicator}{pn.Name}{endEscapeIndicator}")
                    : properties.Select(pn =>
                                        $"{startEscapeIndicator}{(nameMap.ContainsKey(pn.Name) ? nameMap[pn.Name] : pn.Name)}{endEscapeIndicator}"));

                if (onlyOnceColumns != null && onlyOnceColumns.Any())
                {
                    var onlyOnceProperties = properties.Where(pn => onlyOnceColumns.Contains(pn.Name)).Select(pn => pn).ToArray();
                    var onlyOnceData       = parameters.First();
                    foreach (var t in onlyOnceProperties)
                    {
                        var currentProperty = t;
                        var parameterValue  = currentProperty.GetValue(onlyOnceData, null);
                        if (useSqlParams)
                        {
                            var parameterName = $"@{(nameMap.ContainsKey(t.Name) ? nameMap[t.Name] : t.Name)}";
                            onlyOnceRow.Add(parameterName);
                            sqlParameters.Add(parameterName, parameterValue);
                        }
                        else
                        {
                            onlyOnceRow.Add(string.Format(AdoNetFormatProvider, "{0}", parameterValue));
                        }
                    }
                }

                var dataRows        = new List <string>();
                var multiProperties = onlyOnceColumns == null ? properties : properties.Where(pn => !onlyOnceColumns.Contains(pn.Name)).Select(pn => pn).ToArray();
                var parameterCount  = 0;

                foreach (var row in parameters)
                {
                    foreach (var currentProperty in multiProperties)
                    {
                        var parameterValue = currentProperty.GetValue(row, null);
                        if (useSqlParams)
                        {
                            var parameterName = string.Format(IndexedParameterTemplate, parameterCount);
                            dataRows.Add(parameterName);
                            sqlParameters.Add(parameterName, parameterValue);
                            ++parameterCount;
                        }
                        else
                        {
                            dataRows.Add(string.Format(AdoNetFormatProvider, "{0}", parameterValue));
                        }
                    }

                    values.Add($"{string.Join(",", onlyOnceRow.Concat(dataRows))}");
                    dataRows.Clear();
                }
            }

            var query = string.Format(insertIntoValuesTemplate, tableName, columns, string.Join(storageConstants.UnionAllSelectTemplate, values));

            return(storage.ExecuteAsync(query, command =>
            {
                if (!useSqlParams)
                {
                    return;
                }

                foreach (var sp in sqlParameters)
                {
                    var p = command.CreateParameter();
                    p.ParameterName = sp.Key;
                    p.Value = sp.Value ?? DBNull.Value;
                    p.Direction = ParameterDirection.Input;
                    command.Parameters.Add(p);
                }
            }, cancellationToken));
        }