SalesOrder.Save() exception  Topic is solved

Discussions relating to Jiwa 7 plugin development, and the Jiwa 7 API.

SalesOrder.Save() exception

Postby Mark Shipman » Wed Jul 29, 2020 4:17 pm

Populating a SalesOrder object with 3 inventory item lines after CreateNew() and then Save() throws an exception:

"New transaction is not allowed because there are other threads running in the session."

The Jiwa Database SQLTransaction is empty.

What have I missed?
Mark Shipman
Software Developer
Mark Shipman
Occasional Contributor
Occasional Contributor
 
Posts: 21
Joined: Mon Mar 02, 2020 1:44 pm
Location: Auckland, New Zealand

Re: SalesOrder.Save() exception

Postby SBarnes » Wed Jul 29, 2020 6:22 pm

Hi Mark,

Without seeing the code it's hard to comment but essentially the lines you should need would be something like the below where MP_SalesOrderCSVImport is collection of objects with the relevant properties from a csv file, this code is out of a project I did recently and actually works.

Code: Select all
 JiwaFinancials.Jiwa.JiwaSales.SalesOrder.SalesOrder salesorder = manager.BusinessLogicFactory.CreateBusinessLogic<JiwaFinancials.Jiwa.JiwaSales.SalesOrder.SalesOrder>(null);
 salesorder.CreateNew(JiwaFinancials.Jiwa.JiwaSales.SalesOrder.SalesOrder.NewSalesOrderTypes.e_NewSalesOrder, DebtorID, true, "", "", " ", manager.Database.CurrentLogicalWarehouseID);

foreach (MP_SalesOrderCSVImport line in Lines)
{
                object KeyToGetBackHere = "";
                salesorder.SalesOrderLines.AddInventoryItem(line.InventoryID, JiwaFinancials.Jiwa.JiwaSales.SalesOrder.SalesOrderLineCollection.SalesOrderLineInventorySeedTypes.e_SalesOrderLineInventoryID, ref KeyToGetBackHere);
                salesorder.SalesOrderLines[(KeyToGetBackHere as string)].QuantityOrdered = line.Quantity_Ordered;
}

salesorder.Save();



By the sounds of it from your error you may be starting a transaction which don't need to do.

As I said without seeing the code it is hard to comment.
Regards
Stuart Barnes
SBarnes
Shihan
Shihan
 
Posts: 1696
Joined: Fri Aug 15, 2008 3:27 pm
Topics Solved: 191

Re: SalesOrder.Save() exception

Postby Mark Shipman » Thu Jul 30, 2020 1:31 pm

Hi Stuart,

Thank you so much for helping with this.

Your code snippet provided one piece of enlightenment that I'd not been able to discover through the F12 object explorer in VS2019: how to assign a specific logical warehouse ID.

This code is part of a standalone plugin:
Code: Select all
class ConfigurationForm : JiwaFinancials.Jiwa.JiwaApplication.Maintenance.UserInterface{ ... }


