Search code examples
unit-of-workaspnetboilerplate

How to wrap the Unit of Work with an interceptor in ABP framework


The author of the article https://www.codeproject.com/Articles/1080517/Aspect-Oriented-Programming-using-Interceptors-wit is explaining on how to create an interceptor in "ASPNET Boilerplate" framework. In my case, I want to create an interceptor (or two interceptors, one for Start and the other for End) that will wrap the Unit of Work. I need to call a stored procedure called spStart when a method in any AppService starts, and call the spEnd when the calls are comitted to the database. Since the Unit of Work in ABP is also an interceptor, this is what I've done so far: Following the steps in the article I provided the link above, I did the following:

  • Added two interceptors: StartInterceptor and EndInterceptor with their Registrar classes
  • Registered them in the ApplicationModule class
  • Also in the ApplicationModule I added the following code:

    IocManager.IocContainer.Register(Component.For<IApplicationService>() .Interceptors(InterceptorReference.ForType<StartInterceptor>()).First, Component.For<StartInterceptor>() .Interceptors(InterceptorReference.ForType<EndInterceptor>()).Last, Component.For<StopInterceptor>());

This code should make the StartInterceptor run first, and the EndInterceptor run last. Considering that the Unit of Work interceptor will be in between, and that we will be using the logic to await for the async method that is intercepted to return the result, that should give us the option to wrap the unit of work. However this is what happens: When the spStart runs, everything is ok. This stored procedure runs before the Unit of Work interceptor so no problems there. However, when the spEnd runs, it says "Transaction should be disposed before the connection can be used to execute SQL statements" or "The operation is not valid for the state of the transaction"... Seems that the UoW runs in parallel with my stored procedure. Has anyone else had the same issue with ABP? How did you solve it?


Solution

  • if you encounter transaction problem you may need to get active transaction from the active connection. when you get the transaction assign it to your sql command.

    private DbCommand CreateCommand(string commandText, CommandType commandType, params SqlParameter[] parameters)
    {
        var command = Context.Database.GetDbConnection().CreateCommand();
    
        command.CommandText = commandText;
        command.CommandType = commandType;
        command.Transaction = GetActiveTransaction();
    
        foreach (var parameter in parameters)
        {
            command.Parameters.Add(parameter);
        }
    
        return command;
    }
    
    private void EnsureConnectionOpen()
    {
        var connection = Context.Database.GetDbConnection();
    
        if (connection.State != ConnectionState.Open)
        {
            connection.Open();
        }
    }
    
    private DbTransaction GetActiveTransaction()
    {
        return (DbTransaction)_transactionProvider.GetActiveTransaction(new ActiveTransactionProviderArgs
        {
            {"ContextType", typeof(PhoneBookDbContext) },
            {"MultiTenancySide", MultiTenancySide }
        });
    }
    
    
    
    
    public async Task<GetUserByIdOutput> ExecuteMyStoredProcedure(EntityDto input)
    {
        EnsureConnectionOpen();
    
        using (var command = CreateCommand("SELECT dbo.GetUsernameById(@id)", CommandType.Text, new SqlParameter("@id", input.Id)))
        {
            var username = (await command.ExecuteScalarAsync()).ToString();
            return new GetUserByIdOutput() { Username = username };
        }
    }
    

    Further info: https://www.codeproject.com/Articles/1199648/Using-Stored-Procedure-User-Defined-Function-and-V