This blogpost of Arend Jan Kauffman reminded me of a topic that I wanted to write a very long time now. Namely working with Variants. Sometimes I get the remark: “what the hell are they useful for? “. And to be honest: Variants could be bloody useful in some cases. Cases to make stuff even more generic then only working with RecordRef, FieldRef and such. The keyword is “Generic” here. Just trying not to create code that is copy-pasteable (if that is a word at all) .. but try that different parts of the application will run exactly the same code.
When you read the above .. you immediately think of RecordRef, RecordID, FieldRef, … all those kind of things. And indeed .. Variants can help you be even more generic .. in combination with RecordRef.
I’m going to try to explain this with a very simple use case which came to speak on a confcall yesterday: Opening a Card page from any record. You know, sometimes, opening a card for a record depends on the data on which card page you would like to open. If you look at the Sales list page, you see this:
What if I want to make this code a little bit (let’s say “much more”) generic/repeatable? First of all: I don’t want to code like that on a page .. and second: I can imagine that other tables/data/pages has the same kind of “Open-Card-For-Certain-Records”-issues.. .
What if …
What if I could write something like this?
On any of the pages, for any record, any place, any time, any … (one?). So, exacly the same code on the “Card” action of the “Sales List” as on the “Card” action of the “Purchase List”. You would expect you need two functions, because you’re passing either the Sales Header of Purchase Header record.
How can Variants help me in this?
Let’s use the “DrillDown” method to explain what I did.. .
This is what I the result is in my two pages:
You can see that I have exactly the same function of the same codeunit, and passing a record. A different type of record. One belongs to Sales Header, the other to Purchase Header.
When you drill down, you see that Variants is helping me on this:
As you can see: I’m handling the Variants .. and even went a small stem further. With the code above, you’re even able to not only pass different tables, but you’re also able to use that same function to pass RecordRef. How Generic does one want to be?
When you drill down to the “OpenCardPage”-function, you arrive in my codeunit where I manage all this, for all records:
This way, it’s centrally managed, one place, copy-able functions, .. and most important: re-usable functions (not like the default one on the page itself).
Is this all?
Not necessarily. There is a lot you can do with Variants. It’s not my intention to show you everything (that’s not possible), just a few thouchpoints.. .
So may be another touchpoint then?
Well .. one of the PRS “Design Patterns” is “Facade” (or at least, that’s what we call it now – might change ;-)). Typically in a Facade pattern, you define “codeunits” as pieces of “apps” you’re going to execute. Like a codeunit for a certain scale to get the weight of something during a business process. Another Scale – another codeunit.
In some cases, the setup is going to define which record you’re going to “send” with your codeunit. For example, I have some kind of “Validation Framework” where I set up which codeunits have to run on which triggers and so on.. . Set up could look like this:
Codeunits, that has to run on certain tables .. .
When you’re calling your generic codeunit (which we call the “Facade”-codeunit), it’s typical you’re getting a RecordRef again .. and passing RecordRef to a codeunit is not possible.. .
So this is what you could do:
First: create a variant of your recref
Second: Pass that variant to the codeunit.
This is somewhat similar to Arend Jan’s blog (he’s using it with pages).
At first .. it’s difficult to imagine what this can do for you. But in terms of “repeatability”, “upgradeability” and “maintainability” .. it is sooo powerful :-). Hopefully you agree.
If you have any other examples, please share! :-).
This is great, thanks for sharing with practical examples
y’re welcome 🙂
Hey, nice to see your solution so soon after the talk yesterday, let me share this with my team.
Well, I actually prepared a whole different example .. but this one was more understandable, so I took that one.
In our inhouse solution, we have elaborated it a little bit more .. making it possible for Lists as well .. and let you choose to run it MODAL or not.. . It’s just a few lines more.
We also use it for all the frameworks that we have developer, that are linked with RECORDID to any other functionality in NAV. Something like the default RecordLinks. The big advantage is that you can have a list of your entire table (like the RecordLink table), and drill down to the exact page of that record where the linked record is all about.. . Actually just like the Notification-functionality (drilling down to the document where you added the notification) .. but then for our own stuff :-).
Just an idea …
I use variant quite often when i need to do generic functions, the last thing i did was some sort of a try-get construct for our main solution. The problem was that we moved from a country specific implementation to a W1 implementation. Since not every field we use is available on W1 i needed a way to retrieve the field values when they are there but ignore it when not. Here is what i used:
TryGetCode(_Record : Variant;_FieldName : Text) : Code
IF TryGetFieldRefByName(_Record, _FieldName, FRef) THEN
IF FORMAT(FRef.TYPE) = ‘Code’ THEN
“Location Code” := Framework.TryGetCode(Item, ‘Location Code’);
I realy like the generic way, i hope you will come up with more of that stuff 🙂
how does the code for ‘TryGetFieldRefByName’ look like?
Hi wakestar, its just a simple FieldRef loop to get the field by using the field’s name, here the code:
TryGetFieldRefByName(_Record : Variant;_FieldName : Text;VAR _FRef : FieldRef) : Boolean
FOR i := 1 TO RecRef.FIELDCOUNT DO BEGIN
_FRef := RecRef.FIELDINDEX(i);
IF _FRef.NAME = _FieldName THEN
I was just working on an example myself .. but you saved me the time. Thanks, man!
If you’re not blogging the example, I will (and give you the full credits ovbiously) 😉
Hi Waldo, Feel free to use my example and blog about it. A credit with reference to my blog would be nice 😀
Have a nice day!
Will do 😉
Thanks… that’s what I was expecting…
… since there is no
_FRef := RecRef.FIELDNAME(‘my fieldname’)
Smart programming is the highway to repeatability;-)
Thanks for that example, “deV.ch” 🙂
I looked at the codeunit example again…
To me your table (screenshot) looks like you could setup any table to run with any codeunit.
But if I understand correctly you always have to define the record type in the target codeunit (table no property) to be able to pass the record as a variant. Which means that every codeunit can only handle one table.
If you don’t define table no… you cannot access the variant which you pass..
Or am I missing something?
I noticed that your variant parameter in the code sample above is passed by reference (VAR). Are you sure that’s necessary?
In my tests in NAV 2013, it seems records passed in as variants are *always* treated as “by value”, and recordrefs are *always* treated as “by reference”, regardless of whether the variant in question is VAR or not. As a consequence, the function’s behaviour strongly differs (in terms of active filters, temporary, active sort order), depending on whether you passed it a recordref or a record.
In my book, that reduces not only the elegance of the solution, but also its usability…
Would you agree?
In fact .. the example above won’t even compile in NAV 2015! :-).
The compiler will error out when you’re passing e.g. a recref variable to a byref variant parameter. Which makes sense. You’ll only be able to pass variants to ByReference variant-parameters .. .
Good catch 😉
Unfortenately this doesn’t seem to work with pre 2013 installations unless I’m doing something wrong.
I tried to test in 5.0 SP1 and 2009 R2 but it can’t cast a a record to variant in the function call.
I used your code to implement Facade pattern and I receive the following error when I call the part
‘Invalid table handle.The record parameter has not been properly initialized’
Any susggestions why this doesn’t work (I use NAV 2009 R2)?
If I remember correctly, the Variant-thing doesn’t really work on NAV2009 .. . You might want to verify that, because it’s just an assumption based on a (terrible) memory 😉
Thanks, based on the error I copied I would say definitely it doesn’t work on 2009R2, I needed to know if anybody had witnessed such error message and if tried to resolve it in any manner
I got the same error only with classic client in Nav 2009 R2. With RTC client seems to work properly.
I cant believe I only found this now. Thank you so much for this Info… I’ve been searching for ways to programm more flexible codeunits for a while.
Glad you like it 🙂
Have you made some experience with NAV2015 CU3? I get a compile error for type mismatch Variant := Record
I haven’t implemented CU3 yet.
But one thing pops up in my mind: check if that specific Variant is a parameter that is set up By Reference. You shouldn’t assign a record/recordref to it, because the “byref” would fail. I know NAV2015 is giving an error on that now ..
Hm ok, we pass it By Value and seems not to work. I’ll going to test it with CU4. Thanks for your comment
Same behavior in NAV2015 CU4…
I just read your post 2 years later… but it’s still actual for me!
I am looking to your example, and I can’t figure out how/when is the variabler SalesHeader intantiate in the CU 51531, fonction OpenCardPage_SalesHeader(…)? I have to try it by myself…
Thanks for all your post,
Have a nice day,
We use it every day .. and still loving it! 🙂
I just write and test your example.
And for me, one line is missing to open the card on the right record, in the OpenCardPage_SalesHeader function :
WITH SalesHeader DO BEGIN
Do you agree? Or do I miss something?
I don’t want to be “the student who surpasses the teacher”, and want to be sure of my understanding…
Thanks for your time
We are moving a company that make rubber bales to NAV 2013R2 now. They have only a handful of items, but requires many variants.
BUT they need to move the bales between variants without item journals, i.e. only as a warehouse movement.
What do you think is the best way to achieve this?
I think you have the wrong impression of this blogpost. This post is about the technical datatype “Variant”, not the functional “Item Variant” :-).
Sorry, I can’t help you with this. I suggest you put your question on the MiBuSo forum…
Hi, Trying this in NAV 2016 – gives compilation error, that function expects variant and I’m trying to pass a record to it..
Microsoft Dynamics NAV Development Environment
Type conversion is not possible because 1 of the operators contains an invalid type.
Variant := Record
my guess is that you declared your Variant parameter By Reference. IN that case, you would only be able to pass variants. If the parameter is not by reference, you can pass records.. .
Have you turned of so the parameter is not marked as var (By reference)?
How about a new Variant Function Like Ok := MyVariant.ISEQUAL(NewVariant);
returns True with both NewVariant is the same value as MyVariant.
Not a bad idea at all.. although it’s comparing objects. And when is an object equal .. might be difficult to define that.. .
thank you very much for your nice tutorial.
I am wondering if it is possible to call functions of a RecordRef-variable?
For example : If two tables have the same functions, with same parameters, but different code, this might be interesting. Do you know if there is some field-ref alike possibility for functions?
I was not able to find out anything useful, neither in NAV nor in the internet.
Thanks Andre 😉
Well, that’s not possible, I’m afraid. It should be, I totally agree, but it isn’t .. :(.
I’ve used this very handy approach many times, but in 2018 the variant can no longer be set to VAR which severely limits what we can use it for. Do you have any info on this?
I think the compiler-error is there from before 2018, no?
From what I know: it never worked anyway. The variant was never a “real” reference, so the compiler actually is right to not allow it.
You might want to test the “var” in your older release, and you probably will see it’s not really behaving as such…
(now I hope I remember this correctly … ;-))
Well. nobody complained so far 🙂
I’ll go with a VAR RecRef instead