Как я могу сделать 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...