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

发布于 2024-12-28 00:53:21 字数 2056 浏览 1 评论 0原文

我使用 Erik Pool 的 实现ICodeWriterFilterService 和 Manny Grewal 的 GenerateOption 充当模型,过滤掉 CRMSvcUtil 生成的文件中不需要的实体。虽然 Erik 建议为 GenerateOptionSet 方法返回 true 来生成选项集的 enum,但这样做会重复所使用的任何全局选项集。任何特定实体(如之一该帖子的评论)。

为了解决这个问题,我检查选项集是否已经生成,如果是,我返回默认选项(大多数情况下可能是 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项目中时,编译错误

无法将类型“Microsoft.Xrm.Sdk.OptionSetValue”转换为“int”

每行看起来像 的代码总是会抛出

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

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

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

I use Erik Pool's implementation of ICodeWriterFilterService and Manny Grewal's GenerateOption function as a model to filter out unwanted entities in the file that CRMSvcUtil generates. While Erik recommends returning true for the GenerateOptionSet method to generate enums for option sets, doing so duplicates any of the global option sets that are used by any particular entity (as mentioned in one of the comments on that post).

To address this, I check to see if the option set has been already generated, and if so, I return the default option (presumably false for most cases) as in the below.

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

But when incorporating the generated file in my CRM projects, the compilation error

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

is always thrown by every line of code that looks like

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

.

As a workaround, I use a separate project where I filter the entities I need, run CRMSvcUtil with the arguments Erik suggests, replace the troublesome part of the code (int)(value) (where value is an OptionSetValue) with value.Value after the file is generated, and then resave the file, and all issues go away.

My question is this: do I need to do something differently that will fix this compilation error with the default CRMSvcUtil generated file without doing something so hackish as altering that generated file?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

情深如许 2025-01-04 00:53:21

您可以使用 ICustomizeCodeDomService 接口重写选项集的 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

}

}

You can use the ICustomizeCodeDomService interface to rewrite the SetAttributeValue method for the optionSets. Snippet below:

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

}

}

傲世九天 2025-01-04 00:53:21

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

Some changes to the UpdateEnumSetter method:

    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));
                        }
                    }
                }
            }
        }
    }
单身狗的梦 2025-01-04 00:53:21

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

这不是一个完美的解决方案,但是它一直在研究我研究过的几个样本到目前为止。

I'm betting that Guarav's answer is the real way to go, but in the absence of documentation surrounding CRMSvcUtil, I'm forced to use my workaround. (I use a separate project where I filter the entities I need, run CRMSvcUtil with the arguments Erik suggests, replace the troublesome part of the code(int)(value) (where value is an OptionSetValue) with value.Value after the file is generated, and then resave the file.)

Not a perfect solution, but it's been working on the few samples I've worked with so far.

落墨 2025-01-04 00:53:21

事实证明,此错误与当类型可供使用时尝试创建如下代码所示的选项集的代码有关。请注意,唯一的区别是为返回类型和强制转换选择了正确的类型。

应该可以更新codegen的东西来修复这个错误,但是最好让微软正确地修复这个该死的东西,我会制定一个解决方案,但我真的没有时间现在就实现它,因为即使我们必须处理 optionssetvalue 类,我们也有一个基本有效的解决方案。

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

It turns out that this fault is to do with the code attempting to make optionsets that look like the code below when the types are available for use. Note the only difference is the correct type being chose for the return type and the cast.

It should be possible to update the codegen stuff to fix this bug, but it might be better to get microsoft to fix the damn thing properly, I would make a solution but I don't really have time to implement it right now because we have a mostly working solution even if we have to deal with the optionsetvalue class.

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");
    }
}
望笑 2025-01-04 00:53:21

我终于能够生成带有一组经过过滤的实体和无错误选项集的早期绑定类。我通过这个帖子找到了大部分答案,所以谢谢大家。但问题是很难将所有各种建议编译成实际......编译的东西。所以我想我应该为了其他人的利益发布我的最终解决方案,这对我有用。

我使用 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>

I finally am able to generate early bound class with a filtered set of entities and error free option set. I found the bulk of my answer through this thread, so thanks guys. The problem though is it's difficult to compile all of the various suggestions into something that actually... compiles. So I thought I'd post my final solution for the benefit of others, here's what worked for me.

I used Erik Pool's, Manny Grewal's, and Peter Majeed's solution for outputting only distinct enums with proper values, then combined that with Gaurav Dalal's solution (updated by JFK007 to fix the cast error) to re-write the SetAttributeValue that caused the (int)(value) error. And as an added bonus, I used the same solution for filtering distinct option sets to also filter for distinct option set values (which was an issue in my org).

The result is a class library containing CodeWriterFilter and CustomizeCodeDomService, the cmd batch file to run CrmSvcUtil.exe, and the filter.xml to filter the entities.

In your class library add references to CrmSvcUtil.exe, Microsoft.Xrm.Sdk, and System.Runtime.Serialization then compile the dll and copy it to the same the folder as your CrmSvcUtil.exe. Use the command I've included to reference your new assembly and build the early bound class file.

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 Command 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>
凉墨 2025-01-04 00:53:21

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 时没有错误...

It looks like there was a bug in the crmsrvcutil that has since been fixed. My code for OptionSet properties now looks like this:

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

And I get no error setting the OptionSetValue...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文