Quantcast
Channel: Dynamics 365 Blog
Viewing all 773 articles
Browse latest View live

Adding an image to a Role Center page to fill the content area on tablets

$
0
0

With the introduction of the new Microsoft Dynamics NAV Tablet client, developers are looking for ways to create simple Role Centers that are ideal for use on small-screen mobile devices. If your Role Center has a few cues but no content, adding a company logo is one simple way to make it look great and personal! This blog post explains how that can be done in a few simple steps.

Note that this feature is implemented only for the Role Center pages in the Microsoft Dynamics NAV Tablet client.

Step 1: Set up the Role Center page that is used by the Microsoft Dynamics NAV Tablet client

As an example I will modify the existing Order Processor Role Center page and remove all content parts. The modified page will now look as illustrated below.

Note that the content page must contain either image or content parts. Cues are considered navigation elements which is why the SO Processor Activities part or any other part containing cues can only be added to the Role Center page in this particular scenario.

When the page is opened in the Microsoft Dynamics NAV Tablet client, it should look as illustrated below.

The page presented above does not look ideal, does it? So how do we make it more appealing? The answer is simple; by adding a company logo and make it shine.

Step 2: Add an image to the content area of the Role Center page

The developer needs a table, or more specifically a table field of type BLOB, to store an image. In this post, the Company Information table (79) is used because it has a Picture field that stores a company logo. In a real-life scenario, a dedicated table is a better solution. Because the Role Center page can contain only groups and parts, the second object that the developer needs is the card part page that will contain an image. The snippet below represents a sample page. Notice that the image has the ShowCaption property set to No. It indicates to the tablet client that such an image should fill the entire screen. Such behaviour is only implemented for Role Center pages in the tablet client.

OBJECT Page [Desired Id] Company Logo

{

  OBJECT-PROPERTIES

  {

    […]

  }

  PROPERTIES

  {

    SourceTable=Table79;

    PageType=CardPart;

  }

  CONTROLS

  {

    { 1   ;0   ;Container ;

                ContainerType=ContentArea }

 

    { 2   ;1   ;Field     ;

                SourceExpr=Picture;

                ShowCaption=No }

  }

  CODE

  {

    […]

  }

}

The last step is to modify the selected Role Center page and add the part that hosts the page created earlier. The part must be added to the RoleCenterArea, as illustration below.

Step 3: Upload the desired image to be used as logo

In case the Company Information table is used to store the image, the simplest way to upload an image is to open the Company Information page (1) in edit mode and then select an image to be displayed on the Role Center page.

Now when the Role Center page is opened, it will look like the page presented in the following picture. It is simple and looks great.

Notes

The image width and height is set to be 100% in CSS and that impacts how the image will look on a page. There are a few important things to be aware of:

  1. The image will scale to the available width of the content area and scale accordingly to its original ratio. That behaviour requires using an image that will look great on supported screen sizes and will not need to have vertical scrollbars.
  2. Because the image will grow or shrink, it is essential to upload an image of the desired resolution so that a user will not experience a decreased image quality caused by scaling. It is important to balance quality versus loading time.
  3. The image presented in the illustration above has size of 1800 x 1200 and fits Surface RT and iPad screens perfectly (the image is scaled down).

NAV Design Pattern - Currently Active Record

$
0
0

One of the good things about having the NAV Design Patterns project open to the community, is that we get to hear about the experiences and design considerations of the NAV partners, which brings to light practices from other places than MDCC. Find below a pattern described by Henrik Langbak and Kim Ginnerup, from Bording Data in Denmark.

Best regards,

The NAV Design Patterns team

Meet the Pattern

Date controlled data is expensive to find in the database. This pattern describes how using a view with a sub-select and a linked table object will minimize the returned dataset.
A side effect is reduced and simplified code, increased performance and a more scalable solution that is almost independent of the amount of records in the table.

Know the Pattern

There is no way in Microsoft Dynamics NAV to get a set of records from the database, which all have the newest starting date, that is less than or equal to today’s date. Having an ending date on the record will help, but it introduces some other problems. In Microsoft Dynamics NAV, this is normally done by reading too many records, either at the SQL Server level or in the middle tier and throw away the ones you do not need. That is a waste of resources:

  • SQL Server reads too many records
  • Too much data sent over the network.
    (If SQL Server and Microsoft Dynamics NAV Server are on different machines.)
  • The Microsoft Dynamics NAV service tier receives and throw away data.

Ending Date Problem

Ending Date may introduce some problems of its own.

If your design requires you, to have one and only one active record per key in a dataset, then Ending Date introduces the possibility for overlapping or holes in the timeline.
Ending Date creates a dependency between two records. Changing a Starting Date, requires you to update the previous record. Changing the Ending Date requires you to update the next record.
If you add a record in between you will have to update both the before and the after record.

The pattern we describe here will work whether there is an Ending Date or Not.

The pattern is also relevant for other types than date. The pattern is usable whenever you have dependencies between rows in a table. 

Use the pattern whenever you read a set of data containing a Starting Date and you need to implement a loop to throw away unwanted records. An example could be Codeunit 7000 “Sales Price Calc. Mgt.”. In this codeunit there are many loop constructs to find prices and discounts.

Use the Pattern

In the following example, we have a fictive table containing: Code, Starting Date and Price. The Primary Key consist of Code, Starting Date. The Database is the Demo Database, and the Company is Cronus.

1. Create the view

You will need to create the view before you define the Table Object.
You will need to create a view for every company in the database. 

CREATE VIEW [dbo].[CRONUS$PriceView]
AS
SELECT [Code], [Starting Date], [Price]
FROM dbo.[CRONUS$Price] AS A
WHERE [Starting Date] =
        (SELECT MAX([Starting Date])
         FROM dbo.[CRONUS$Price] AS B
         WHERE B.[Code] = A.[Code] AND
               B.[Starting Date] <= GETDATE())

Test the view to ensure that you get the correct result. It is much easier to test now than later.

2. Create the Table object

Read more on the NAV Design Patterns Wiki...

Welcome to the new Microsoft Dynamics NAV Dev Center on MSDN

$
0
0

We have moved the Microsoft Dynamics NAV dev center to a new platform, and we hope you are going to find it a good source of information going forward. The dev center is in the same location as before but looks somewhat differently. Remember good old grey? Well, it's gone - take a look here:

http://msdn.microsoft.com/en-us/dynamics/nav/

