Call Business Object Within BPM Using C# - Help Needed

I’m trying to rewrite a BPM we use as an update method on a BAQ.

Currently, the BPM goes through a ton of BO method widgets and works perfectly for a single return BAQ.

We are updating the BAQ to return multiple “dirty” rows and we need the BPM to be able to handle that. My thought was to rewrite the entire BPM into a C# custom code block and put a foreach statement around it.

The problem I have currently is that I can’t seem to get the syntax right. Having gone through some of the code snippets within the below post I tried using the following line:

var bo=Erp.BO.EngWorkBenchSvc.GetService(Db);

Referenced Post:

Doing this I get the following error:
image
image

I could really use a refresher on the syntax behind this stuff if anyone has a cheat sheet somewhere.

Personally, I put all my custom code stuff in functions and call that from BPMs. Keeps all my poorly written garbage in one place for convenient bug-fixing. It also keeps the BPM’s super stripped down (usually just a condition block followed by a function call block). I find this way, way more manageable.

Once in there, I’m abusing this.CallService. You then put the service in the <> and a lamba expression (x => x.stuff) to access the methods within the service.

Simple example that could easily be implemented in a widget if it wasn’t for other requirements that I had.

this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>  ewb.Update(ref ewbTS));

Example using parameters. Lots of parameters.

 this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
        ewb.GetDetailsFromMethods(
            EcoGroup,
            PartNum,
            Revision,
            method.AltMethod,
            DateTime.Today, //AsOfDate
            false, //CompleteTree Returns whole ECOGroup?
            method.basePartNum,
            method.baseRevisionNum,
            method.baseAltMethod,
            false, //return dataset
            false, //ipGetDatasetForTree True = callGetDatasetFor, False = call GetByID
            false, //UseMethodForParts
            false, false /*Configurator-related*/));
3 Likes

This is the way. It’s very easy to manage.

And don’t forget descriptive groups in the BPMs !

2 Likes

And the description field in Function Maint.
Also the notes tab, where I usually document the inputs/outputs plus any other development notes.

3 Likes

Change to this

var bo = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.EngWorkBenchSvcContract>(Db);

Add a reference


1 Like

The code works for the original request. I’m now trying to use it with one of the other BO methods.

this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb => ewb.GetDatasetForTree(myUser,myPart,myRev,“”,“”,BpmFunc.Now(),false,false, out tsWB));

The BO calls for 8 in variables and 1 out variable. However, when I have the out variable in there I get the following error:

image

The method itself returns the dataset. Try this:

tsWB = this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb => 			
	ewb.GetDatasetForTree(myUser, myPart, myRev,
                          “”,  “”, BpmFunc.Now(), false, false));

Are you familiar with the API Swagger page? Biz Logic Teseter? Both are super useful when trying to test out unfamiliar BO’s.

Related note, be absolutely sure you want to use that method. It believe it will return everything in the eco group, which if (like most places) your engineers keep their groups open permanently, can mean returning hundreds of old parts long-since checked back in.

Im familiar with the API Swagger page. Never heard of the Biz Logic Teseter. Is it available to Gov Cloud users?

Thanks for explaining how the service handles the outbound return. Using the code you provided I get the following error:

image

In this instance, tsWB is a table set (EngWorkBenchTableset). Is this error because I have not declared tsWB in the c# code? If so how would I go about declaring a table set in this instance?

No, that one is just for locally hosted installs.

Yes.

//The verbose, technically recommended way to instantiate a new object.
EngWorkBenchTableset tsWB = new EngWorkBenchTableset();

//The way everyone actually does it.
var tsWB = new EngWorkBenchTableset();

You can also declare it at the function level if it’s going to be used outside of the one code block.

1 Like

I have used the Business Logic Tester successfully in the cloud. It uses the client DLLs to connect to the server and not REST. Think of it as Swagger for WCF. If you can connect via the Smart Client, then the Business Object Tester should work too. I had to ask Epicor support to send it to me. On-Prem users can install it from their servers.

1 Like

After declaring tsWB both ways you described I still get the same error. Im wondering if this is saying that what is being returned by the service call is void.

Also really appreciate all the help with this!

I actually had to test it out myself since I didn’t have an example offhand to compare. Turns out I had to enclose the method call in a bracket to get it to work. Why? No idea, but I’ve now got homework for later.

this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
{
    var tsWB = ewb.GetDatasetForTree(ecoGrp, part, rev, "",
                                     BpmFunc.Now(), false, false);
});

EDIT: key terms here are “lambda expressions” vs “lambda statements”. This is a statement lambda. The expression lambda wasn’t working. My guess is that, despite this being a single line, this is actually doing a couple things in one go thus it requires a statement block with the braces (which can be omitted on simpler expressions).

Good news is that I don’t think I need to rewrite anything I’ve done up to now. I rarely use the built-in Get methods (and when I do, I use the widgets) because they pull far, far more data than I typically need. LINQ FTW.

3 Likes

I love when helping turns into learning. :slight_smile:

3 Likes

That worked perfectly! Thanks for the clarification on the cause.

