bgmulinari / B1SLayer

A lightweight SAP Business One Service Layer client for .NET
MIT License
122 stars 42 forks source link

Connect to multiple companies on the same server #42

Closed juaxpa closed 1 year ago

juaxpa commented 1 year ago

Hello, first of all, thanks for this amazing job, it's very useful.

I am trying to achieve a connection to multiple companies on the same server but I'm having troubles because the post/patch methods are going to the same service layer instance.

I have the following:

List<Configuration> listConfigs;
listConfigs = new List<Configuration>();
Configuration config = new Configuration("DB1", "manager", g_Pass, serviceLayerURI);
listConfigs.Add(config);
config = new Configuration("DB2", "manager", g_Pass, serviceLayerURI);
listConfigs.Add(config);
config = new Configuration("DB3", "manager", g_Pass, serviceLayerURI);
listConfigs.Add(config);

The "Configuration" class constructor is the following:

public Configuration(string pDatabaseName, string pSAPUsername, string pSAPPassword, string pServiceLayerURI)
{
       DatabaseName = pDatabaseName;
       SAPUsername = pSAPUsername;
       SAPPassword = pSAPPassword;
       serviceLayer = new SLConnection(pServiceLayerURI, DatabaseName, SAPUsername, SAPPassword);
}

Inside the "Configuration" class I have methods that does post/patch to serviceLayer depending if the item exists or not (control is done previously). Then in the main program, I have a for each loop which use the method to post/patch items to each companies that are in the "List" with ItemMasterData objects, the problem is, it seems the post/patch is done to always the 1st company in the List which serviceLayer was connected to.

But when I declare SLConnection one by one in the code it seems to work fine and as expected (if I create SLConnection sl1, SLConnection sl2, etc.).

I've checked if the Cookies SessionId were ok using the "call.Request.Cookies" and it seems so but only the 1st database is "updated" (multiple times).

My objective is to be able to replicate an item from one "master" database (not in the List) to other "child" databases (in the List).

Do you have any idea of the problem?

bgmulinari commented 1 year ago

Hi, @juaxpa. It's a bit difficult to understand exactly what's happening without having the full context of your code.

B1SLayer manages the connection in the SLConnection object, so if you are connecting to multiple databases/companies, you need multiple instances of SLConnection (one for each) and use the appropriate one when making each request.

When you say that when you declare your SLConnection objects one by one it works as expected, it makes me think that there's something wrong on your Configuration class and all your requests are being originated from the same SLConnection instance, which should not be the case as explained above.

If you are able to provide more context or maybe a code snippet where I can simulate the issue, that would be helpful.

juaxpa commented 1 year ago

Of course, I will provide a better code sample of what I'm trying to achieve.

In the example below, updating DB1, DB2 and DB3 with items from DBMASTER does not work, only the DB1 is updated like if the Request was sent only to DB1 but since in the code it's looping through every configs it should execute request to DB1, then DB2 and DB3... but it's not and I don't understand why.

It is a test app for the moment so it's not well organized but here's the context:

Main console Program.cs:

static List<Configuration> listConfigs;
static Configuration mrdConfig;

static void Main(string[] args)
{
    SetupConfig();
    StartSync();
}

static void SetupConfig()
{
    g_Pass = "password";
    serviceLayerURI = "https://.........";
    mrdConfig = new Configuration("DBMASTER", "manager", g_Pass, serviceLayerURI);

    listConfigs = new List<Configuration>();
    Configuration config = new Configuration("DB1", "manager", g_Pass, serviceLayerURI);
    listConfigs.Add(config);
    config = new Configuration("DB2", "manager", g_Pass, serviceLayerURI);
    listConfigs.Add(config);
    config = new Configuration("DB3", "manager", g_Pass, serviceLayerURI);
    listConfigs.Add(config);
}

async static void StartSync()
{
    // gathering all the items from the MASTER database where an UDF is set to synchronize the item.
    await mrdConfig.GetItemsToSynchronize();
    if (mrdConfig.listItemMasterData.Count > 0)
    {
        foreach (Configuration fe_Config in listConfigs)
        {
            // then following the list of items from the MASTER database, I get these same items from the "child" databases
            await fe_Config.GetItems(mrdConfig.listItemMasterData);
            foreach (ItemMasterData fe_Item in mrdConfig.listItemMasterData)
            {
                ItemMasterData itemProd = fe_Config.listItemMasterData.FirstOrDefault(o => o.ItemCode == fe_Item.ItemCode);
                if (itemProd != null)
                {
                    // I compare the two items to ensure they are different, if not, I won't update them, it's unnecessary.
                    if (fe_Item.GetConcat(true) != itemProd.GetConcat(false))
                    {
                        fe_Item.SetAction(ItemMasterData.ItemMode.Update);
                        fe_Config.listOfItemsForAction.Add(fe_Item);
                    }
                }
                else
                {
                    fe_Item.SetAction(ItemMasterData.ItemMode.Create);
                    fe_Config.listOfItemsForAction.Add(fe_Item);
                }
            }
            // after every checks are done, I start the actions to update/create the items according to what's been decided before.
            fe_Config.ProcessItemMasterData();
        }
       // then for each items, if it has been synchronized successfully, I update their status in the MASTER database. This part is not debugged for the moment and it's not working as expected but that's not my main issue :)
        foreach (ItemMasterData fe_Item in mrdConfig.listItemMasterData)
        {
            fe_Item.syncStatus = true;
            foreach (Configuration fe_Config in listConfigs)
            {
                ItemMasterData itemProd = fe_Config.listOfItemsForAction.FirstOrDefault(o => o.ItemCode == fe_Item.ItemCode);
                if (itemProd != null)
                {
                    Console.WriteLine("Sync status " + fe_Item.ItemCode + ": " + itemProd.syncStatus.ToString());
                    fe_Item.syncStatus &= itemProd.syncStatus;
                }
            }
            if (fe_Item.syncStatus)
            {
                mrdConfig.SetSynchronizeSuccess(fe_Item.ItemCode);
            }
        }
    }
    else
    {
        Console.WriteLine("No Item Master Data in 'to synchronize' status");
    }
}