At the dev center, your and your coworkers can find the latest readiness videos for Microsoft Dynamics NAV 2015 and Microsoft Dynamics NAV 2013 R2. You will also find links to other getting-started content in the MSDN Library, and tips for other sources of information about developing solutions based on Microsoft Dynamics NAV. You can find links to the latest downloads of the product, including the latest cumulative updates, and direct links to the three app stores.

We hope you will find the new dev center easier to navigate and less likely to contain outdated information. We plan to add more content to the dev center in the coming months, so add the dev center to your list of sites to check up on from time to time.

 

Best regards

Susanne and Eva from the NAV UA team

How to Compile a Database Twice as Fast (or faster)

$
0
0

Compiling a complete NAV database can take quite a while. Even on powerful development machines with a lot of CPU cores this is still the case - the development environment wasn't designed for the multi-core era. The only way to speed things up is to use separate instances of the development environment and have each compile a (distinct) subset of the objects in the database. With the new Development Environment Commands for PowerShell that were included in the Microsoft Dynamics NAV 2015 Development Shell, this has become a lot easier.

Before heating up those cores, let's first introduce the command that we need for this: Compile-NAVApplicationObject. In the following example we'll assume that the database and Development Shell reside on the same machine. To compile all non-compiled objects in a database named the command simply takes a parameter that specifies the database (i.e., MyApp) and optionally if and how schema changes should be synchronized:

Compile-NAVApplicationObject -DatabaseName MyApp -SynchronizeSchemaChanges No

To compile all objects in a database regardless their current compiled state use the Recompile switch:

Compile-NAVApplicationObject -DatabaseName MyApp -SynchronizeSchemaChanges No -Recompile

The command also takes a filter, e.g.:

Compile-NAVApplicationObject -DatabaseName MyApp -Filter ID=1..100

compiles all non-compiled objects with an ID in the range 1 to 100.

Now to parallelize the compilation process we need to partition the set of objects in distinct sets that can be compiled in parallel. The most straightforward way to do this is based on object type. For each object type we can start a compilation job using the AsJob parameter. Using this parameter an instance of the development environment is started in the background and a handle to this background job is returned. PowerShell comes with a set of commands to work with jobs, for instance, to get the output of a job (Receive-Job) or to wait for a job to finish (Wait-Job). Occasionally, race conditions could occur while updating the object table. As a result, some objects may fail to compile. Therefore, after all background jobs have completed we compile all non-compiled objects in a final sweep. This is all we need to understand the following function that compiles all objects in a database in 7 parallel processes:

function ParallelCompile-NAVApplicationObject
(
[Parameter(Mandatory=$true)]
$DatabaseName
)
{
$objectTypes = 'Table','Page','Report','Codeunit','Query','XMLport','MenuSuite'
$jobs = @()
foreach($objectType in $objectTypes)
{
$jobs += Compile-NAVApplicationObject $DatabaseName -Filter Type=$objectType -Recompile -SynchronizeSchemaChanges No -AsJob
}

Receive-Job -Job $jobs -Wait
Compile-NAVApplicationObject $DatabaseName -SynchronizeSchemaChanges No
}

  
Just for fun, let's measure the performance gain. We can do this using Measure-Command:

Measure-Command { Compile-NAVApplicationObject MyApp -SynchronizeSchemaChanges No -Recompile }
Measure-Command { ParallelCompile-NAVApplicationObject MyApp } 

 

These two screenshots from task manager illustrate the difference in CPU utilization: while running the non-parallel version CPU utilization is hovering around 40%; while running the parallel version CPU utilization is maxed out at 100%.

            

                    

On my laptop (with 2 CPU cores) compilation of the 4148 objects in the W1 application takes 8 minutes and 46 seconds using the non-parallel version; using the parallel version it takes only 4 minutes and 32 seconds. Machines with more CPU cores may produce even better results. Note that when parallelizing this way (i.e., based on object type) only four processes are active most of the time - compilation of Queries, XMLports and MenuSuites finishes relatively quick. So if you have a machine with a lot of cores (say 6 or 8) that you want to put to use, you need to find a way to partitioning the set of objects into a larger number of smaller sets.

Who beats 4 minutes and 32 seconds? And please share how you did it!

NAV Design Pattern - Document Pattern

$
0
0

If you’re just starting with NAV, this pattern is a must-know for any NAV developer or consultant. Thanks to Xavier Garonnat from knk Ingénierie in France for documenting it.

Best regards,

The NAV Patterns team

Meet the Pattern

A document structure contains a header and a set of lines. Each line is linked to the header and could have common data with header.

Know the Pattern

This pattern should be used as a basis to build any document, showing a header and multiple lines in the same page.  Basically, a document is at least composed of two tables and three pages, as shown below:

Use the Pattern

You should use it any time you have to capture and store a document.

Example 

To build this example from scratch, you will need:

  • Two tables, one for the header (called “Document Header”), and one for the document lines (called “Document Line”). Each document will be composed of “1 to N” line(s).
  • Three pages, one for the header, one for the subpage (lines), and the last for the document list obviously.

Table "Document Header": Is the "header" table of your document (like Sales Header, Purchase Header, Transfer Header…)  

  • Add a field "No." (Code 20): Should be the first field and primary key of your documents, to be driven by Serial No. (See corresponding design pattern)

For this sample, I just added a “Sell-to Customer No.” to this table. Don’t forget to manage deletion of lines with trigger OnDelete().

Table "Document Line": will store the lines of the document 

  • Add a field "Document No." (Code 20): Should be the first field and is related to table "Document Header": set TableRelation to your "Document Header" table
  • Add a field "Line No." (Integer): this field will be populated automatically by the subpage page (see AutoSplitKey)

First (Primary) Key must be “Document No.,Line No.". On table properties, set PasteIsValid to No (to avoid copying/pasting lines, will be implemented by “Copy document”, another pattern).

For my sample, I just add a couple of fields: “Item No.” and “Quantity” to this table (just copy/paste standard fields from “Sales Line” table and delete trigger code, this will insure that each field will be well designed)

Page "Document Subpage”: will display the lines in the main form, and will be in charge of assigning line number automatically.

Create the page for table “Document Line” with the wizard by selecting the ListPart template, add all yours fields except the primary key (“Document No.” and “Line No.”).

