I’m trying to call function B from function A but and found that as soon as I enable requires transaction on A or B the function throws the following error “Sorry! Something went wrong. Please contact your system administrator. Correlation Id: b90a31bb-27dc-475e-a099-5c6ca21fdff1”
Function A needs to update information but it was getting a bit too big so I tried breaking it up using the helper function B.
Function A will update OrderHed info while B only gets shipping info from OrderHed and OrderMsc.
Here is Function A (Requires Transaction)
// FUNCTION: RefreshApprovals
// Return values
hasSalesHold = false;
output = "";
var context = (Erp.ErpContext)Ice.Services.ContextFactory.CreateContext();
var erpContext = new Erp.Internal.Lib.CCredChk(context);
using(var txScope = Ice.IceContext.CreateDefaultTransactionScope())
{
// UPDATE APPROVALS ON HEADER
foreach(var oHead in (from h in erpContext.Db.OrderHed.With(LockHint.UpdLock) where
h.Company == this.CompanyID &&
h.OrderNum == orderNum
select h))
{
bool interCompany = false;
foreach(var cust in (from c in erpContext.Db.Customer where
c.Company == this.CompanyID &&
c.CustNum == oHead.CustNum
select c))
{
interCompany = cust.ICCust;
}
if(interCompany)
{
oHead.OrderApproved_c = true;
}
var shippingInfo = this.ThisLib.GetShippingInfo(orderNum);
string salesHolds = shippingInfo.output;
hasSalesHold = (!shippingInfo.shippingApproved && ! interCompany);
oHead.ApprovalInfo_c = output;
}
erpContext.Db.Validate();
txScope.Complete();
Here is Function B (Does not require transaction)
// FUNCTION: GetShippingInfo
// Return Values
shippingApproved = false;
output = "";
foreach(var oMisc in (from o in Db.OrderMsc where
o.Company == this.CompanyID &&
o.OrderNum == orderNum &&
(o.MiscCode == "FRTC" ||
o.MiscCode == "FRT")
select o))
{
shippingApproved |= oMisc.DocMiscAmt > 0.0M; // Check if they have a freight charge of more than zero
}
if(!shippingApproved) // If no freight charge do they have account info?
{
foreach(var oHead in (from h in Db.OrderHed where
h.Company == this.CompanyID &&
h.OrderNum == orderNum
select h))
{
shippingApproved |= oHead.PayFlag != "" && oHead.PayAccount != "" && oHead.PayBTAddress1 != "" && oHead.PayBTZip != "";
}
}
if(!shippingApproved) output += "No shipping! Add freight or payors shipping account info (Manifest Detail > Billing)\n";
I found that if I remove every call to Db in function B then I don’t get an error. Is there a better way to break up my function into helper methods or should I just leave everything in function A?
I assumed it was because I was not passing the erpContext from A to B. I guess I’m unfamiliar with how to do that and I’m also not sure what you mean by “You need to pick one either the default Tran scope or the transaction checkbox”. Could you elaborate?
Thanks for the responses I’m new to using functions to update values in Db. I’m putting together some simple functions to understand how context is handled when calling another function. I’ll post my findings here. In the mean time I was wondering do I need these lines in order to update values in the database
or could I just call Db.Validate without it? What is the purpose of those lines? I found that I couldn’t get my function to update without them in the past.
You should never instantiate your ERP Context Here, or establish your own Transaction Scope that creates double transactions and can cause all sorts of fun record locks and nightmares.
I tested your advice out by making two simple functions. Function A (“Requires Transaction”) sets an order’s OrderComment field with the customers name and time stamp. It gets the customers name from Function B.
I updated my BPM that was calling the function to loop through a list of orders instead of the DB directly and now it runs without issue.
I wanted to clarify. Should I only loop through read only Dbs as a list and still loop through the write Dbs directly? For example, my function A (Requires transaction) would look like this
// FUNCTION A
foreach(var oHead in (from h in Db.OrderHed.With(LockHint.UpdLock) where
h.Company == this.CompanyID &&
h.OrderNum == inputOrderNum
select h))
{
// CALL FUNCTION B
var customerName = this.ThisLib.B(oHead.CustNum);
oHead.OrderComment = customerName + " " + DateTime.Now.ToString("MM/dd/yyyy h:mm:ss tt");
}
Db.SaveChanges();
and my function B would look like this
// FUNCTION B
var customers = (from c in Db.Customer where
c.Company == this.CompanyID &&
c.CustNum == inputCustNum
select c);
foreach(var customer in customers)
{
outputName = customer.Name;
}
So you should NEVER ← here’s that word again take this as most of the time
You shouldn’t loop through an open linq query the way you are doing it
foreach(var oHead in (from h in Db.OrderHed.With(LockHint.UpdLock) where
h.Company == this.CompanyID &&
h.OrderNum == inputOrderNum
select h))
^ this can sometimes particularly when using transactions leave an open “cursor” ← not really but close enough and cause issues. Particularly if you are using transactions and jumping functions.
I recommend you make your linq query .ToList() so that you resolve that query and pull results, then you loop through the list and make your changes. Like this
var orderList = (from h in Db.OrderHed.With(LockHint.UpdLock) where
h.Company == this.CompanyID &&
h.OrderNum == inputOrderNum
select h).ToList();
foreach(var oHead in orderList)
Also it isn’t necessary to provide a LockHint.UpdLock (ever) SQL uses optimistic locking let SQL manage itself it is better for everyone.