I initially avoided including code, because it seemed such a big segment. This is the method that is failing:
Code: Select all
        private bool PersistNewJiwaSalesOrder(Order order, SystemSettings systemSettings, List<IdMap> inventoryItemMaps, out string invoiceId)
        {
            Stopwatch stopwatch = new Stopwatch();

            // {order} is the external sales order being imported
            if (order != null && order.id.HasValue)
            {
                JiwaFinancials.Jiwa.JiwaSales.SalesOrder.SalesOrder salesOrder = JiwaManager.BusinessLogicFactory.CreateBusinessLogic<JiwaFinancials.Jiwa.JiwaSales.SalesOrder.SalesOrder>(null);

                // Because this will separate web-sales
                string debtorId= systemSettings.ResolveDebtorId();

                // Because logical warehouse determines where the stock is taken from
                string logicalWarehouseId = systemSettings.ResolveLogicalWarehouseId(jiwaManager);

                jiwaSalesOrder.CreateNew(
                    SalesOrder.NewSalesOrderTypes.e_NewSalesOrder,
                    // ...and debtor is assigned now
                    debtorId, true,
                    string.Empty, string.Empty, string.Empty, // These two lines were added following your code snippet
                    logicalWarehouseId);

                // Because it may be useful to attribute web-sales to a particular staff member
                //jiwaSalesOrder.Staff.ReadRecord(systemSettings.ResolveJiwaStaffId(jiwaManager));

                jiwaSalesOrder.InitiatedDate =
                    order.date_created.HasValue ?
                        order.date_created.Value :
                        DateTime.Now;
                jiwaSalesOrder.Reference = order.id.Value.ToString();

                // Because name and address are important
                jiwaSalesOrder.CashSalesCompanyName = order.CompanyName();
                if (string.IsNullOrEmpty(jiwaSalesOrder.CashSalesCompanyName))
                {
                    jiwaSalesOrder.CashSalesName = order.CustomerName();
                }
                else
                {
                    jiwaSalesOrder.CashSalesContactName = order.CustomerName();
                }

                // Because these must not be a mix of shipping and billing address details,
                // AddressLines() is:
                //   an extension method that creates a list of strings
                //   from the external order shipping or billing addresses
                List<string> addresses = order.AddressLines();
                jiwaSalesOrder.CashSalesAddress1 = addresses[0];
                jiwaSalesOrder.CashSalesAddress2 = addresses[1];
                jiwaSalesOrder.CashSalesAddress3 = addresses[2];
                jiwaSalesOrder.CashSalesAddress4 = addresses[3];
                jiwaSalesOrder.CashSalesPostCode = addresses[4];
                jiwaSalesOrder.CashSalesPhone = order.billing.phone;

                // Because there will be order lines
                foreach (OrderLineItem orderLineItem in Order.line_items)
                {
                    decimal orderQuantity = orderLineItem.quantity ?? decimal.Zero;
                    decimal orderPrice = orderLineItem.price ?? decimal.Zero;

                    // Because an order line MUST have qty>0 and price>0 (our rule)
                    if (orderQuantity > decimal.Zero && orderPrice > decimal.Zero)
                    {
                        object newOrderLineKey = null;

                        // Because product id should be first choice
                        IdMap itemMap = inventoryItemMaps.FindFirstMatch(Constants.Entity.Product, orderLineItem.product_id.Value.ToString());
                        if (itemMap != null)
                        {
                            salesOrderLines.AddInventoryItem(itemMap.JiwaIdentity, SalesOrderLineCollection.SalesOrderLineInventorySeedTypes.e_SalesOrderLineInventoryID, ref newOrderLineKey);
                        }
                        else if (!string.IsNullOrEmpty(orderLineItem.sku)) // Their sku carries PartNo
                        {
                            salesOrderLines.AddInventoryItem(orderLineItem.sku, SalesOrderLineCollection.SalesOrderLineInventorySeedTypes.e_SalesOrderLinePartNo, ref newOrderLineKey);
                        }

                        // Because this means a line was added
                        if (newOrderLineKey != null)
                        {
                            // Because resolving this once is more effective
                            SalesOrderLine salesOrderLine = salesOrderLines[newOrderLineKey as string];

                            salesOrderLine.HiddenSetQuantityOrdered(ref orderQuantity);
                            salesOrderLine.HiddenSetDiscountedPrice(ref orderPrice);
                        }
                    }
                }

                if (salesOrder != null)
                {
                    if (salesOrder.SalesOrderLines.Count.Equals(order.line_items.Count))
                    {
                        // Because this is "all lines"
                        logMessage =
                            string.Format(
                                Constants.Messages.Information.SalesOrderTransformSuccess,
                                order.id.Value, salesOrder.SalesOrderLines.Count,
                                methodName);
                        AddInformationLog(AerpSchema.Log.Directions.ToJiwa, 0, logMessage);
                    }
                    else
                    {
                        // Because this "only some lines"
                        logMessage =
                            string.Format(
                                Constants.Messages.Warning.SalesOrderTransformFailure,
                                order.id.Value,
                                salesOrder.SalesOrderLines.Count, order.line_items.Count,
                                methodName);
                        AddWarningLog(AerpSchema.Log.Directions.ToJiwa, 0, logMessage);
                    }

                    // Because the changes need to be persisted
                    stopwatch.Restart();
                    salesOrder.Save();

                    // Because the object needs to be fully populated by re-Reading
                    salesOrder.Read(salesOrder.RecID);
                    invoiceId = salesOrder.RecID;

                    // Because this is detail that may be a useful audit
                    stopwatch.Stop();
                    logMessage =
                        string.Format(
                            Constants.Messages.Verbose.ImportedSalesOrderSaved,
                            order.id.Value, salesOrder.InvoiceID,
                            methodName);
                    AddVerboseLog(AerpSchema.Log.Directions.ToJiwa, stopwatch.ElapsedMilliseconds, logMessage);

                    return true;
                }
                else
                {
                    // Because this [shouldn't, but] may happen
                    logMessage =
                        string.Format(
                            Constants.Messages.Error.SalesOrderImportFailed,
                            order.id.Value, methodName);
                    AddErrorLog(AerpSchema.Log.Directions.ToJiwa, 0, logMessage);
                }
            }
            else
            {
                // Because this should never happen...
                logMessage =
                    string.Format(
                        Constants.Messages.Error.SalesOrderNotDefinedOrInvalid,
                        methodName);
                AddErrorLog(AerpSchema.Log.Directions.ToJiwa, 0, logMessage);
            }

            invoiceId = string.Empty;
            return false;
        }


