The Evolution of Application Architecture

When I started developing ASP.Net applications (.Net 1.1, around 2004), the world of application development looked simple:

Application connecting to a database and the “universe” (anything it needs to interface to)

(Before that, I developed in mainly Delphi, also in Oracle Forms or ASP/VB, and it always looked like this)

The web application would connect directly to the (SQL Server) database given the support of Visual Studio designers, query builders, etc., and the semi-automatic connection between parameters of database statements and the values of query string parameters, control properties, and so on.

Of course, the “simple” approach had some drawbacks, and I even created a tool called graspx to work around the drawbacks (rather than changing the approach, as I realized later), such as finding the SQL statements retrieving and writing data to figure out database dependencies.

The first step away from the direct application/database connection is to generate a code model based on the database model

Application using a code model representing the database model

Since the code model is derived from the database model, changes in the database model immediately cause the compilation to break if tables or fields are deleted.

However, the application is still monolithic, which restricts the ability to write unit and module tests for each piece of functionality (especially if you chose to develop ASP.Net web sites rather than web applications).

So, let’s separate the business logic from the user interface part (this can be done in classic ASP.Net as well, but it’s trickier. In ASP.Net MVC, you are forced strongly encourage to follow this pattern).

Application using a business layer implementing functionality, communicating with the database via a ORM, and interfacing to other environments using separate libraries.

The business layer may encapsulate various functionality, such as communicating with systems other than the database, handling different data, interfacing to other systems, and so on. We extract the interfacing code to separate assemblies for each system, and route the calls through the business layer assembly.

Usually, the front end application is not the single application accessing the database. You need to add a service that executes asynchronous operations, or write an import or export tool, allow batch processing and so on. These applications do not need the full functionality of the existing business layer (with all its dependencies on other systems), so we create a separate business layer assembly which only provides the data model that application requires:

Application using a business layer implementing functionality, communicating with the database via a ORM, and interfacing to other environments using separate libraries.

This architecture is not even restricted to a certain kind of applications (in my case, web applications with a certain database), you can also apply it to other scenarios:

CRM applications using a business layer implementing functionality, communicating with CRM database via the CRM data classes, and interfacing to other environments using separate libraries.

From my work on CRM projects, I noticed that these projects tend to look like charts 1 and 2. If you start out with the separation of layers and responsibilities, you can easily get a nice architecture, even if it is not obvious from the beginning.

Displaying CRM 2011 Entity Customizations using XSLT

Sometimes it is necessary to extract information about customized entities and attributes in text form, e.g. to document entities or to compare two solutions.

An exported CRM solution is a Zip archive containing a couple of XML files, the most important of them being the customizations.xml.

This XML file can be extracted from the zip, and further processed by an XSLT file, such as the one I am going to describe here.

The output of this XSLT is a text file listing all customized entities, and their fields in tabular form: name, data type, required, display name, description. For clarity, tabs are shown as \t in the listing.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" indent="yes"/>

The Entity template displays the entity name and invoke the attributes template for every attribute:

  <xsl:template match="ImportExportXml/Entities/Entity">
  <xsl:text>
\t</xsl:text>
Entity <xsl:value-of select="Name/."/> (<xsl:value-of 
                               select="Name/@OriginalName"/>)
  <xsl:if test="EntityInfo/entity/attributes/attribute">
    <xsl:apply-templates 
      select="EntityInfo/entity/attributes/attribute"></xsl:apply-templates>
  </xsl:if>
  </xsl:template>

The Attribute template displays the attribute’s properties. The @langcode condition needs to be adjusted to the language you want to extract from your customizations.xml.

  <xsl:template match="EntityInfo/entity/attributes/attribute">
    <xsl:text>
\t</xsl:text>
    <xsl:value-of select="@PhysicalName"/>
    <xsl:text>\t</xsl:text><xsl:value-of select="Type"/>
    <xsl:if test="MaxLength">(<xsl:value-of 
                               select="MaxLength"/>)</xsl:if>
    <xsl:text>\t</xsl:text><xsl:value-of 
                               select="RequiredLevel" ></xsl:value-of>
    <xsl:text>\t"</xsl:text>
    <xsl:value-of 
      select="displaynames/displayname[@languagecode='1031']/@description" />
    <xsl:text>"</xsl:text>
    <xsl:if test="Descriptions">
    <xsl:text>\t// </xsl:text>
      <xsl:value-of 
      select="Descriptions/Description[@languagecode='1031']/@description" />
    </xsl:if>
    <xsl:if test="optionset/options">
      <xsl:apply-templates 
        select="optionset/options/option"></xsl:apply-templates>
    </xsl:if>
  </xsl:template>

