Как я могу сделать CRMSvcUtil.exe генерирует незамысловатые, безошибочные ранние привязанные наборы опций?

я использую реализацию пула Эрика ICodeWriterFilterService и Мэнни Гревал по GenerateOption функция в качестве модели для фильтрации нежелательных объектов в файле, который CRMSvcUtil генерирует. В то время как Эрик рекомендует вернуться true на GenerateOptionSet метод генерации enums для наборов опций это дублирует любой из глобальных наборов опций, используемых любым конкретным объектом (как указано в один из комментариев на этот пост).

для адресуйте это, я проверяю, был ли уже создан набор опций, и если да, я возвращаю опцию по умолчанию (предположительно 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))));

.

в качестве обходного пути я использую отдельный проект, где я фильтрую сущности мне нужны, run CRMSvcUtil с аргументами, которые предлагает Эрик, замените хлопотную часть кода (int)(value) (где value это OptionSetValue) С value.Value после создания файла, а затем повторно сохраните файл,и все проблемы уйдут.

мой вопрос таков: мне нужно сделать что-то по-другому, что исправит эту ошибку компиляции с помощью default CRMSvcUtil сгенерированный файл, не делая что-то настолько хакерское, как изменение этого сгенерированного файла?

6 ответов


можно использовать ICustomizeCodeDomService интерфейс для перезаписи SetAttributeValue метод для optionSets. Фрагмент ниже:

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

}

}


некоторые изменения метода 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));
                        }
                    }
                }
            }
        }
    }

Я держу пари, что ответ Гуарава-это реальный путь, но в отсутствие документации, окружающей CRMSvcUtil, Я вынужден использовать обходной путь. (Я использую отдельный проект, где я фильтрую объекты, которые мне нужны, run CRMSvcUtil с аргументами, которые предлагает Эрик, замените хлопотную часть кода(int)(value) (где value - это OptionSetValue) С value.Value после создания файла, а затем повторно сохраните файл.)

не идеальное решение, но оно работало на немногих образцах Я работал с ним до сих пор.


оказывается, что эта ошибка связана с кодом, пытающимся сделать наборы опций, которые выглядят как код ниже, когда типы доступны для использования. Обратите внимание, разница только правильный тип избрал для возвращаемого типа и литой.

Это должны можно обновить материал codegen, чтобы исправить эту ошибку, но, возможно, было бы лучше, чтобы 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");
    }
}

я, наконец, могу генерировать ранний связанный класс с отфильтрованным набором сущностей и безошибочным набором опций. Я нашел основную часть своего ответа через эту тему, так что спасибо, ребята. Проблема в том, что трудно скомпилировать все различные предложения в то, что на самом деле... составляет. Поэтому я решил опубликовать свое окончательное решение на благо других, вот что сработало для меня.

я использовал Эрик пула, Мэнни Гревал, и решение Петра Маджид для вывода только отдельные перечисления с правильными значениями, А затем объединили это с решением Гаурава далала (обновлено JFK007, чтобы исправить ошибку приведения), чтобы переписать SetAttributeValue, вызвавшее (int)(value) ошибка. И в качестве дополнительного бонуса я использовал то же решение для фильтрации различных наборов опций, чтобы также фильтровать значения различных наборов опций (что было проблемой в моей организации).

результатом является библиотека классов, содержащая CodeWriterFilter и CustomizeCodeDomService, командный файл cmd для запуска CrmSvcUtil.exe и filter.xml для фильтрации сущности.

в библиотеке классов добавьте ссылки на CrmSvcUtil.exe, Microsoft.Xrm.Sdk и System.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
.в 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>

похоже, что в 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...