Some extra information?

The Add*Log() methods write rows in a Database table. Is this perhaps my issue?

If I check the Jiwa Manager.Database.SQLTransaction, it is null up to the Save() method being invoked. Checking after this is problematic because of the exception.
Mark Shipman
Software Developer
Mark Shipman
Occasional Contributor
Occasional Contributor
 
Posts: 21
Joined: Mon Mar 02, 2020 1:44 pm
Location: Auckland, New Zealand

Re: SalesOrder.Save() exception

Postby Scott.Pearce » Thu Jul 30, 2020 1:34 pm

Show us those Log() methods...
Scott Pearce
Senior Analyst/Programmer
Jiwa Financials
User avatar
Scott.Pearce
Senpai
Senpai
 
Posts: 765
Joined: Tue Feb 12, 2008 11:27 am
Location: New South Wales, Australia
Topics Solved: 230

Re: SalesOrder.Save() exception

Postby SBarnes » Thu Jul 30, 2020 1:38 pm

Ditto on Scott's comment, or just return from the log method temporarily as that will identify that's the problem if it works whilst the return is there.
Regards
Stuart Barnes
SBarnes
Shihan
Shihan
 
Posts: 1696
Joined: Fri Aug 15, 2008 3:27 pm
Topics Solved: 191

Re: SalesOrder.Save() exception

Postby Mike.Sheen » Thu Jul 30, 2020 1:40 pm

Scott.Pearce wrote:Show us those Log() methods...


Yep... either those methods or something else is creating a transaction and leaving that uncommitted - so when we in our sales order Save() method try to create a new transaction the error occurs. I'd hope the Log method just issues a SqlCmd to insert and either 1) Doesn't create or use a transaction which implicitly creates a transaction - or - 2) Does explicitly create a transaction but always disposes of it.

Asynchronous operations are often also tripped up by this - if the PersistNewJiwaSalesOrder method which calls the Save() method of the sales order is invoked asynchronously then you will encounter similar issues.
Mike Sheen
Chief Software Engineer
Jiwa Financials

If I do answer your question to your satisfaction, please mark it as the post solving the topic so others with the same issue can readily identify the solution
User avatar
Mike.Sheen
Overflow Error
Overflow Error
 
Posts: 2583
Joined: Tue Feb 12, 2008 11:12 am
Location: Perth, Republic of Western Australia
Topics Solved: 807

Re: SalesOrder.Save() exception

Postby Mark Shipman » Thu Jul 30, 2020 1:58 pm

