Tag Archives: Development

Partial Records

Partial Records are a new capability of Business Central introduced in Business Central 2020 release wave 2. It allows specifying fields that should be loaded when accessing SQL based data.

How does it work? Without using partial records, Business Central normally load all data from the record even if only one of them is required. The partial records, developer specify which fields are needed and the Business Central than load only that fields.

This is especially (based on Microsoft notes) important when Tableextensions are used. In that case, each Tableextension is stored as a separate table on the SQL server and, when the data from this table are loaded, SQL server joins the primary table, extension table (and other extension tables if exist) using join. Although the join is done using primary keys, the query is still much more performance-intensive than the query that accesses only one table.

Related Functions

There are four related functions:

  • [Ok := ] Record.SetLoadFields([Fields: Any,…])
    • Using the function SetLoadFields we specify a set of fields that server should load from the database. Fields have to be specified before the record is retrieved from the database (similar as SetAutoCalcFields()).
    • If the function is called more than once, only fields specified within the last call are loaded.
  • [Ok := ] Record.AddLoadFields([Fields: Any,…])
    • Similar function to SetLoadFields that has one big difference: if the function is called multiple times, the new call does not reset fields that were already set (even if they were set using SetLoadFields).
  • Ok := Record.AreFieldsLoaded(Fields: Any,…)
    • Checks whether the fields specified as parameters are already retrieved from the database.
  • [Ok := ] Record.LoadFields(Fields: Any,…)
    • This function allows loading data from the data source that was not loaded when the last partial record was retrieved (because the field was not specified using SetLoadFields nor AddLoadFields).

More specifically

In the first part, I wrote that only specified fields are loaded when the row is fetched from the database; however, if the fields were not loaded (using SetLoadFields / AddLoadFields) and system needs their value, they are automatically fetched using Just-In-Time (JIT) mechanism.

When JIT loading occurs, the system automatically loads data using the primary keys of the current record. This fetch might fail if the record was changed (modified / renamed / deleted) since the original data was retrieved. And that is the reason why the function LoadFields exists – when JIT loading occurs automatically, there is no way how to resolve if the loading fails. With LoadFields developers can implement explicit error handling for these fails.

Example

The example below is from Microsoft Docs and shows how to use SetLoadFields. As is mentioned on the Docs, in this case, the example is nine times faster than the same code without the Partial Records functionality.

 procedure ComputeArithmeticMean(): Decimal;
 var
     Item: Record Item;
     SumTotal: Decimal;
     Counter: Integer;
 begin
     Item.SetLoadFields(Item."Standard Cost");
     if Item.FindSet() then begin
         repeat
             SumTotal += Item."Standard Cost";
             Counter += 1;
         until Item.Next() = 0;
         exit(SumTotal / Counter);
     end;
 end

Microsoft example

As this is a very different approach to developing custom functionalities, I will definitely check the performance with some advance example codes in some of the next articles.

Current status of AL Code Analyzers

With the last update of Analyzers Rules, there are some new issues with warnings. This is not a comprehensive list of all bugs but contains information about problems that I found on projects that I am working on / managing.

General Analyzer Rules

  • AL0604 (for Runtimes under 6.0)
    • DESCRIPTION: Use of implicit with
    • AL TEXT: Use of implicit ‘with’ will be removed in the future. Qualify with ‘Rec.’. This warning will become an error in a future release.
    • CURRENT PROBLEM: This warning should be shown (together with linked quick fix) only for Runtime >= 6. For Runtimes below this, some required functionality (like Editable/Enable = Rec.”Field Name”) is not supported.
    • LINKS: https://github.com/microsoft/AL/issues/6095

CodeCop Analyzer Rules

  • AA0072
    • DESCRIPTION: The name of variables and parameters must be suffixed with the type or object name.
    • AL TEXT: The name of {0} is not valid. The name of variables and parameters must be suffixed with the type or object name.
    • CURRENT PROBLEM: This rule is still under discussion on AL GIT due to some unexpected/strange behaviour. The most discussed issues are: Whether the company prefix/suffix of the object should be mandatory in variables names; Whether reserved words should be avoided; That subscribers parameters should not be check (as it’s not a problem of subscriber but of published); …
    • LINKS: https://github.com/microsoft/AL/issues/6120
  • AA0150
    • DESCRIPTION: Do not declare parameters by reference if their values are never changed.
    • AL TEXT: Parameter ‘{0}’ is declared by reference but never changed in method ‘{1}’.
    • CURRENT PROBLEM: The rule checks parameters in Codeunits that implements interfaces with methods that contain reference parameters. This should not be check.
    • LINKS: https://github.com/microsoft/AL/issues/6211
  • AA0232
    • DESCRIPTION: The FlowField of a table should be indexed.
    • AL TEXT: The FlowField {0} of {1} should be added to the SIFT key.
    • CURRENT PROBLEM: The rule does not respect field type and suggests adding SIFT index on non-numeric fields (like date, datetime, …).
    • LINKS: https://github.com/microsoft/AL/issues/6140
  • AA0240
    • DESCRIPTION: Email and Phone No must not be present in any part of the source code.
    • AL TEXT: The {0} ‘{1}’ must not contain email addresses or phone numbers.
    • CURRENT PROBLEM: This rule currently checks translation files and evaluate some field (or other) IDs of an object as a phone number.
    • LINKS: https://github.com/microsoft/AL/issues/6039