Then edit the properties: 

  • Set AutoSplitKey, DelayedInsert and MultipleNewLines to Yes: this combination will make your subpage work as required. 
  • AutoSplitKey is used to set NAV calculate the last field of the key (“Line No.”) with proper numbers (10000, 20000…).

Read more about this pattern on NAV Design Patterns Wiki community page...

Understanding Error Code 85132273 in Connection with GETLASTERRORCODE Statements

$
0
0

We recently received a few interesting requests about an error code appearing under particular circumstances: 85132273. This error might typically arise with the Windows Client but also with Web Services and NAS Services as well.

Digging under the hood, this error message is related to a Primary Key violation in SQL Server tables.

In most of cases, this could be due to C/AL code such as the following:

IF NOT MyTable.INSERT THEN …

This is quite an easy and diffuse syntax that looks innocent and clean (admittedly, we even have a few instances of it in the CRONUS demonstration database). However, it is a problematic use of code because it forces an INSERT into a table and if the record already exists, an implicit error is thrown. That gives us that violation of the primary key.

From a development perspective, we recommend that you refactor the code, where possible, in the following way (under a LOCKTABLE):

IF NOT MyTable.GET THEN MyTable.INSERT …

This code is preferable for at least two reasons:

  1. More logically correct

    It does not throw an implicit error caused by a primary key violation. Therefore developers could use GETLASTERRORCODE statement family in a more logical way. This statement family may catch, then, the violation of the primary key server side even though no visible runtime error would ever be show to the user. As short example, this code:

    CLEARLASTERROR;

    SalesPost.RUN;

    MESSAGE(GETLASTERRORTEXT);

    might display the error code 85132273 if there are nested statements like IF NOT INSERT THEN in the SalesPost codeunit.

    Please, be aware that this type of trapped error will not be displayed if also codeunit.RUN has a C/AL exception handling such as:

    CLEARLASTERROR;

    IF NOT SalesPost.RUN THEN

      MESSAGE(GETLASTERRORTEXT);

    In this case, any nested trapped error that might arise from IF NOT INSERT THEN will not be intercepted and no error text will be shown for this kind of violation of primary key.

  2. More efficient in some cases.

    INSERT statements with an implicit violation of the primary key are costly and if the code is typically something like IF NOT INSERT THEN MODIFYand the number of MODIFY statements is high, the statement with IF NOT GET THEN INSERT ELSE MODIFY or, better, IF GET THEN MODIFY ELSE INSERT would be typically faster in terms of performance.

If you intend to implement error statement like GETLASTERRORMESSAGE, GETLASTERRORCODE and GETLASTERRORCALLSTACK, then you might notice that C/SIDE error code refers to a DB:RecordExists. Attached to this blog post, you will find a simple page object as proof of concept to demonstrate when and how this error might arise for a better understanding.

Please use the attached page object only for testing and understanding, and do not use it on a live environment. It is just to demonstrate the error code in a standard CRONUS demonstration database.

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

 

Duilio Tacconi                                      Microsoft Dynamics Italy         

Microsoft Customer Service and Support (CSS) EMEA

Special thanks to Peter Elmqvist from nabsolution.se 

Code Upgrade: Merging Version Lists

$
0
0

Merging cumulative updates into your own Microsoft Dynamics NAV solutions has become a lot easier with the new merge utilities. They are based on the concept of three-way merge to reconcile two sets of changes made (independently) to a given base version. For example, when applying Microsoft Dynamics NAV 2015 Cumulative Update 1 to a customized database, the customized database and the cumulative update are both derived from Microsoft Dynamics NAV 2015 RTM, which is the shared based version in that case.

There is one aspect, though, that raised quite a few questions from the community and that is merging of the Version List object property.  Out-of-the-box the merge command offers only very limited support to handle this. You can either choose to clear the version list or take the version list from the modified or target object. Since the Version List is just text, we couldn't really assume how it is used generically and provide corresponding merge capabilities. However, the Merge-NAVApplicationObject Windows PowerShell cmdlet provides very rich output, and in combination with the command for setting object properties it is possible to deal with version lists any way you like.

What appears in the PowerShell console when the Merge-ApplicationObject cmdlet completes is just a summary. The cmdlet actually returns a collection of objects that each contain information about a merge. To inspect what information is available, you can assign the result to a variable and use the Get-Member comman, as shown in the following code snippet:

$m = Merge-NAVApplicationObject ...

$m | Get-Member -MemberType Properties

 

   TypeName: Microsoft.Dynamics.Nav.Model.Tools.MergeInfo

 

Name        MemberType   Definition

----        ----------   ----------

PSPath      NoteProperty System.String PSPath=

Conflict    Property     Microsoft.Dynamics.Nav.Model.Tools.ApplicationObjectFileInfo Conflict {get;}

Error       Property     Microsoft.Dynamics.Nav.Model.Tools.ErrorInfo Error {get;}

Id          Property     int Id {get;}

MergeResult Property     Microsoft.Dynamics.Nav.Model.Tools.MergeResult MergeResult {get;}

Modified    Property     Microsoft.Dynamics.Nav.Model.Tools.ApplicationObjectFileInfo Modified {get;}

ObjectType  Property     string ObjectType {get;}

Original    Property     Microsoft.Dynamics.Nav.Model.Tools.ApplicationObjectFileInfo Original {get;}

Result      Property     Microsoft.Dynamics.Nav.Model.Tools.ApplicationObjectFileInfo Result {get;}

Target      Property     Microsoft.Dynamics.Nav.Model.Tools.ApplicationObjectFileInfo Target {get;}

 

The output of the merge is a collection of MergeInfo objects. Each MergeInfo object represents a merge operation for an application object and contains information about the original, modified, target and result objects. As such, the MergeInfo objects give us access to the original, modified, and target version list, such as:

$minfo = $m | select -first 1

$minfo.Modified.VersionList

Assuming that given the three input version lists we can determine the desired version list for the merge result, we can define a function that takes such a MergeInfo object to calculate the resulting version list:

function New-NAVVersionList($MergeInfo)

{

...

}

 

Before we implement this function, let's first see how we could use it. The Set-NAVApplicationObjectProperty cmdlet updates the object properties (including the version list) for all application objects in a text file:

Set-NAVApplicationObjectProperty -TargetPath objects.txt -VersionListProperty 'v1'

When we iterate over all MergeInfo objects, we can use this command together with the New-NAVVersionList function to update the version list for each object in the merge result:

foreach($mInfo in $m)

