2012-01-20 10 views
8

Uso l'implementazione di Erik Pool di ICodeWriterFilterService e la funzione GenerateOption di Manny Grewal come modello per filtrare le entità indesiderate nel file generato da CRMSvcUtil. Mentre Erik consiglia di restituire true per il metodo GenerateOptionSet per generare enums per i set di opzioni, così facendo viene duplicato uno qualsiasi dei set di opzioni globali utilizzati da una determinata entità (come indicato in one of the comments in quel post).Come posso fare in modo che CRMSvcUtil.exe generi insiemi di opzioni precoci e non duplicati privi di errori?

Per risolvere questo problema, controllo se il set di opzioni è già stato generato e, in tal caso, restituisco l'opzione predefinita (presumibilmente false per la maggior parte dei casi) come indicato di seguito.

//list of generated option sets, instantiated in the constructor 
private List<string> GeneratedOptionSets; 

public bool GenerateOptionSet 
    (OptionSetMetadataBase optionSetMetadata, IServiceProvider services) 
{ 
    if (!GeneratedOptionSets.Contains(optionSetMetadata.Name)) 
    { 
     GeneratedOptionSets.Add(optionSetMetadata.Name); 
     return true; 
    } 

    return _defaultService.GenerateOptionSet(optionSetMetadata, services); 
} 

Ma quando incorpora il file generato nei miei progetti di CRM, l'errore di compilazione

Cannot convert type 'Microsoft.Xrm.Sdk.OptionSetValue' to 'int' 

viene sempre generata da ogni riga di codice che assomiglia

this.SetAttributeValue 
    ("address1_shippingmethodcode", new Microsoft.Xrm.Sdk.OptionSetValue(((int)(value)))); 

.

Per aggirare il problema, io uso un progetto separato in cui a filtrare le entità di cui ho bisogno, eseguire CRMSvcUtil con gli argomenti Erik suggerisce, sostituire la parte problematica del codice (int)(value) (dove value è un OptionSetValue) con value.Value dopo che il file è generato, quindi salvare nuovamente il file e tutti i problemi scompaiono.

La mia domanda è questa: ho bisogno di fare qualcosa di diverso che risolverà questo errore di compilazione con il file predefinito CRMSvcUtil generato senza fare qualcosa di hackerato come alterando quel file generato?

+0

