Replies: 8 comments 10 replies
-
Hi @mike, is this implemented in the current low code plugins by via the accelerator because Nathan Rose found out that Set() is not longer a valid function, and that Patch() had to be used. I have tested this too. I've been looking at how sync classic workflows might map/convert to low code plugins. I think Error() should stop the low code plugin without any PowerFx after the Error() executing. It will be confusing to people if the following creates an annotation. If(NewRecord.Name <> OldRecord.Name, |
Beta Was this translation helpful? Give feedback.
-
I suggest getting rid of the NewRecord term. Only have OldRecord and CurrentRecord. This should also be available post op to check whether a value has changed. Don't block any registration that would be possible in a plugin. So delete post op is completely valid. Sounds like a good idea to forbid implicit scope, or force implicit scope to be CurrentRecord would be fine as well. What is the reasoning for not having formula columns as Retrieve plugins. This would mitigate this issue |
Beta Was this translation helpful? Give feedback.
-
My two pence/cents: |
Beta Was this translation helpful? Give feedback.
-
Point 11: After noticing the new plugin trace viewer in the latest version of the Dataverse Accelerator solution I think it might be useful to add a Trace() or Telemetry() function to automatically log to the ilogger and/or (traditional) tracelog. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Hello @MikeStall, Thanks. |
Beta Was this translation helpful? Give feedback.
-
Is it possible to run automated plugin when a record is related / unrelated (associated/dissociated) between 2 tables having a relationship? |
Beta Was this translation helpful? Give feedback.
-
anyone has success by updating a LookUp column with Set for a new record? |
Beta Was this translation helpful? Give feedback.
-
This memo describes a proposal to finalize the programming model for Low Code plugins.
Low Code plugins run as a C# Plugin which includes the power fx nugets and binds maker expressions into the dataverse eventing pipeline.
This is the proposed GA behavior; so it has some changes from current preview behavior.
See migration steps below. Notable breaking changes from current:
NewRecord
. Instead ofXPrevRecord()
, useOldRecord
.Field2
, but instead sayNewRecord.Field2'. This applies to Set as well: we're require
Set(NewRecord.Field1, OldRecord.Field2*10)`Core scenarios
Low Code plugins can be triggered as both Custom APIs ("Instant") and Entity messages ("Automated").
For "Instant Plugins" (aka APIs):
Triggered: Explicitly invoke as an unbound action
For "automated plugins"
Triggered: registered on an entity's messsage (Create/Update/Delete/etc) and Stage (pre-op/post-op/etc)
Principles
Versioning Concerns
Expressions must not change meaning in the future. For example, if an expression is written on monday, and a new field is added to a Dataverse on tuedsay, the expression can't rebind to that new field in a way that would change semantics.
Consider the following kinds of changes that can happen in a dataverse environment:
User
,NewRecord
,OldRecord
, etc... but future identifiers can be added.Step to ensure versioning:
NewRecord
. An expression can bind toNewRecord.Field1
, but not toField1
directly.Common Features
All Power Fx Language Features
Log Code plugins uses the C# interpreter implementation of Power Fx, which enables all language features, including:
This is more feature rich than the Power Fx Formula Columns - which have the restriction of compiling to SQL. (see https://learn.microsoft.com/en-us/power-apps/maker/data-platform/formula-columns)
No implicit scope and top-level objects.
Any object at top-level scope must be explicitly specified. This is especially important for versioning issues - we don't want an expression to change meaning if you add new fields.
Builtin top-level objects
Environment
object.User
object.Table References
Any table reference must be explicitly added in the UI.
For example, in order for the expression to
Collect(Accounts, {... })
, theAccounts
table needs to be added in the UI.This helps with intellisense - Collect/Patch intellisense can suggest a short list of tables.
It also is critical for versioning - adding new tables in the future does not change the expression's semantics.
Additional objects.
For instant plugins, the input parameters are accessible as top-level objects.
For automated plugins, there is
NewRecord
andOldRecord
. Fields on current entity are not directly accessible and must be accessed likeNewRecord.Field1
.Validation Errors
If a plugin returns a runtime error, such as by using the Error() function, then it will block the operation. This can be used to implement validation rules.
Validation rules should be done in pre-op.
See https://learn.microsoft.com/en-us/power-platform/power-fx/reference/function-iferror for more details on Power Fx error functions.
Access Dataverse Tables
Plugins can read and write to dataverse tables. This includes Collect(), Patch(), Filter(), LookUp().
Delegation operations are supported and a warning is issued if an expression can't be delegated.
These operations will run directly against the Plugin's IOrganizationService (not the current entity) and will directly operate on the database. They will run in the transaction context.
Variables
Plugins do not support contexts, named formulas, or variables. Whereas Power Apps lets makers use Set() to define new variables, in Plugins, the only valid usage of Set() is where it is already explicitly enabled (such as setting the fields on the current entity).
Plugins do support With(), which can be used to create aliases and factor the expressions.
Key changes from current behavior:
Tables use the plural display name and are serialized as logical names.
If there is a conflict in display name, Power Fx will disambiguate by using
Display (Logical)
Access Connectors
Plugins supports accessing connectors. This should use the same syntax as the connector expressions in Power Apps.
Access current user
Plugins run in the context of a user and can get access to that user via the standard
User
object. See #2318 for details on user.Notably, this includes the
DataverseUserId
field which can be used to resolve the current user against the 'Users' table.LookUp(Users, ThisRecord.User = [@User].DataverseUserId)
Instant Plugins (Unbound Actions)
Instant plugins are Unbound Actions and don't have an explicit entity.
They receive a set of input parameters and return a set of output parameters.
inputs and outputs
The input parameter are incoming symbols.
They can return a record, where each field is an output parameter.
For example, this expression will take 2 input parameters (
x
,y
) and return 2 output parameters (add
,mul
):{ add: x + y, mul : x * y }
New behavior
If the plugin defines only a single output parameter, we can streamline and the expression can just return the single value.
x+y
Types
This just supports primitive parameter types.
To pass an entity, pass the guid and lookup. For example, to return a Name parameter that lookups up Input_Name
{Name: LookUp(Accounts, ThisRecord.Account=Guid(Input_Name)).'Account Name' }
Automated Plugins
Automated plugins run with a Current Entity. They may need to access fields from the entity and see which fields are changed (in an "Update" message).
They don't have an explicit return value. but they may need to:
Automated plugins: Referring to current and previous record
There are special objects:
OldRecord
- for Update and Delete, this refers to the previous record. This is the same asLookUp(Table, ThisRecord.Id = NewRecord.Id)
NewRecord
- for Create and Update messages - this refers to the record with the updates applied. For Create pre-op, since the entity is not yet created, NewRecord's primary key id will blank. NewRecord also has all fields filled out (not just the field that changed).TBD: we may add
CurrentRecord
for other future messages such as Read.This allows rules like:
There is no explicit scope, so you must say
NewRecord.Field1
and not justField1
.Updating fields in the current entity
For Automated plugins, to update fields in the current entity, use Set().
This will update the Current Entity (and not trigger a write to the database until the plugin succeeds and the plugin infrastructure persists the change back to the database)
Set(NewRecord.Field1, NewRecord.Field2 * 10)
Set() on OldRecord is a failure since OldRecord is not mutable.
Updating fields on Read
We don't currently support Read messages, but once we do - Set becomes very important as a means to calculate on-demand fields (that are not persisted in the database). This is how formula columns work today. And it will be very useful for creating Power Fx columns on Virtual Tables (where the rows are external to the database)
Eventing
This section explain the interaction with the eventing pipeline in more detail.
Some key observations:
OldRecord
), but the same operation in post-op will now return the record with updates applied.Migration
This calls out some notable differences from the existing implementation.
Our plan to mitigate impact is:
OldRecord
in the maker's expressions. This is the same model that Power Apps follows when they make breaking changes.Open Questions
Field1 * 10
, then an Update Pre-op plugin will see the new value of Field1 with the old value of Field2. Can we mitigate the confusion?Beta Was this translation helpful? Give feedback.
All reactions