The Option template displays the option value (for bool and picklist) and its description

  <xsl:template match="optionset/options/option">
    <xsl:text>
\t\t</xsl:text>
    <xsl:value-of select="@value"/>
    <xsl:text>\t"</xsl:text>
    <xsl:value-of 
      select="labels/label[@languagecode='1031']/@description"/>
    <xsl:text>"</xsl:text>
  </xsl:template>

Ignore every other text node in the source XML:

  <xsl:template match="text()">
  </xsl:template>
</xsl:stylesheet>

Feel free to adjust the output according to your needs.

Left-to-Right Tab Order in CRM 2011 Form

Looking for a solution to set the tab order in a CRM 2011 Form to tab horizontally (rather than vertically), I found a couple of solutions (such as here and here).

The solutions, however, were not complete, and required a bit of tweaking:

  • including the tabs for headers inside a form
  • setting the tabIndex higher than the navigational tab indexes

Finally, my function looks like this:

function TabOrderLefttoRight() {
    for (var i = 0; i < crmForm.all.length; i++) {
        var element = crmForm.all[i];
        if (element.tabIndex && element.tabIndex != "0") {
            if (element.className == 'ms-crm-Hidden-NoBehavior') 
                continue;
            if (element.tagName == 'A') {
                if (element.className != 'ms-crm-InlineTabHeaderText') 
                    continue;
            }

            element.tabIndex = 10000 + (i * 10);
        }
    }
}

Simply add a call to TabOrderLeftToRight() in your form’s onLoad event.

Fatal Error 682 when accessing MS CRM 2011

Developing a CRM 2011 application, I unexpectedly received the SqlException

Warning: Fatal error 682 occurred at [date, time]. Note the error and time, and contact your system administrator.

The complete trace entry in the MS CRM log file looks like this:

