Microsoft Dynamics CRM 2015: The Exciting Bits

September 25th, 2014

Well, to be honest, your mileage may vary, but there were parts of the recently-released Microsoft Dynamics CRM 2015 Release Preview Guide that made my ears perk up a bit. We knew that we’d be seeing improvements with the tablet client, so the announcement of an enhanced “offline” mode is no surprise, but I’m glad to see that the Business Rules functionality is getting some love.

One of the handiest additions to Microsoft Dynamics CRM 2013 was the ability to create simple UI rules without needing to hack out some JavaScript. Granted, the functionality was limited, since the conditions you could assign only allowed you to use the “and” logical operator (much like what you see when adding conditions to a CRM Workflow), but the Business Rules provided a cool, quick way to define and organize how a form would behave. With Microsoft Dynamics CRM 2015 though, not only are we getting access to the “or” operator and simple grouping, but we’re also getting If/If Else/Else support, practically making the Business Rules a full-fledged client-side version of the CRM Workflow functionality.

Not only that, but we’re also getting Calculated Fields, which have to have been a very popular request for Microsoft to implement for years. JavaScript will still have its place in CRM 2015, especially if it’s necessary to query for other records or manipulate strings, but we’re definitely moving closer to a system where we can whip up some pretty complicated behavior without resorting to custom code.

Properties vs. AllProperties

September 18th, 2014

There is a great article that I recently found about Properties vs. AllProperties. It describes the way that AllProperties is meant to replace Properties, but Properties was left in place for backwards compatibility. The unconventional PropertyBag data type stores its keys in all lowercase, thus not supporting case-sensitive keys, while the conventional Hashtable does support case-sensitive keys. On top of that, while entries added to Properties get propagated to AllProperties with a lowercase key, entries added to AllProperties do not get propagated to Properties.

To read more of the article, please click here.

Troubleshooting tabindex in IE

September 4th, 2014

Recently I was working on a UI project, and started to have issues with tabindex – but only in IE (go figure). The UI was essentially a large grid with custom tabbing, and the user could also add new rows/fields at any time, so the tabindex values were always being adjusted and had the potential to get pretty large. At the very bottom of the form was a textarea that would also need focus, but it needed to be the very last item in the tab order.

As some of us end up doing with things like z-index, I decided to take what I thought was the easy solution and assign the textarea a tabindex of some random large number like 99999.

<textarea id="notes" tabindex="99999"> </textarea>

While testing I realized that the textarea would never actually get focus in IE, so I began to wonder if there were actually limitations to tabindex values – turns out there are! The IE documentation for the tabindex property states:

“For Internet Explorer 5.01 or above, the attribute may be set to any value in the valid range of -32767 to 32767.”

…so there was my answer. While other browsers handled larger integers, IE was once again a bit different, and after adjusting my tabindex my field was once again added to the tab order!

Need to import complex data into CRM 2013? It may be easier than you think!

August 28th, 2014

While CRM 2013’s Import Wizard can provide a quick-and-dirty way to get data into the system, there’s a limit to what you can do and how much you can control how the wizard responds to missing data. Sometimes you just happen to have a huge table of historical data that you need in CRM, which may or may not be referencing records that already exist in the system, but will definitely need to be referencing something once they’re imported. Or maybe that historical data will need to reference something like a Contact or Account, but you’d like the lookup logic to be more complex than a simple “do any of these fields contain the value from this column?” condition.

The words “custom app” or “one-time use” tend to send up red flags, but that’s no excuse to try to force a square peg into a round hole. While you could always export data from CRM and import it into a database temporarily, allowing you to analyze it and craft a series of Excel files for import, it may actually be more cost-effective to go ahead and write that one-time use custom app. The truth is, as long as you know how to knock the logic out in C# and you have the connection info for both the source and the destination, it’s very easy to do.

