The Evolution of Application Architecture

October 22, 2012

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.

Advertisements

Displaying CRM 2011 Entity Customizations using XSLT

September 19, 2012

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

July 25, 2012

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

June 13, 2012

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”

June 6, 2012

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

April 25, 2012

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

April 25, 2012

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);