{

  $versionList = New-NAVVersionList $mInfo

  Set-NavApplicationObjectProperty -TargetPath $mInfo.Result -VersionListProperty $newVersionList

}

What remains is the implementation of the function that actually calculates the new version list based on the version list of the three inputs (original, modified, and target). For instance, to simply concatenate modified to target:

function New-NAVVersionList($MergeInfo)

{

  # MyCustomVersion,TheirModifiedVersion

  "$($MergeInfo.Target.VersionList),$($MergeInfo.Modified.VersionList)"

}

How version lists are often used is that for each add-on or customization for which an application object is changed a version tag is added (separated by comma). In turn, each version tag typically consists of some product code and a version number. For example a Microsoft Dynamics NAV 2015 W1 object that was customized for my add-on (MY) and their add-on (THEIR) could get version list: NAVW18.00.00,MY1.1,THEIR6.2. Note that the version number for the NAV version tag is composed of a major, minor, and build number. The build number is typically updated in the case of cumulative updates.

When merging an update into your code any version tag might be updated or new version tags might be added. The result of merging version lists should contain one tag for each product code that occurs in the modified and target version lists and it should be the tags with the highest version number for that product code. With these requirements our New-NAVVersionList function becomes this:

function New-NavVersionList($MergeInfo, [string[]]$ProductCode = 'NAVW1')