MSDN provides a handy guide on how to set up a barebones version of such an app: Walkthrough: Build a console application that connects to Microsoft Dynamics CRM using developer extensions. All that’s left is to add code to pull your source data, logic to perform lookups, and populate CRM Entity objects to your heart’s content!

Maintaining Index Values While Paging with Knockout.js

August 18th, 2014

Recently I was working on a project that included a grid with large amounts of data. This grid not only displayed data but also allowed the user to edit, and the saving process contains a lot of validation and behind-the-scenes adjustments to data, so the usual knockout binding wasn’t always enough. In many cases, when the user updated a particular field, we wanted to default/disable/show/hide other fields within the same row. To help with this, we ended up using a custom binding handler that would add an attribute to each field with the index of the object in the observableArray. This process was adapted from this example by Ryan Niemeyer, and looks like this:

ko.bindingHandlers.setIndex = {
	init: function (element, valueAccessor, allBindings, data, context) {
		var prop = valueAccessor();
		element.setAttribute(prop, context.$index());
	}
};

To use this in your binding, you would add the following (with “arrayIndex” being the attribute name):

<input type='text' data-bind="setIndex: 'arrayIndex'" />

Now, whenever we wanted to retrieve the index of the item in our script, we could access the value of the arrayIndex attribute. This worked awesome, until the day came where we wanted to introduce paging to the grid. To page the observableArray that was binding to the grid, I used a slightly modified (for our needs) version of this paging extender on jsFiddle. The problem was that when I navigated to any of the pages, we’ll say page three, and updated the record in row 1, when I navigated back to the first page it had actually updated data in row 1 of the first page (and the row on page three was now blank).While troubleshooting I noticed that the knockout $index() value was not actually taking pagination into consideration, and instead was only relative to the particular page that I was viewing. So rather than page one having indexes of 0-9 and page two having 10-19, each page would always only be 0-9. This wouldn’t normally be an issue, except that all of our behind-the-scenes changes were happening based on the arrayIndex attribute (derived from $index()), so no matter what page you were on, you would always be updating the first page of data.

To fix this, all we had to do was update our binding handler to accomodate our new paging:

ko.bindingHandlers.setIndex = {
	init: function (element, valueAccessor, allBindings, data, context) {
		var prop = valueAccessor();
		//$parent.DETAIL references our observableArray
		element.setAttribute(prop, ((context.$parent.DETAIL.currentPage() - 1) * 10) + context.$index());
	}
};

With this, each page would set the arrayIndex to the correct value, and our edits once again worked properly. Reiterating what I touched on above, this isn’t normally something you would encounter unless you are making custom index-based changes, but in the event that you are, hopefully this helps prevent some of the headaches!

Database Table Compare Using Excel VBA

August 11th, 2014

One of several integration points on a current project is to an SAP Sybase ASE database (v16.0). The Sybase database was ported from the 12.5 version, and part of the testing was to verify that the data being pushed to the new version matched what’s being pushed to old version.

I couldn’t find any decent [free] tools that would do a data compare against tables in two different Sybase databases, and I ran into some issues trying to use Entity Framework against Sybase: 1) the old Sybase database didn’t have primary keys set on the tables and there wasn’t a .NET Provider for it, and 2) the new Sybase v16.0 didn’t have any decent documentation on the topic. So I decided to roll my own simple comparison leveraging ODBC, Excel 2013 and VBA.

Since the tables in both database were identical in terms of the schema, and all I needed to do was to compare the data on a field by field basis, Excel was an option that could handle it pretty easily and it was a tool the users were familiar with.

To start with, I setup two User DSN ODBC connections, one to each of the Sybase databases (Figure 1) and configured them (Figure 2).
ODBCAdmin
Figure 1. ODBC Data Source Administrator

DSNConfigure
Figure 2. DSN Configure, ASE

Next I created an Excel Macro-Enabled Workbook (.xlsm) with a worksheet for each of the tables I needed to compare: CMC_PAGR_PARENT_GR, CMC_GRGR_GROUP, etc. (Figure 3).
ExcelCompare
Figure 3. Excel Workbook

