Skip to content
  • Blog
  • Archives
Search
Close

Better CRM

Make your Dynamics CRM customizations a well written piece of code!

Tag: metadata

Creating early-bounds manually

June 13, 2017June 13, 2017 pawelgradecki3 Comments

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:


public class MyAccount : Entity
{
public MyAccount() : base("account") { }
public string odx_clientcode
{
get
{
return this.GetAttributeValue<string>("odx_clientcode");
}
set
{
this.SetAttributeValue("odx_clientcode", value);
}
}
}

view raw

MyAccount.cs

hosted with ❤ by GitHub

And I will try to use it like that:


public List<MyAccount> GetAccounts()
{
using (var ctx = new OrganizationServiceContext(this.service))
{
return ctx.CreateQuery<MyAccount>()
.Where(x => x.odx_clientcode != null)
.ToList();
}
}

view raw

MyAccountRepository.cs

hosted with ❤ by GitHub

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:


using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("DemoCodeNoProxy")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("DemoCodeNoProxy")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: Microsoft.Xrm.Sdk.Client.ProxyTypesAssemblyAttribute()]

view raw

AssemblyInfo.cs

hosted with ❤ by GitHub

We need to decorate our class with proper Microsoft.Xrm.Sdk.Client.EntityLogicalNameAttribute:


[EntityLogicalName("account")]
public class MyAccount : Entity
{
public MyAccount() : base("account") { }
public string odx_clientcode
{
get
{
return this.GetAttributeValue<string>("odx_clientcode");
}
set
{
this.SetAttributeValue("odx_clientcode", value);
}
}
}

view raw

MyAccount.cs

hosted with ❤ by GitHub

If we will run our code now, we will get another exception:

System.ServiceModel.FaultException`1: ‘attributeName’

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:


[EntityLogicalName("account")]
public class MyAccount : Entity
{
public MyAccount() : base("account") { }
[AttributeLogicalName("odx_clientcode")]
public string odx_clientcode
{
get
{
return this.GetAttributeValue<string>("odx_clientcode");
}
set
{
this.SetAttributeValue("odx_clientcode", value);
}
}
}

view raw

MyAccount.cs

hosted with ❤ by GitHub

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:


public class MyEntity : Entity
{
public MyEntity(string entityName) : base(entityName) { }
protected string GetAttributeName(string propertyName)
{
return "odx_" + propertyName.ToLowerInvariant();
}
protected T GetAttribute<T>([CallerMemberName]string propertyName = null)
{
return this.GetAttributeValue<T>(GetAttributeName(propertyName));
}
protected void SetAttribute(object value, [CallerMemberName]string propertyName = null)
{
this.SetAttributeValue(GetAttributeName(propertyName), value);
}
}

view raw

MyEntity.cs

hosted with ❤ by GitHub

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:


[EntityLogicalName("account")]
public class MyAccount : MyEntity
{
public MyAccount() : base("account") { }
[AttributeLogicalName("odx_clientcode")]
public string ClientCode
{
get => GetAttribute<string>();
set => SetAttribute(value);
}
}

view raw

MyAccountFinal.cs

hosted with ❤ by GitHub

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:


public class MyEntity : Entity
{
protected string GetAttributeName(string propertyName)
{
return this.GetType().GetProperties().FirstOrDefault(p => p.Name == propertyName).GetCustomAttribute<AttributeLogicalNameAttribute>().LogicalName;
}
protected T GetAttribute<T>([CallerMemberName]string propertyName = null)
{
return this.GetAttributeValue<T>(GetAttributeName(propertyName));
}
protected void SetAttribute(object value, [CallerMemberName]string propertyName = null)
{
this.SetAttributeValue(GetAttributeName(propertyName), value);
}
}
[EntityLogicalName("account")]
public class MyAccount : MyEntity
{
public MyAccount()
{
this.LogicalName = typeof(MyAccount).GetCustomAttribute<EntityLogicalNameAttribute>().LogicalName;
}
[AttributeLogicalName("odx_clientcode")]
public string ClientCode
{
get => GetAttribute<string>();
set => SetAttribute(value);
}
}

view raw

MyAccountFinal2.cs

hosted with ❤ by GitHub

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!

Solution Component types in Dynamics 365

April 26, 2017October 23, 2017 pawelgradeckiLeave a comment

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:

  1. Using dedicated OrganizationRequest: for example EntityMetadata, AttributeMetadata etc
  2. Using RetrieveRequest (or simply IOrganizationService Retrieve message)
  3. 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.dll
Additional 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)
165 Similarity Rule similarityrule  Similarity rule  RetrieveRequest (Target = similarityrule)

 

About me

My name is Paweł Gradecki. I'm a Dynamics 365 solution architect, but I'm a also a huge fan of .NET, C# good programming patterns and habits. I worked in many CRM projects as a Consultant, Lead Developer and/or Architect. Many times I've dived deep into the guts of CRM, I'd like to share with you what I've learnt.

Stack Overflow


profile for Pawel Gradecki at Stack Overflow, Q&A for professional and enthusiast programmers

Social

  • View pawel.gradecki’s profile on Facebook
  • View pawelgradecki’s profile on Twitter
  • View pawelgradecki’s profile on LinkedIn
  • View pawelgradecki’s profile on GitHub

Dynamics 365 Community

Dynamics Community

Tags

Actions activity alert API appointment attachment autonumbering Azure Client-side cognitive services CRM Customizations Development Dynamics 365 Dynamics 365 9.0 Dynamics CRM 2011 Dynamics CRM 2013 Dynamics CRM 2015 Dynamics CRM 2016 early-bounds editable-grid email Environment Error Export form Integration internals Javascript localization metadata migration navigation OrgDBOrgSettings Plug-ins plugins poc PowerApps Query Builder Error server-side SharePoint Solution Solution Component solutions speech subgrid Tooling transactions validation WebAPI Workflow
My Tweets
Website Powered by WordPress.com.
Back to top
  • Follow Following
    • Better CRM
    • Already have a WordPress.com account? Log in now.
    • Better CRM
    • Customize
    • Follow Following
    • Sign up
    • Log in
    • Report this content
    • View site in Reader
    • Manage subscriptions
    • Collapse this bar