{

    $allVersions = @() + $MergeInfo.modified.versionlist -split ','

    $allVersions += $mergeInfo.target.versionlist -split ','

 

    $mergedVersions = @()

    foreach ($code in $ProductCode)

    {

        # keep the "highest" version tag for $code

        $mergedVersions += $allVersions | where { $_ -like "$code*" } | sort | select -last 1

 

        # remove all $code version tags

        $allVersions = $allVersions | where { $_ -notlike "$code*" }

    }

 

    # return a ,-delimited string consisting of the "highest" versions for each $ProductCode and any other tags

    $mergedVersions += $allVersions

    $mergedVersions -join ','

 

This function was brought to you by Bas Graaf.

On behalf of him and the rest of the Dynamics NAV team, your blog editor wishes you all Happy Holidays!

NAV Design Pattern - the Released Entity Pattern

$
0
0

Happy New Year, everyone!

With the new year, here's a new pattern to warm us up. Coming from a Danish partner, this pattern describes how to handle data entities when they need to be in a consistent or in a certain state before further processing can be allowed. Thank you Henrik Langbak and Kim Ginnerup from Bording Data for sharing this knowledge with the NAV developers community.

Best regards,

The NAV Patterns team

Meet the Pattern

This pattern prevent data from being used elsewhere before it is in a system consistent state.
Microsoft Dynamics NAV inserts a record as soon as the primary key has been set. But the record may not be in a valid state at this point in time. How do you know if a newly inserted record is ready for use?

Know the Pattern

Whenever you need to stall the release of data, you can use this pattern.

Because Microsoft Dynamics NAV decides when a record is written to the database, it may not be in a system consistent state. Nobody should use the record before everything is in place and the record is valid. An inserted record may even have data in other tables that needs to be inserted and in a valid state before other parts of the system can use the data without running into a problem.

Data entered into the system may have to be approved by a second person before it can be used.

Data requires different parties (e.g. Departments) to add information before data is valid.

The solution is an Option Field with two or three values:
(Open, Released) or (Open, Pending, Released)

The states should be interpreted as:

State

Description

Open

Not all data is in place. The record is system inconsistent. The record or record hierarchy is invisible for all other parts of the system.

Pending

The record is system consistent. But is awaiting someone to manually raise the state to Released.
The record is still invisible.

Released

All data is in place and the record is system consistent, and ready for use. It is now visible for the rest of the system. The state can never be reversed.

The option field name: Release State.

This pattern is very similar to the Blocked Entity pattern, but it has one significant difference.
The record is not visible to any part of the system, before it is in the Released state.
There is no going back. When the Released state is reached, it will stay that way for the life of the record. In case of a tri-state, it is ok to bypass Pending seen from a system state perspective.

If there is a hierarchy, e.g. Header and Lines, then the Release State field resides on the Header. As long as the Header remains unreleased, the lines are considered inconsistent and must not be used.

The important and critical part of this pattern is that the whole application needs to obey the “Release State”-contract or the system will fail. 

Use the Pattern

To use this pattern you need to create an Option Field named: “Release State” with at least the two states: Open, Released. 

Read more about this pattern on the NAV Design Patterns Wiki community page...


C/AL Coding Guidelines used at Microsoft Development Center Copenhagen

$
0
0

Recently, the NAV Design Patterns team published the C/AL Coding Guidelines that we use at Microsoft Development Center Copenhagen to the Dynamics NAV Community Wiki site. Please join us there if you want to read and discuss the guidelines. 

However, we've been asked for a printable version of the guidelines as well, so we attached a PDF file with the current version to this blog post. This PDF file will not be updated if and when the guidelines change, so please consider them a snapshot as of January 2015. For more information, see the Dynamics NAV Community Wiki.

 

Best regards,

The NAV Design Patterns team

“Timer” usage in Microsoft Dynamics NAV 2013

$
0
0

Last time we have seen our “old” NTimer.dll was Microsoft Dynamics NAV 2009. It was placed in  ..\Common\Microsoft Dynamics NAV\Timer\.. folder.
Usually we have used it for NAS or other looping tasks.

We want to have the same in Microsoft Dynamics 2013 and later, however we see that the same doesn’t work anymore.
So how it is now?

Since Microsoft Dynamics NAV 2013 timer becomes “.NET” type in add-in’s folder. Now it is named “Microsoft.Dynamics.Nav.Timer.dll” and placed on server in C:\Program Files\Microsoft Dynamics NAV\70\Service\Add-ins\Timer.. folder.
Usage, manage and expected results are now different.

First of all this is server side timer.

We use timer as DotNet type variable like:
Timer@1000 : DotNet "'Microsoft.Dynamics.Nav.Timer, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.Microsoft.Dynamics.Nav.Timer" WITHEVENTS;

Use variable in code like:
IF ISNULL(Timer) THEN BEGIN
              Timer := Timer.Timer;
              Timer.RedirectExceptions := TRUE;
              Timer.Interval := 10000; // 10 seconds
              Timer.Start();
            END;

It has two events “Elapsed” and “ExceptionOccurred” (if RedirectExceptions = TRUE).
Event “Elapsed” is executed when we want with code we want.
If we use “Timer” in NAS then code in “Elapsed” event is code we want to be executed by NAS.

And here comes big point:
If error in our C\AL code occurs then error is logged to events, NAS stops and session is terminated. We have option in NAV service config – “Retry Attempts Per Day”. Default value is 3. It means that after error occurs and NAS stops, there will be NAS restart and this will be done 3 times. If we have started NAS first time manually then after error occurs there will be 3 attempts to restart NAS. Finally we’ll have 4 errors in event viewer and need to restart NAS manually.
To avoid such situations we need move all our c\al code to codeunit and catch all errors with c\al code like:
…….
IF NOT CODEUNIT.Run(xxx) THEN
  InsertError(“A business logic error occurred: “ + GETLASTERRORTEXT);
……..
Here in “Elapsed” event we execute some codeunit and if error during execution occurs, it is logged with some function InsertError.

Event “ExceptionOccurred” is executed when unknown exceptions like polling data from http stream causing io/timeout etc. exception comes to timer. So in this event we can log some network issues with code like:
InsertError(“A .NET exception occurred: “ + e.Exception.Message);
Where “e.Exception.Message” is error message text received by timer. This error is logged to events and, NAS stops and session is terminated. We have the same situation like in “Elapsed” event: NAS will be restarted as many times as described in config settings.

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

Gedas Busniauskas

Microsoft Lithuania
Microsoft Customer Service and Support (CSS) EMEA

Memory usage when is used .NET components

$
0
0
We already had few support request where NAV developers see big memory consumption increase when using .NET component.
Especially when method is inside some loop.
Like in this sample:

  FOR count := 1 TO 10000000 DO BEGIN
        varDOTNET :=varDOTNET.DotNetObject();
        varDOTNET.SetValue('par');
        CLEAR(varDOTNET);
   END;

 

Even it looks like we create variable and then clear it – we can see continues memory usage increase in windows task manager.
But this is not “memory leak” this is how NAV is managing memory. If you start process again then memory usage decrease and increase to the same number.
So only during processing there could be issue that few users running the same code comes to memory limits.
Resolution is to transfer .NET method execution to local function

 FOR count := 1 TO 10000000 DO 
 CallToFunction(‘par’);
…………

PROCEDURE CallToFunction@1(parameter@1170000001 : Text);
VAR
  varDOTNET@1170000000 : DotNet "'MemoryExample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.MemoryExample.DotNetObject";

        varDOTNET :=varDOTNET.DotNetObject();
        varDOTNET.SetValue(parameters);
        CLEAR(varDOTNET);

 

Also here are other coding ways where you can transfer part DotNet variable execution to functions. But it is always more effective transfer execution to function where "garbage collector" can make his job faster and more effective.

 

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

Gedas Busniauskas

Microsoft Lithuania
Microsoft Customer Service and Support (CSS) EMEA


How to control keyboards events in Add-in

$
0
0

With Microsoft Dynamics NAV Role Tailored Client we’ve received possibility to add ControlAddIn to any field on page. This opens huge possibilities to extend functionality with own controls.
We can create any .Net control with Visual Studio, register it in NAV controls and add it to any field on page by describe control name in field “ControlAddIn” property.
Then whenever user put focus on this field, NAV forwards full control to AddIn and now AddIn does whatever it wants.

And here comes first issue: if AddIn controls everything then all NAV shortcuts are not active and then what to keyboard key to click if want to go on page to next field/control? Only way is to use mouse and click on another control. This is not best way for UX, especially if you want to do that fast.

Issue was resolved with Cumulative Update 9 for Microsoft Dynamics NAV 2013 R2 (Build 37221) released in July 2014.
https://mbs.microsoft.com/partnersource/global/deployment/downloads/hot-fixes/NAV2013R2PlatformHotfixOverview
With this update NAV keyboard events are left for NAV – user can scroll per controls like in any other page.

But here comes another issue: what to do if we want to leave some keyboard events to AddIn? For example we are using RichTextBox in AddIn for text managing, here <enter> key splits line and jumps to new line. But with CU9 cursor jumps to next page control as <enter> is used by NAV. What to do? We need something flexible: leave event to NAV or to AddIn depends what addin does.

Here comes Cumulative Update 14 for Microsoft Dynamics NAV 2013 R2 (Build 38801) released in December 2014. It allows us to choose where <enter> must to be: in NAV or in Addin.

With this update new interface is implemented in Microsoft.Dynamics.Framework.UI.Extensibility, it is “IControlAddInEnterKeyHandler”. And now we can decide how <enter> key is acting,
will cursor stay in AddIn control

public class ControlAddInWithKeyboardFilter : StringControlAddInBase, IControlAddInEnterKeyHandler

    {

        private RichTextBox rtb;

                 public override bool AcceptsEnterKey

        {

            //get { return false; }

            get { return true; }

        }

  

        protected override Control CreateControl()

        {

            rtb = new RichTextBox();

            rtb.Multiline = true;

            return rtb;

 

        }

 

Or cursor jumps to next NAV control:
  public class ControlAddInWithKeyboardFilter : StringControlAddInBase, IControlAddInEnterKeyHandler

    {

        private RichTextBox rtb;

 

                public override bool AcceptsEnterKey

        {

            //get { return true; }

            get { return false; }

        }

 

       

 

        protected override Control CreateControl()

        {

            rtb = new RichTextBox();

            rtb.Multiline = true;

            return rtb;

         }

 

 

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

Gedas Busniauskas

Microsoft Lithuania
Microsoft Customer Service and Support (CSS) EMEA

.NET Exception Handling in C/AL

$
0
0

This pattern is brought to you by Mostafa Balat from the Dynamics NAV team here at MDCC in Denmark.

Best regards,

The NAV Patterns team

Meet the Pattern

When there is a need to use .NET classes within C/AL, one of the main challenges is to handle the exceptions the methods of these .NET classes may throw. Eventually, if not handled, they will basically bubble up as runtime errors, halting the current operation a user is doing without having a chance to properly display errors in a user-friendly format.

Know the Pattern

Using the .NET classes in order to extend the functionality in Microsoft Dynamics NAV usually triggers the need to create an add-in assembly. This is a pretty powerful approach and opens the door for empowering Microsoft Dynamics NAV with new and extra functionality while harnessing the full power of the .NET Framework.

For example, integration with a web service in Microsoft Dynamics NAV can be done to extend the existing functionality, or to benefit from a service model offered through a 3rd party. To do so, it is possible to write a .NET add-in to handle the required bi-directional communication between Microsoft Dynamics NAV and the web service. Alternatively, the implementation itself can be done in C/AL, with no add-in dependency. The latter option simplifies customization, deployment and upgradeability. Additionally, it builds up on the knowledge Microsoft Dynamics NAV developers have with C/AL programming.

On the other hand, not using an add-in exposes Microsoft Dynamics NAV to runtime errors due to unhandled exceptions that get thrown at different levels. The first is the communication layer, in which HTTP requests and responses are exchanged. The second is the business logic layer, at which the content of the requests and response is being prepared and groomed using XML Elements and being retrieved or edited based on the respective XPaths.

Use the Pattern

When .NET classes are used, they may throw exceptions upon failure. Some of these exceptions cannot pre-checked (e.g. like existence of a file on disk) and will only be figured out at runtime. Eventually, to present the error reason to a user and explain what needs to be done to address it, the exception needs to be handled gracefully. This also protects the client for unexpected crashes that may deteriorate the user experience.

Read more about .NET Exception Handling on the Design Patterns Wiki..

Coffee Break - Search in a Dynamics NAV object file using Windows PowerShell

$
0
0

This coffee break post illustrates how to search a text file for specific words or a phrase. You can do this with Windows PowerShell in any text files, but let's use some Dynamics NAV objects exported as text. Technically speaking we are reading a text file then piping it line for line through a search cmdlet, which pipes matching lines further to a log.txt file.

Coffee Break 5 - Searching through a Dynamics NAV text file

Customer story:

The developer wants an automated way of locating all occurrences of a string (table name, field name, comment, ...) in a country-specific version of Dynamics NAV, in this example the Norwegian version. And we will log the output of this search to a log file in c:\NAVApp\Log. 

Exporting objects from Dynamics NAV:

Prerequisites:

Crete a folder C:\MyNAVApp with a subfolder \Log so that the resulting full path is C:\MyNAVApp\Log.

For this purpose we use the Microsoft Dynamics NAV Application Merge Utilities. Note that these install to the equivalent of C:\Program Files (x86)\Microsoft Dynamics NAV\80\RoleTailored Client. This time we don't need to import the Management module, only the application merge utilities:

Import-Module "${env:ProgramFiles(x86)}\Microsoft Dynamics NAV\80\RoleTailored Client\Microsoft.Dynamics.Nav.Model.Tools.psd1"

  • Note 1: Make sure to import Microsoft.Dynamics.Nav.Model.Tools.psd1, and not NavModelTools.ps1.
  • Note 2: This will load the path for finsql.exe too, and use the finsql in the client folder.

Set a few variables. Assuming that we work in folder C:\MyNAVApp\, and we will be searching for where "G/L Account" table reference is used. And we will log the output of this search to a log file in the c:\MyNAVApp\Log folder.

$objectPath = 'C:\MyNAVApp'
$sourcepath = Join-Path $ObjectPath '\MyObjects'

$NAVobjects = Join-Path$ObjectPath'NAVobjects.txt'
$LogPath = Join-Path$ObjectPath'\log\whereused.txt'
#Note, a search string can also be an array of strings
$SearchString = '”G/L Account”'

 

Export the objects you like, either all objects:
Export-NAVApplicationObject  -DatabaseName"Demo Database NAV (8-0)" -DatabaseServer ".\NAVDEMO" -Path$NAVObjectFile

Or filter (choose the filter you like):

$FilterString = "Version List=*NAVNO*"
#or

$FilterString = "Modified=Yes"
Export-NAVApplicationObject  -DatabaseName"Demo Database NAV (8-0)" -DatabaseServer ".\NAVDEMO" -Path$NAVObjects -Filter$FilterString 

#Split into individual object files

split-navapplicationobjectfile -Source $NAVobjects -Destination $sourcepath -PassThru -Force


Now load the list of files in the folder. We're using the -File parameter with Get-ChildItem to limit the scope to files only (sub folders are not included).

$myobjects = Get-ChildItem -Path $SourcePath -Filter *txt -File

The next line shows a very simple way to read through all text files in the specified path (c:\MyNAVApp) and for each file searches for the search string (in our case "G/L Account) and for each hit pipe the source line to the log file along with the line number. For this we will use the Select-String cmdlet, that can work directly on Objects with File Info (objects returned by calling  Get-ChildItem cmdlet).

$myobjects | Select-String$SearchString | Out-FileFilepath $LogPath

Note that using the parameters and segments above implies that:

  • The script raises an error if the $ObjectPath does not exist.
  • Out-File will overwrite the existing file per pipeline
  • Out-File will append lines to the file, per pipeline object

 

Jasminka Thunes, Escalation Engineer Dynamics NAV EMEA

Lars Lohndorf-Larsen, Escalation Engineer Dynamics NAV EMEA

Bas Graaf, Senior Software Engineer Dynamics NAV

 

Coffee Break | More piping with Dynamics NAV

$
0
0

 Did you see our first coffee break about piping at Windows PowerShell and Piping? Let's dig a bit further.

Coffee Break 6 - Return to piping

This time we will use more piping and other ways to look at a PowerShell object and format it in different ways. For the example here we will use Get-NAVServerInstance and the results from that cmdlet. But everything in this post would apply in the same way on other cmdlets, like

Get-NAVServerUser
Get-NAVWebService
Get-Process
Get-Service

 

Change how data is displayed

Import the NAV management module so we can use the NAV cmdlets

import-module'C:\Program Files\Microsoft Dynamics NAV\80\Service\Microsoft.Dynamics.Nav.Management.dll'

Run the following commands and check how the result differs:
Get-NAVServerInstance
Get-NAVServerInstance | Format-Table
Get-NAVServerInstance | Format-Table -AutoSize

 

Select parameters:

Select which columns to return

Get-NAVServerInstance | Select-Object ServerInstance, State

But... How do you know which columns are available? Simply pipe the cmdlet into Get-Member:

Get-NavServerInstance | Get-Member

This shows you a list of members, including these properties

Default                                                                                                      
DisplayName                                                                                                 
ServerInstance                                                                                              
ServiceAccount                                                                                            
State                                                                                                    
Version

Formatting Output

The most usual formats are list and table. Confusingly to a Dynamics NAV person, Format-List is like a card display, and Format-Table is just like a list. Run these to see the difference:
Get-NAVServerInstance | Select-Object ServerInstance, State | Format-List
Get-NAVServerInstance | Select-Object ServerInstance, State | Format-Table

Some of the most useful other formats (to replace the last bit of the pipe above):

Group-Object State
Sort-Object State
ConvertTo-Html
Export-Csv -Path c:\x\servers.txt
Out-gridview
Clip

Especially Clip is very useful - it sends the result directly to your clipboard so you can paste it into Notepad or somewhere else.

Note that formatting pipes may destroy the object in order to display it, so always do the formatting as the last part of a pipe. Except if you want to follow it by an Out-cmdlet.

 

Jasminka Thunes, Escalation Engineer Dynamics NAV EMEA

Lars Lohndorf-Larsen, Escalation Engineer Dynamics NAV EMEA

Bas Graaf, Senior Software Engineer Dynamics NAV


The Multi-File Download Cookbook

$
0
0

Today's pattern is not a pattern but a "cookbook" that shows how to use the FileManagement library to download multiple files as a .zip file through Microsoft Dynamics NAV in a way that works on all types of clients.

Abstract

The goal of this pattern is to enable the users to download multiple files as a zip file instead of downloading one by one.  On the Microsoft Dynamics NAV Web client, this is the preferred way of delivering multiple files since it is one of the web patterns and we cannot use the File Management codeunit to place files silently on the machine.

Description

When generating reports that consists of multiple, and usually an unknown number of files, the developer will have to handle the download which also depends on the client the user is on. The problem is that the Microsoft Dynamics NAV Windows client has access to the user’s file system, whereas the web client does not. Following web guidelines, and the fact that client-side .NET is not available in Web client, you can’t initiate multiple downloads which requires the developer respond to the type of client. In some browsers it is possible to download files one-by-one in the Web client by using a confirm dialog, however this is a hack and should not be used.

To solve this problem, a generic download mechanism is used that is client dependent event when multiple files need to be downloaded. For the Web client, the files are compressed using ZIP, and for the Windows client the files are downloaded directly to the file system.

The pattern is usable for all objects that output multiple files and is available in both Windows client and Web client.

Usage

The pattern consists of two steps: 1) Gathering the files and 2) downloading the file(s)

For first step consists of a loop that goes through the files that needs to be downloaded. If it is on the Web client, the files are added to a ZIP archive server-side using a naming convention defined by the integration function GetSeriesFileName. This function takes a filename and number, and transforms it to unique names following a meaningful deterministic pattern e.g. prepend an integer before the file extension. The same function is used when the temporary files are created server side, so the files can be found deterministically later. This removes the need for storing filenames and consequently allows an arbitrary number of files. The second integration function: GetTotalNumberOfFiles, returns the total number of files generated during the data processing and makes the pattern able to handle an arbitrary number of files.

The second step is the actually download of file(s). For the Web client this consists of closing the ZIP achieve and downloading via the standard download handler that works in the Web client. For the Win client, the files are saved directly to the client during the first step.

Code 1: File loop shows an example implementation of this pattern. ServerFileName is generated at the beginning of the report/codeunit, and is the base for GetSeriesFilename. The file that is actually written to during data processing is stored in another variable which holds the output from GetSeriesFilename on the current file number. Note; the example code will only create a ZIP file if there in fact are multiple files to be downloaded. 

Read more on the NAV Design Patterns Wiki....

Best regards,

Martin Dam at Microsoft Development Center Copenhagen

Culture Settings on NAV Web Services

$
0
0

We live in a world of global interaction, and this is reflected in the software that we use at work and at home. As expected this culture differences brings also more complexity and one more extra care for our development side. For instance, a Germany company that is now expanding its business to USA do need to take care of the cultural differences present on our system as date formats, number formats, and so on. Since in Germany we do for instance use the date as DD/MM/YY and in USA MM/DD/YY, we do have an issue when integrating those two systems.

So for such cases, in Microsoft Dynamics NAV 2013, Microsoft Dynamics NAV 2013 R2, and Microsoft Dynamics NAV 2015, you can set up the cultural settings so that our Microsoft Dynamics NAV system understands how to “translate” culture-specific data when exchanging data between the two countries.

Here below I show an example of how to configure and use Microsoft Dynamics NAV web services on different cultural environments (as I explained before I did this using the example of Germany and USA culture settings).

First lets discuss the necessary setup in Microsoft Dynamics NAV:

  1. In the CustomSettings.config file for the relevant Microsoft Dynamics NAV Server instance, add the following key:
    <add key="ServicesCultureDefaultUserPersonalization" value="true"/>
  2. Make sure that the server where our middletier (the Microsoft Dynamics NAV Server instance) is running has the right regional settings.  

    Please do use the standard format Additional settings such as Decimal, and so on. 
  3. Finally, in the Microsoft Dynamics NAV client, on the Personalization Card, set the relevant language for the Microsoft Dynamics NAV Server user  - in this example Language ID 1031 for German.

For more information, see the Web Services and Regional Settings section here: https://msdn.microsoft.com/en-us/library/dd355398%28v=nav.80%29.aspx .

This finishes the setup part. Now we need to be sure that our VB application is using the correct code to “translate” the input date into the correct format when using Microsoft Dynamics NAV web services. This will indicates to our Microsoft Dynamics NAV Server how we do want to insert the date in our NAV database.

For this example I will take the Date and Decimal from our C# application project and convert them to pass the Data to Web Services that will finally “insert” it into our NAV System.
As for my setup in Microsoft Dynamics NAV, for this example I'm using the German language/cultural settings. In this example, I create a table and a page called WebServicesCultureTable and WebServicesCulturePage.

 

The page displays the following fields from the table:

Actually we will only use now the CultureCode ( the primary key), CultureDate that we will use to set the Date, and finally the CultureDecimal that we will use to check if the decimals are been passed correctly.

In the Visual Studio, in my C# project, I have created an application that can be used to insert the desired values for test.
So there I will use my web services to “pass” to Microsoft Dynamics NAV the date 11-08-15 (with the format) and the decimal 33.73M (we need this M so that C# understands that this is a decimal number). This class refers to a button in my C# application, but it resumes the necessary setting up for the Culture format that we need to use in order to our Web Server to understand correct the formats.

        private void button3_Click(object sender, EventArgs e)

        {

            NavOData.NAV nav = new NavOData.NAV(new Uri("http://localhost:7148/DynamicsNAV71/OData/Company('CRONUS%20AG')"));

            //Here define the companies, if need set a new company

            nav.Credentials = CredentialCache.DefaultNetworkCredentials;

 

            //Cultural Setting to Use on Date/Decimal

            System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("de-DE");

            System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE");

 

            //Converting Cultural Date

            String TESTDate = "11-08-15";

            DateTime finalDate = DateTime.ParseExact(TESTDate, "DD-MM-YY", CultureInfo.CurrentCulture);

 

           //Converting Cultural Decimal

           decimal DecimalTEST = 33.73M;

           System.Convert.ToDecimal(DecimalTEST, new System.Globalization.CultureInfo("de-DE"));

 

            var WebServicesCultureTable = new NavOData.WebServicesCulturePage

            {

                //Here we can create new rows

                CultureCode = "DE",

                CultureDate = finalDate,

                CultureDecimal = DecimalTEST,

            };

            nav.AddToWebServicesCulturePage(WebServicesCultureTable);

            nav.SaveChanges();

        }

Finally I inserted manually direct on the page the following test:

Then using my new button3 = Insert, I inserted the values to test on the web service side.

There you go: the culture personalization does works as desired!

Bonus info: This test I did using Microsoft Dynamics NAV 2013 R2 Cumulative Update 6 (build # 39665), but this functionality has been available since Microsoft Dynamics NAV 2013 Cumulative Update 9, and is also working as it should in Microsoft Dynamics NAV 2013 R2 and Microsoft Dynamics NAV 2015.

 

Best Regards,

Daniel De Castro Santos Silva 

Microsoft Dynamics NAV Support Engineer

New Application Design Patterns: Totals and Discounts on Subpages

$
0
0

To increase discoverability and productivity, critical information, such as statistics, can be moved from separate pages onto the document where it is needed. For example, documents should clearly display totals and discounts information to provide quick overview, make it easy to relate the amounts to the document currency, and to allow the user to see and apply discounts directly on the document.

Description

Before Microsoft Dynamics NAV 2015, totals and discount information was scattered between the document and the Statistics page, which made it less discover-able and less usable for new users. It was difficult to see the discount amounts, relate the amounts to the currency of the invoice, and it was not-intuitive that you had to update the Statistics page to see updates on the document. In addition, the result of the update was not visible on the main document, which added to the confusion.

Totals and discount information logically belongs on the document, as is the case in many competitor products.

The Statistics page contains too much information for most common scenarios, and only a part of the information is visible on the related document. See the following example for sales order statistics.

With the pattern implemented, selected statistics fields are placed on the sales order, as in the following example.

This new layout gives precise and complete information about totals and discounts, with discounts on the left side and totals on the right side. The currency is clearly shown, and the layout resembles a printed document. The values are always updated (with some exceptions that are explained in the next following sections), and the user can apply a discount directly on the document.

Read more on the NAV Design Patterns wiki...

By Nikola Kukrika at Microsoft Development Center Copenhagen

NAV Design Pattern: Using C/AL Query Objects to Detect Duplicates

$
0
0

This pattern uses queries to create an efficient way to detect duplicate entries in a table. This is, for example, useful when trying to find out which customers or contacts have the same names, so we can merge them later.

Description

Duplicate detection has several requirements in Microsoft Dynamics NAV. One method to eliminate duplication is by defining the relevant field as the primary key. However, this method is not always practical either due to the size of the field or due to business requirements that dictate how duplicates are detected but not necessarily how they are eliminated. An example of this method is to detect contacts with the same name and take action to merge them if they are.

Before Dynamics NAV 2013, the only possibility was to iterate through the table in a loop and then create a sub-loop where another instance of the same table is filtered to check for duplicates. For example, to check for duplicate names in the Customer table, the code would look like this:

PROCEDURE HasDuplicateCustomers@26() : Boolean;

    VAR

      Customer@1000 : Record 18;

      Customer2@1001 : Record 18;

    BEGIN

      IF Customer.FINDSET THEN

        REPEAT

          Customer2.SETRANGE(Name,Customer.Name);

          IF Customer2.COUNT > 1 THEN

            EXIT(TRUE);

        UNTIL Customer.NEXT = 0;

      EXIT(FALSE);

    END;


This code would involve setting filters on the Customer table as many times as there are records in the table. This is an expensive operation.

 Starting with Dynamics NAV 2013, we can use queries to create a more efficient implementation of the same logic. 

Usage

The solution involves that you create a query to return duplicates, and then invoke it from a method that would test the value of the query to identify if duplicates were found.

Step 1 – Creating the Query

  • The query must be created with the table we want to search in as the dataitem.
  • The field we want to search for must be created as a grouped field.
  • Create a totaling field on the count, and add a filter for Count > 1. This ensures that only records with more than one instance of the field that we selected in the previous step are included in the query result.

Continuing with our Customer Name example, here is how the query would look:

ELEMENTS

  {

    { 1   ;    ;DataItem;                    ;

               DataItemTable=Table18 }

    { 2   ;1   ;Column  ;                    ;

               DataSource=Name }

    { 3   ;1   ;Column  ;                    ;

               ColumnFilter=Count_=FILTER(>1);

               MethodType=Totals;

               Method=Count }

  }

Step 2 – Invoking the Query to Check for Duplicates

Now that the query is created, all we need to do is to invoke the query and check if any records are returned, which would mean that there are duplicates.

Here is an alternate implementation of the HasDuplicateCustomers method using the query that we created:

PROCEDURE HasDuplicateCustomersWithQuery@27() : Boolean;

    VAR

      CustomerDuplicate@1000 : Query 70000;

    BEGIN

      CustomerDuplicate.OPEN;

      EXIT(CustomerDuplicate.READ);

    END;

 

Read more on NAV Design Patterns wiki...

by Abshishek Ghosh and Bogdan Sturzoiu at Microsoft Development Center Copenhagen

Microsoft Dynamics NAV 2016 Works Natively with Microsoft Dynamics CRM

$
0
0
With Microsoft Dynamics NAV 2016, we are enabling a tight integration between Dynamics NAV 2016 and Microsoft Dynamics CRM to allow for a more efficient lead to cash process. We are ensuring the users of the two products can make more informed decisions without switching products. The integration is enabled with a default definition from... Read more
Viewing all 773 articles
Browse latest View live