Here is the exception I got when retrieving a list of records from CRM:
A proxy type with the name account has been defined by another assembly. Current type: Plugins.Sdk.Account, Plugins, Version=1.0.1.0, Culture=neutral, PublicKeyToken=0b5679ac811dc738, Existing type: CrmSdk.Account, PluginRegisterTool, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nullIn this case I was retrieving a list of PluginAssembly records, but the same exception happens when I try to retrieve ANY kind of entity. Also note that "Plugins.Sdk.Account, Plugins" is a type in the plug-in assembly that I'm trying to register, and "CrmSdk.Account, PluginRegistrationTool" is the type in the application that's actually doing the registration.
Parameter name: account
A look at the stack trace also gives us a hint about what's really happening here:
Server stack trace:
at Microsoft.Xrm.Sdk.AppDomainBasedKnownProxyTypesProvider.AddTypeMapping(Assembly assembly, Type type, String proxyName)
at Microsoft.Xrm.Sdk.KnownProxyTypesProvider.LoadKnownTypes(Assembly assembly)
at Microsoft.Xrm.Sdk.KnownProxyTypesProvider.RegisterAssembly(Assembly assembly)
at Microsoft.Xrm.Sdk.KnownProxyTypesProvider.InitializeLoadedAssemblies()
at Microsoft.Xrm.Sdk.AppDomainBasedKnownProxyTypesProvider..ctor()
So what's really happening here?
In order to register a plug-in I need to know certain information about the plug-in assembly, such as the version number, culture, and public key token. To get this info out of the assembly I use reflection:
PluginAssembly assembly = new PluginAssembly();Assembly dll = Assembly.LoadFile(path);
string[] assemblyProp = dll.GetName().FullName.Split(",= ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);assembly.Culture = assemblyProp[ASSEMBLY_PROP_CULTURE];assembly.Name = assemblyProp[ASSEMBLY_PROP_NAME];assembly.PublicKeyToken = assemblyProp[ASSEMBLY_PROP_PUBLICKEYTOKEN];assembly.Version = assemblyProp[ASSEMBLY_PROP_VERSION];
However, the Microsoft.Xrm.Sdk DLL is also doing its own reflection magic!
As soon as plug-in assembly is loaded into memory by Assembly.LoadFile(), the Microsoft.Xrm.Sdk.KnownProxyTypesProvider will scan and cache all of the entity types in the assembly into its own internal collection. The SDK assembly actually uses this information for converting the generic "Entity" records it gets back from CRM into strongly typed classes (e.g., account, contact, systemuser).
Unfortunately the KnownProxyTypesProvider can only hold one "account" entity, and it doesn't differentiate between two "account" classes from different assemblies (I'm not really sure if this should be considered a bug or not). And so I get an exception because I've filled up the KnownProxyTypesProvider with all the types from the plug-in assembly and not the entity types from the currently running application.
To get around this issue you need to "prime" the KnownProxyTypesProvider with the correct entity types. All you have to do is make sure you retrieve a record from CRM before you load the plug-in assembly:
This is a completely useless query, but the simple act of retrieving a single SystemUser prevents the KnownProxyTypesProvider from going loading the wrong types from the plug-in assembly.QueryExpression query = new QueryExpression(SystemUser.EntityLogicalName);query.PageInfo = new PagingInfo() { Count = 1, PageNumber = 1 };_serviceProxy.RetrieveMultiple(query);
-Erik
Thank you so much Erik for this awesome post! this saved me a ton of time on researching this issue.
ReplyDeleteRegards,
Shloma Baum
FieldOne Systems
http://www.fieldone.com
Hi,
ReplyDeleteThe problem rises from the need of the OrganizationService to guess the correct class from the entity name when deserializing the response from the server to actual objects. This is WCF problem, really.
So, on WCF, You can specify the Assembly to create objects from and eliminate the problem like this:
public IOrganizationService getServer(ServerConnection.Configuration serverConfig)
{
OrganizationServiceProxy _serviceProxy = new OrganizationServiceProxy(
serverConfig.OrganizationUri,
serverConfig.HomeRealmUri,
serverConfig.Credentials,
serverConfig.DeviceCredentials);
// This statement is required to enable early-bound type support.
_serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior( Assembly.GetExecutingAssembly()));
IOrganizationService _service = (IOrganizationService)_serviceProxy;
return _service;
}