My next related question is how do we state that we wish to ignore outbound or inbound variables when there are multiple?

    this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
    {
        tsWB = ewb.CheckOut(myUser, myPart, myRev,"","", BpmFunc.Now(), false, myPass, false, true, false, out false, out false, out false);
    });

image

In the above example, there are four total outputs for the service call. The first three need to be ignored and the fourth is the output to our table set from earlier.

I tried leaving them blank but it called for a bool. I changed them to bool false statements. Then it called for an “out” statement in front of the bool. I added that and ended up with this error.

What would be the proper syntax for this?

Declare the variables beforehand. You don’t have to use them after the method call.

    string checkedOutRev, msg;
    bool flag;

    this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
        ewb.CheckOut(EcoGroup,
        PartNum,
        Revision,
        altMethods.FirstOrDefault(),
        DateTime.Today,
        false, //ipCompleteTree returns complete tree for ipGetDatasetForTree
        true, //ipValidPassword
        false, //ipReturn returns refreshed dataset
        false, //ipGetDatasetForTree False = call GetByID
        true, //ipUseMethodForParts
        out checkedOutRev, out msg, out flag)  );

I got all my code built out and the syntax comes out good. However, I am getting an error when running it.

The error seems to be happening on line 214 of the following code:

//Get user input on part rev from BAQ selection
//Get user op description text from 
//Get user change description from form
//Get op sequence code from user.
int counter = 0;
int counter2 = 0;
string checkedOutRev, msg;
bool flag;
myPart = callContextBpmData.Character01;
myRev = callContextBpmData.Character02;
//myComment = callContextBpmData.Character03; // defined after the dataset has been pulled. This helps combine the existing comment with the new comment.
myReason = callContextBpmData.Character04;
myUser = callContextBpmData.Character05;
myOp = Convert.ToInt32(callContextBpmData.Number01);
myResGrpID = callContextBpmData.Character06;
myQtyPerParent = Convert.ToDecimal(callContextBpmData.Number02);
mySetHours = Convert.ToDecimal(callContextBpmData.Number03);
myProdTime = Convert.ToDecimal(callContextBpmData.Number04);
SkipCheckIn = callContextBpmData.Checkbox01;
EngWorkBenchTableset tsWB = new EngWorkBenchTableset();

//check all inputs
this.PublishInfoMessage("myPart: " + myPart + "\n myRev: " + myRev + "\n myReason: " + myReason + "\n myUser: " + myUser + "\n myOp: " + myOp + "\n myResGrpID: " + myResGrpID , Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");

//Module: PromptPW1
this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb => ewb.PromptForPassword(out myPass));

//Module: GetDSTree 6
this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
{
    tsWB = ewb.GetDatasetForTree(myUser, myPart, myRev,"","", BpmFunc.Now(), false, false);
});

//Module: Op Seq Taken? 1
//PUT IN QUERY CHECK IF OP EXISTS
foreach (var PartOpr_iterator in (from r in Db.PartOpr where r.Company == Session.CompanyID && r.PartNum == myPart && r.RevisionNum == myRev && r.OprSeq == myOp select r))
{ 

//Module: No Valid Op! 1
  if (PartOpr_iterator == null)
  {
    this.PublishInfoMessage("This part and revision does not have Op " + myOp + ". Please review the operations for this part and select a valid Op sequence number.", Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");
    break;
  }
}

//Module: ECOUser? 1
//PUT IN QUERY CHECK IF USER ECO EXISTS
foreach (var ECOGroup_iterator in (from r in Db.ECOGroup where r.Company == Session.CompanyID && r.GroupID == myUser select r))
{ 

//Module: No ECO Group! 1
  if (ECOGroup_iterator == null)
  {
    this.PublishInfoMessage("You do not have permission to make this change. Please contact ERPSupport to add your userID to the ECO group list.", Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");
    break;
  }
}

//Module: GetDsTree 1
this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
{
    tsWB = ewb.GetDatasetForTree(myUser, myPart, myRev,"","", BpmFunc.Now(), false, false);
});

counter = 0;

//Module: SkipCHeckin?2
if(SkipCheckIn == true)
{

//Module: CheckedOutAnyone? 1
    foreach (var ECORev_iterator in (from r in Db.ECORev where r.Company == Session.CompanyID && r.PartNum == myPart && r.RevisionNum == myRev && r.CheckedOut == true select r))
    { 
        counter++;
    }
    
    if(counter > 0)
    {
    
//Module: CheckedOutByYou? 2
        foreach (var ECORev_iterator in (from r in Db.ECORev where r.Company == Session.CompanyID && r.GroupID == myUser &&r.PartNum == myPart && r.RevisionNum == myRev && r.CheckedOut == true select r))
        { 
            counter2++;
        }
        
//Module: Already Checked Out! 2        
        if(counter2 == 0)
        {
             this.PublishInfoMessage("This part and revision has already been checked out to another user. Please check the revision back in before editing any operations.", Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");
             //break;   
        }
    }
    else
    {
//Module: CheckOut 1
        this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
        {
            tsWB = ewb.CheckOut(myUser, myPart, myRev,"","", BpmFunc.Now(), false, myPass, false, true, false, out checkedOutRev, out msg, out flag);
        });
    }
}
else
{
    counter = 0;
    counter2 = 0;

//Module: CheckedOutByYou? 1
    foreach (var ECORev_iterator in (from r in Db.ECORev where r.Company == Session.CompanyID && r.GroupID == myUser &&r.PartNum == myPart && r.RevisionNum == myRev && r.CheckedOut == true select r))
    { 
        counter++;
    }
    if(counter == 0)
    {
//Module: Already Checked Out! 1 
        foreach (var ECORev_iterator in (from r in Db.ECORev where r.Company == Session.CompanyID && r.PartNum == myPart && r.RevisionNum == myRev && r.CheckedOut == true select r))
        { 
            counter2++;
        }
        if(counter2 > 0)
        {
             this.PublishInfoMessage("This part and revision has already been checked out to another user.Please check the revision back in before editing any operations.", Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");
             //break;   
        }
        else
        {
//Module: CheckOut 1
            this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
            {
                tsWB = ewb.CheckOut(myUser, myPart, myRev,"","", BpmFunc.Now(), false, myPass, false, true, false, out checkedOutRev, out msg, out flag);
            });
        }
        
    }
}

//Module: GetECORevData 1
this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
{
    tsWB = ewb.GetECORevData(myUser, true);
});