JFYI. È stata posta una domanda simile [qui] (http://stackoverflow.com/questions/8443981/crm-2011-generated-code-using-icodewriterfilterservice-fails-to-build). – paramosh

+0

@paramosh: Sì, ho appena visto quella domanda ora per qualche motivo. Penso che l'avrei postata comunque perché non ero chiaro delle conseguenze relative alla personalizzazione del metodo 'GenerateOptionSet', ma comunque, si spera che la risposta di Guarav darà una risposta. –

risposta

2

Scommetto che la risposta di Guarav è la vera strada da percorrere, ma in assenza di documenti che circonda CRMSvcUtil, sono costretto a usare la mia soluzione. (Uso un progetto separato dove filtro le entità di cui ho bisogno, esegui CRMSvcUtil con gli argomenti suggeriti da Erik, sostituisci la parte problematica del codice (int)(value) (dove value è un OptionSetValue) con value.Value dopo che il file è stato generato, quindi salva nuovamente il file .)

Non è una soluzione perfetta, ma ha lavorato sui pochi campioni con cui ho lavorato finora.

+0

Poiché questo è contrassegnato come soluzione, è importante sottolineare che questo non è ciò che il codice generato sta tentando di implementare http://stackoverflow.com/a/21771992/1465593 –

4

È possibile utilizzare l'interfaccia ICustomizeCodeDomService per riscrivere il metodo SetAttributeValue per OptionSets. Snippet di seguito:

namespace The.NameSpace 
{ 
using System; 
using System.CodeDom; 
using System.Diagnostics; 
using System.Linq; 

using Microsoft.Crm.Services.Utility; 
using Microsoft.Xrm.Sdk.Metadata; 

/// <summary> 
/// The customize code dom service. 
/// </summary> 
public sealed class CustomizeCodeDomService : ICustomizeCodeDomService 
{ 
    #region Constants and Fields 

    /// <summary> 
    /// The metadata. 
    /// </summary> 
    private IOrganizationMetadata metadata; 

    #endregion 

    #region Properties 



    #endregion 

    #region Public Methods 

    /// <summary> 
    /// The customize code dom. 
    /// </summary> 
    /// <param name="codeCompileUnit"> 
    /// The code compile unit. 
    /// </param> 
    /// <param name="services"> 
    /// The services. 
    /// </param> 
    public void CustomizeCodeDom(CodeCompileUnit codeCompileUnit, IServiceProvider services) 
    { 
     // Locate the namespace to use 
     CodeNamespace codeNamespace = codeCompileUnit.Namespaces[0]; 

     var metadataProviderService = (IMetadataProviderService)services.GetService(typeof(IMetadataProviderService)); 
     var filterService = (ICodeWriterFilterService)services.GetService(typeof(ICodeWriterFilterService)); 

     this.metadata = metadataProviderService.LoadMetadata(); 

     foreach (EntityMetadata entityMetadata in this.metadata.Entities) 
     { 
      if (filterService.GenerateEntity(entityMetadata, services)) 
      { 
       CodeTypeDeclaration entityClass = 
        codeNamespace.Types.Cast<CodeTypeDeclaration>().First(codeType => codeType.Name.ToUpper() == entityMetadata.SchemaName.ToUpper()); 

       UpdateEnumSetter(entityClass, entityMetadata); 

      } 
     } 
    } 

    #endregion 

    #region Private Methods 
    private static void UpdateEnumSetter(
    CodeTypeDeclaration entityClass, EntityMetadata entity) 
    { 
     foreach (var attributeMetadata in entity.Attributes.Where(attributeMetadata => String.IsNullOrWhiteSpace(attributeMetadata.AttributeOf))) 
     { 
      //Match the respective field Name. 
      AttributeMetadata metadata1 = attributeMetadata; 
      foreach (
       CodeTypeMember codeMembers in 
        entityClass.Members.Cast<CodeTypeMember>().Where(
         codeMembers => codeMembers.Name == metadata1.SchemaName)) 
      { 
       var codeProperty = (CodeMemberProperty)codeMembers; 

       if (codeProperty.HasSet) 
       { 
        if (attributeMetadata.AttributeType != null && attributeMetadata.AttributeType.Value == AttributeTypeCode.Picklist) 
        { 
         ((CodeConditionStatement)codeProperty.SetStatements[1]).FalseStatements[0] = 
          new CodeSnippetStatement 
          { 
           Value = 
            String.Format(
             "this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", 
             attributeMetadata.LogicalName) 
          }; 
         Debug.WriteLine(String.Format("{0}.{1}", entity.LogicalName, attributeMetadata.LogicalName)); 
        } 
       } 
      } 
     } 
    } 
    #endregion 

} 

}

+0

Darò un'occhiata alla documentazione, ma gli esempi sembrano scarseggiare. Avete a portata di mano un codice di esempio che implementa questa interfaccia? –

+0

Grazie per il campione. Durante l'esecuzione di crmsvcutil contro questo codice, tuttavia, l'eccezione 'Impossibile lanciare l'oggetto di tipo 'System.CodeDom.Co deExpressionStatement' per digitare 'System.CodeDom.CodeConditionStatement'. Viene generato, presumibilmente dalla riga di codice' ((CodeConditionStatement) codeProperty.SetStatements [1]). FalseStatements [0] = ... '. Qualche idea sul cast corretto qui? –

+0

+1. Per riferimento, ho dovuto combinare questa risposta con la risposta dell'utente1589970 per farlo funzionare. – Polshgiant

4

Alcune modifiche al metodo UpdateEnumSetter:

private static void UpdateEnumSetter(CodeTypeDeclaration entityClass, EntityMetadata entity) 
    { 
     foreach (var attributeMetadata in entity.Attributes.Where(attributeMetadata => String.IsNullOrWhiteSpace(attributeMetadata.AttributeOf))) 
     { 
      AttributeMetadata currentMetadata = attributeMetadata; 
      foreach (CodeTypeMember codeMembers in entityClass.Members.Cast<CodeTypeMember>().Where(codeMembers => codeMembers.Name == currentMetadata.SchemaName)) 
      { 
       CodeMemberProperty codeProperty = (CodeMemberProperty)codeMembers; 
       if (codeProperty.HasSet) 
       { 
        if (attributeMetadata.AttributeType != null && (attributeMetadata.AttributeType.Value == AttributeTypeCode.Picklist || attributeMetadata.AttributeType.Value == AttributeTypeCode.Status)) 
        { 
         if (codeProperty.SetStatements[1].GetType() == typeof(CodeConditionStatement)) 
         { 
          ((CodeConditionStatement)codeProperty.SetStatements[1]).FalseStatements[0] = new CodeSnippetStatement 
          { 
           Value = String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName) 
          }; 
         } 
         else 
         { 
          codeProperty.SetStatements[1] = new CodeSnippetStatement(String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName)); 
         } 
        } 
       } 
      } 
     } 
    } 