And lastly, added some VBA code with logic to connect to the databases using the ODBC connections and also added a reference to the Microsoft ActiveX Data Objects 6.1 Library latest version (Figure 4). Older versions of the data object library works as well.

Sub Main()
    Dim groupId As String
    Dim sqlGroup As String
    groupId = "3005"
    sqlGroup = "SELECT * FROM dbo.CMC_GRGR_GROUP WHERE GRGR_ID = '" & _
                groupId & "'"

    PopulateWorksheet "CMC_GRGR_GROUP", sqlGroup
End Sub

Private Sub PopulateWorksheet(worksheet As String, sql As String)
    Dim col As Integer
    Dim rs As ADODB.Recordset

    ' Get the records from the old database
    Set rs = QueryDatabase("FACETS v12.5", sql)

    ' Copy field names to the first row of the worksheet
    For col = 1 To rs.Fields.Count
        ActiveWorkbook.Worksheets(worksheet).Cells(1, col).Value = _
            rs.Fields(col - 1).Name
    Next

    ' Copy the recordset to the worksheet, starting in cell A2
    ActiveWorkbook.Worksheets(worksheet).Range("A2").CopyFromRecordset rs

    ' Get the records from the new database, and copy the recordset to cell A3
    Set rs = QueryDatabase("FACETS v16.0", sql)
    ActiveWorkbook.Worksheets(worksheet).Range("A3").CopyFromRecordset rs

    HighlightDifferences (worksheet)
End Sub

' Format differences in the data between databases
Private Sub HighlightDifferences(worksheet As String)
    Sheets(worksheet).Select
    Range("A2").Select
    Range(Selection, ActiveCell.SpecialCells(xlLastCell)).Select
    Selection.ColumnDifferences(ActiveCell).Select
    Selection.Style = "Bad"
End Sub

' Query a given Sybase database based on the ODBC name and SQL statement
Private Function QueryDatabase(odbcName As String, sql As String) _
    As ADODB.Recordset
    On Error GoTo ErrorHandler

    ' Declare our variables
    Dim conn, rs

    ' Initialization
    Set rs = New ADODB.Recordset
    Set conn = New ADODB.Connection

    conn.Open odbcName, "userid", "password"
    rs.CursorLocation = adUseClient
    rs.Open sql, conn, ADODB.adOpenForwardOnly, ADODB.adLockBatchOptimistic

    ' Clear the connection: only a disconnected recordset can
    ' be passed around and used later.
    Set rs.ActiveConnection = Nothing

    ' Return the recordset
    Set QueryDatabase = rs
    GoTo ExitHere

ErrorHandler:
    MsgBox "Error " & Err.Number & ": " & Err.Description & " in Main()", _
        vbOKOnly, "Error"
    End

ExitHere:
    conn.Close
    Set conn = Nothing
End Function

VBAReferences
Figure 4. References VBA Project

The logic retrieves the data from each database using the connections and SQL statement provided, populates the worksheet and highlights the cells where the values are different. Also, there are any number of re-factoring that could be done such as, additional formatting, storing the parameters for the SQL statement in a ‘Settings’ worksheet and adding a button to launch the process. However, this basic process was all I needed to verify that the data posted to the new database matched up with what was being posted to the original database. In this example, I am retrieving data from a Sybase database, but it’s even easier from a SQL Server database because you don’t need to setup ODBC connections. In addition, Excel and VBA can be quite powerful and can be used in a variety of ways to provide solutions that’s often overlooked.

Dynamics CRM 2013 Upgrade Gotcha: Sitemap Pointing to the Wrong Help?

July 31st, 2014

We’ve performed several sitemap customizations for clients in the past, so a recent MSDN article, Is your CRM 2013 pointing to CRM 2011 help?, really caught my eye. I admit that I don’t frequently use the online help for development, so learning that there are instances that a CRM upgrade can leave the help link pointing to the CRM 2011 page was a surprise.

