如何使CRMSvcUtil.exe生成无重复,无错误的早期绑定选项集?

时间:2012-01-20 18:37:51

标签: enums dynamics-crm dynamics-crm-2011

我使用Erik Pool的ICodeWriterFilterService实现和Manny Grewal的GenerateOption函数作为模型来过滤掉CRMSvcUtil生成的文件中不需要的实体。虽然Erik建议为true方法返回GenerateOptionSet以为选项集生成enums,但这样做会复制任何特定实体使用的任何全局选项集(如{{{ 3}}在那个帖子上。)

为了解决这个问题,我检查选项集是否已经生成,如果是,我会返回默认选项(大多数情况下可能是false),如下所示。

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

但是当我在CRM项目中合并生成的文件时,编译错误

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

总是被看起来像

的每一行代码抛出
this.SetAttributeValue
    ("address1_shippingmethodcode", new Microsoft.Xrm.Sdk.OptionSetValue(((int)(value))));

作为一种解决方法,我使用一个单独的项目来过滤我需要的实体,使用Erik建议的参数运行CRMSvcUtil,替换代码中的麻烦部分(int)(value)(其中value生成文件后,OptionSetValuevalue.Value,然后重新保存文件,所有问题都会消失。

我的问题是:我是否需要做一些不同的事情来修复这个编译错误,使用默认的CRMSvcUtil生成的文件,而不做像修改生成的文件那样的hackish?

6 个答案:

答案 0 :(得分:4)

您可以使用ICustomizeCodeDomService接口重写optionSets的SetAttributeValue方法。下面的代码段:

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

}

}

答案 1 :(得分:4)

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

答案 2 :(得分:3)

我认为Guarav的答案是真正的方法,但由于没有关于CRMSvcUtil的文档,我被迫使用我的解决方法。 (我使用一个单独的项目来过滤我需要的实体,使用Erik建议的参数运行CRMSvcUtil,替换代码中的麻烦部分(int)(value)(其中value是{{1}在生成文件后使用OptionSetValue,然后重新保存文件。)

不是一个完美的解决方案,但它一直致力于我迄今为止使用过的几个样本。

答案 3 :(得分:2)

事实证明,这个错误与代码试图在类型可用时使代码集看起来像下面的代码有关。请注意,唯一的区别是为返回类型和强制转换选择了正确的类型。

应该可以更新codegen的东西来修复这个bug,但是让microsoft正确修复该死的东西可能会更好,我会做出一个解决方案,但我不是真的现在有时间实现它,因为即使我们必须处理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");
    }
}

答案 4 :(得分:1)

我终于能够使用过滤的实体集和无错误选项集生成早期绑定类。我通过这个帖子找到了我的大部分答案,谢谢你们。但问题是难以将所有各种建议编译成实际......编译的东西。所以我想我会为了别人的利益而发布我的最终解决方案,这里有什么对我有用。

我使用了Erik Pool,Manny Grewal和Peter Majeed的解决方案,仅输出具有适当值的不同枚举,然后将其与Gaurav Dalal的解决方案相结合(由JFK007来修复强制转换错误)以重写导致SetAttributeValue错误的(int)(value)。作为额外的奖励,我使用相同的解决方案来过滤不同的选项集,以过滤不同的选项集值(这是我的组织中的一个问题)。

结果是一个包含CodeWriterFilterCustomizeCodeDomService的类库,要运行CrmSvcUtil.exe的cmd批处理文件,以及用于过滤实体的filter.xml

在您的类库中添加对CrmSvcUtil.exeMicrosoft.Xrm.SdkSystem.Runtime.Serialization的引用,然后编译dll并将其复制到与CrmSvcUtil.exe相同的文件夹中。使用我包含的命令来引用新程序集并构建早期绑定类文件。

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命令批处理文件:

@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>

答案 5 :(得分:0)

看起来crmsrvcutil中有一个错误已被修复。我的OptionSet属性代码现在如下所示:

[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");
    }
}

我没有设置OptionSetValue的错误...