Delete order line through AIF

The following code sample shows how to delete a line of an existing sales order through AIF from outside Dynamics AX 2012 . It uses a partial update, which means that we don’t have to send the whole document back to AX. As the documentation says, we have to include just the fields to change (none in my case) and “any fields required by the document” – that’s why I included fields such as PurchOrderFormNum. Note that you may have different fields set as mandatory.

Also notice how action properties are specified – we’re deleting the line, which means updating the order.

static void Main(string[] args)
{
    using (SalesOrderServiceClient client = new SalesOrderServiceClient())
    {
        EntityKey[] entityKeyList = EntityKeyForSalesId("SO00001");
 
        // Get the order to modify
        var order = client.read(new CallContext(), entityKeyList);
 
        // For demo, always the last order line is deleted
        var lastLine = order.SalesTable[0].SalesLine.Last();
 
        var salesLine = new AxdEntity_SalesLine()
        {
            RecId               = lastLine.RecId,
            RecIdSpecified      = true,
            SalesUnit           = lastLine.SalesUnit,
            action              = AxdEnum_AxdEntityAction.delete,
            actionSpecified     = true
        };
 
        var salesTable = new AxdEntity_SalesTable()
        {
            _DocumentHash           = order.SalesTable[0]._DocumentHash,
            PurchOrderFormNum       = order.SalesTable[0].PurchOrderFormNum,
            ReceiptDateRequested    = order.SalesTable[0].ReceiptDateRequested,
            action                  = AxdEnum_AxdEntityAction.update,
            actionSpecified         = true,
            SalesLine               = new[] { salesLine }
        };
 
        AxdSalesOrder newOrder = new AxdSalesOrder()
        {
            SalesTable = new[] { salesTable }
        };
 
        // Update the order
        client.update(new CallContext(), entityKeyList, newOrder);
    }            
}
 
// Helper method
private static EntityKey[] EntityKeyForSalesId(string salesId)
{
    KeyField field = new KeyField()
    {
        Field = "SalesId",
        Value = salesId
    };
 
    EntityKey key = new EntityKey()
    {
        KeyData = new[] { field }
    };
 
    return new[] { key };
}

Locking in Team Foundation Version Control

If you use Team Foundation Version Control, you have several ways to control whether multiple developers can work on same objects simultaneously. You normally want that, because developers often need to change a common file (a class, a configuration file and so on) and waiting for each other would cause unnecessary delays. But it’s not always the case – maybe you want to lock an individual file before making a sensitive change, some files can’t be easily merged so it’s not wise creating concurrent versions, maybe you’re developing in a shared AX instance and so on.

Before we actually look at locking, it’s important to understand the difference between local and server workspaces, because each of them work with locks differently. A TFS workspace defines which files are downloaded from TFS, tracks edited files and so on. Beginning with TFS 2012, you can choose between local and server workspaces; older versions of TFS support server workspaces only.

Note that you can easily switch between workspace types – simply open workspace properties in Visual Studio, switch to the “advanced” display and set Location to either Local or Server.

WorkspaceProperties

If you use a server workspace, a lot of state information is maintained on server, therefore you need a connection to your TFS to perform actions such as checkout or add. Local workspaces can track such actions by themselves, therefore you may be disconnected from TFS (if you have all files you need already in the workspace). If you want to check in changes to version control, you obviously need connection to server regardless of the type of your workspace.

This clearly leads to a difference in when you can apply locks – because local workspaces don’t have to communicate with server on checkout, you can’t expect them to honor locks. Therefore if you need exclusive check-out locks, you have to use server workspaces. Both type of workspaces can use check-in locks, which means that developers can do local changes but they can’t check them in until the lock is released.

Look at Understand lock types if you need more information about lock types and their use.

One way of taking a lock is as a part of check out. Simply choose a lock type in the Check Out dialog:

Checkout

The picture above is from a server workspace. If I use a local workspace, Check Out lock isn’t available.

LockTypeLocalWorkspace

You can be offline if you don’t need any lock, but you obviously must have a connection to TFS if you want it to lock the file and prevent others from uploading a new version.

Another option is taking a lock manually.

MenuLock