Dart & Flutter: Use of REST GET method

This chapter we will continue with the project that we created before. The project could be found on GitHub.

Now when we have a runnable app with homepage and standardized navigation, we can look on how to load data using REST. We will start with the basic example of GET method and, in the next chapter, we will move to a more advance example.

Firstly, we need to allow using an HTTP package. To do that, we have to add one line in pubspec.yaml in the root directory. You can add a newer version if exists (check here).

 http: ^0.12.2

Then we need to allow access for the app to the Internet using uses-permission in AndroidManifest.xml.

 <uses-permission android:name="android.permission.INTERNET" />

Now we can start. Our goal is to download email from this REST API. To process a request, we have to use some new keywords in Dart – Future, async and await.

Future, async & await

The Future is an object that represents a (potential) value, that will be obtained somehow in the future. In other words, this object promise (yes, I used this term because it is very similar construction) to our app that sometimes in the future, there will be an object of this specific type. Which object will be available is specified using structure Future where the T is the type of the object.

Async and await are special constructions (keywords) define how to create and use asynchronous functions. If we want to create an asynchronous function, we have to add async keyword before the function body. In the same way, if we want to call function asynchronously (that is only possible from functions declared as async) we add a keyword await before the function call.

 Future<int> loadData() async {
   return await sleepAndLoadData();
 }

Example

Now, as we know all necessities, we can go directly to our example. We create an async function fetchRESTTestData using Future where T will be a model class using which we store downloaded data.

As you see, the REST call is straightforward. We just use async get function from HTTP package.

 import 'dart:convert' as JSON;
 import 'package:http/http.dart' as http;

 Future<RESTTestData> fetchRESTTestData() async {

   final response =
       await http.get('https://jsonplaceholder.typicode.com/comments/1');

   if (response.statusCode == 200) {
     return RESTTestData.fromJson(JSON.jsonDecode(response.body));
   } else {
     throw Exception('Failed to load album');
   }
 }

 class RESTTestData {
   final int postId;
   final int id;
   final String name;
   final String email;
   final String body;

   RESTTestData({
     this.postId,
     this.id,
     this.name,
     this.email,
     this.body,
   });

   factory RESTTestData.fromJson(Map<String, dynamic> json) {
     return RESTTestData(
       postId: json['postId'],
       id: json['id'],
       name: json['name'],
       email: json['email'],
       body: json['body'],
     );
   }
 }

Then we have to add our new functionality to the UI. We will use our home page that we made earlier for a start. Firstly, we need to obtain data using our function that we just created and store them in a class variable. To achieve that we use function initState on our HomePage. This method is called only once when the state is initialized (in our example, when we open the app or open the home page from the menu).

   @override
   void initState() {
     super.initState();
     futureRESTTestData = fetchRESTTestData();
   }

It is done in the same way as any other element using the Builder class. As we are working with Future object (that hold our REST data), we need to work with FutureBuilder.

             FutureBuilder<RESTTestData>(
               future: futureRESTTestData,
               builder: (context, snapshot) {
                 if (snapshot.hasData) {
                   return Text("Data from REST: " + snapshot.data.email);
                 }
                 return CircularProgressIndicator();
               },
             ),

And that’s all! We can build the app and upload it on our Android device or run it using the emulator. You can see the final result on the emulator picture below.

All related changes described above are on my GitHub.

Interfaces in AL (part 3)

This is 3. part from the article series about Interfaces in AL Language. See the first one or the second one.

In previous parts, I have described the basics of Interfaces in AL Language (what is an interface, how to work with them in AL and started working on advanced example).

In the last part, we will continue with our advanced example and will add Enums to use the whole power of Interfaces.

Enums

Enums are described in a separate article.

Example

So, we use our Advanced example from part 2 as a starting point hence those who haven’t read the second part yet, check it first. Now, we create Enum specifically for this example.

 enum 50100 "TKA Inspection File Type"
 {
    Extensible = true;
    
    value(0; " ")
    {
        Caption = ' ';
    }
    value(5; "TKA BLB Type")
    {
        Caption = 'BLB Type';
    }
    value(10; "TKA XXS Type")
    {
        Caption = 'XXS Type';
    }
 }