The article has a great infographic that covers how to correct the issue, but it boils down to:

  1. Export a solution with the sitemap.
  2. Extract the files from the solution and open up the XML for the site map.
  3. Locate the “Help_Resource_Center” SubArea node and update the “Url” value to “http://go.microsoft.com/fwlink/?LinkId=296269″

And, while you’re in there, check if there are any other changes you should be making based on this TechNet article: Verify new areas are available in the navigation bar.

Generating Selected Entities for MS CRM Early Bound Entities

July 24th, 2014

On a recent project I had to create a web service that returned data from a MS CRM system based on provided parameters (account name and contact name). I prefer to work with the early bound, strongly typed XRM entities as opposed to using the web service calls, QueryExpression, ConditionExpression, etc., because the code is much shorter and easier to read – especially for anyone that has worked with Linq.

It takes a little more overhead to set up and create the XRM entities, though, and by default when you use crmsvcutil.exe it generates the early bound classes for every entity in the system. That’s a lot of overhead when you only need a small subset of entities like I did. In this case, I needed two entities: account and contact. I’ve had to do this before and always have to look up exactly how to do it. I have two blog posts that I’ve referenced when doing this. Both are very helpful, have actual code as well as a good explanation and wanted to share them. First is this post by Erik Pool. The second is this one by “Busy xRM Architects”. They both use a similar idea – read the entities to generate classes for from a file. One uses a text file and the other uses an XML file.

Hopefully this helps get the information out there and allows people to generate smaller classes!

Dynamics CRM 2013 Spring Release – Timer Control

July 3rd, 2014

One of the neater additions to CRM 2013 in the Spring ’14 Release is the ability to add a Timer control to a form. While it’s specifically mentioned that this new control can be used for service level agreements, the timer is generic enough that it can be used with any DateTime field in conjunction with a workflow to develop some handy time-dependent functionality. You just pick a DateTime field for it to look at, specify the Success/Waiting/Cancel Condition fields and values, then position it on the form.

There’s a great tutorial on how to add a Timer to the Lead form at Adding Timer Control to Lead Entity in Dynamics CRM 2013 Spring release, but I’d like to call out some things to keep in mind right here:

  1. The Success/Waiting/Cancel Condition fields can all be different.
  2. Only Option Set controls are available to select for the Condition fields.
  3. If you cannot add a Timer to the form because the Timer button is disabled, make sure that you’ve installed the latest product updates via the “Install Product Updates” link on the Administration page.

Getting the Current User Information in a Plugin

June 26th, 2014

I was working with MS CRM 2011 and I had a situation where I needed to get the information about the current user in the middle of plugin execution. When you start digging around the documentation in MS CRM you’ll find that there are two properties that look like they might have the information you need:

  • InitiatingUserId
  • UserId

Here’s an example of the code you’ll see in a plugin:

public void Execute(IServiceProvider serviceProvider)
{
    IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

    // Which one is correct?
    var initiatingUserId = context.InitiatingUserId;
    var userId = context.UserId;
}

How do you know which one you need? What’s the difference? The difference is that the UserId property may or may not be the same as the InitiatingUserId. If in your plugin step registration “Run in User’s Context” field has the value “Calling User” then the UserId and InitiatingUserId will match. If you specify a user in the “Run in User’s Context” field, then the UserId field will contain the user ID of the person you specify and the InitiatingUserId will be the actual CRM user whose action triggered the plugin.

My bet is that most people are usually looking for the InitiatingUserId, so it probably makes sense to use that property even if you specify “Calling User” for the “Run in User’s Context” field. That way, if the “Run in User’s Context” value ever changes, InitiatingUserId will still have the correct value. If you use “UserID”, it’s possible you’ll get a different value than what you expect.

Hopefully this helps someone figure out which value is the correct one for your situation.