If you want TFS to take a Check Out lock automatically for certain file types (especially because you can’t merge them), you can configure it in team project collection settings. One option to get to the right form is Team > Team Project Collection Settings > Source Control…

TPCSourceControlMenu
TPCSourceControlSettings

Here you can edit or add file types and configure whether multiple checkouts should be allowed:

EditFileType

And finally, you can disable multiple checkouts for the whole team project (regardless of file type). Open source control settings for an individual team project (Team > Team Project Settings > Source Control…) and uncheck Enable multiple check-out.

TPSourceControlSettings

People sometimes think that it’s all or nothing (everything locked/unlocked), but you clearly have more options available. And the impact of workspace types can be very confusing for those who don’t understand workspaces.

All pictures are from Visual Studio 2013 connected to Visual Studio Online.

Table inheritance roots

I’m preparing a talk about table inheritance in AX 2012 and I wanted to know how many hierarchies exist in standard AX. Therefore I looked for all tables with SupportsInheritance = Yes and Extends = “”. It’s more than I expected (collected in AX 2012 R3):

  • AgreementHeader
  • AgreementHeaderExt_RU
  • AgreementHeaderHistory
  • AgreementHeaderHistoryExt_RU
  • AgreementLine
  • AgreementLineHistory
  • AifDocumentFilter
  • AifEndpointActionValueMap
  • AifPort
  • BankLC
  • BankLCLine
  • CaseDetailBase
  • CatProductReference
  • CatUserReview
  • CatVendProdCandidateAttributeValue
  • CollabSiteLink
  • CustInterestTransLineIdRef
  • CustInvLineBillCodeCustomFieldBase
  • CustInvoiceLineTemplate
  • CustVendDirective_PSN
  • CustVendRoutingSlip_PSN
  • DIOTAdditionalInfoForNoVendor_MX
  • DirPartyTable
  • EcoResApplicationControl
  • EcoResCategory
  • EcoResInstanceValue
  • EcoResProduct
  • EcoResProductMasterDimensionValue
  • EcoResProductVariantDimensionValue
  • EcoResValue
  • FBGeneralAdjustmentCode_BR
  • HRPDefaultLimit
  • HRPLimitAgreementException
  • IntercompanyActionPolicy
  • KanbanQuantityPolicyDemandPeriod
  • MarkupMatchingTrans
  • MCRCustCategory
  • PaymCalendarRule
  • PayrollPayStatementLine
  • PayrollProviderTaxRegion
  • PayrollTaxEngineTaxCode
  • PayrollTaxEngineWorkerTaxRegion
  • PCConstraint
  • PCPriceElement
  • PCProductConfiguration
  • PCRuntimeCache
  • PCTableConstraintColumnDefinition
  • PCTableConstraintDefinition
  • PCTemplateAttributeBinding
  • RetailChannelTable
  • RetailPeriodicDiscount
  • RetailPeriodicDiscountLine
  • RetailProductAttributesLookup
  • RetailPubEcoResCategory
  • RetailPubRetailChannelTable
  • RetailReturnPolicyLine
  • RetailTillLayoutZoneReference
  • SysManagedCodeExpression
  • SysManagedCodeExpressionParameter
  • SysPolicyRule
  • SysPolicyRuleType
  • TAMFundCustCategory
  • TradeNonStockedConversionLog
  • TrvEnhancedData
  • UserRequest
  • VendRequest

Microsoft MVP Program

MVP_Horizontal_30I’ve just received my second Microsoft MVP award (in Dynamics AX) and it may be a good time to answer a few common questions about the MVP program.

MVP means Most Valuable Professional and it’s an award given by Microsoft to people active in Microsoft technical communities. People who blog about Microsoft technologies, answer questions in forums, give talks, write open-source software or help others in different ways.

It’s given for one year only, therefore you have to prove your expertise and contribution again if you want to receive the award for another year. There are about four thousand MPVs in the world across all technologies (32 of them in the area of Dynamics AX). You can get more “official” information and find individual MPVs on mvp.microsoft.com.

The award is partially a form of appreciation from Microsoft and an official recognition of technical expertise. But it’s also an opportunity for both sides to work together more closely. MVPs get access to preliminary versions of software and non-yet-public information (which helps with preparing for what’s going to happen; it doesn’t mean that MVPs are allowed to share such information). Microsoft, on the other hand, can get feedback and ideas from experienced professionals from outside Microsoft.