We have an Enum that is similar to the original Option Data Type. But what is the advantage of Enums? With Enums, we can specify an implementation for every value in the object using property Implementation.

Firstly, the Enum must implement an interface(s). Then for every value, we can define a Codeunit object that implements the interface for the specific value. If the Enum implements an interface all values must have concrete specification through defined Codeunit. We can use property DefaultImplementation that will be used for values that do not have implementation specification defined directly.

 enum 50100 "TKA Inspection File Type" implements "TKA Inspection Format"
 {
    Extensible = true;
    DefaultImplementation = "TKA Inspection Format" = "TKA Default Format";
    
    value(0; " ")
    {
        Caption = ' ';
    }
    value(5; "TKA BLB Type")
    {
        Implementation = "TKA Inspection Format" = "TKA BLB Inspec. Format Mgnt.";
        Caption = 'BLB Type';
    }
    value(10; "TKA XXS Type")
    {
        Implementation = "TKA Inspection Format" = "TKA BLB Inspec. Format Mgnt.";
        Caption = 'XXS Type';
    }
 }

And finally, how to use this implementation. With the old approach, there will be an Options field on a page to allow users to choose a format that they are importing. Then in the code, there will be if/case statement which decides based on selected option value which Codeunit should be run. That means if we want to add a new format, we would have to change existing code (that could be in a different extension and impossible to change!).

However, with Enums, we have an Enum field on a page to allow users to choose the format (the Enum which is extensible from other extensions). Then in the code, we create an interface variable and assign the selected value of Enum. Then we call the requested methods using the interface object. That means no change is required when we need to add new supported format.

 codeunit 50100 "TKA Import Management"
 {
    procedure ImportFile(SelectedInspectionFileType: Enum "TKA Inspection File Type"; FilePath: Text)
    var
        InspectionFormat: Interface "TKA Inspection Format";
    begin
        InspectionFormat := SelectedInspectionFileType;
        InspectionFormat.LoadFile(FilePath);
    end;
 }

Dart & Flutter: Project Initialization

We have already prepared development environment and Android Emulators in previous articles. In this chapter, we will look at how to create a new project and on the generated files.

To start with, open Visual Studio Code (or any other support development environment) and run the command “Flutter: New Project”. Once we have created a new project, we should see something simulator to the picture below.

There are many files in the root directory in the newly generated project. Some of the files are generally known (like .gitignore, I will skip them) some are specific to Flutter.

Subdirectories

  • android
    • This directory contains all the Android related files. The most important things to know is that in this directory (directories), the resources like icons or images are stored.
    • From the configuration files, the most important is “AndroidManifest.xml” that specifies everything related just to Android (for example system permissions that to the app requires to run)
  • ios
    • Similar to the android directory. As these articles are focus on the Android app, I will skip the directory.
  • lib
    • The main folder where we should write the application code. There are some patterns of how the files should be structured in this directory on which we will look in some of the next articles.
  • test
    • This folder is used to store and manage testing code for the application.

Files in the root directory

  • .metadata
    • This file tracks properties of the Flutter project and should not be changed manually.
  • .packages
    • This file is generated automatically by Pub (Flutter package manager), and like the previous file, it should not be changed manually.
  • pubspec.lock
    • Similar file to .packages, used by Pub to get the concrete versions for every used package or dependency.
  • pubspec.yaml
    • This is probably the only file we will sometimes change in the future. The file specifies all flutter packages we have available. If we need to add a new third-party package (for example HTTP), we should do it in this file.
  • tka_demo.iml
    • Auto-Generated file specifying internal project properties.

main.dart file

 import 'package:flutter/material.dart';

 void main() {
   runApp(MyApp());
 }

 class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       title: 'Flutter Demo',
       theme: ThemeData(
         primarySwatch: Colors.blue,
         visualDensity: VisualDensity.adaptivePlatformDensity,
       ),
       home: MyHomePage(title: 'Flutter Demo Home Page'),
     );
   }
 }

 class MyHomePage extends StatefulWidget {
   MyHomePage({Key key, this.title}) : super(key: key);

   final String title;

   @override
   _MyHomePageState createState() => _MyHomePageState();
 }

 class _MyHomePageState extends State<MyHomePage> {
   int _counter = 0;

   void _incrementCounter() {
     setState(() {
       _counter++;
     });
   }

   @override
   Widget build(BuildContext context) {
     return Scaffold(
       appBar: AppBar(
         title: Text(widget.title),
       ),
       body: Center(
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: <Widget>[
             Text(
               'You have pushed the button this many times:',
             ),
             Text(
               '$_counter',
               style: Theme.of(context).textTheme.headline4,
             ),
           ],
         ),
       ),
       floatingActionButton: FloatingActionButton(
         onPressed: _incrementCounter,
         tooltip: 'Increment',
         child: Icon(Icons.add),
       ),
     );
   }
 }

