Не удается сохранить пользовательское свойство из пользовательского компонента в файл DTSX

#c# #ssis #ssis-2012

#c# #ssis #ssis-2012

Вопрос:

Я пытаюсь создать свой первый пользовательский исходный компонент SSIS, но не могу заставить его сохранять пользовательские свойства в файле .dtsx.

Согласно https://docs.microsoft.com/en-us/sql/integration-services/extending-packages-custom-objects/persisting-custom-objects , все, что мне было нужно, это реализовать интерфейс IDTSComponentPersist, но это не работает, LoadFromXML и SaveToXML никогда не вызываются. Ни при сохранении файла, ни при загрузке пакета.

Однако, если ваш объект имеет свойства, которые используют сложные типы данных, или если вы хотите выполнять пользовательскую обработку значений свойств по мере их загрузки и сохранения, вы можете реализовать интерфейс IDTSComponentPersist и его методы LoadFromXML и SaveToXML . В этих методах вы загружаете (или сохраняете) из XML-определения пакета фрагмент XML, который содержит свойства вашего объекта и их текущие значения. Формат этого фрагмента XML не определен; это должен быть только правильно сформированный XML.

Когда я сохраняю пакет SSIS и заглядываю внутрь XML, я получаю следующее: тип данных не определен и значений нет : введите описание изображения здесь

Я что-то пропустил, чтобы установить?

Чтобы упростить задачу, я создал небольшой тестовый проект. Исходный проект пытается сохранить список struct с 2 строками и 1 целым числом, но оба имеют одинаковое «неправильное» поведение, SaveToXML и LoadFromXML никогда не вызываются.

Вот мой код:

 using System;
using System.Collections.Generic;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Pipeline;
using Microsoft.SqlServer.Dts.Runtime;
using System.Xml;
using System.ComponentModel;
using System.Globalization;
using System.Drawing.Design;
using System.Windows.Forms.Design;
using System.Windows.Forms;

