dotnet-starter-kit icon indicating copy to clipboard operation
dotnet-starter-kit copied to clipboard

How to use transactions

Open kienxuandaoit89 opened this issue 2 years ago • 12 comments

Is your feature request related to a problem? Please describe.

How to use transactions with this framework? (example CRUD is used IRepositoryWithEvents)

kienxuandaoit89 avatar Jul 07 '22 09:07 kienxuandaoit89

Yes I want to know about this as well. @MikaelHild @iammukeshm could you please suggest how to implement transactionspipeline in dotnet-web-api????

Sarmadjavediqbal avatar May 01 '23 08:05 Sarmadjavediqbal

Also suggest which nuget package to use for transactions.

Sarmadjavediqbal avatar May 01 '23 08:05 Sarmadjavediqbal

@kienxuandaoit89 you raised this issue last year. Have you found the solution yet???

Sarmadjavediqbal avatar May 01 '23 08:05 Sarmadjavediqbal

@iammukeshm

Sarmadjavediqbal avatar May 01 '23 08:05 Sarmadjavediqbal

Ardalis specification does not include transactions but can be implemented by using the dbContext, i.e. you could implement your own Repository for the purpose.

See this for an example https://github.com/ardalis/CleanArchitecture/issues/327#issuecomment-1121167844 and read more here: https://github.com/ardalis/CleanArchitecture/issues/327

MikaelHild avatar May 02 '23 06:05 MikaelHild

@MikaelHild @iammukeshm I tried to implement unitOfWork pattern but I am getting following error,

error:

{ "messages": [], "source": "Microsoft.Data.SqlClient.SqlCommand+<>c", "exception": "BeginExecuteReader requires the command to have a transaction when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized.", "errorId": "6d4329bc-c24f-4852-ba88-35caf4b9ad6d", "supportMessage": "Provide the ErrorId 6d4329bc-c24f-4852-ba88-35caf4b9ad6d to the support team for further analysis.", "statusCode": 500 }

Following is my Code:

using Mapster;
using Microsoft.Data.SqlClient;
using API.Application.Common.Custom.IUnitOfWork;
using API.Domain.MyAPI.Entities;
using System.Data;
using System.Transactions;

namespace API.Application.MyAPI.Challan.Commands.Update;
public class UpdateChallanRequest : IRequest<ChallanDto>
{
    public int Id { get; set; }
    public decimal Property1 { get; set; }
    public DateTime Property2 { get; set; }
}

public class UpdateChallanRequestValidator : CustomValidator<UpdateChallanRequest>
{
    public UpdateChallanRequestValidator()
    {
    }
}

internal class UpdateChallanRequestHandler : IRequestHandler<UpdateChallanRequest, ChallanDto>
{
    private readonly IRepositoryWithEvents<TrnsChallanGeneration> _repos;
    private readonly IDapperRepository _repository;
    private readonly IUnitOfWork _unitOfWork;
    private IDbTransaction _transaction;
    private IStringLocalizer<(someClass1, someClass2)> _localizer;

    public UpdateChallanRequestHandler(IDapperRepository repository, IUnitOfWork unitOfWork, IStringLocalizer<(someClass1, someClass2)> localizer, IRepositoryWithEvents<someClass1> repos, IDbTransaction transaction)
    {
        _repository = repository;
        _unitOfWork = unitOfWork;
        _localizer = localizer;
        _repos = repos;
        _transaction = transaction;
    }

    public async Task<ChallanDto> Handle(UpdateChallanRequest request, CancellationToken cancellationToken)
    {
        _unitOfWork.BeginTransaction();
        var challan = _repos.GetByIdAsync(request.Id, cancellationToken);

        string query;
        string query1;

        var parameters = new
        {
            ID = request.Id,
            param1 = request.Property1,
            param2 = request.Property2
        };

        query = "BEGIN" +
                            "UPDATE dbo.someTable1" +
                            "SET someColumn1=@param1, Active=0, someColumn2=@param2, flagPaid=true" +
                            "WHERE dbo.someTable1.id = @ID;" +
                        "END";
        var result = await _repository.QueryAsync<someClass1>(query, parameters, cancellationToken: cancellationToken);

        await _unitOfWork.SaveChangesAsync();


        try
        {
            if(challan.Id == null)
            {
                throw new ArgumentNullException();
            }
            else if (request.Id != null && challan.Id != null)
            {
                query1 = "BEGIN" +
                            "INSERT INTO dbo.someTable2" +
                            "(someColumn1, someColumn2, someColumn3)" +
                            "VALUES (" +
                                "SELECT someColumn1 from dbo.someTable1 WHERE dbo.someTable1.id = @ID;," +
                                "SELECT someColumn2 from dbo.someTable1  WHERE dbo.someTable1.id = @ID;," +
                                "SELECT someColumn3 from dbo.someTable1  WHERE dbo.someTable1.id = @ID;," +
                        "END";
                await _repository.QueryAsync<someClass1>(query1, parameters, cancellationToken: cancellationToken);

                await _unitOfWork.SaveChangesAsync();

                _unitOfWork.Commit();
            }
        }
        catch(Exception ex)
        {
            _unitOfWork.Rollback();
            throw new ArgumentNullException("Challan Not Found.");
        }

        return result.Adapt<ChallanDto>();
    }
}

Sarmadjavediqbal avatar May 03 '23 06:05 Sarmadjavediqbal

Mukesh have written an article on using Dapper with transactions for this purpose. In the code above I think you're missing the transaction with the repository.

https://codewithmukesh.com/blog/using-entity-framework-core-and-dapper/

MikaelHild avatar May 03 '23 06:05 MikaelHild

Thank you @MikaelHild brother but this still does not solve my problem. I am using Mediatr and mukesh's example shows the solution without mediatr.

Sarmadjavediqbal avatar May 03 '23 07:05 Sarmadjavediqbal

https://github.com/iammukeshm/EFCoreAndDapper

Checkout the repo above. It's basically the same but you either have to expose the ApplicationDbContext using an interface in the Application project, or handle the Request in the infrastructure project and use the DbContext to handle the transaction.

It could possibly also be solved by using Pipelines in mediator. https://medium.com/swlh/transaction-management-with-mediator-pipelines-in-asp-net-core-39317a19bb8d

MikaelHild avatar May 03 '23 07:05 MikaelHild

@MikaelHild Could you please correct my code and tell me what could be wrong in it????

Sarmadjavediqbal avatar May 03 '23 11:05 Sarmadjavediqbal

@MikaelHild I have figured it out. I was opening 2 connections with database where as System.Transaction allows only 1 or if you want to check and validate some value from database you must close the connection first before opening another connection with System.Transaction.

Sarmadjavediqbal avatar May 06 '23 18:05 Sarmadjavediqbal

This issue is resolved so can be closed now.

Sarmadjavediqbal avatar May 06 '23 18:05 Sarmadjavediqbal