Logging:
Code: Select all
        /// <summary>
        /// The common method for writing integration log messages
        /// </summary>
        /// <param name="logType">
        /// Specify the type of log to add
        /// </param>
        /// <param name="direction">
        /// Specify the direction of the operation being logged.
        /// </param>
        /// <param name="elapsedMilliseconds">
        /// Specify the number of milliseconds elapsed for this operation
        /// </param>
        /// <param name="logMessage">
        /// Specify the message to log
        /// </param>
        /// <returns></returns>
        private bool AddLog(LogType logType, string direction, long elapsedMilliseconds, string logMessage)
        {
            //const string formatString = @"EXECUTE {0} @logDateTime = {1}, @direction = {2}, @elapsedMilliseconds = {3}, @logMessage = {4};";

            //string uspName = ResolveStoredProcedureName(logType);

            //// Because an empty procedure name is not valid
            //if (!string.IsNullOrEmpty(uspName))
            //{
            //    // Because the procedures all take the same parameters
            //    string sql =
            //        string.Format(formatString,
            //            uspName,
            //            MsftSqlServer.SqlScrub(DateTime.Now),
            //            MsftSqlServer.SqlScrub(direction),
            //            MsftSqlServer.SqlScrub(elapsedMilliseconds),
            //            MsftSqlServer.SqlScrub(logMessage)
            //        );

            //    using (System.Data.SqlClient.SqlCommand webEnabledInventoryCommand =
            //        new System.Data.SqlClient.SqlCommand(sql, JiwaDatabase.SQLConnection, JiwaDatabase.SQLTransaction))
            //    {
            //        JiwaDatabase.ExecuteNonQuery(webEnabledInventoryCommand);
            //        return true;
            //    }
            //}

            return false;
        }

        /// <summary>
        /// Responsible for writing an exception log message
        /// </summary>
        internal bool AddExceptionLog(string direction, long elapsedMilliseconds, string logMessage)
        {
            return AddLog(LogType.Exception, direction, elapsedMilliseconds, logMessage);
        }

        /// <summary>
        /// Responsible for writing an error log message
        /// </summary>
        internal bool AddErrorLog(string direction, long elapsedMilliseconds, string logMessage)
        {
            return AddLog(LogType.Error, direction, elapsedMilliseconds, logMessage);
        }

        /// <summary>
        /// Responsible for writing an warning log message
        /// </summary>
        internal bool AddWarningLog(string direction, long elapsedMilliseconds, string logMessage)
        {
            return AddLog(LogType.Warning, direction, elapsedMilliseconds, logMessage);
        }

        /// <summary>
        /// Responsible for writing an information log message
        /// </summary>
        internal bool AddInformationLog(string direction, long elapsedMilliseconds, string logMessage)
        {
            return AddLog(LogType.Information, direction, elapsedMilliseconds, logMessage);
        }

        /// <summary>
        /// Responsible for writing an verbose log message
        /// </summary>
        internal bool AddVerboseLog(string direction, long elapsedMilliseconds, string logMessage)
        {
            return AddLog(LogType.Verbose, direction, elapsedMilliseconds, logMessage);
        }


The common AddLog() method has the DB work commented out and the exception still occurs.