The "Configuration" class is the following:

SLConnection serviceLayer;
public IList<ItemMasterData> listItemMasterData;
public List<ItemMasterData> listOfItemsForAction;
string DatabaseName { get; set; }
string SAPUsername { get; set; }
string SAPPassword { get; set; }
public Configuration(string pDatabaseName, string pSAPUsername, string pSAPPassword, string pServiceLayerURI)
{
    DatabaseName = pDatabaseName;
    SAPUsername = pSAPUsername;
    SAPPassword = pSAPPassword;
    serviceLayer = new SLConnection(pServiceLayerURI, DatabaseName, SAPUsername, SAPPassword);
    listItemMasterData = new List<ItemMasterData>();
    listOfItemsForAction = new List<ItemMasterData>();
}

public async Task GetItemsToSynchronize()
{
    try
    {
        Console.WriteLine("Gathering " + DatabaseName);
        string valuesToGather = "ItemCode, ItemName, etc."; // removed all values to shorten the code
        // "U_APStatut eq '3' means "Items needs to be synchronized"
        listItemMasterData = await serviceLayer.Request("Items").Select(valuesToGather).Filter("U_APStatut eq '3'").WithPageSize(0).GetAllAsync<ItemMasterData>(); 
     }
     catch (AggregateException ex)
     {
         foreach (var inEx in ex.InnerExceptions)
         {
             Console.WriteLine("[" + DatabaseName + "] Error during data gathering: " + inEx.Message);
         }
     }
}

public async Task GetItems(IList<ItemMasterData> pListOfItemCodes)
{
    try
    {
        Console.WriteLine("Gathering " + DatabaseName);
        string valuesToGather = "ItemCode, ItemName, etc."; // removed all values to shorten the code
        var filterString = "ItemCode eq 'dummy'";
        foreach (ItemMasterData fe_Item in pListOfItemCodes)
        {
            filterString += " or ItemCode eq '" + fe_Item.ItemCode + "'";
        }
        listItemMasterData = await serviceLayer.Request("Items").Select(valuesToGather).Filter(filterString).WithPageSize(0).GetAllAsync<ItemMasterData>();
    }
    catch (AggregateException ex)
    {
        foreach (var inEx in ex.InnerExceptions)
        {
            Console.WriteLine("[" + DatabaseName + "] Error during data gathering: " + inEx.Message);
        }
    }
}

public void CreateItemMasterData(ItemMasterData itemToCreate)
{
    try
    {
        serviceLayer.Request("Items").WithTimeout(100).PostAsync(itemToCreate).Wait();
        itemToCreate.syncStatus = true;
        Console.WriteLine("Item Master Data created: " + itemToCreate.ItemCode);
    }
    catch (AggregateException ex)
    {
        foreach (var inEx in ex.InnerExceptions)
        {
            itemToCreate.syncStatus = false;
            Console.WriteLine("[" + DatabaseName + "] Error during creation of item " + itemToCreate.ItemCode + ": " + inEx.Message);
        }
    }
}

public void UpdateItemMasterData(ItemMasterData itemToUpdate)
{
    try
    {
        serviceLayer.Request("Items", itemToUpdate.ItemCode).WithTimeout(100).PatchAsync(itemToUpdate).Wait();
        itemToUpdate.syncStatus = true;
        Console.WriteLine("Item Master Data updated: " + itemToUpdate.ItemCode);
    }
    catch (AggregateException ex)
    {
        foreach (var inEx in ex.InnerExceptions)
        {
            itemToUpdate.syncStatus = false;
            Console.WriteLine("[" + DatabaseName + "] Error during update of item " + itemToUpdate.ItemCode + ": " + inEx.Message);
        }
    }
}

public void SetSynchronizeSuccess(string pItemCode)
{
    try
    {
        var item = new { ItemCode = pItemCode, U_APStatut = "1" };
        serviceLayer.Request("Items", pItemCode).WithTimeout(100).PatchAsync(item).Wait();
        Console.WriteLine("Item Master Data synchronized: " + pItemCode);
    }
    catch (AggregateException ex)
    {
        foreach (var inEx in ex.InnerExceptions)
        {
            Console.WriteLine("[" + DatabaseName + "] Error during update of item to synchronized status " + pItemCode + ": " + inEx.Message);
        }
    }
}

public void ProcessItemMasterData()
{
    foreach (ItemMasterData fe_Item in listOfItemsForAction)
    {
        switch (fe_Item.itemAction)
        {
            case ItemMasterData.ItemMode.Create:
                CreateItemMasterData(fe_Item);
                break;
            case ItemMasterData.ItemMode.Update:
                UpdateItemMasterData(fe_Item);
                break;
        }
    }
}
juaxpa commented 1 year ago

Well, I made another test this morning after a good sleep and... it seems it's well updating each database (using modification history I can see it's updating each DB), but the ItemMasterData object seems to be well updated for the 1st DB in the list, and then the other DB are updated with their actual ItemMasterData, so the item is updated but since it's the same, nothing change. Maybe I have to dig deeper into my code, there may be a stupid mistake...

juaxpa commented 1 year ago

Ok it was a mistake on my side about object reference. I'm sorry for bothering. Thanks for your help anyway.