namespace TestCase
{
    public class MyConverter : TypeConverter
    {
        public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
        {
            return false;
        }
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType.Name.ToUpper() == "STRING")
                return string.Join(",", ((List<string>)value).ToArray());
            else
                return ((string)value).Split(',');
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (value.GetType().Name.ToUpper() == "STRING")
                return ((string)value).Split(',');
            else
                return string.Join(",", ((List<string>)value).ToArray());
        }
    }

    class FancyStringEditor : UITypeEditor
    {
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.Modal;
        }
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            var svc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
            List<string> vals = (List<string>)value;
            string valsStr = string.Join("rn", vals.ToArray());
            if (svc != null)
            {
                using (var frm = new Form { Text = "Your editor here" })
                using (var txt = new TextBox { Text = valsStr, Dock = DockStyle.Fill, Multiline = true })
                using (var ok = new Button { Text = "OK", Dock = DockStyle.Bottom })
                {
                    frm.Controls.Add(txt);
                    frm.Controls.Add(ok);
                    frm.AcceptButton = ok;
                    ok.DialogResult = DialogResult.OK;
                    if (svc.ShowDialog(frm) == DialogResult.OK)
                    {
                        vals = new List<string>();
                        vals.AddRange(txt.Text.Split(new string[] { "rn" }, StringSplitOptions.RemoveEmptyEntries));
                        value = vals;
                    }
                }
            }
            return value;
        }
    }

    [DtsPipelineComponent(ComponentType = ComponentType.SourceAdapter,
                            CurrentVersion = 0,
                            Description = "Test class for saving",
                            DisplayName = "Test class",
                            IconResource = "None",
                            NoEditor = false,
                            RequiredProductLevel = Microsoft.SqlServer.Dts.Runtime.Wrapper.DTSProductLevel.DTSPL_NONE,
                            SupportsBackPressure = false,
                            UITypeName = "None")]
    public class TestSave : PipelineComponent, IDTSComponentPersist
    {
        private string _NbBadWordProperty = "NbBadWord";
        private string _ListBadWordsProperty = "ListBadWords";
        private List<string> _badWords;

        public IDTSCustomProperty100 _nb;
        public IDTSCustomProperty100 _list;

        public TestSave()
        {
            _badWords = new List<string>();
            _badWords.Add("Word1");
            _badWords.Add("Word2");
            _badWords.Add("Word3");
        }

        public void LoadFromXML(System.Xml.XmlElement node, IDTSInfoEvents infoEvents)
        {
            System.Windows.Forms.MessageBox.Show("Oh god! we're inside LoadFromXML!!");
        }

        public void SaveToXML(System.Xml.XmlDocument doc, IDTSInfoEvents infoEvents)
        {
            System.Windows.Forms.MessageBox.Show("Oh god! we're inside SaveToXML!!");
            XmlElement elementRoot;
            XmlNode propertyNode;

            // Create a new node to persist the object and its properties.  
            elementRoot = doc.CreateElement(String.Empty, "NBElement", String.Empty);
            XmlAttribute nbEl = doc.CreateAttribute("Nbelement");
            nbEl.Value = _badWords.Count.ToString();
            elementRoot.Attributes.Append(nbEl);

            // Save the three properties of the object from variables into XML.  
            foreach (string s in _badWords)
            {
                propertyNode = doc.CreateNode(XmlNodeType.Element, "BadWord", String.Empty);
                propertyNode.InnerText = s;
                elementRoot.AppendChild(propertyNode);
            }

            doc.AppendChild(elementRoot);
        }

        private IDTSCustomProperty100 GetCustomPropertyByName(string name)
        {
            foreach (IDTSCustomProperty100 prop in this.ComponentMetaData.CustomPropertyCollection)
                if (prop.Name.ToUpper() == name)
                    return prop;
            return null;
        }

        public override DTSValidationStatus Validate()
        {
            return DTSValidationStatus.VS_ISVALID;
        }
        public override void ProvideComponentProperties()
        {
            try
            {
                base.ProvideComponentProperties();

                // reset the component
                this.ComponentMetaData.OutputCollection.RemoveAll();
                this.ComponentMetaData.InputCollection.RemoveAll();

                // Add custom properties
                if (GetCustomPropertyByName(_NbBadWordProperty) == null)
                {
                    _nb = this.ComponentMetaData.CustomPropertyCollection.New();

                    _nb.Name = _NbBadWordProperty;
                    _nb.Description = "Number of bad word to filter";
                    _nb.State = DTSPersistState.PS_DEFAULT;
                    _nb.Value = _badWords.Count;

                    _nb.ExpressionType = DTSCustomPropertyExpressionType.CPET_NOTIFY;
                }

                if (GetCustomPropertyByName(_ListBadWordsProperty) == null)
                {
                    IDTSCustomProperty100 _list = this.ComponentMetaData.CustomPropertyCollection.New();
                    _list.Name = _ListBadWordsProperty;
                    _list.Description = "List of bad words";
                    _list.State = DTSPersistState.PS_DEFAULT;

                    _list.TypeConverter = typeof(MyConverter).AssemblyQualifiedName;
                    _list.Value = _badWords;
                    _list.ExpressionType = DTSCustomPropertyExpressionType.CPET_NOTIFY;
                    
                    _list.UITypeEditor = typeof(FancyStringEditor).AssemblyQualifiedName;
                }

                // add input objects
                // none

                // add output objects

                IDTSOutput100 o2 = this.ComponentMetaData.OutputCollection.New();
                o2.Name = "Dummy output";
                o2.IsSorted = false;

                foreach (IDTSCustomProperty100 p in this.ComponentMetaData.CustomPropertyCollection)
                {
                    if (p.Name == _ListBadWordsProperty)
                    {
                        MyConverter c = new MyConverter();
                        List<string> l = (List<string>)p.Value;

                        foreach (string s in l)
                        {
                            IDTSOutputColumn100 col1 = o2.OutputColumnCollection.New();
                            col1.Name = s.Trim();
                            col1.Description = "Bad word";
                            col1.SetDataTypeProperties(Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_WSTR, 500, 0, 0, 0);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show("Critical error: "   ex.Message);
            }
        }

    }
}
 

Update1:

Добавьте TypeConverter и UITypeEditor. Все то же поведение (не сохраняется «сложный» тип данных).

Когда я добавляю исходный компонент в поток данных, я получаю это, все выглядит нормально: введите описание изображения здесь

Я могу редактировать свойство, без проблем введите описание изображения здесь

введите описание изображения здесь

Но когда я сохраняю пакет SSIS и просматриваю xml, свойство по-прежнему не сохраняется и по-прежнему имеет тип данных System.NULL:

введите описание изображения здесь

Спасибо!

Комментарии:

1. Вы определили IDTSCustomProperty100? См . : social.msdn.microsoft.com/Forums/sqlserver/en-US /…

2. в ProvideComponentProperties, где я создаю оба свойства. Я также безуспешно пытаюсь использовать TypeConverter.

3. Вы видели, что в моей ссылке было: [EditorAttribute(typeof(LightShapeEditor), typeof(System. Рисование. Дизайн. UITypeEditor))]

4. Конечно, но UITypeEditor — это когда вы определили редактор. Это не так. И в другом классе (не в TestSave) у меня установлен UIEditor для класса, но он по-прежнему ведет себя так же (ничего не сохраняется / не загружается, функция никогда не вызывается).

5. У вас есть список слов. Использует ли другой случай список?

Ответ №1:

Важное примечание — основываясь на определении Microsoft интерфейса IDTSComponentPersist и примерах кода SaveToXML , найденных в Интернете, я подозреваю, что пользовательское сохранение может быть реализовано только в пользовательских задачах SSIS, менеджерах соединений и счетчиках.

Что ж, пожалуйста, решите для себя, действительно ли вам нужно реализовать сохранение пользовательских объектов. Ваши пользовательские свойства, похоже, хорошо вписываются в стандартные типы данных Int32 и String.
Важное примечание от Microsoft —

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

Итак, вам действительно нужно проделать большую работу, чтобы сохранить все свойства компонента, в том числе LocaleID из вашего образца, на случай, если кому-то потребуется его изменить. Я бы, вероятно, сохранил ListBadWords пользовательское свойство в виде строки без сохранения пользовательского XML.

В вашем коде — наиболее возможной причиной System.Null проблемы с типом данных является то, что ProvideComponentProperties() метод вызывается при инициализации компонента, когда он добавляется в поток данных. Тип данных свойства определяется динамически в данный момент, переменная _badwords еще не инициализирована и является ссылочным типом, поэтому она определяется как Null ссылка. ProvideComponentProperties() Метод используется для определения пользовательских свойств и установки их значений по умолчанию, чтобы решить вашу проблему — set

 if (GetCustomPropertyByName(_ListBadWordsProperty) == null)
   {
   IDTSCustomProperty100 _list = this.ComponentMetaData.CustomPropertyCollection.New();
   _list.Name = _ListBadWordsProperty;
   _list.Description = "List of bad words";
   _list.State = DTSPersistState.PS_DEFAULT;

   _list.TypeConverter = typeof(MyConverter).AssemblyQualifiedName;
   // This is the change
   _list.Value = String.Empty;
   _list.ExpressionType = DTSCustomPropertyExpressionType.CPET_NOTIFY;
                    
   _list.UITypeEditor = typeof(FancyStringEditor).AssemblyQualifiedName;
   }    
 

Если вы настроены на реализацию пользовательского сохранения XML — пожалуйста, изучите пример кода Microsoft и другие источники. Сохранение выполняется немного другим способом. Основное отличие заключается в том, что внутри elementRoot свойств компонента каждое свойство создается под своим собственным XML-узлом. Узел InnerText используется для хранения значения свойства, а необязательные атрибуты узла могут хранить дополнительную информацию.

Комментарии:

1. Спасибо за ваши комментарии. Это тестовый проект, чтобы сосредоточиться на этом поведении. Реальный проект имеет несколько более сложный тип данных (список struct с 2 string 1 int). _badWords инициализируется в конструкторе. Я пытался установить _badWord = new List<string>(); добавив несколько слов перед _list . Значение = _badWords, но у меня все еще есть система. Тип данных Null, и ничего не сохраняется. Но реальная проблема заключается в IDTSComponentPersist. Функция SaveToXML никогда не вызывается , поэтому я не могу выполнить никаких действий (ни LoadFromXML).

2. @MLeblanc Хм, вероятно, вызов MessageBox из SaveToXML не очень хорошая идея. Если вы поместите точку останова в SaveToXML и попытаетесь отладить методы во время разработки — достигнута ли точка останова?

3. Сначала я входил в файл, но ничего не было записано. Я пытаюсь установить точку останова, но поскольку компонент используется в другом проекте (пакет интеграции), точка останова не достигнута, ни система. Диагностика. Отладчик. Break(); . Единственный найденный способ прервать выполнение — это поставить Систему. Диагностика. Отладка. Утверждение (false);. Это работает (прерывает выполнение кода), если поместить в ProvideComponentProperties , но assert не попадает в функцию SaveToXML, функция не вызывается.

4. @MLeblanc, у меня плохое предчувствие, что IDTSComponentPersist интерфейс применим только к задачам SSIS, счетчикам и менеджерам подключений. Просмотрев определение интерфейса MS и образцы кода SaveToXML в Интернете, и не нашли ни одного образца пользовательского компонента DFT с пользовательской сохраняемостью. Вероятно, вам лучше переключиться на обычное сохранение свойств.

5. Да, я пришел к тому же выводу. я думаю, будет лучше сохранить данные в виде строки, закодированной в формате JSON. спасибо за ваше время!