Stored procedures:
Code: Select all
CREATE OR ALTER PROCEDURE [dbo].[usp_LogException]
(
   @logDateTime         DATETIME,
   @direction            CHAR(2),
   @elapsedMilliseconds   BIGINT,
   @logMessage            VARCHAR(MAX)
)
AS
   -- SET NOCOUNT ON added to prevent extra result sets from
   -- interfering with SELECT statements.
   SET NOCOUNT ON;

   DECLARE @emptyUUID AS UNIQUEIDENTIFIER = CAST( N''00000000-0000-0000-0000-000000000000'' AS UNIQUEIDENTIFIER ) ;
   --   0 == Exception
   DECLARE @logType AS TINYINT = 1 ;

   --   Data Validation
   IF   LEN( LTRIM( RTRIM( ISNULL( @logMessage, N'''' ) ) ) ) = 0
   BEGIN
      --   Because this means the procedure succeeds, but the result is "fail"
      SELECT @emptyUUID AS [RowUuid] ;

      RETURN;
   END

   --   Because a named transaction is not dependent on the default transaction
   BEGIN TRANSACTION NewLog;

   INSERT INTO [dbo].[IntegrationLog]
      (
         [LogDateTime],
         [LogType],
         [Direction],
         [ElapsedMilliseconds],
         [LogMessage]
      )
   VALUES
      (
         @logDateTime,
         @logType,
         @direction,
         @elapsedMilliseconds,
         @logMessage
      )
   ;

   IF   @@ERROR = 0
   BEGIN
      COMMIT TRANSACTION NewLog;

      --   Because RowUuid must be unique
      SELECT
            [RowUuid]
      FROM   [dbo].[IntegrationLog]
      WHERE
            [LogDateTime]   = @logDateTime
      AND      [LogType]      = @logType
      AND      [Direction]      = @direction
      AND      [LogMessage]   = @logMessage
      ;
   END
   ELSE
   BEGIN
      ROLLBACK TRANSACTION NewLog;

      --   Because this means the procedure succeeds, but the result is "fail"
      SELECT @emptyUUID AS [RowUuid] ;
   END
;

CREATE OR ALTER PROCEDURE [dbo].[usp_LogError]
(
   @logDateTime         DATETIME,
   @direction            CHAR(2),
   @elapsedMilliseconds   BIGINT,
   @logMessage            VARCHAR(MAX)
)
AS
   -- SET NOCOUNT ON added to prevent extra result sets from
   -- interfering with SELECT statements.
   SET NOCOUNT ON;

   DECLARE @emptyUUID AS UNIQUEIDENTIFIER = CAST( N''00000000-0000-0000-0000-000000000000'' AS UNIQUEIDENTIFIER ) ;
   --   1 == Error
   DECLARE @logType AS TINYINT = 1 ;

   --   Data Validation
   IF   LEN( LTRIM( RTRIM( ISNULL( @logMessage, N'''' ) ) ) ) = 0
   BEGIN
      --   Because this means the procedure succeeds, but the result is "fail"
      SELECT @emptyUUID AS [RowUuid] ;

      RETURN;
   END

   --   Because a named transaction is not dependent on the default transaction
   BEGIN TRANSACTION NewLog;

   INSERT INTO [dbo].[IntegrationLog]
      (
         [LogDateTime],
         [LogType],
         [Direction],
         [ElapsedMilliseconds],
         [LogMessage]
      )
   VALUES
      (
         @logDateTime,
         @logType,
         @direction,
         @elapsedMilliseconds,
         @logMessage
      )
   ;

   IF   @@ERROR = 0
   BEGIN
      COMMIT TRANSACTION NewLog;

      --   Because RowUuid must be unique
      SELECT
            [RowUuid]
      FROM   [dbo].[IntegrationLog]
      WHERE
            [LogDateTime]   = @logDateTime
      AND      [LogType]      = @logType
      AND      [Direction]      = @direction
      AND      [LogMessage]   = @logMessage
      ;
   END
   ELSE
   BEGIN
      ROLLBACK TRANSACTION NewLog;

      --   Because this means the procedure succeeds, but the result is "fail"
      SELECT @emptyUUID AS [RowUuid] ;
   END
;
CREATE OR ALTER PROCEDURE [dbo].[usp_LogWarning]
(
   @logDateTime         DATETIME,
   @direction            CHAR(2),
   @elapsedMilliseconds   BIGINT,
   @logMessage            VARCHAR(MAX)
)
AS
   -- SET NOCOUNT ON added to prevent extra result sets from
   -- interfering with SELECT statements.
   SET NOCOUNT ON;

   DECLARE @emptyUUID AS UNIQUEIDENTIFIER = CAST( N''00000000-0000-0000-0000-000000000000'' AS UNIQUEIDENTIFIER ) ;
   --   2 == Warning
   DECLARE @logType AS TINYINT = 2 ;

   --   Data Validation
   IF   LEN( LTRIM( RTRIM( ISNULL( @logMessage, N'''' ) ) ) ) = 0
   BEGIN
      --   Because this means the procedure succeeds, but the result is "fail"
      SELECT @emptyUUID AS [RowUuid] ;

      RETURN;
   END

   --   Because a named transaction is not dependent on the default transaction
   BEGIN TRANSACTION NewLog;

   INSERT INTO [dbo].[IntegrationLog]
      (
         [LogDateTime],
         [LogType],
         [Direction],
         [ElapsedMilliseconds],
         [LogMessage]
      )
   VALUES
      (
         @logDateTime,
         @logType,
         @direction,
         @elapsedMilliseconds,
         @logMessage
      )
   ;

   IF   @@ERROR = 0
   BEGIN
      COMMIT TRANSACTION NewLog;

      --   Because RowUuid must be unique
      SELECT
            [RowUuid]
      FROM   [dbo].[IntegrationLog]
      WHERE
            [LogDateTime]   = @logDateTime
      AND      [LogType]      = @logType
      AND      [Direction]      = @direction
      AND      [LogMessage]   = @logMessage
      ;
   END
   ELSE
   BEGIN
      ROLLBACK TRANSACTION NewLog;

      --   Because this means the procedure succeeds, but the result is "fail"
      SELECT @emptyUUID AS [RowUuid] ;
   END
;

CREATE OR ALTER PROCEDURE [dbo].[usp_LogInformation]
(
   @logDateTime         DATETIME,
   @direction            CHAR(2),
   @elapsedMilliseconds   BIGINT,
   @logMessage            VARCHAR(MAX)
)
AS
   -- SET NOCOUNT ON added to prevent extra result sets from
   -- interfering with SELECT statements.
   SET NOCOUNT ON;

   DECLARE @emptyUUID AS UNIQUEIDENTIFIER = CAST( N''00000000-0000-0000-0000-000000000000'' AS UNIQUEIDENTIFIER ) ;
   --   3 == Information
   DECLARE @logType AS TINYINT = 3 ;

   --   Data Validation
   IF   LEN( LTRIM( RTRIM( ISNULL( @logMessage, N'''' ) ) ) ) = 0
   BEGIN
      --   Because this means the procedure succeeds, but the result is "fail"
      SELECT @emptyUUID AS [RowUuid] ;

      RETURN;
   END

   --   Because a named transaction is not dependent on the default transaction
   BEGIN TRANSACTION NewLog;

   INSERT INTO [dbo].[IntegrationLog]
      (
         [LogDateTime],
         [LogType],
         [Direction],
         [ElapsedMilliseconds],
         [LogMessage]
      )
   VALUES
      (
         @logDateTime,
         @logType,
         @direction,
         @elapsedMilliseconds,
         @logMessage
      )
   ;

   IF   @@ERROR = 0
   BEGIN
      COMMIT TRANSACTION NewLog;

      --   Because RowUuid must be unique
      SELECT
            [RowUuid]
      FROM   [dbo].[IntegrationLog]
      WHERE
            [LogDateTime]   = @logDateTime
      AND      [LogType]      = @logType
      AND      [Direction]      = @direction
      AND      [LogMessage]   = @logMessage
      ;
   END
   ELSE
   BEGIN
      ROLLBACK TRANSACTION NewLog;

      --   Because this means the procedure succeeds, but the result is "fail"
      SELECT @emptyUUID AS [RowUuid] ;
   END
;

CREATE OR ALTER PROCEDURE [dbo].[usp_LogVerbose]
(
   @logDateTime         DATETIME,
   @direction            CHAR(2),
   @elapsedMilliseconds   BIGINT,
   @logMessage            VARCHAR(MAX)
)
AS
   -- SET NOCOUNT ON added to prevent extra result sets from
   -- interfering with SELECT statements.
   SET NOCOUNT ON;

   DECLARE @emptyUUID AS UNIQUEIDENTIFIER = CAST( N''00000000-0000-0000-0000-000000000000'' AS UNIQUEIDENTIFIER ) ;
   --   4 == Verbose
   DECLARE @logType AS TINYINT = 4 ;

   --   Data Validation
   IF   LEN( LTRIM( RTRIM( ISNULL( @logMessage, N'''' ) ) ) ) = 0
   BEGIN
      --   Because this means the procedure succeeds, but the result is "fail"
      SELECT @emptyUUID AS [RowUuid] ;

      RETURN;
   END

   --   Because a named transaction is not dependent on the default transaction
   BEGIN TRANSACTION NewLog;

   INSERT INTO [dbo].[IntegrationLog]
      (
         [LogDateTime],
         [LogType],
         [Direction],
         [ElapsedMilliseconds],
         [LogMessage]
      )
   VALUES
      (
         @logDateTime,
         @logType,
         @direction,
         @elapsedMilliseconds,
         @logMessage
      )
   ;

   IF   @@ERROR = 0
   BEGIN
      COMMIT TRANSACTION NewLog;

      --   Because RowUuid must be unique
      SELECT
            [RowUuid]
      FROM   [dbo].[IntegrationLog]
      WHERE
            [LogDateTime]   = @logDateTime
      AND      [LogType]      = @logType
      AND      [Direction]      = @direction
      AND      [LogMessage]   = @logMessage
      ;
   END
   ELSE
   BEGIN
      ROLLBACK TRANSACTION NewLog;

      --   Because this means the procedure succeeds, but the result is "fail"
      SELECT @emptyUUID AS [RowUuid] ;
   END
;


The SProcs use named transactions, which is a pattern I have used extensively [without this issue] in environments from desktop and MVC to Azure deployed Web API.

There are also a number of places where I read rows from DB tables using SQL DataReader. Is there a recommended pattern for doing this in a Jiwa plugin?
Mark Shipman
Software Developer
Mark Shipman
Occasional Contributor
Occasional Contributor
 
Posts: 21
Joined: Mon Mar 02, 2020 1:44 pm
Location: Auckland, New Zealand

Re: SalesOrder.Save() exception

Postby Mark Shipman » Thu Jul 30, 2020 2:04 pm

Mike.Sheen wrote:Asynchronous operations are often also tripped up by this - if the PersistNewJiwaSalesOrder method which calls the Save() method of the sales order is invoked asynchronously then you will encounter similar issues.


This is running from a plugin and the user clicks a button to run the import.

The button busies the UI and runs everything on the UI thread: nothing asynchronous unless Jiwa does something in the UI.

If I was to async stuff I would use the Async-Await pattern (I don't do this here).
Mark Shipman
Software Developer
Mark Shipman
Occasional Contributor
Occasional Contributor
 
Posts: 21
Joined: Mon Mar 02, 2020 1:44 pm
Location: Auckland, New Zealand

Re: SalesOrder.Save() exception

Postby Mike.Sheen » Thu Jul 30, 2020 2:13 pm

We can best help you from here if you could post a plugin which demonstrates the issue.

Copy your plugin and then strip out as much as you can which isn't related to the problem - then verify the problem occurs with the copied, scaled back plugin and post it here. We should be able to work out the problem fairly quickly - unless the problem is actually caused by a completely separate plugin which is being naughty and starting a transaction and not closing it before your plugin gets invoked.

It's a bit of a pain to do, but if you spend the 20 minutes or so doing that we can get you a solution pretty quickly.
Mike Sheen
Chief Software Engineer
Jiwa Financials

If I do answer your question to your satisfaction, please mark it as the post solving the topic so others with the same issue can readily identify the solution
User avatar
Mike.Sheen
Overflow Error
Overflow Error
 
Posts: 2583
Joined: Tue Feb 12, 2008 11:12 am
Location: Perth, Republic of Western Australia
Topics Solved: 807

Re: SalesOrder.Save() exception

Postby Mark Shipman » Thu Jul 30, 2020 2:22 pm

Mike.Sheen wrote:We can best help you from here if you could post a plugin which demonstrates the issue.

Copy your plugin and then strip out as much as you can which isn't related to the problem - then verify the problem occurs with the copied, scaled back plugin and post it here. We should be able to work out the problem fairly quickly - unless the problem is actually caused by a completely separate plugin which is being naughty and starting a transaction and not closing it before your plugin gets invoked.

It's a bit of a pain to do, but if you spend the 20 minutes or so doing that we can get you a solution pretty quickly.


Thank you.

My plugin is pretty simple, but may not provide you what you are looking for:
Code: Select all
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using JiwaFinancials.Jiwa;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.Drawing;
using ServiceStack;
using ServiceStack.DataAnnotations;
using ServiceStack.Model;

#region "FormPlugin"
public class FormPlugin : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaFormPlugin
{

    public override object InitializeLifetimeService()
    {
        // returning null here will prevent the lease manager
        // from deleting the Object.
        return null;
    }

    public void SetupBeforeHandlers(JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm JiwaForm, JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
    {
      System.Diagnostics.Debugger.Break();
    }

    public void Setup(JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm JiwaForm, JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
    {
      System.Diagnostics.Debugger.Break();
    }
}
#endregion

#region "BusinessLogicPlugin"
public class BusinessLogicPlugin : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogicPlugin
{

    public override object InitializeLifetimeService()
    {
        // returning null here will prevent the lease manager
        // from deleting the Object.
        return null;
    }

    public void Setup(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic JiwaBusinessLogic, JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
    {
    }
}
#endregion

#region "ApplicationManagerPlugin"
public class ApplicationManagerPlugin : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaApplicationManagerPlugin
{

    public override object InitializeLifetimeService()
    {
        // returning null here will prevent the lease manager
        // from deleting the Object.
        return null;
    }

    public void Setup(JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
    {
      //System.Diagnostics.Debugger.Break();
      //using(AERP.WooCommerce.ConfigurationForm configurationForm = new AERP.WooCommerce.ConfigurationForm())
      //{
      //   configurationForm.Manager = Plugin.Manager;
      //   configurationForm.ShowDialog();
      //}
    }
}
#endregion

#region "CustomFieldPlugin"
public class CustomFieldPlugin : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaCustomFieldPlugin
{
    public override object InitializeLifetimeService()
    {
        // returning null here will prevent the lease manager
        // from deleting the Object.
        return null;
    }

    public void FormatCell(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Col, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaCustomFieldValues HostObject, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
    {
    }

    public void ReadData(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaCustomFieldValues HostObject, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
    {
    }

    public void ButtonClicked(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Col, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaCustomFieldValues HostObject, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
    {
    }
}
#endregion

#region "LineCustomFieldPlugin"
public class LineCustomFieldPlugin : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaLineCustomFieldPlugin
{
    public override object InitializeLifetimeService()
    {
        // returning null here will prevent the lease manager
        // from deleting the Object.
        return null;
    }

    public void FormatCell(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Col, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaLineCustomFieldValues HostItem, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
    {
    }

    public void ReadData(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaLineCustomFieldValues HostItem, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
    {
    }

    public void ButtonClicked(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Col, int Row, JiwaFinancials.Jiwa.JiwaApplication.IJiwaLineCustomFieldValues HostItem, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomField CustomField, JiwaFinancials.Jiwa.JiwaApplication.CustomFields.CustomFieldValue CustomFieldValue)
    {
    }
}
#endregion

#region "SystemSettingPlugin"
public class SystemSettingPlugin : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaSystemSettingPlugin
{
    public override object InitializeLifetimeService()
    {
        // returning null here will prevent the lease manager
        // from deleting the Object.
        return null;
    }

    public void FormatCell(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Col, int Row, JiwaFinancials.Jiwa.JiwaApplication.SystemSettings.Setting SystemSetting)
    {
    }

    public void ReadData(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Row, JiwaFinancials.Jiwa.JiwaApplication.SystemSettings.Setting SystemSetting)
    {
    }

    public void ButtonClicked(JiwaFinancials.Jiwa.JiwaApplication.IJiwaBusinessLogic BusinessLogicHost, JiwaFinancials.Jiwa.JiwaApplication.Controls.JiwaGrid GridObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaForm FormObject, int Col, int Row, JiwaFinancials.Jiwa.JiwaApplication.SystemSettings.Setting SystemSetting)
    {
    }
}
#endregion

#region "ScheduledExecutionPlugin"
public class ScheduledExecutionPlugin : System.MarshalByRefObject, JiwaFinancials.Jiwa.JiwaApplication.IJiwaScheduledExecutionPlugin
{
    public void Execute(JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin, JiwaFinancials.Jiwa.JiwaApplication.Schedule.Schedule Schedule)
    {
        lock (JiwaFinancials.Jiwa.JiwaApplication.Manager.CriticalSectionFlag)
        {
            // place processing code in here
        }
    }

    public void OnServiceStart(JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
    {
    }

    public void OnServiceStopping(JiwaFinancials.Jiwa.JiwaApplication.Plugin.Plugin Plugin)
    {
    }
}
#endregion

namespace Zaplutus
{
   #region User Interface
   public class PluginHarnessForm : AERP.WooCommerce.ConfigurationForm
   {
      public PluginHarnessForm() : base() {}
   }
   #endregion
}


Discovered that referencing an external assembly allows the "plugin" to be compiled externally using all of the current C# language features (like nameof()) available in VS2019.

Might take me a little longer than 20 minutes to get the code compiling in the plugin editor... ;)
Mark Shipman
Software Developer
Mark Shipman
Occasional Contributor
Occasional Contributor
 
Posts: 21
Joined: Mon Mar 02, 2020 1:44 pm
Location: Auckland, New Zealand

Next

Return to Technical and or Programming

Who is online

Users browsing this forum: No registered users and 2 guests