Как сделать так, чтобы CRMSvcUtil.exe генерировал недублированные, безошибочные наборы параметров с ранним связыванием? - PullRequest
9 голосов
/ 20 января 2012

Я использую реализацию Erik Pool 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))));

.

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

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

Ответы [ 6 ]

4 голосов
/ 10 августа 2012

Некоторые изменения в методе 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));
                        }
                    }
                }
            }
        }
    }
4 голосов
/ 23 января 2012

Вы можете использовать интерфейс 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

}

}

3 голосов
/ 18 июня 2012

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

Не идеальное решение, но оно работает над несколькими образцами, с которыми я работал до сих пор.

2 голосов
/ 14 февраля 2014

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

должно быть в состоянии обновить материал 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");
    }
}
1 голос
/ 14 февраля 2015

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

Я использовал решение Эрика Пула, Мэнни Гривала и Питера Маджида для вывода только отдельных перечислений с правильными значениями, а затем объединил его с решением Гаурава Далала (обновлено 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

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>
0 голосов
/ 01 ноября 2012

Похоже, что была ошибка в 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 ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...