//Module: GetDsTree 5
this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
{
    tsWB = ewb.GetDatasetForTree(myUser, myPart, myRev,"","", BpmFunc.Now(), false, false);
});

//Module: GetECORevData 1
this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
{
    tsWB = ewb.GetECORevData(myUser, true);
});

//Module: GetDsTree 5
this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb =>
{
    tsWB = ewb.GetDatasetForTree(myUser, myPart, myRev,"","", BpmFunc.Now(), false, false);
});

//Module: set shortdesc1
myShortDesc = (from ECORev_Row in Db.ECORev
           where ECORev_Row.Company == Session.CompanyID && 
                 ECORev_Row.GroupID == myUser && 
                 ECORev_Row.PartNum == myPart && 
                 ECORev_Row.RevisionNum == myRev
           select ECORev_Row.RevShortDesc).FirstOrDefault();

//Module: Set myComment 1
myComment = (from ECORev_Row in Db.ECORev
           where ECORev_Row.Company == Session.CompanyID && 
                 ECORev_Row.GroupID == myUser && 
                 ECORev_Row.PartNum == myPart && 
                 ECORev_Row.RevisionNum == myRev
           select ECORev_Row.RevDescription).DefaultIfEmpty(".").FirstOrDefault();

//Check 
this.PublishInfoMessage("My Comment: " + myComment + " My Short Desc: " + myShortDesc, Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");

//Module: is mycomment blank 1
if(myComment == "")
{

//Module: set mycomment=. 1
    myComment = ".";
}

//Module: shortdesc is blank? 1
if(myShortDesc == "")
{
    
//Module: set shortdesc = . 1
    myShortDesc = ".";
}

//Module: tsWB RowMod=u1
/* update ECOOpDtl*/

Erp.Tables.ECORev ECORev;

ECORev = (from ECORev_Row in Db.ECORev
           where ECORev_Row.Company == Session.CompanyID && 
                 ECORev_Row.GroupID == myUser&&
                 ECORev_Row.PartNum == myPart && 
                 ECORev_Row.RevisionNum == myRev
           select ECORev_Row).FirstOrDefault(); //Get the ECOOpDtl record to update... 
if (ECORev != null)
{
  ECORev.RevShortDesc = myShortDesc;
  ECORev.RevDescription = myComment;
}

//Module: CheckECORev tsApp1 1
this.CallService<Erp.Contracts.EngWorkBenchSvcContract>(ewb => ewb.CheckECORevApproved(false, myPass, ref tsWB));

Error:

Even though this pops up it still checks out the part into your eco group. I also reapproved the revision and checked it back in but the error still comes up.

Is this something that just needs to be suppressed and if so how?

My first thought is that GetDatasetForTree is grabbing more than you think.

My second thought is that you could probably reduce the number of LINQ calls to the DB. I’m also wary that you’re making direct Db calls instead of copying it to a list and working on that.

It also looks like you’re setting Db fields directly instead of using the tableset and update methods? Yikes.

2 Likes

Looks like just descriptions.
Reassuring Jimmy Fallon GIF by The Tonight Show Starring Jimmy Fallon

1 Like

Currently, the code only checks out a part and unapproves it. The ECORev.RevShortDesc and ECORev.RevDescription are there so that I can see that something is happening table-wise when I do the checkout.

Also, this is all being done in our pilot system not live so no worries about breaking stuff here.

Also I have a partial solution to why the error is happening:

The code I’m creating here is based on the works of @NateS. In his post above he mentions that the row mod for tsWB needs to be marked as “U” before the unapproved method can be called.

However when I try to go into tsWB.ECORev.RowMod I get the following error:

Any ideas on how I can swap the RowMod on this sentax-wise?

I have to add:

using Erp.Tablesets;

to my BPM Usings and References.

1 Like