Is there a way to improve this dynamics ax update job - axapta

I'm working on an AX 2009 installation. The job is to update the WMSOrderTrans table. Here is what I have got so far:
WMSOrderTrans wmsOrderTrans;
;
while select wmsOrderTrans
{
if (wmsOrderTrans.BBBpackingSlipExists())
{
ttsBegin;
wmsOrderTrans.selectForUpdate(true);
wmsOrderTrans.BBBPackingSlipExists = NoYes::Yes;
wmsOrderTrans.doUpdate();
ttsCommit;
}
}
The job takes about an hour to finish on the test system. This makes me worry about the performance on the production system.
At the moment the code has been written like this to have minimal locking issues (selectForUpdate is done for each row if it should be updated and is then immediatly committed). The reason is, that users will be working on the system, when the job is running.
My question is, if there is a reasonable way to implement this job in a way with less transaction overhead.
while select forUpdate ...
... does not seem to be an option, because it would lock the table until the job is finished.
Any input is appreciated.
This is the code for the BBBPackingSlipExists method:
display boolean BBBpackingSlipExists()
{
InventDim inventDimCur;
InventDim inventDimPackSlip;
InventTrans inventTransPackSlip;
;
select firstonly RecId from inventTransPackSlip
where inventTransPackSlip.InventTransId == this.inventTransId
&& (inventTransPackSlip.StatusIssue == StatusIssue::Deducted
|| inventTransPackSlip.StatusIssue == StatusIssue::Sold)
&& !inventTransPackSlip.PackingSlipReturned
exists join inventDimCur
where inventDimCur.inventDimId == this.inventDimId
exists join inventDimPackSlip
where inventDimPackSlip.inventDimId == inventTransPackSlip.inventDimId
&& inventDimCur.inventSerialId == inventDimPackSlip.inventSerialId
;
if (inventTransPackSlip.RecId != 0 && this.isReserved)
{
return true;
}
return false;
}

This looks like a prime candidate to convert to set based logic, I'd go for something like this. Please note that the job isn't tested at all since I don't have a 2009 environment handy (this doesn't even compile on 2012) so if you need to change the code feel free to edit it into my answer.
Note that the isreserved check is built into the query as well as the exists joins from the packingslipexists method
static void Job250(Args _args)
{
WMSOrderTrans wmsOrderTrans;
InventDim inventDimCur;
InventDim inventDimPackSlip;
InventTrans inventTransPackSlip;
;
wmsOrderTrans.skipDatabaseLog(true);
wmsOrderTrans.skipDataMethods(true);
wmsOrderTrans.skipEvents(true);
update_recordset wmsOrderTrans setting BBBPackingSlipExists = NoYes::Yes
where wmsOrderTrans.isReserved
exists join inventTransPackSlip
where inventTransPackSlip.InventTransId == wmsOrderTrans.inventTransId
&& (inventTransPackSlip.StatusIssue == StatusIssue::Deducted
|| inventTransPackSlip.StatusIssue == StatusIssue::Sold)
&& !inventTransPackSlip.PackingSlipReturned
exists join inventDimCur
where inventDimCur.inventDimId == wmsOrderTrans.inventDimId
exists join inventDimPackSlip
where inventDimPackSlip.inventDimId == inventTransPackSlip.inventDimId
&& inventDimCur.inventSerialId == inventDimPackSlip.inventSerialId;
}
See the documentation on update_recordset and why the skip* methods might be necessary

Related

Set default data in field x++ PurchCreateOrder

