If you have ever come across the problem that Dynamics CRM out-of-the-box documents integration creates SharePoint folders with GUID appended to the record name, you probably wonder if this can be disabled in a supported way. The answer is – yes it can, but you cannot do it using CRM Web Interface. Instead, it can be achieved by using the tool OrgDBOrgSettings which is available alongside all other Dynamics 365 related downloads. Firstly, go to the:
Scroll down and choose CRM2016-Tools-KBXXXXXXX-ENU-amd64.exe. After downloading the tool, extract it to some folder and open Microsoft.Crm.SE.OrgDBOrgSettingsTool.exe.config file. Inside you will have to provide some authentication details – most likely you are not connecting to CRM 4.0, so you can skip OrgDBOrgSettingsTool_CrmService_CrmService, but you should fill:
Which (after providing the password) will give you all the settings for the orgname (/u specifies if this is unique name of the organization or friendly name), so for example:
And now all the folders created by Dynamics 365 in SharePoint will not have this ugly GUID after the name of the record (of course you should remember not to have duplicated names for your entity records, otherwise you are asking for trouble).
Wait, what? Who on earth would do such terrible thing? For those of you who are not aware of that (are there any?), there is a great tool included inside SDK binaries, which is called “crmsvcutil.exe” and allows to generate proxy classes that can be used for calling Dynamics 365 Organization Service (for commands and query operations). If you have ever worked with any WCF service, you’ll probably know a tool called “svcutil.exe” which basically does the same thing – only here this tool adds some extra metadata, which is used under the hood by CRM SDK to convert correctly the proxy (often referred to as “early-bound class”) to Entity class object, which is in fact the class used by the Organization Service in most of its methods.
Having said that, you probably wonder, why I even consider not using this tool. It greatly improves the coding performance, it gives the developer intellisense, nobody has to use any magic strings – everything is strongly typed and most of the errors can be found at compile time. Yes, that is totally correct – but you also have this XX-thousands lines long file, which is taking years to open and making your VS cry, when you do so, your DLL with logic takes 10MB instead of 100KB, because of that file (not mentioning PDB file…), all the custom properties and custom entities are not following .NET naming convention (being lowercase with some silly prefix like “new_” or whatever you choose it to be). Also have a look at your recent project – how many entities and attributes have you used in there to justify generation of thousands of unnecessary classes, enums, fields and attributes? If you would say that in % of total code, it would not be larger than 10% (I dare you to check!).
I understand that there are some third party tools, or even open-source tools to generate proxies not for all entities but only for some chosen ones. These things are great, but they still produce some code, that we cannot control (under the hood they are still using “crmsvcutil.exe”) and do not fix the problem of naming fields or generating all 150 properties for Account, even if we need only 10 of them in our plugin/external application.
Of course, I don’t suggest to skip using proxy classes – I’m a big fan of them. What I suggest, is trying to build your own model of proxy class – without any special tool. You will have full control of what they contain, how they can behave and how many of them are there in your project. I’m not talking here about systems containing one plugin, that simply does the only one thing ,that could not have been achieved by workflow, business rule or calculated field. I’m talking about big, enterprise-class systems, which require hundreds or thousands of man-days to be built – including some fancy integrations with external systems, many plugins or custom workflow activities, following the best modern approaches of code engineering, like high coverage of unit tests. In such systems, keeping clean model and including only the things that the application is actually using is a crucial part. I will not write about technical debt and all those standard phrases that will tell you that the more time you will spend developing good and clean code, the less time you will have to spend after a year when you will have to fix something or extend your application. Writing your own proxy class is not hard at all and I assure you that once you start to do so, you will not spend too much of your time for that. Also one more thing before I start showing you the code – why creating a model in WCF, MVC or MVVM application is not a problem for you and it is in Dynamics 365?
Ok, so let’s go to some examples. I will be using C# 7.0 features, so make sure you have Visual Studio 2017 installed, if you want to try my examples. I will start from creating some class called MyAccount that I will use to query some Account data from CRM. It will look like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Unfortunately, this will crash with the following error:
System.ArgumentException: ‘The specified type ‘DemoCodeNoProxy.MyAccount’ is not a known entity type.’
Well this makes sense – we never told the OrganizationService client, how it can serialize/deserialize our custom class into something that it understands. First thing we should do is adding Microsoft.Xrm.Sdk.Client.ProxyTypesAssemblyAttribute to the .dll file which has our custom model, I usually do this inside AssemblyInfo.cs file:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
We need to decorate our class with proper Microsoft.Xrm.Sdk.Client.EntityLogicalNameAttribute:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Not saying much I have to admit, but problem is quite easy to guess – LINQ to CRM has to map our strongly typed properties to attribute names on CRM side. This is simply done by odding Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute for our property:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
We can now run our code and… it works correctly! Great. Now let’s improve it a little to really boost our productivity. First thing I would do would be renaming property from “odx_clientcode” to “ClientCode” to follow standard C# naming convention. Can we simplify the code for our property? Sure let’s create a base class for all our custom proxy classes:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
As you can see, I’m using some convention here – I’m assuming that all my Dynamics365 attributes are named the same as my properties in my model – but only with lowercase and prefix. If I follow this convention and use the above class, I can simplify my proxy class to look like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
As you can see, adding new properties requires really little effort. If you don’t like to follow my convention (because you don’t have control over the CRM naming), you can pimp it up further by reading names of attributes from AttributeLogicalNameAttribute, so that you are sure to be using right name. Very simplified version of such approach can look like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
So we need only to add an attribute with proper name for our class and each property – everything else will be done automatically, there are no more places where we use the logical names from CRM. Isn’t it great?
MyEntity class is also a great place to add any commonly used methods which can simplify your work even more. Good luck!
I believe that if you have ever written a CRM plugin, you are aware of the event execution pipeline (https://msdn.microsoft.com/en-us/library/gg327941.aspx) and that the operation is run in transaction which means that if there will be any exception, the whole transaction will be rolled back. Pre-operation plugins and Post-Operation plugins are also run in transaction, so if there will be an exception raised by the plugin, changes will not be reflected in CRM. This includes not only chaning attributes for the entity that the plugin is running on – create/update/delete changes on any other entities are also rolled back. This is really convenient – we can perform a bunch of operations and we are sure that even if anything goes wrong during Post-Operation, everything will be changed back to the original state.
The usage of transactions can be embraced even more by using custom Actions and ExecuteTransactionRequest. Custom Actions can also be run with a rollback option (which can be enabled or disabled on Action configuration page). ExecuteTransactionRequest allows to group different OrganizationRequests and send them in bulk to the server – they will be run in single transaction.
Ok, but how this transactional behaviour works in real world? To make it easier to understand for you I created a custom entity “new_lock” and prepared a very simple CRM plugin:
public void Execute(IServiceProvider serviceProvider)
{
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(null);
var @lock = new Entity("new_lock");
@lock.Id = new Guid("590424F1-611F-E711-80D2-00155D010402");
@lock["new_name"] = "lockme";
service.Update(@lock);
Thread.Sleep(30 * 1000);
}
I registered this plugin on Create message of the Contact entity. As you can see it simply retrieves the “new_lock” record and updates its name to some different value and then waits 30 seconds. Let’s open this record in a browser:
Now, let’s create a Contact (using a different browser window). After clicking “Save” you will notice that the loader starts but the data is not getting saved. That’s ok because our plugin is running in the background and it takes about 30 seconds to finish. If you try to save the opened “new_lock” record with some different name, you will notice that… you can’t do this:
This is the principal idea of transactions – by creating a Contact we opened a transaction and “locked” the “new_lock” record by updating it. Before the transaction is finished nothing can write to this record. After 30 seconds, our contact is created and the “new_lock” is updated with the new value. Is this a value from the plugin or from what we have entered on the form? Well it’s the second option, because we updated the value after the transaction has done it (it does not matter that it waited 30 seconds after that).
How can we make use of that transactional behaviour of CRM? I believe that a very cool feature that we can develop is proper auto-numbering. It’s pretty well known problem which is usually handled by some custom counter entity. The algorithm usually works like that:
Run plugin on create of numbered entity
Retrieve Counter
Assign current value of counter to entity record
Update counter with the next value
Plugin ends
If you don’t have many users or some custom apps creating entities, you will probably never run into any concurrency problems. But imagine what will happen if you have two concurrent calls and both retrieved value of the counter before point 3 was fulfiled. This means that both records will have the same number and counter will be increased only by 1. Some may say – ok let’s use standard locking, we can have a static object to lock on and before plugin starts it’s core logic, the running thread must obtain the lock. That would work flawlessly on single-frontend-server environments. But what happens if there are more application servers for CRM? You cannot synchronize threads between different servers…
Knowing what we already know about transactions, we can easily modify this scenario to:
Run plugin on create of numbered entity
Retrieve Counter
Update some dummy counter property to some dummy value (here we will lock the counter for usage only for this transaction)
Retrieve Counter (now we are sure nobody will modify its value)
Assign current value of counter to entity record
Update counter with the next value
Plugin ends (and any concurrent operations start from point 3)
This should work even on multi-server environments, because the transactions are locked on database level. Very simplified code (so pardon the style) can look like this:
public class TransactionPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(null);
var counter = GetCounter(service);
counter["new_name"] = "lockme";
service.Update(counter);
//now counter is locked
//we have to get it again to prevent race conditions
counter = GetCounter(service);
var target = (Entity)context.InputParameters["Target"];
target["new_number"] = counter["new_nextnumber"];
counter["new_nextnumber"] = (int)counter["new_nextnumber"] + 1;
service.Update(counter);
//after finishing the transaction, concurrent executes can go on from the lock
}
private Entity GetCounter(IOrganizationService service)
{
//return some counter
}
}
UPDATE 21.07.2017
I prepared more detailed code for autonumbering plugin, which I have successfully used in my projects. The plugin works on Pre-Create of Account.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
As you can see I’m retrieving my “new_lock” entity by its name using QueryExpression and after locking it with Update, I’m retrieving it again using Retrieve message. It is very important, because the following code will not work correctly:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
If you try to run that, you will discover that you have a lot of duplicates. The reason is that the second RetrieveMultiple gets exactly the same value as the first one, so if we already obtained counter and some other thread will modify its value – this change will be not reflected. This is very important, so make sure you are not falling for this trap – for me this feature is quite surprising, QueryExpression results seem to be cached somehow in plugin context. What is even more interesting the following version will work flawlessly:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
So simply replacing service.RetrieveMultiple with RetrieveMultipleRequest causes the results to be retrieved from the database, not from some kind of cache. That is something you should probably be aware of when implementing your own solution.
CRM 2016 brought one great improvement – possibility to choose what subcomponents we want to include in the solution. We can choose single fields, views or relationships for an entity. Previously when an entity has been added to a solution, all its subcomponents were automatically added to this solution. This great feature allows to better control what is transferred between environments. Who has never imported a view or some chart that was changed on testing environment to live environment, by accident, please raise your hand. Currently we can include only the components that were changed and make sure nothing else will come along. Of course, it requires changing the mindset – quite recently I was asking a many-year CRM customizer, why he has added all components for Account entity to our solution, if the only thing that was changed were few added fields. “Because it always was like that” is not satisfying answer for me, as you probably already figured out 🙂
Because of this new feature, I believe that it’s important to better understand what is in fact kept in the solution from the metadata and data perspective. Every element in a solution is called Solution Component. It’s a simple entity that has a type and few values that help identify it (like ObjectId) if it’s a metadata or data component and if it includes all Subcomponents, does not include subcomponents or is only a shell of a component. Documentation on MSDN (https://msdn.microsoft.com/en-us/library/mt608054.aspx) clearily defines all solution component types, and the most important fields, which are:
componenttype:Â The object type code of the component.
ismetadata:Â Indicates whether this component is metadata or data. (1 : Metadata, 0 : Data)
objectid:Â Unique identifier of the object with which the component is associated
rootcomponentbehavior: Indicates the include behavior of the root component (0 : Include Subcomponents, 1 : Do not include subcomponents, 2 : Include As Shell Only)
rootsolutioncomponentid:Â The parent ID of the subcomponent, which will be a root
I researched all available solution components. Below you can find a summary of their names, logical names, short description and how to get them using SDK. Basically there are three ways of getting this data using SDK:
Using dedicated OrganizationRequest: for example EntityMetadata, AttributeMetadata etc
Using RetrieveRequest (or simply IOrganizationService Retrieve message)
Using RetrieveMultiple specifying QueryExpression with proper entity name and id equal to ObjectId from Solution Component object. Don’t ask me why simple Retrieve does not work for this components – it simply does not.
Interesting thing is that some components are known as not-transferable through solution (like Duplicate Detection Rules). I will try to investigate in the future if it’s possible to add them to the solution using SDK and successfully transfer to another environment using solution.
UPDATE: I investigated if it’s possible to add a solution component that is listed below, but it’s not available through UI, so for example Duplicate Detection Rule:
var solutionComponentRequest = new AddSolutionComponentRequest()
{
ComponentType = 44,
ComponentId = duplicateRule.Id,
SolutionUniqueName = "solutionName"
};
service.Execute(solutionComponentRequest);
Unfortunately such operation ends with with an exception:
An unhandled exception of type 'System.ServiceModel.FaultException`1' occurred in Microsoft.Xrm.Tooling.Connector.dllAdditional information: Invalid component type provided 44
Solution components summary:
Type
Name
Logicalname
Comment
SDK
1
Entity
n/a
Entity metadata
RetrieveEntityRequest
2
Attribute
n/a
Attribute metadata
RetrieveAttributeRequest
3
Relationship
n/a
Relationship metadata, but it’s always bound to EntityRelationship metadata, internal
Obtained together with EntityMetadata using RetrieveRelationshipRequest
4
AttributePicklistValue
n/a
Value of the option set option, internal
–
5
AttributeLookupValue
n/a
Value of the lookup text,
internal
–
6
ViewAttribute
n/a
Attributes used in views, internal
–
7
LocalizedLabel
n/a
Localized label (metadata in CRM that you can localize)
RetrieveLocLabelsRequest
8
RelationshipExtraCondition
n/a
Relationship metadata, internal
Obtained together with EntityMetadata using RetrieveRelationshipRequest
9
OptionSet
n/a
Option set metadata
RetrieveOptionSetRequest
10
EntityRelationship
n/a
Entity relationship metadata
RetrieveRelationshipRequest
11
EntityRelationshipRole
n/a
Relationship role (feature depreciated since CRM 2011, after introducing Connections)
–
12
EntityRelationshipRelationships
n/a
Mapping between EntityRelationship and Relationship, internal
Obtained together with EntityMetadata using RetrieveRelationshipRequest
13
ManagedProperty
n/a
Managed property metadata
RetrieveManagedPropertyRequest
14
EntityKey
n/a
Entity key metadata
RetrieveEntityKeyRequest
20
Role
role
Security role
RetrieveRequest (Target = role)
21
Role Privilege
roleprivileges
Security role privilege
RetrieveRequest (Target = roleprivileges)
22
Display String
displaystring
Display string
RetrieveRequest (Target = displaystring)
23
Display String Map
displaystringmap
 Display string mapping
 RetrieveRequest (Target = displaystringmap)
24
Form
 form
internal
–
25
Organization
organization
 Organization entity
 RetrieveRequest (Target = organization)
26
Saved Query
savedquery
 View
 RetrieveRequest (Target = savedquery)
29
Workflow
workflow
 Process
 RetrieveRequest (Target = workflow)
31
Report
report
 Report
 RetrieveRequest (Target = report)
32
Report Entity
reportentity
 Entity bound to report
 RetrieveRequest (Target = reportentity)
33
Report Category
reportcategory
 Category of report
 RetrieveRequest (Target = reportcategory)
34
Report Visibility
reportvisibility
 Visibility of report
 RetrieveRequest (Target = reportvisibility)
35
Attachment
attachment
 Attachment entity
 RetrieveMultiple request using QueryExpression(“attachment”)
36
Email Template
template
 Email template
 RetrieveRequest (Target = template)
37
Contract Template
contracttemplate
 Contract template
 RetrieveRequest (Target = contracttemplate)
38
KB Article Template
kbarticletemplate
 KB Article template
 RetrieveRequest (Target = kbarticletemplate)
39
Mail Merge Template
mailmergetemplate
 MailMerge template
 RetrieveRequest (Target = mailmergetemplate)
44
Duplicate Rule
duplicaterule
 Duplicate rule
 RetrieveRequest (Target = duplicaterule)
45
Duplicate Rule Condition
duplicaterulecondition
 Duplicate rule condition
 RetrieveRequest (Target = duplicaterulecondition)
46
Entity Map
entitymap
 Entity mapping
 RetrieveRequest (Target = entitymap)
47
Attribute Map
attributemap
 Attribute mapping
 RetrieveRequest (Target = attributemap)
48
Ribbon Command
ribboncommand
 Ribbon command
 RetrieveMultiple request using QueryExpression(“ribboncommand”)
49
Ribbon Context Group
ribboncontextgroup
 Ribbon context group
 RetrieveMultiple request using QueryExpression(“ribboncontextgroup”)
50
Ribbon Customization
ribboncustomization
 Ribbon customization (Application Ribbon)
 RetrieveMultiple request using QueryExpression(“ribboncustomization”) or you can use RetrieveApplicationRibbonRequest
52
Ribbon Rule
ribbonrule
 Ribbon rule
 RetrieveMultiple request using QueryExpression(“ribbonrule”)
53
Ribbon Tab To Command Map
ribbontabtocommandmap
 Ribbon tab to command mapping
 RetrieveMultiple request using QueryExpression(“ribbontabtocommandmap”)
55
Ribbon Diff
ribbondiff
 Ribbon diff
 RetrieveMultiple request using QueryExpression(“ribbondiff”)
59
Saved Query Visualization
savedqueryvisualization
 Chart
 RetrieveRequest (Target = savedqueryvisualization)
60
System Form
systemform
 Form
 RetrieveRequest (Target = systemform)
61
Web Resource
webresource
 Web resource
 RetrieveRequest (Target = webresource)
62
Site Map
sitemap
 Site map
 RetrieveRequest (Target = sitemap)
63
Connection Role
connectionrole
 Connection role
 RetrieveRequest (Target = connectionrole)
65
Hierarchy Rule
hierarchyrule
 Hierarchy rule
 RetrieveRequest (Target = hierarchyrule)
66
Custom Control
customcontrol
 Custom control
 RetrieveRequest (Target = customcontrol)
68
Custom Control Default Config
customcontroldefaultconfig
 Custom control default config
 RetrieveRequest (Target = customcontroldefaultconfig)
70
Field Security Profile
fieldsecurityprofile
 Field Security Profile
 RetrieveRequest (Target = fieldsecurityprofile)
71
Field Permission
fieldpermission
 Field permission
 RetrieveRequest (Target = fieldpermission)
80
App Module
appmodule
 App module
RetrieveMultiple request using QueryExpression(“appmodule”)
90
Plugin Type
plugintype
 Plugin type
 RetrieveRequest (Target = plugintype)
91
Plugin Assembly
pluginassembly
 Plugin assembly
 RetrieveRequest (Target = pluginassembly)
92
SDK Message Processing Step
sdkmessageprocessingstep
 Plugin step
 RetrieveRequest (Target = sdkmessageprocessingstep)
93
SDK Message Processing Step Image
sdkmessageprocessingstepimage
 Plugin step image
 RetrieveRequest (Target = sdkmessageprocessingstepimage)
95
Service Endpoint
serviceendpoint
 Service endpoint
 RetrieveRequest (Target = serviceendpoint)
150
Routing Rule
routingrule
 Routing rule
 RetrieveRequest (Target = routingrule)
151
Routing Rule Item
routingruleitem
 Routing rule item
 RetrieveRequest (Target = routingruleitem)
152
SLA
sla
 Sla agreement
 RetrieveRequest (Target = sla)
153
SLA Item
slaitem
 Sla item
 RetrieveRequest (Target = slaitem)
154
Convert Rule
convertrule
 Automatic creation rule
 RetrieveRequest (Target = convertrule)
155
Convert Rule Item
convertruleitem
 Automatic creation rule item
 RetrieveRequest (Target = convertruleitem)
161
Mobile Offline Profile
mobileofflineprofile
 Mobile offline profile
 RetrieveRequest (Target = mobileofflineprofile)
162
Mobile Offline Profile Item
mobileofflineprofileitem
 Mobile offline profile item
 RetrieveRequest (Target = mobileofflineprofileitem)
Editable grids are a long awaited feature of Dynamics 365. Without any fancy coding, they allow to provide users a nice and easy to use interface, for updating multiple records in an Excel-like experience. We can easily create a subgrid on the form, configure it to use an Editable grid as control and we are good to go, we can edit all the grid columns.
But what if we would like to edit only some of the columns of the grid, not all of them? This is not configurable, either we have an editable grid with all the columns editable, or we don’t have editable grid at all. The only way of handling this with configuration is enabling Field Security Profile – if user will not have privilege to update the field, it will appear as disabled for him. Fortunately there are quite a few client SDK features, that will allow us to achieve our goal with a little bit of JavaScript. First, we should register a function for OnRecordSelect event on our grid:
And my gridRowSelected function looks like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This function basically gets all attributes for the selected row and for specified attribute (in my case it was “odx_monthid”) disables the grid cell. Effects are the following:
Great, so only Month column is currently not editable, but we can still edit Amount column. The only drawback of this solution is that this “lock” on the field is shown only when you click on the row (because we registered the function to be fired on OnRowSelect event). Couldn’t it be fired for OnLoad of the form? Unfortunately, according to MSDN documentation:
we cannot run this, when form loads, because the internals of the grid are not yet loaded. Still it is quite an acceptable workaround. Maybe in future releases, editable grid will receive some more features like configuration of disabled columns.
Recently I was caught up in the discussion about early bound entities and late bound entities. One of the argument of the “late bound supporters” was the fact, that using late-bound allows reusing the same plugin for multiple entities (so for example create a plugin that runs for all activities or a common plugin for leads/opportunities/accounts). This argument is not valid, I will show you how to write a clean code, that uses modern approach for coding and is perfectly testable.
Let’s start with the late bound version of the plugin to give you an idea what are we trying to achieve here:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
The plugin simply adds values of “new_netamount” and “new_margin” fields on the entity and sets field “new_totalsum” to a resulting value. Of course this can be simply achieved by using calculated field, but I just want to show the basic idea here, real world scenario is usually much more complicated.
If we register this field on pre-update of any entity containing field “new_totalsum”, “new_netamount” and “new_margin” it would basically do its job. If you are not an experienced CRM Developer, who remembers CRM 3.0 or 4.0, just an ordinary .NET Developer, you would probably say, that this code looks ugly. It’s almost like it was taken from some JavaScript library – where are all the types, what is the type of “new_totalsum”, which year is that, are we still in 2017? And yes, you’ll be right – this code simply looks awful, does not allow to use any of great refactoring tools, because it uses some “magic” dictionaries and keys and without access to CRM you would not even have a clue, what are the proper types of the properties. It’s exactly the same story as with ORM – who of us still uses plain ADO .NET and not, for example, Entity Framework? Such approach is much less error prone, testable and also much more friendly for CRM newbies. Code using early bounds may look like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Now it’s much better, but there is a significant problem with this code. We assumed that our plugin runs on Account entity. What if we will register it on Contact entity? Well, it will simply crash when we will try to convert our target!
So the basic approach would be to create a switch statement and simply check the entity type:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Ok so now we can register the plugin on multiple entities, but our code looks even worse than the first version, because we have a lot of code that is copy-pasted. Imagine that the operation would be much more complex and would change frequently in time – we would have to change all the switch statements every time.
To overcome this problem we will take advantage of the fact that all early-bound classes are partial, so they can implement any interface we want. Let’s create such interface and some helper partial classes:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
But unfortunately, our “Target” is an entity and we have to somehow convert it to an early bound. I developed a simple utility class that is capable of that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Simple explanation of the code – during first execution of the plugin, all the early-bound types are cached in a dictionary, that can be later fast accessed from the code (in this case it’s InterfaceMocker). It simply instantiates the early-bound and copies all attributes inside. There is also an extension method that simplifies the usage for Entity. Now our plugin can simply look like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
And that’s it! This code looks much better then late bound version, provides type-checking for entity attributes and also is very nice when we implement unit tests, as we have an interface to test, not a full early-bound. If our logic is more complex we can create some additional classes that will be dependent upon IEntityWithTotalSum interface, not Account or Opportunity entity.