Interfaces in AL (part 2)

This is 2. part from the article series about Interfaces in AL Language. See the first one.

In the first part of this series, I have described what Interfaces are and showed the example of how they can be created. In this article, I will show a more advanced example of interfaces and how we can handle with these structure within Visual Studio Code.

Snippets

As usual, Microsoft added shortcut (snippet) to help with creating a basic layout of the interface object. The snippet is called “tinterface”.

I also already mentioned that I prefer (and strongly recommend) snippets from Visual Studio Code plugin “waldo’s CRS AL Language Extension” created by Waldo. However, in this case, there is nothing special from the snippet provided by Microsoft.


 // tinterface
 interface MyInterface
 {
    procedure MyProcedure();
 }

 // tinterfacewaldo
 interface IMyInterface
 {
    procedure MyProcedure();
 }

Example

Now we can look at the example. Our goal is to create functionality to import and process different types of machine inspection files. The files are of two types – BLB and XXS. Both have a completely different structure, and also data that provide are different, so they need to be processed differently.

In the old approach, we would have added if/case statement on every place where we need a different behaviour. So if we had to add another format later, we would have to change the code on these places once again. Not anymore with interfaces!

So we will create a new object of type Interface. This is the only type where we do not assign object ID.

 interface "TKA Inspection Format"
 {
    procedure LoadFile(FilePath: Text);
    procedure ProcessFile(InspectionFile: Record "TKA Inspection File"): Boolean;
 }

Now, we create Codeunit that implements our interface. To define which interface we are implementing, we use construction “Codeunit ID OBJECTNAME implement INTERFACE 1, INTERFACE 2, …”

We can see that the interface name is underlined and error messages are shown. As we use an interface, we have to define (implement…) every method from this interface(s). That can be done using “Quick Fixes” – a yellow light bulb or using a combination of Ctrl + .

 codeunit 50100 "TKA BLB Inspec. Format Mgnt." implements "TKA Inspection Format"
 {
    procedure LoadFile(FilePath: Text)
    begin
    end;
    procedure ProcessFile(InspectionFile: Record "TKA Inspection File"): Boolean
    begin
    end;
 }

Now we have a basic structure of the Codeunit that implements our interface. We can add some functionality and create another one in the same way with different functionality.

 codeunit 50100 "TKA BLB Inspec. Format Mgnt." implements "TKA Inspection Format"
 {
    procedure LoadFile(FilePath: Text)
    begin
        ImportFileFromXML(FilePath);
    end;
    procedure ProcessFile(InspectionFile: Record "TKA Inspection File"): Boolean
    begin
        DoSomething();
        DoTask1();
        exit(IsOk());
    end;
 }
 codeunit 50101 "TKA XXS Inspec. Format Mgnt." implements "TKA Inspection Format"
 {
    procedure LoadFile(FilePath: Text)
    var
        TKAFileManagement: Codeunit "TKA File Management";
    begin
        TKAFileManagement.DecryptFile(FilePath);
        while TKAFileManagement.HasNextLine() do  
            TKAFileManagement.ImportDecryptedLine(FilePath);
    end;
    procedure ProcessFile(InspectionFile: Record "TKA Inspection File"): Boolean
    begin
        DoSomethingDifferent();
        if HasError() then
            exit(false);

        DoFinalThing();
        exit(true);
    end;
 }

And finally, how to benefit from the interface. When we need to call any of the methods in the interface, we just need to define which implementation of the interface we are using.

 codeunit 50102 "TKA Some Settings"
 {
    procedure TKAInspectionFormatFactory(var TKAInspectionFile: Interface "TKA Inspection Format")
    begin
        // Get specific Implementation based on settings (for example)

        if Something() then
            TKAInspectionFile := TKABLBInspecFormatMgnt;
        else
            TKAInspectionFile := TKAXXSInspecFormatMgnt;
    end;
 }

 codeunit 50101 "TKA Some Mgnt. Object"
 {
    procedure ImportAndProcessFile()
    var
        TKAInspectionFile: Record "TKA Inspection File"

        TKAInspectionFormat: Interface "TKA Inspection Format";
        FilePath: Text;
    begin
        ...
        TKAInspectionFormatFactory(TKAInspectionFormat);
        if TKAInspectionFormat.LoadFile(FilePath) then begin
            ...
            TKAInspectionFormat.ProcessFile(TKAInspectionFile);
        end;
        ...
    end;
 }

Now we have a solution that requires only one change in method TKAInspectionFormatFactory if we need to add new supported file format.

In the next part, I will explain how to use Interface together with Enums – that is the most universal usage for an interface that is currently available in Business Central.