+0

+1 grazie. Combinare questo con la risposta di Guarav ha funzionato per me. – Polshgiant

0

Sembra che ci fosse un bug nel crmsrvcutil che da allora è stata fissata. Il mio codice per le proprietà OptionSet ora è simile al seguente:

[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("prioritycode")] 
public Microsoft.Xrm.Sdk.OptionSetValue PriorityCode 
{ 
    get 
    { 
     return this.GetAttributeValue<Microsoft.Xrm.Sdk.OptionSetValue>("prioritycode"); 
    } 
    set 
    { 
     this.OnPropertyChanging("PriorityCode"); 
     this.SetAttributeValue("prioritycode", value); 
     this.OnPropertyChanged("PriorityCode"); 
    } 
} 

E non ottengo nessun errore durante l'impostazione di OptionSetValue ...

+2

Sto usando l'ultima versione crmsvcutil (5.0.9690.3218) e il codice generato per le mie proprietà OptionSet è ancora in uscita nel modulo problematico dal post originale. :/ – Polshgiant

+0

@Polshgiant prova questo: https://xrmearlyboundgenerator.codeplex.com/ – Daryl

2

Si scopre che questo errore ha a che fare con il codice che tenta di creare set di opzioni che assomigliano al codice sottostante quando i tipi sono disponibili per l'uso. Nota l'unica differenza è il tipo corretto scelto per il tipo di ritorno e il cast.

E dovrebbe essere possibile aggiornare la roba codegen per risolvere questo bug, ma potrebbe essere meglio per ottenere Microsoft per risolvere il dannato correttamente, vorrei fare una soluzione, ma io in realtà non hanno il tempo di implementare in questo momento perché abbiamo una soluzione per lo più funzionante, anche se dobbiamo fare i conti con la classe optionsetvalue.

public enum entityname_optionsetname 
{ 
    Value = 200 
} 

[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("myprefix_fieldname")] 
public entityname_optionsetname myprefix_FieldName 
{ 
    get 
    { 
     Microsoft.Xrm.Sdk.OptionSetValue optionSet = this.GetAttributeValue<Microsoft.Xrm.Sdk.OptionSetValue>("myprefix_fieldname"); 
     if ((optionSet != null)) 
     { 
      return ((entityname_optionsetname)(System.Enum.ToObject(typeof(Microsoft.Xrm.Sdk.OptionSetValue), optionSet.Value))); 
     } 
     else 
     { 
      return null; 
     } 
    } 
    set 
    { 
     this.OnPropertyChanging("myprefix_FieldName"); 
     if ((value == null)) 
     { 
      this.SetAttributeValue("myprefix_fieldname", null); 
     } 
     else 
     { 
      this.SetAttributeValue("myprefix_fieldname", new Microsoft.Xrm.Sdk.OptionSetValue(((int)(value)))); 
     } 
     this.OnPropertyChanged("myprefix_FieldName"); 
    } 
} 
1

Sono finalmente in grado di generare una classe associata in anticipo con un set di entità filtrate e un set di opzioni prive di errori. Ho trovato la maggior parte della mia risposta attraverso questo thread, quindi grazie ragazzi. Il problema però è difficile compilare tutti i vari suggerimenti in qualcosa che in realtà ... compila. Quindi ho pensato di pubblicare la mia soluzione finale a beneficio degli altri, ecco cosa ha funzionato per me.

