Function strReplace not working in D365FO

If you agree with me, the life of a solution architect/developer is never boring. If you think you will be writing simple code in 15 minutes, you are probably wrong, as you will most likely experience some unexpected challenges, as I did 😊. Surely you have developed a solution that uses string manipulation functions. In most cases, you will need to replace one or more occurrences of a string in your code, especially if you are trying to mix static data with dynamic data in Microsoft Dynamics 365 for Finance and Operations. Right? Recently, I used the same approach as most of you with X++ Global::strReplace() and found potential drawbacks, or in some cases, it does not work as expected and I think this is a bug in Microsoft code. Therefore, I decided to share these results with you.

The Problem

While implementing and testing some new features, I came across a strange behavior of the X++ strReplace() function, which has potential drawbacks or, in some cases even doesn't work. Usually, when using strReplace(), I don't go into detail about how it is implemented, but simply use it as expected. If we look at the implementation, you can see that it uses Microsoft .Net RegEx class to replace all occurrences in the source string.

By calling strReplace() in X++ like this:

I would expect the result to be “My invoice AP-0002 with amount $0.50”, but the actual result is “My invoice AP-0002 with amount @InvoiceAmount@.50”. Strange, right? I was debugging my code and found out that everything is fine with it, so I started looking at the implementation of Global:strReplace().

As you can see, the Global::strReplace() function uses the .Net RegEx.Replace method. There is nothing wrong with this, except that the function also accepts regular expressions for FROM and TO string parameters (_fromStr and _toStr). This means that you can also use reserved substitution words for regular expressions like $0, ${name}, $&, etc.. And if you take a closer look at the above code, you will see that string parameter FROM is escaped, but TO is NOT. Finally, I found the problem.

The Solution

When I thought about how I could solve this problem, I decided, like probably many of you, to build my implementation of the strReplace() function. I moved the code into .NET assembly, referenced in the D365FO project, and created a static class StringHelper with the static Replace method, which can then be used from the X++ code.

Then I began to consider whether this was the best option.

What about performance?

I wanted to implement the string replace function optimized with .NET managed code because I will use it for massive string replacement in batch operations. I thought about avoiding memory allocation, CPU usage, boxing, etc. as much as possible. Then I wrote a few different implementations for string replace by using the XUnit test framework and the BenchmarkDotNet framework.

Then I used the XUnit testing framework to find which two implementations have best performance. The tests have contained 1, 10, 100, 1000, 10000 string replacement iterations with 100-word "lorem ipsum ..." strings and produced the following results.

Of the tests performed above, I used the fastest implementation performed with the StringBuilder class to also compare it with the BenchmarkDotNet framework. For the final test, I used RegEx.Replace and the following implementation.

Before I started to make the benchmark test, I decided I will check only one run on the method with different sizes of the input string to be replaced. In the graph below N states how many words were used in the string by using “Lorem Ipsum …” generated sample string.

Conclusion

In the beginning, the task I was working on seemed simple. But when I began to delve into the problem, I was pushed to find the best possible solution, as most of you probably will 😊. So, the best choice for our solution was to use RegEx.Replace, with minor changes, by escaping the string parameter newValue.

 

4 thoughts on “Function strReplace not working in D365FO

  1. Why not just use the string.Replace method? That seems much less error prone, and probably faster too. It can readily be used from X++. Here is an example that happens to be in C#:

    result = “My invoice @InvoiceNumber@ with amount @InvoiceAmount@.”

    .Replace(“@InvoiceNumber@”, “AP-0002”);

    result = result

    .Replace(“@InvoiceAmount@”, “$0.50”);

     

     

    1. Hi Peter,
      I 100% agree with you that the .Net String.Replace is a lot faster than RegEx.Replace, but we were looking at how to solve the case insensitive string replace also. This is why a combination of RegEx.Replace and .Net String.Replace was used in the final implementation. So basically we covered both cases. The timings were posted for case insensitive string replace.

  2. Hi Team,

    strReplace function will works in D365 FO.

    Please find the below code for your reference.

    Notes resultString;

    str input = “ABCD730XYZ”
    str searchString = “730”;
    str replaceString = “731”;
    resultString = strReplace(input, searchString, replaceString);

    1. Hi Edwin,

      The strReplace() function works correctly in your case. I tested it again on 10.0.40, and it still has the same issues.

      Here is the test code:
      // Edwin sample:
      str input = 'Edwin sample: ABCD730XYZ';
      str searchString = '730';
      str replaceString = '731';
      str resultString = strReplace(input, searchString, replaceString);
      info(resultString);

      // The problematic one:
      str result = strReplace('Problematic one: My invoice @InvoiceNumber@ with amount @InvoiceAmount@.', '@InvoiceNumber@', 'AP-0002');
      result = strReplace(result, '@InvoiceAmount@', '$0.50');
      info(result);

      The result:

      • Edwin sample: ABCD731XYZ
      • Problematic one: My invoice AP-0002 with amount @InvoiceAmount@.50.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Docentric respects your privacy. Learn how your comment data is processed >>

Docentric respects your privacy. Learn how your comment data is processed >>