[2012-06-12 11:40:43.387] Process: w3wp |Organization:00000000-0000-0000-0000-000000000000 |Thread: 15 |Category: Platform.Sql |User: 00000000-0000-0000-0000-000000000000 |Level: Error | DynamicMetadataCacheLoader.LoadMetadataContainerFromDatabase
>Exception while loading Metadata Cache from Database with LoadMasks = All, OData using new connection and new transaction. Exception: System.Data.SqlClient.SqlException (0x80131904): Warnung: Schwerwiegender Fehler 682 um Jun 12 2012 11:40AM. Notieren Sie den Fehler und den Zeitpunkt, und wenden Sie sich an den Systemadministrator.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlDataReader.HasMoreRows()
at System.Data.SqlClient.SqlDataReader.ReadInternal(Boolean setTimeout)
at Microsoft.Crm.CrmDataReader.Read()
at Microsoft.Crm.Metadata.MultiOrgSharableMetadataCacheLoader.LoadDescriptions(Boolean isSystemDataLoaded, CrmDbConnection connection, IDbTransaction transaction, MetadataContainer systemContainer, MetadataContainer aggregateContainer, TableFillPropertiesMultiOrg properties, CounterList counter)
at Microsoft.Crm.Metadata.MultiOrgSharableMetadataCacheLoader.LoadContainerData(Boolean isSystemDataLoaded, Boolean isSystemLanguageDataLoaded, CrmDbConnection connection, IDbTransaction transaction, MetadataContainer systemContainer, MetadataContainer aggregateContainer, List`1 fillProperties, CounterList counter)
at Microsoft.Crm.Metadata.MultiOrgSharableMetadataCacheLoader.BuildMetadataContainerFromDatabase(LoadMasks masks, CrmDbConnection connection, IDbTransaction transaction, Guid organizationId, CounterList counter)
at Microsoft.Crm.Metadata.DynamicMetadataCacheLoader.LoadMetadataContainerFromDatabase(LoadMasks masks, CrmDbConnection connection, CrmTransaction transaction, Guid organizationId, CounterList counter)
[2012-06-12 11:40:43.398] Process: w3wp |Organization:00000000-0000-0000-0000-000000000000 |Thread: 15 |Category: Platform.Sdk |User: 00000000-0000-0000-0000-000000000000 |Level: Error | ServiceModelTraceRedirector.TraceData
><TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Error"><TraceIdentifier>http://msdn.microsoft.com/de-DE/library/System.ServiceModel.Diagnostics.TraceHandledException.aspx</TraceIdentifier><Description>Handling an exception.</Description><AppDomain>/LM/W3SVC/2/ROOT-1-129839675491242152</AppDomain><Exception><ExceptionType>System.Data.SqlClient.SqlException, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType><Message>Warnung: Schwerwiegender Fehler 682 um Jun 12 2012 11:40AM. Notieren Sie den Fehler und den Zeitpunkt, und wenden Sie sich an den Systemadministrator.</Message><StackTrace> at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
> at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
> at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
> at System.Data.SqlClient.SqlDataReader.HasMoreRows()
> at System.Data.SqlClient.SqlDataReader.ReadInternal(Boolean setTimeout)
> at Microsoft.Crm.CrmDataReader.Read()
> at Microsoft.Crm.Metadata.MultiOrgSharableMetadataCacheLoader.LoadDescriptions(Boolean isSystemDataLoaded, CrmDbConnection connection, IDbTransaction transaction, MetadataContainer systemContainer, MetadataContainer aggregateContainer, TableFillPropertiesMultiOrg properties, CounterList counter)
> at Microsoft.Crm.Metadata.MultiOrgSharableMetadataCacheLoader.LoadContainerData(Boolean isSystemDataLoaded, Boolean isSystemLanguageDataLoaded, CrmDbConnection connection, IDbTransaction transaction, MetadataContainer systemContainer, MetadataContainer aggregateContainer, List`1 fillProperties, CounterList counter)
> at Microsoft.Crm.Metadata.MultiOrgSharableMetadataCacheLoader.BuildMetadataContainerFromDatabase(LoadMasks masks, CrmDbConnection connection, IDbTransaction transaction, Guid organizationId, CounterList counter)
> at Microsoft.Crm.Metadata.DynamicMetadataCacheLoader.LoadMetadataContainerFromDatabase(LoadMasks masks, CrmDbConnection connection, CrmTransaction transaction, Guid organizationId, CounterList counter)
> at Microsoft.Crm.Metadata.DynamicMetadataCacheLoader.LoadCacheFromDatabaseInternal(LoadMasks masks, CrmDbConnection connection, CrmTransaction transaction, IOrganizationContext context, CounterList counter)
> at Microsoft.Crm.Metadata.DynamicMetadataCacheLoader.LoadCacheFromDatabase(LoadMasks masks, IOrganizationContext context, CounterList counter)
> at Microsoft.Crm.Metadata.DynamicMetadataCacheFactory.LoadMetadataCache(LoadMethod method, CacheType type, IOrganizationContext context)
> at Microsoft.Crm.Metadata.MetadataCache.LoadCache(IOrganizationContext context, Boolean fileOnlyIfExists)
> at Microsoft.Crm.Metadata.MetadataCache.GetInstance(IOrganizationContext context)
> at Microsoft.Crm.BusinessEntities.BusinessEntityMoniker..ctor(Guid id, String entityName, IOrganizationContext orgContext)
> at Microsoft.Crm.Caching.UserDataCacheLoader.LoadCacheData(Guid key, ExecutionContext context)
> at Microsoft.Crm.Caching.ObjectModelCacheLoader`2.LoadCacheData(TKey key, IOrganizationContext context)
> at Microsoft.Crm.Caching.CrmMultiOrgCache`2.CreateEntry(TKey key, IOrganizationContext context)
> at Microsoft.Crm.Caching.CrmMultiOrgCache`2.LookupEntry(TKey key, IOrganizationContext context)
> at Microsoft.Crm.Authentication.UserManagementFactory.UpdateAccessTime(Guid userId, UserAuthenticationAccessType accessType, DateTime accessTime, IOrganizationContext context)
> at Microsoft.Crm.Authentication.UserManagementFactory.UpdateAccessTime(Guid userId, Guid organizationId, UserAuthenticationAccessType accessType, DateTime accessTime)
> at Microsoft.Crm.Authentication.WindowsIdentityAuthorizationManager.Authenticate(OperationContext operationContext)
> at Microsoft.Crm.Authentication.WindowsIdentityAuthorizationManager.CheckAccessCore(OperationContext operationContext)
> at System.ServiceModel.Dispatcher.AuthorizationBehavior.Authorize(MessageRpc&amp;amp; rpc)
> at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc&amp;amp; rpc)
> at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)</StackTrace><ExceptionString>System.Data.SqlClient.SqlException (0x80131904): Warnung: Schwerwiegender Fehler 682 um Jun 12 2012 11:40AM. Notieren Sie den Fehler und den Zeitpunkt, und wenden Sie sich an den Systemadministrator.
> at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
> at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
> at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
> at System.Data.SqlClient.SqlDataReader.HasMoreRows()
> at System.Data.SqlClient.SqlDataReader.ReadInternal(Boolean setTimeout)
> at Microsoft.Crm.CrmDataReader.Read()
> at Microsoft.Crm.Metadata.MultiOrgSharableMetadataCacheLoader.LoadDescriptions(Boolean isSystemDataLoaded, CrmDbConnection connection, IDbTransaction transaction, MetadataContainer systemContainer, MetadataContainer aggregateContainer, TableFillPropertiesMultiOrg properties, CounterList counter)
> at Microsoft.Crm.Metadata.MultiOrgSharableMetadataCacheLoader.LoadContainerData(Boolean isSystemDataLoaded, Boolean isSystemLanguageDataLoaded, CrmDbConnection connection, IDbTransaction transaction, MetadataContainer systemContainer, MetadataContainer aggregateContainer, List`1 fillProperties, CounterList counter)
> at Microsoft.Crm.Metadata.MultiOrgSharableMetadataCacheLoader.BuildMetadataContainerFromDatabase(LoadMasks masks, CrmDbConnection connection, IDbTransaction transaction, Guid organizationId, CounterList counter)
> at Microsoft.Crm.Metadata.DynamicMetadataCacheLoader.LoadMetadataContainerFromDatabase(LoadMasks masks, CrmDbConnection connection, CrmTransaction transaction, Guid organizationId, CounterList counter)
> at Microsoft.Crm.Metadata.DynamicMetadataCacheLoader.LoadCacheFromDatabaseInternal(LoadMasks masks, CrmDbConnection connection, CrmTransaction transaction, IOrganizationContext context, CounterList counter)
> at Microsoft.Crm.Metadata.DynamicMetadataCacheLoader.LoadCacheFromDatabase(LoadMasks masks, IOrganizationContext context, CounterList counter)
> at Microsoft.Crm.Metadata.DynamicMetadataCacheFactory.LoadMetadataCache(LoadMethod method, CacheType type, IOrganizationContext context)
> at Microsoft.Crm.Metadata.MetadataCache.LoadCache(IOrganizationContext context, Boolean fileOnlyIfExists)
> at Microsoft.Crm.Metadata.MetadataCache.GetInstance(IOrganizationContext context)
> at Microsoft.Crm.BusinessEntities.BusinessEntityMoniker..ctor(Guid id, String entityName, IOrganizationContext orgContext)
> at Microsoft.Crm.Caching.UserDataCacheLoader.LoadCacheData(Guid key, ExecutionContext context)
> at Microsoft.Crm.Caching.ObjectModelCacheLoader`2.LoadCacheData(TKey key, IOrganizationContext context)
> at Microsoft.Crm.Caching.CrmMultiOrgCache`2.CreateEntry(TKey key, IOrganizationContext context)
> at Microsoft.Crm.Caching.CrmMultiOrgCache`2.LookupEntry(TKey key, IOrganizationContext context)
> at Microsoft.Crm.Authentication.UserManagementFactory.UpdateAccessTime(Guid userId, UserAuthenticationAccessType accessType, DateTime accessTime, IOrganizationContext context)
> at Microsoft.Crm.Authentication.UserManagementFactory.UpdateAccessTime(Guid userId, Guid organizationId, UserAuthenticationAccessType accessType, DateTime accessTime)
> at Microsoft.Crm.Authentication.WindowsIdentityAuthorizationManager.Authenticate(OperationContext operationContext)
> at Microsoft.Crm.Authentication.WindowsIdentityAuthorizationManager.CheckAccessCore(OperationContext operationContext)
> at System.ServiceModel.Dispatcher.AuthorizationBehavior.Authorize(MessageRpc&amp;amp; rpc)
> at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc&amp;amp; rpc)
> at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)</ExceptionString><DataItems><Data><Key>HelpLink.ProdName</Key><Value>Microsoft SQL Server</Value></Data><Data><Key>HelpLink.EvtSrc</Key><Value>MSSQLServer</Value></Data><Data><Key>HelpLink.EvtID</Key><Value>21</Value></Data><Data><Key>HelpLink.BaseHelpUrl</Key><Value>http://go.microsoft.com/fwlink</Value></Data><Data><Key>HelpLink.LinkId</Key><Value>20476</Value></Data></DataItems></Exception></TraceRecord>

I originally thought that something was wrong with the restored CRM database, when I found a similar bug report. The comments clearly indicated that there was a problem with (another) CRM database, rather than CRM itself.

Go to the Services control panel and stop the services accessing the MSCRM databases

  • Microsoft CRM Async Service (MSCRMAsyncService)
  • Microsoft Dynamics CRM Unzip Service (MSCRMUnzipService)
  • MS CRM Sandbox Service (MSCRMSandboxService)

and the corresponding IIS webs or the IIS service in IIS Manager.

In SSMS, execute the statement

EXEC sp_who

to find out whether there are open connections to the database causing the error.

Next, run

DBCC CHECKDB
WITH NO_INFOMSGS

to check the database for errors and consistency. Some of the results (message codes) I got were (original in German, English versions as found on the internet):

Meldung 8925, Ebene 16, Status 2, Zeile 1
Tabellenfehler: Objektquerverknüpfung: Seite (1:366305), Slot 0, in Objekt-ID 229575856, Index-ID 1, Partitions-ID 72057609057206272, Zuordnungseinheits-ID 72057594044350464 (LOB data-Typ) verweist auf Seite (1:366560), Slot 0, in Objekt-ID 229575856, Index-ID 1, Partitions-ID 2377900618270900224, Zuordnungseinheits-ID 72057594044350464 (LOB data-Typ).

Msg 8925, Level 16, State 1, Line 1
Table error: Cross object linkage: Page (1:5193082), slot 89, in object ID 1575168857, index ID 6, refers to page (1:5193939), slot 0, in object ID 1575168857, index ID 1.

Meldung 8928, Ebene 16, Status 1, Zeile 1
Objekt-ID 162815642, Index-ID 1, Partitions-ID 72057609051242496, Zuordnungseinheits-ID 72057594068992000 (LOB data-Typ): Seite (1:599672) konnte nicht verarbeitet werden. Einzelheiten finden Sie in anderen Fehlermeldungen.

Msg 8928, Level 16, State 1, Line 2 Object ID 645577338, index ID 0: Page (1:168576) could not be processed. See other errors for details.

Meldung 8929, Ebene 16, Status 1, Zeile 1
Objekt-ID 162815642, Index-ID 1, Partitions-ID 72057609051242496, Zuordnungseinheits-ID 72057609471328256 (In-row data-Typ): Es wurden Fehler in Daten außerhalb von Zeilen gefunden mit der ID 1288110080, im Besitz von data, Datensatz identifiziert durch RID = (1:1036135:0).

Msg 8929, Level 16, State 1, Line 1
Object ID 2: Errors found in text ID 852426752 owned by data record identified by RID = (1:110:19) id = 1925581898 and indid = 2.

Meldung 8939, Ebene 16, Status 98, Zeile 1
Tabellenfehler: Objekt-ID 162815642, Index-ID 1, Partitions-ID 72057609051242496, Zuordnungseinheits-ID 72057594068992000 (LOB data-Typ), Seite (1:599672). Fehler bei Test (IS_OFF (BUF_IOERR, pBUF->bstat)). Die Werte sind 12716041 und -4.

Msg 8939, Level 16, State 1, Line 2 Table error: Object ID 1797637899, index ID 0, page (1:168576). Test (m_headerVersion == HEADER_7_0) failed. Values are 0 and 1.

Meldung 8961, Ebene 16, Status 1, Zeile 1
Tabellenfehler: Objekt-ID 229575856, Index-ID 1, Partitions-ID 72057609057206272, Zuordnungseinheits-ID 72057594044350464 (Unknown-Typ). Der Datenknoten außerhalb von Zeilen auf Seite (1:366501), Slot 0, Text-ID 35185262002176 stimmt nicht mit seinem Verweis von Seite (1:366305), Slot 0 überein.

Msg 8961, Level 16, State 1, Line 1
Table error: Object ID 434100587, index ID 1, partition ID 72057594146521088, alloc unit ID 71804568277286912 (type LOB data). The off-row data node at page (1:2487), slot 2, text ID 341442560 does not match its reference from page (1:2487), slot 0.

Meldung 8965, Ebene 16, Status 1, Zeile 1
Tabellenfehler: Objekt-ID 162815642, Index-ID 1, Partitions-ID 72057609051242496, Zuordnungseinheits-ID 72057594068992000 (LOB data-Typ). Auf den Datenknoten außerhalb von Zeilen auf Seite (1:599672), Slot 0, Text-ID 1288110080 wird von Seite (1:1036135), Slot 0 verwiesen, er wurde jedoch im Scan nicht betrachtet.

Msg 8965, Level 16, State 1, Line 1
Table error: Object ID 2. The text, ntext, or image node at page (1:438), slot 3, text ID 852426752 is referenced by page (1:71), slot 4, but was not seen in the scan.

DBCC finished with the message

Von CHECKDB wurden 0 Zuordnungsfehler und 10 Konsistenzfehler in der My_MSCRM-Datenbank gefunden.
repair_allow_data_loss ist die minimale Reparaturstufe für die Fehler, die mit DBCC CHECKDB (My_MSCRM) gefunden wurden.

CHECKDB found 0 allocation errors and 29 consistency errors in database ‘My_MSCRM’.
repair_allow_data_loss is the minimum repair level for the errors found by DBCC CHECKDB (My_MSCRM).

This final message tells you to run DBCC with the REPAIR_ALLOW_DATA_LOSS option. This option can only be applied if the database runs in single-user mode:

ALTER DATABASE MY_MSCRM 
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DBCC CHECKDB ('MY_MSCRM', REPAIR_ALLOW_DATA_LOSS)
WITH NO_INFOMSGS;

If this command still lists unrecoverable errors, use the REPAIR_REBUILD option:

DBCC CHECKDB ('MY_MSCRM', REPAIR_REBUILD)
WITH NO_INFOMSGS

After completion, reset the database to multi-user mode again:

ALTER DATABASE MY_MSCRM SET MULTI_USER 
WITH ROLLBACK IMMEDIATE

Start the CRM services again, as well as the IIS web sites.

Working around CRM2011 Error Message “Unable to load the plugin type”

Working on a CRM 2011 solution during a migration from CRM 4, I tried to upload the solution and watched the green progress bar reach about 80%, when the install routine decided that it’s time to rollback, and decreased the progress bar again.

The upload log showed the error message “Unable to load the plugin type”, as reported by others here and here and everywhere.

Since all my work of the day would have been lost, I tried to track down the cause of the error:

Using the Plugin Registration Tool, I saw that all the steps of one specific plugin assembly where disabled, since the assembly had not yet been upgraded to CRM 2011.

I could remove the plugin assembly from the solution I was working on, but I could not uninstall it. I figured out I had to modify the solution.zip I wanted to upload.

First, I removed the PluginAssemblies directory from the zip file.

Next, I edited solution.xml, and removed all entries starting with

      <RootComponent type="91"

as they refer to the plugin assemblies which are now removed from the zip.

Finally, I edited customization.xml, and removed the sections

  <SolutionPluginAssemblies>
    <PluginAssembly.... >
      ...
    </PluginAssembly>
  </SolutionPluginAssemblies>
  <SdkMessageProcessingSteps>
    <SdkMessageProcessingStep ...>
      ...
    </SdkMessageProcessingStep>
  </SdkMessageProcessingSteps>

After moving the two files to the solution.zip, the upload performed successfully.

Simplifying CRM Queries using Object Initializers

When I joined my first CRM project more than half a year ago, I found the way to create queries quite tedious:

  • create the condition expressions
  • create the filter expression
  • optionally create the link expressions
  • create the query expression

leading to endless pages of query construction code, as illustrated in this MSDN sample and elsewhere.

Fortunately I found that you can streamline CRM4 queries by using object initializers introduced in VS 2008. No more proliferation of temporary variables storing query parts

public contact GetContactUniqueByName(Guid organisationId, 
  string firstname, string lastname, string email)
{
  QueryExpression query = new QueryExpression {
    EntityName = EntityName.contact.ToString(),
    ColumnSet = new ColumnSet {
      Attributes = contactColumns
    },
    Criteria = new FilterExpression {
      FilterOperator = LogicalOperator.And,
      Conditions = new ConditionExpression[] { 
        new ConditionExpression {
          AttributeName = "parentcustomerid",
          Operator = ConditionOperator.Equal,
          Values = new object[] { organisationId }
        },
        new ConditionExpression {
          AttributeName = "firstname",
          Operator = ConditionOperator.Equal,
          Values = new string[] { firstname },
        },
        new ConditionExpression {
          AttributeName = "lastname",
          Operator = ConditionOperator.Equal,
          Values = new string[] { lastname }
        },
        new ConditionExpression {
          AttributeName = "emailaddress1",
          Operator = ConditionOperator.Equal,
          Values = new string[] { email }
        }
      }
    }
  };
  ...
}

Trying to apply the same strategy to CRM2011, I found that the Conditions property is no longer assignable, which means you cannot pass the ConditionExpression array as object initializer.

A little helper method works around this new restriction

public FilterExpression CreateFilter(LogicalOperator op, 
    params ConditionExpression[] conditions)
{
  var filter = new FilterExpression(op);
  filter.Conditions.AddRange(conditions);
  return filter;
}

allowing the above query to be re-written (using the shortened ConditionExpression constructor) for CRM2011 as:

public contact GetContactUniqueByName(Guid organisationId, 
    string firstname, string lastname, string email)
{
  QueryExpression query = new QueryExpression
  {
    EntityName = Contact.EntityLogicalName,
    ColumnSet = new ColumnSet(true),
    Criteria = CreateFilter(LogicalOperator.And,
        new ConditionExpression(
          "parentcustomerid", ConditionOperator.Equal, organisationId),
        new ConditionExpression(
          "firstname", ConditionOperator.Equal, firstname),
        new ConditionExpression(
          "lastname", ConditionOperator.Equal, lastname),
        new ConditionExpression(
          "emailaddress1", ConditionOperator.Equal, email)
	)
  };

  ...
}

Retrieving the Attribute Names of CRM 2011 Entities

In CRM 2011, the CrmSvcUtil tool generates C# proxy classes for your CRM 2011 entities.

These classes are derived from the Entity class of the CRM SDK, the entities’ attributes are marked up with an AttributeLogicalName attribute which stores the attribute’s name in the database.

For queries, we need to list these attribute names, and it would be nice if the compiler used the data in the attributes to implement a type-safe compile-safe retrieval of attribute names by providing the properties of the C# classes.

Based on my code to retrieve property names from LinQ expressions, I wrote an extension method to retrieve the attribute names using the AttributeLogicalName attribute:

public static class EntityExtensions
{
  public static string GetAttributeName<T, P>(this T record, 
                          Expression<Func<T, P>> attribute) 
    where T : Entity
  {
    MemberExpression memberExpression = 
      (MemberExpression)attribute.Body;
    var member = memberExpression.Member;

    var nameAttributes = member.GetCustomAttributes(
      typeof(AttributeLogicalNameAttribute), true);
    if (nameAttributes != null && nameAttributes.Length > 0)
    {
      var logicalName = 
        (nameAttributes[0] as AttributeLogicalNameAttribute)
        .LogicalName;
      return logicalName;
    }
    throw new ArgumentException(string.Format(
      "{0} is not a CRM property of entity {1}", 
      member.Name, typeof(T).Name));
  }
}

We can then write a sample program like this

static void Main(string[] args)
{
  var account = new Account();
  Console.WriteLine(account.GetPropertyName(a => a.AccountId));
  Console.WriteLine(account.GetAttributeName(a => a.AccountId));
  Console.WriteLine(account.GetPropertyName(a => a.AccountNumber));
  Console.WriteLine(account.GetAttributeName(a => a.AccountNumber));
}

resulting in this output

AccountId
accountid
AccountNumber
accountnumber

Using this extension method, we can write a type-safe ColumnSet helper to provide the parameters for the built-in ColumnSet constructor:

public class ColumnSet<T> where T: Entity
{
  List<string> columns = new List<string>();

  public ColumnSet<T> Add<P>(Expression<Func<T, P>> expression)
  {
    columns.Add(EntityExtensions.GetAttributeName(default(T), 
                                                  expression));
    return this;
  }

  public string[] Columns { get { return columns.ToArray(); } }
}

The .Add() method implements a Fluent interface as it returns the object, allowing for concatenation of .Add() calls:

var columns = new ColumnSet<Account>()
  .Add(a => a.AccountId)
  .Add(a => a.AccountNumber);

The result can be passed to the ColumnSet constructor:

var columnset = new ColumnSet(columns.Columns);

CrmService.Retrieve throws SoapException if Record does not exist

If you call the CrmService.Retrieve method with a non-existing id parameter, a SoapException is raised instead of simply returning null.

This exception can be handled by specifically catching SoapException and analyzing the exception’s Detail property (source):

BusinessEntity result = null;
try
{
  result = crmService.Retrieve(entityName, entityId, new AllColumns());
}
catch (SoapException sex)
{
  XmlNode n = sex.Detail.SelectSingleNode("//error//code");
  if (n.InnerText == "0x80040217")
    result = null;
  else throw;            
}

The Detail property is really an XmlNode which can be queried in XPath using the SelectSingleNode method. Its string representation in case of failed data retrieval looks like this:

<detail>
  <error>
    <code>0x80040217</code>   
    <description>contact With Id = [some guid] Does Not Exist</description>
    <type>Platform</type> 
  </error>
</detail>

I tried to verify the “magic” error code 80040217 and found this list of Dynamics CRM 4 Error Codes on MSDN, which states that the error codes can also be found in the SDK file SDK\Server\Helpers\CS\CrmHelpers\ErrorCodes.cs.

Retrieving the Current User in Dynamics CRM 4 ISV ASP.Net

If you are manipulating user-related data in a Dynamics CRM ASP.Net application (i.e. under the ISV sub-directory), you need to retrieve the currently logged-on user, for example to set the ownerid value for a newly created record belonging to the current user..

Initial research led me to the WhoAmIRequest class which I excepted to return the current systemuser (as many others).

However, what the request really returns is the systemuser associated with the Dynamics login credentials, as the vaguely formulated introduction on MSDN suggests:

Contains the data needed toretrieve the system user ID for the currently logged on user or the user under whose context the code is running

To retrieve the current user, the CRM-related HttpModules are required in the web.config:

<httpModules>
  <add name ="MapOrg" 
    type="Microsoft.Crm.MapOrgEngine, Microsoft.Crm,
      Version=4.0.0.0, Culture=neutral, 
      PublicKeyToken=31bf3856ad364e35"/>
  <add name ="CrmAuthentication" 
    type="Microsoft.Crm.Authentication.AuthenticationEngine,
      Microsoft.Crm, Version=4.0.0.0, Culture=neutral, 
      PublicKeyToken=31bf3856ad364e35"/>
</httpModules>

These modules are typically included in the CRM’s web.config, so there should be no need to include them in ISV as well. You must not remove them using the <remove name=”CrmAuthentication” /> tag!

You can then go ahead and create a CrmAuthenticationToken from the web request using the static method CrmAuthenticationToken.ExtractCrmAuthenticationToken():

Guid GetCallingUserId()
{
  if (Request.IsLocal)
  {
    return new Microsoft.Crm.Sdk.CrmAuthenticationToken()
      .CallerId;
  }
  else
  {
    string orgname = Request["orgname"];
    return Microsoft.Crm.Sdk.CrmAuthenticationToken
      .ExtractCrmAuthenticationToken(Context, orgname)
      .CallerId;
  }
}

How do you pass the organization name to the .aspx?

In the ISV.config, set the MenuItem‘s PassParams attribute to “1” if the menu item defines a Url property.

If the menu item defines a JavaScript attribute to execute JavaScript upon selection, include the ORG_UNIQUE_NAME variable in the JS code:

<MenuItem JavaScript="window.open(
  '../../../ISV/path/to/mypage.aspx?orgname='
  + ORG_UNIQUE_NAME + '&amp;other-params');">

(thanks to SO for this hint)

Type-safe Queries in Dynamics CRM 4

Avoiding string literals and writing type-safe code as supported by the compiler are among my favorite code-writing philosophies.

So when I started to work on a Dynamics CRM project, which by default uses strings for table and column names (fortunately, table names can be accessed by the EntityNames enumeration), I wished there was a Linq-like way to access the CRM Service.

Therefore, the first code I wrote was a method implementing a type-safe query encapsulating RetrieveMultiple():

public static List<T> RetrieveMultiple<T>(this CrmService service) 
  where T : BusinessEntity
{
  List<T> result = new List<T>();

  QueryExpression query = new QueryExpression();
  query.EntityName = typeof(T).Name;      
  query.ColumnSet = new AllColumns();

  BusinessEntityCollection retrieved = service.RetrieveMultiple(query);
  foreach (T item in retrieved.BusinessEntities)
    result.Add(item);

  return result;
}

While not very powerful and flexible, this method provides both type-safe compilablity and avoidance of string literals.

After some research, I found that there are already libraries supporting Linq queries for Dynamics CRM:

These libraries come with an overhead though:

  • LINQtoCRM requires all queries to be created through its CrmQueryProvider class.
  • xRM provides Linq queries through its XrmDataContext class which requires a connection string in the .config file and generating DTOs using CrmSvcUtil.

Both options may be challenging to include in an already existing project.