MVPs receives a few other benefits – this is what I appreciate the most:

The first one is MVP Global Summit, a multi-day meeting of MVPs and Microsoft employees. It was my first summit last year and I simply loved it. Microsoft kept us really busy with upcoming changes of Dynamics AX and I met many enthusiastic people (including AX MVPs that I knew only from internet), which is not always the case in my job. It’s going to be a bit different this year, because there may be no AX content at all (the main reason is the collision with Convergence 2014 Europe), but it should give me an opportunity to visit sessions for other technologies and meet even more people.

Another useful benefit is access to some online resources (some of them would normally require being a Microsoft partner; a great thing for a freelancer) and free licenses to software (both from Microsoft and other companies, e.g. Telerik).

If you want to become an MVP, the hardest part is doing something valuable for the community. And doing it a lot. Getting a nomination is easy – see Nominate an MVP page; you can even nominate yourself. Then you have to report your activity (number of blog posts etc.) in the previous your and wait for Microsoft to compare your activity with others… Good luck.

Printing dynamic parameters (AX 2012 / SSRS)

In addition to parameters specified in data contracts, reports can also use queries. It’s very handy, because users can specify filters and sorting in exactly the same way as they’re used to from other places in Dynamics AX, they can use query expressions and so on.

ReportDialog
RangeSelection

A relative common request is printing parameter values on the report itself. It’s easy with parameters defined in data contracts – an expression like =Parameters!UsersToInclude.Value will do the job. But the same approach doesn’t work with dynamic parameters (that’s how query parameters get represented in SSRS).

I recommend the following approach. Use a report data provider class – very often, it’s already in place. Add a new data table to hold information about query ranges and expose it to the report. I’m using an existing table, TmpSysQuery, so we don’t have to discuss the design of the table. In processReport(), we extract ranges from the current query and save them to our temporary table.

class LedgerJournalPostControlDP extends SRSReportDataProviderBase
{
    …
    TmpSysQuery tmpSysQuery;
}
 
[SRSReportDataSetAttribute(tablestr(TmpSysQuery))]
public TmpSysQuery getTmpSysQuery()
{
    select tmpSysQuery;
    return tmpSysQuery;
}
 
public void processReport()
{
    …
    tmpSysQuery = MyQueryUtils::rangesToTable(this.query());
}

Because the logic for extracting ranges from a Query object may be needed from other places as well, I’ve put it into a separate class. This is the content of the method:

public static TmpSysQuery rangesToTable(Query _query)
{
    QueryBuildDataSource    qbds;
    QueryBuildRange         queryBuildRange;
    TmpSysQuery             tmpSysQuery;
    LabelType               tableLabel;
    int                     occurrence;
    int                     dataSourceNo;
    int                     i;
 
    if (!_query)
    {
        return tmpSysQuery;
    }
 
    for (dataSourceNo = 1; dataSourceNo <= _query.dataSourceCount(); dataSourceNo++)
    {
        qbds = _query.dataSourceNo(dataSourceNo);
        if (qbds.enabled())
        {
            occurrence = SysQuery::tableOccurrence(_query, qbds.table(), dataSourceNo);
            tableLabel = tableId2pname(qbds.table()) + SysQuery::tableOccurrenceText(occurrence);
 
            for (i = 1; i <= qbds.rangeCount(); i++)
            {
                queryBuildRange = qbds.range(i);
 
                if (queryBuildRange.value() && queryBuildRange.status() != RangeStatus::Hidden)
                {
                    tmpSysQuery.clear();
                    tmpSysQuery.DataSourceNo = qbds.uniqueId();
                    tmpSysQuery.TableLabel   = tableLabel;
                    tmpSysQuery.FieldLabel   = fieldId2pname(qbds.table(), queryBuildRange.field());
                    tmpSysQuery.RangeValue   = queryBuildRange.value();
                    tmpSysQuery.insert();
                }
            }
        }
    }
 
    return tmpSysQuery;
}

Then we add a new data set in our report and show query ranges in a tablix in exactly the same way as other data:

PrintedRanges