I want to set a default value based on curUserid() in PurchCreateOrder. How can I put data in my field on form extension ?
Is there any better option of doing this ? Fields are bound to datasource and I have different fields with differentdatasources.
My code is giving me abnormal termination error with nullreferences. XPPCompiler
[ExtensionOf(tableStr(PurchTable))]
final class PurchCreateOrderGetDefAdress_Extension
{
void initvalue(PurchaseType _purchaseType)
{
next initvalue(_purchaseType);
PurchTable purchtable;
LogisticsPostalAddress logpostadress;
UserInfoSz usrsz;
str user = curUserId();
select firstonly logpostadress where logpostadress.city == 'lub';
// select firstonly InventSiteId, InventLocationId from purchtable join usrsz where purchtable.InventSiteId == usrsz.InventSiteId && usrsz.IsDefault == true;
select firstonly InventSiteId from usrsz where usrsz.UserId == user && usrsz.IsDefault == true;
purchtable.initValue();
purchtable.deliveryname = 'asasasasas' ;//logpostadress.Address;
purchtable.inventsiteid = usrsz.InventSiteId;
purchtable.inventlocationid = usrsz.InventSiteId;
info(strFmt("%1, %2, %3", logpostadress.Address, usrsz.InventSiteId));
}
}
The error is straight forward.
Error The augmented class 'PurchTable' provides a method by this name, but this method cannot be used as a chain of command method since the parameter profile does not match the original method.
Take a look at the highlighted parameter profile and compare to yours.
Edit: Take a look at these links for more info on Chain of Command (CoC):
https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/extensibility/method-wrapping-coc
https://channel9.msdn.com/Blogs/mfp/X-Chain-Of-Command (This video is excellent)

While select with if statement syntax - what is the purpose of the if statement?

I have come across a strange syntax that I have never seen in x++ before, but it compiles and works (as far as I can tell). I was curious if anybody has seen or used this before and could explain: what is the purpose of the if statement within the context of a while select?
InventBatch inventBatch;
InventTrans inventTrans;
InventTransOrigin inventTransOrigin;
InventDim inventDim;
ttsBegin;
while select Qty, DatePhysical from inventTrans
where inventTrans.StatusReceipt == StatusReceipt::Arrived
join inventTransOrigin
where inventTransOrigin.RecId == inventTrans.InventTransOrigin
&& inventTransOrigin.InventTransId == "SomeIdFromSomewhere"
join inventDim
where inventDim.inventDimId == inventTrans.inventDimId
&& inventDim.inventBatchId
if (inventTrans)
{
inventBatch = InventBatch::find(inventDim.inventBatchId, inventTrans.ItemId, true);
inventBatch.Field1 = inventTrans.Qty;
inventBatch.Field2 = inventTrans.DatePhysical;
inventBatch.update();
}
ttsCommit;
When you do a while select, generally you put {} to wrap your code, but you can also do the same thing as if statements, if you omit the {} and the immediately proceeding line gets executed for each loop.
if (true)
info("Hello World");
if (true)
{
info("Hello World");
}
while select SalesTable
info(SalesTable.SalesId);
while select SalesTable
{
info(SalesTable.SalesId);
}
Regarding the code you have typed above, it's idiotic. In AX, in older versions of the code if (common) would often only evaluate common.RecId != 0, but in later ones, I believe it will evaluate true if the buffer is returned with some data. In a while select however, it will always return true as the select is only returning records when it's true.
You could/should just delete literally only the if (inventTrans) line and leave the brackets and it will be readable/normal code.

Converting from Sql to Linq

I have, what I thought was a pretty straight-forward query.
In normal Sql this would read:
SELECT [column names]
FROM agentscheduledetail
WHERE (date = '2012-07-04') AND
(
exception = 'Break (No Sign Off)' OR
exception = 'Break' OR
exception = 'Break (Signed Out)'
)
This returns approx 900 records.
However, when I try to enter this into my controller, I end up with around 300,000 records - so I think my AND and ORs are not working. I've tried Linqer, but can't get it to work (I'm aware this may not be actual LINQ but the equivalent query in VS - if there is a linq version... I'd be grateful for that too if possible).
My controller query is:
var dte = DateTime.Today;
return View(db.agentscheduledetails.Where
(
d => d.date == dte && d.agentName.StartsWith("ta") &&
(
d.exception == "Break (No Sign Off)" ||
d.exception == "Break" ||
d.exception == "Break (Signed Out)"
)
).ToList()
);
Can anyone either a) let me know where I'm going wrong with my && || (and/or), or b) is there a way of stepping through the code in VS, to see what the above query translates to in normal SQL so I can try to figure out where I'm going wrong?
Thanks for any help,
Mark
The following is perhaps a simplified version of what you are trying to do, also your LINQ contains an additional statement compared to the SQL where it is comparing the agent name?
var currentDate = DateTime.Today;
var exceptionTypes = new List<string>() { "Break (No Sign Off)",
"Break", "Break (Signed Out)" };
db.agentscheduledetails.Where(d => d.date == currentDate &&
exceptionTypes.Contains(d.exception));
One thing that you could try is getting hold of a copy of LinqPad, this will let you run your LINQ statement against a database and will show you what the generated SQL statement is.
Aside from anything else,
d.agentName.StartsWith("ta")
does not appear in your original sql...?