ho usato Erik piscina, Manny Grewal di, e la soluzione di Peter Majeed per emettere solo le enumerazioni distinti con valori corretti, poi combinato che, con la soluzione di Gaurav Dalal (aggiornato da JFK007 di correggere l'errore espressi) di riscrivere la SetAttributeValue che ha causato l'errore (int)(value). E come bonus aggiuntivo, ho usato la stessa soluzione per filtrare i set di opzioni distinte per filtrare anche i valori di set di opzioni distinti (che era un problema nella mia org).

Il risultato è una libreria di classi che contiene CodeWriterFilter e CustomizeCodeDomService, il file batch cmd per eseguire CrmSvcUtil.exe, e il filter.xml per filtrare le entità.

Nella libreria di classi aggiungere riferimenti a CrmSvcUtil.exe, Microsoft.Xrm.Sdk, e quindi System.Runtime.Serialization Compilare la DLL e copiarlo nella stessa cartella come il vostro CrmSvcUtil.exe. Utilizzare il comando che ho incluso per fare riferimento al nuovo assembly e creare il file di classe associato all'inizio.

CodeWriterFilter:

using System; 
using System.Collections.Generic; 
using System.Xml.Linq; 
using Microsoft.Crm.Services.Utility; 
using Microsoft.Xrm.Sdk.Metadata; 
using System.Text.RegularExpressions; 
using Microsoft.Xrm.Sdk; 

namespace SvcUtilFilter 
{ 
    /// <summary> 
    /// CodeWriterFilter for CrmSvcUtil that reads list of entities from an xml file to 
    /// determine whether or not the entity class should be generated. 
    /// </summary> 
    public class CodeWriterFilter : ICodeWriterFilterService 
    { 
     //list of entity names to generate classes for. 
     private HashSet<string> _validEntities = new HashSet<string>(); 

     //reference to the default service. 
     private ICodeWriterFilterService _defaultService = null; 

     //list of generated option sets, instantiated in the constructor 
     private List<string> GeneratedOptionSets; 

     //list of generated options, instantiated in the constructor 
     private List<string> GeneratedOptions; 

     /// <summary> 
     /// constructor 
     /// </summary> 
     /// <param name="defaultService">default implementation</param> 
     public CodeWriterFilter(ICodeWriterFilterService defaultService) 
     { 
      this._defaultService = defaultService; 
      this.GeneratedOptionSets = new List<string>(); 
      this.GeneratedOptions = new List<string>(); 
      LoadFilterData(); 
     } 

     /// <summary> 
     /// loads the entity filter data from the filter.xml file 
     /// </summary> 
     private void LoadFilterData() 
     { 
      XElement xml = XElement.Load("filter.xml"); 
      XElement entitiesElement = xml.Element("entities"); 
      foreach (XElement entityElement in entitiesElement.Elements("entity")) { 
       _validEntities.Add(entityElement.Value.ToLowerInvariant()); 
      } 
     } 

     /// <summary> 
     /// /Use filter entity list to determine if the entity class should be generated. 
     /// </summary> 
     public bool GenerateEntity(EntityMetadata entityMetadata, IServiceProvider services) 
     { 
      return (_validEntities.Contains(entityMetadata.LogicalName.ToLowerInvariant())); 
     } 

     //All other methods just use default implementation: 

     public bool GenerateAttribute(AttributeMetadata attributeMetadata, IServiceProvider services) 
     { 
      return _defaultService.GenerateAttribute(attributeMetadata, services); 
     } 

     public bool GenerateOption(OptionMetadata optionMetadata, IServiceProvider services) 
     { 
      //return _defaultService.GenerateOption(optionMetadata, services); 
      string label = optionMetadata.Label.UserLocalizedLabel.Label; 

      //remove spaces and special characters 
      label = Regex.Replace(label, @"[^a-zA-Z0-9]", string.Empty); 
      if (label.Length > 0 && !char.IsLetter(label, 0)) { 
       label = "Number_" + label; 
      } 
      else if (label.Length == 0) { 
       label = "empty"; 
      } 

      if (!GeneratedOptions.Exists(l=>l.Equals(label))) { 
       GeneratedOptions.Add(label); 
       optionMetadata.Label = new Label(label, 1033); 
       return _defaultService.GenerateOption(optionMetadata, services); 
      } 
      else { return false; } 
     } 

     public bool GenerateOptionSet(OptionSetMetadataBase optionSetMetadata, IServiceProvider services) 
     { 
      //return _defaultService.GenerateOptionSet(optionSetMetadata, services); 
      if (!GeneratedOptionSets.Contains(optionSetMetadata.Name)) { 
       GeneratedOptionSets.Add(optionSetMetadata.Name); 
       return true; 
      } 

      return _defaultService.GenerateOptionSet(optionSetMetadata, services); 
     } 

     public bool GenerateRelationship(RelationshipMetadataBase relationshipMetadata, EntityMetadata otherEntityMetadata, IServiceProvider services) 
     { 
      return _defaultService.GenerateRelationship(relationshipMetadata, otherEntityMetadata, services); 
     } 

     public bool GenerateServiceContext(IServiceProvider services) 
     { 
      return _defaultService.GenerateServiceContext(services); 
     } 
    } 
} 

CustomizeCodeDomService:

using System; 
using System.CodeDom; 
using System.Diagnostics; 
using System.Linq; 

using Microsoft.Crm.Services.Utility; 
using Microsoft.Xrm.Sdk.Metadata; 

namespace SvcUtilFilter 
{ 
    /// <summary> 
    /// The customize code dom service. 
    /// </summary> 
    public sealed class CustomizeCodeDomService : ICustomizeCodeDomService 
    { 
     #region Constants and Fields 

     /// <summary> 
     /// The metadata. 
     /// </summary> 
     private IOrganizationMetadata metadata; 

     #endregion 

     #region Properties 



     #endregion 

     #region Public Methods 

     /// <summary> 
     /// The customize code dom. 
     /// </summary> 
     /// <param name="codeCompileUnit"> 
     /// The code compile unit. 
     /// </param> 
     /// <param name="services"> 
     /// The services. 
     /// </param> 
     public void CustomizeCodeDom(CodeCompileUnit codeCompileUnit, IServiceProvider services) 
     { 
      // Locate the namespace to use 
      CodeNamespace codeNamespace = codeCompileUnit.Namespaces[0]; 

      var metadataProviderService = (IMetadataProviderService)services.GetService(typeof(IMetadataProviderService)); 
      var filterService = (ICodeWriterFilterService)services.GetService(typeof(ICodeWriterFilterService)); 

      this.metadata = metadataProviderService.LoadMetadata(); 

      foreach (EntityMetadata entityMetadata in this.metadata.Entities) { 
       if (filterService.GenerateEntity(entityMetadata, services)) { 
        CodeTypeDeclaration entityClass = 
         codeNamespace.Types.Cast<CodeTypeDeclaration>().First(codeType => codeType.Name.ToUpper() == entityMetadata.SchemaName.ToUpper()); 

        UpdateEnumSetter(entityClass, entityMetadata); 

       } 
      } 
     } 

     #endregion 

     #region Private Methods 
     private static void UpdateEnumSetter(CodeTypeDeclaration entityClass, EntityMetadata entity) 
     { 
      foreach (var attributeMetadata in entity.Attributes.Where(attributeMetadata => String.IsNullOrWhiteSpace(attributeMetadata.AttributeOf))) { 
       AttributeMetadata currentMetadata = attributeMetadata; 
       foreach (CodeTypeMember codeMembers in entityClass.Members.Cast<CodeTypeMember>().Where(codeMembers => codeMembers.Name == currentMetadata.SchemaName)) { 
        CodeMemberProperty codeProperty = (CodeMemberProperty)codeMembers; 
        if (codeProperty.HasSet) { 
         if (attributeMetadata.AttributeType != null && (attributeMetadata.AttributeType.Value == AttributeTypeCode.Picklist || attributeMetadata.AttributeType.Value == AttributeTypeCode.Status)) { 
          if (codeProperty.SetStatements[1].GetType() == typeof(CodeConditionStatement)) { 
           ((CodeConditionStatement)codeProperty.SetStatements[1]).FalseStatements[0] = new CodeSnippetStatement { 
            Value = String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName) 
           }; 
          } 
          else { 
           codeProperty.SetStatements[1] = new CodeSnippetStatement(String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName)); 
          } 
         } 
        } 
       } 
      } 
     } 
     #endregion 
    } 
} 

CrmSvcUtil_run.cmd comando Batch File:

@echo off 

set url=https://[organization].api.crm.dynamics.com/XRMServices/2011/Organization.svc 

echo. 
echo Generating CrmSvcUtil Proxy class in output folder 
echo. 

CrmSvcUtil.exe /metadataproviderservice:"MetadataProvider.IfdMetadataProviderService, 
MetadataProvider" 
/url:https://[organization].api.crm.dynamics.com/XRMServices/2011/Organization.svc /out:Xrm.cs 
/namespace:Xrm /serviceContextName:XrmServiceContext /serviceContextPrefix:Xrm 
/u:[username] /p:[password] 
/codewriterfilter:SvcUtilFilter.CodeWriterFilter,SvcUtilFilter 
/codecustomization:SvcUtilFilter.CustomizeCodeDomService,SvcUtilFilter 

echo. 
pause 

filter.xml

<filter> 
    <entities> 
    <entity>systemuser</entity> 
    <entity>team</entity> 
    <entity>role</entity> 
    <entity>businessunit</entity> 
    <entity>account</entity> 
    <entity>product</entity> 
    <entity>transactioncurrency</entity> 
    </entities> 
</filter> 
Problemi correlati