add LedgerTrans.DocumentNum field to BankAccountStatment report

I want to add LedgerTrans.DocumentNum field to BankAccountStatment report
BankAccountStatment report has a datasource "BankAccountTable"
How can I perfrom this?
Note: LedgerTrans.DocumentNum can be reached through BankAccoutTrans.AccountId = BankAccountTable.AccountId then LedgerTrans.voucher = BankAccountTrans.Voucher
You can try declaring LedgerTrans ledgerTrans; in classDeclaration and adding following code in the report's fetch method before calling element.send(bankAccountTrans):
select firstonly ledgerTrans
where ledgerTrans.TransDate == bankAccountTrans.TransDate
&& ledgerTrans.Voucher == bankAccountTrans.Voucher
&& ledgerTrans.DocumentNum != "";
After that you'd only need to add a new display field in the ReportDesign\AutoDesignSpecs\Body:_2 section with the following code:
//BP Deviation Documented
display DocumentNum documentNum()
{
return ledgerTrans.DocumentNum;
}
I didn't try it but it should work. As an alternative you can declare ledgerTrans in the fetch method, add element.send(ledgerTrans) after selecting ledgerTrans, and add a standard String field in the section mentioned above, Table=LedgerTrans, DataField=DocumentNum. Then no display method is needed.
P.S. I assumed you're using AX 2009 but for other versions of AX the logic remains the same.
This is simple:
display DocumentNum documentNum()
{
return (select firstonly DocumentNum from ledgerTrans
where ledgerTrans.TransDate == bankAccountTrans.TransDate
&& ledgerTrans.Voucher == bankAccountTrans.Voucher
&& ledgerTrans.DocumentNum != "").DocumentNum;
}
Drag the method to the desired print location.

Filtering on linked table in Axapta/Dynamics Ax

I have a form in Axapta/Dynamics Ax (EmplTable) which has two data sources (EmplTable and HRMVirtualNetworkTable) where the second data source (HRMVirtualNetworkTable) is linked to the first on with "Delayed" link type.
Is there a way to set an filter on the records, based on the second data source, without having to change the link type to "InnerJoin"?
You could use "Outer join" instead of "Delayed" then change the join mode programmaticly when there is search for fields on HRMVirtualNetworkTable.
Add this method to class SysQuery:
static void updateJoinMode(QueryBuildDataSource qds)
{
Counter r;
if (qds)
{
qds.joinMode(JoinMode::OuterJoin);
for (r = 1; r <= qds.rangeCount(); r++)
{
if (qds.range(r).value() && qds.range(r).status() == RangeStatus::Open)
{
qds.joinMode(JoinMode::InnerJoin);
break;
}
}
}
}
In the executeQuery() on the EmplTable datasource:
public void executeQuery()
{;
SysQuery::updateJoinMode(this.queryRun() ? this.queryRun().query().dataSourceTable(tableNum(HRMVirtualNetworkTable)) : this.query().dataSourceTable(tableNum(HRMVirtualNetworkTable)));
super();
}
Sometimes this.queryRun() return null so use this.query() instead.
Update:
Note that the above is not relevant for AX 2012 and later, where you can use query filters in outer joins. See How to Use the QueryFilter Class with Outer Joins.
You can do it programmaticaly by joining QueryBuildDataSource or by extended filter (Alt+F3, Right click on datasorce, 1:n and find sev\condary DS)

Resources