Работа с бизнес-объектами

отредактировано 20:31 Раздел: FastReport .NET
Возник такой вопрос: Как пройти по всем источникам данных переданного в отчет бизнес-объекта?

Пробовал сделать так:
foreach (FastReport.Data.BusinessObjectDataSource bods in report.Dictionary.DataSources)
{
   //...
}
но этот метод проходит только по источникам данных "нулевого" уровня. Вложенные источники данных бизнес-объекта в этот обход не попадают.

Комментарии

  • отредактировано 20:31
    С помощью рекурсии:
        private void EnumDataSources(BusinessObjectDataSource root)
        {
          //делаем что-то с root...
    
          // смотрим вложенные источники
          foreach (Column c in root.Columns)
          {
            if (c is BusinessObjectDataSource)
              EnumDataSources(c as BusinessObjectDataSource);
          }
        }
    

    Использовать так:
    foreach (FastReport.Data.DataSourceBase bods in report.Dictionary.DataSources)
    {
      if (bod is BusinessObjectDataSource)
        EnumDataSources(bod as BusinessObjectDataSource);
    }
    

    Учтите следующий момент: после того как вызвали report.RegisterData(bobject, "my object"), вложенные источники не создаются! Они создадутся после того, как юзер пощелкает в окне "Данные/Выбрать данные для отчета". Если нужно сразу создавать какую-то структуру с указанным уровнем вложенности, используйте вызов report.RegisterData(bobject, "my object", max_nesting_level).
  • отредактировано 20:31
    Спасибо.
  • отредактировано November 2009
    Добрый день,

    возникла такая вот проблема:
    Имеется бизнес-объект:
    [DisplayName("Организационные единицы")]
        public class Organization
        {
            private string  _name;
            [DisplayName("Имя")]
            public string  Name
            {
                get { return _name; }
                set { _name = value; }
            }
    
            private string _type;
            [DisplayName("Категория")]
            public string Type
            {
                get { return _type; }
                set { _type = value; }
            }
    
            private AssociationContainer _orgenizationRespForProcess;
            [DisplayName("Отвечает за")]
            public AssociationContainer OrgenizationRespForProcess
            {
                get { return _orgenizationRespForProcess; }
                set { _orgenizationRespForProcess = value; }
            }
        }
    
        public class AssociationContainer
        {
            public AssociationContainer()
            {
                _processes = new List<Process>();
            }
            private List<Process> _processes;
            [DisplayName("Процессы")]
            public List<Process> Processes
            {
                get { return _processes; }
                set { _processes = value; }
            }
        }
    
        public class Process
        {
            private string _name;
            [DisplayName("Имя")]
            public string Name
            {
                get { return _name; }
                set { _name = value; }
            }
    
            private string _type;
            [DisplayName("Категория")]
            public string Type
            {
                get { return _type; }
                set { _type = value; }
            }
        }
    

    Создается экземпляр:
    private Organization GetOrganization()
            {
                Process process1 = new Process();
                process1.Name = "Процесс 1";
                process1.Type = "Бизнес-процесс";
    
                Process process2 = new Process();
                process2.Name = "Процесс 2";
                process2.Type = "Бизнес-процесс";
    
                AssociationContainer container = new AssociationContainer();
                container.Processes.Add(process1);
                container.Processes.Add(process2);
    
                Organization organization = new Organization();
                organization.Name = "Организационная единица 1";
                organization.Type = "Главная организационнная единица";
                organization.OrgenizationRespForProcess = container;
    
                return organization;
            }
    

    И создается бизнес-объект:
    List<Organization> businessObjects = new List<Organization>();
    businessObjects.Add(GetOrganization());
    

    Далее, имеется экзепляр report объекта Report и в нем необходимо зарегистрировать данные:
    //report - загруженный из файла отчет
    //Если в словаре отчета нет источников данных
    if (report.Dictionary.DataSources.Count == 0)
    {//просто регистрируем данные и активируем все источники данных
      report.RegisterData(businessObjects, "Организационные единицы", 3);
      foreach (FastReport.Data.DataSourceBase bod in report.Dictionary.DataSources)
      {
         bod.Enabled = true;
      }
      //(1*)
     }
    else
    {//В словаре шаблона отчета есть зарегистрированные источники даннных.
    
    //(2*)
    }
    

    (1*): Будет ли достаточно активизировать только корневые источники данных, чтобы в этом случае все вложенные источники данных также стали активными (т.е. чтобы при вызове редактора они сразу бы попали в дерево данных, и пользователю не надо было вручную их добавлять)?

    (2*): Здесь необходимо синхронизировать словарь источника данных. В моем случае в передаваемом бизнес-объекте может измениться как значение DisplayName атрибутов любого свойства или класса, так и сама структура бизнес-объекта.
    С проверкой того, отличается ли передаваемый бизнес-объект от зарегистрированного в отчете я вроде справился:
    ...
    //Создаем временный отчет для получения обновленного словаря отчета
    Report tempReport = new Report();
    //и регистрируем в нем все данные
    tempReport.RegisterData(businessObjects, "Организационные единицы", 3);
    //Флаг, показывающий, что передаваемый бизнес-объект полностью совпадает с зарегистрированным в отчете.
    bool resultOfChecking = true;
    //Список для источников данных из словаря отчета, которых нет в передаваемом бизнес-объекте.
    List<FastReport.Data.DataSourceBase> sourcesToRemove = new List<FastReport.Data.DataSourceBase>();
    //Рекурсивно идем по словарям, главный словарь - словарь сохраненного шаболона отчета
    foreach (FastReport.Data.DataSourceBase currentTestingBod in report.Dictionary.DataSources)
    {
       //Если в словаре временного отчета есть текущий проверяемый бизнес-объект,
       if (tempReport.Dictionary.DataSources.FindByAlias(currentTestingBod.Alias) != null)
       {
          //проверяем вложенные источники данных
          bool resultOfCheckingEmbadded = CheckEmbeddedDataSources(0, currentTestingBod.Columns, 
                                tempReport.Dictionary.DataSources.FindByAlias(currentTestingBod.Alias).Columns);
          if (resultOfChecking)
          {
               resultOfChecking = resultOfCheckingEmbadded;
          }
       }
       else //Иначе, **в словаре временного отчета нет текущего проверяемого бизнес-объекта.**
       {//добавляем этот источник в список источников данных, которых нет в передаваемом бизнес-объекте.
                          
         sourcesToRemove.Add(currentTestingBod);
         resultOfChecking = false;
       }
    }
    
    //!Тут необходимо удалить из словаря отчета все источники данных, которые попали в список sourcesToRemove 
    foreach (FastReport.Data.DataSourceBase sToRemove in sourcesToRemove)
    {
       //Как???
    }
    
    //!Тут необходимо зарегистрировать в отчете все новые(появившиеся) и активировать их
    //Тоже Как???
    
    ...
          
    /// <summary>
    /// Метод проверки/синхронизации вложенных источников данных.
    /// </summary>
    /// <param name="levelOfEmbeddence">Уровень вложенности родительской проверки.</param>
    /// <param name="collectionToCheck">Коллекция, которую надо проверить.</param>
    /// <param name="checkingCollection">Коллекция, с которой надо сверять.</param>
    /// <returns>True - если все объекты полностью прошли проверку, иначе - false.</returns>
    private bool CheckEmbeddedDataSources(int levelOfEmbeddence, FastReport.Data.ColumnCollection collectionToCheck,
                FastReport.Data.ColumnCollection checkingCollection)
    {
      bool resultOfChecking = true;
      int currentLevel = levelOfEmbeddence + 1;
      string spacer = GetSpacer(currentLevel);
      List<FastReport.Data.Column> columnsToRemove = new List<FastReport.Data.Column>();
      //Для каждой колонки в проверяемой коллекции.
      foreach (FastReport.Data.Column currentColumn in collectionToCheck)
      {
        //Если в коллекции для сверки есть такая колонка
        if (checkingCollection.FindByAlias(currentColumn.Alias) != null)
        {
          //Необходимо проверить вложенные источники данных
          bool resultOfCheckingEmbadded = CheckEmbeddedDataSources(currentLevel, currentColumn.Columns,
                            checkingCollection.FindByAlias(currentColumn.Alias).Columns);
          if (resultOfChecking)
          {
              resultOfChecking = resultOfCheckingEmbadded;
          }
        }
        else //иначе **в коллекции для сверки нет такой колонки**
        {
           columnsToRemove.Add(currentColumn);
           resultOfChecking = false;
        }
      }
      //!Тут необходимо удалить из коллекции все источники данных, которые попали в sourcesToRemove список
      foreach (FastReport.Data.Column columnToRemove in columnsToRemove)
      {
         //Как???
      }
    return resultOfChecking;
    }
    

    Т.е. источники данных которые есть в словаре отчета, но нет в передаваемом бизнес-объекте этим методом выявляются. А вот как корректно удалить их из словаря отчета я так и не понял. :)
    И вторая задача: в бизнес-объекте могут появиться новые источники данных, их надо зарегистрировать в отчете и активировать. Получилось сделать, но только частично: для корневых бизнес-объектов вроде работает все нормально, а вот если появился новый источник данных на втором(и более) уровне вложенности бизнес-объекта, после вызова RegisterData(bobject, "Name on new source", level); новый источник не добавляется в коллекцию Columns родителя (для добавления приходится лезть в Данные ->Выбрать данные для отчета… и вручную его активировать).
  • отредактировано 20:31
    Здравствуйте,
    написал:
    (1*): Будет ли достаточно активизировать только корневые источники данных, чтобы в этом случае все вложенные источники данных также стали активными (т.е. чтобы при вызове редактора они сразу бы попали в дерево данных, и пользователю не надо было вручную их добавлять)?

    Да.
    написал:
    (2*): Здесь необходимо синхронизировать словарь источника данных.

    При вызове RegisterData синхронизация осуществляется автоматически. Старые поля удаляются, новые - добавляются. Здесь есть один момент: если добавляется новое поле - источник данных, то оно получает Enabled = false, и добавляется без колонок. Надо зайти в меню "Выбрать данные для отчета" и выбрать этот источник данных и его колонки.

    Желательно избегать таких ситуаций (смена структуры данных после того, как отчет создан). Здесь есть две проблемы:
    - если в объектах "Текст" есть ссылка на старое поле (которое было удалено в результате синхронизации), отчет выдаст ошибку при запуске.
    - если бэнд "Данные" подключен к источнику данных, который был удален в результате синхронизации, то тоже ничего хорошего не произойдет.
  • отредактировано November 2009
    написал:
    Старые поля удаляются, ...
    Хм... а у меня после вызова RegisterData старые поля остаются :) , буду проверять.
    написал:
    Желательно избегать таких ситуаций (смена структуры данных после того, как отчет создан). Здесь есть две проблемы:
    - если в объектах "Текст" есть ссылка на старое поле (которое было удалено в результате синхронизации), отчет выдаст ошибку при запуске.
    - если бэнд "Данные" подключен к источнику данных, который был удален в результате синхронизации, то тоже ничего хорошего не произойдет.

    Чтоб отчет не выпадал с ошибкой, я и использую методы CheckEmbeddedDataSources и тот, что его вызывает. При построении отчета, при неудачной проверке поябляется окно "Бла Бла отчет устарел, невозможно построить отчет, обновите шаблон и т.д.". При вызове дизайнера, появляется окно с именами источников, которые устарели, и пользователь должен ручками исправить привязку данных там где это необходимо.
    написал:
    - если бэнд "Данные" подключен к источнику данных, который был удален в результате синхронизации, то тоже ничего хорошего не произойдет
    Если в дизайнере вызвать просмотр отчета, то выпадают ошибки, а бэнд сам отвязывается от данных, иногда даже сам перепривязывается к нужному источнику. Вообщем творит чудеса. А вот текстовые поля... Да, пользователь будет их собственноручно фиксить, и пока все не исправит отчета не получит :) .
    Избежать таких ситуаций, к сожалению не получится: бизнес-объекты собираются в рантайме, и в немже могут меняться как угодно.
    Оснавная проблема в том, что отчет который храниться в памяти может отличаться от сохраненного в файле. Например, загрузили отчет из файла, отредактировали, но при закрытии дизайнера выбрали "не сохранять". в файле изменения не схранились, а в объекте в памяти - сохранились. Приходится в методе закрытия редактора плясать с бубном:
    public bool CloseControl()
            {
                if (designerCtrl.CloseAll())
                {
                    try
                    {
                                         
                        Designer.Report.Load(Designer.Report.FileName);
                    }
                }
             }
    

    При вызове Designer.Report.Load(Designer.Report.FileName); возникает такая ситуация, что в отчете снова появляется старый ситочник данных и вменсто одного источника данных в отчете их два: новый и старый.
  • отредактировано November 2009
    А, вот еще вопрос:
    написал:
    ... то оно получает Enabled = false, и добавляется без колонок. Надо зайти в меню "Выбрать данные для отчета" и выбрать этот источник данных и его колонки.
    А програмно это никак никак сделать нельзя?
  • отредактировано 20:31
    написал:
    Хм... а у меня после вызова RegisterData старые поля остаются , буду проверять.

    Вот простой пример
    private void button1_Click(object sender, EventArgs e)
            {
                List<Organization> businessObjects = new List<Organization>();
                businessObjects.Add(GetOrganization());
    
                Report report = new Report();
              //  report.Load(Application.StartupPath + Path.DirectorySeparatorChar + "test.frx");
    
                report.RegisterData(businessObjects, "OU", 3);
                
                report.Design();
            }
    
    запускаем, ставим галочки на источнике данных, сохраняем отчет с именем test.frx в папку Application.StartupPath
    раскоменчиваем код и переименовываем регистрируемый объект:
    private void button1_Click(object sender, EventArgs e)
            {
                List<Organization> businessObjects = new List<Organization>();
                businessObjects.Add(GetOrganization());
    
                Report report = new Report();
    //!
               report.Load(Application.StartupPath + Path.DirectorySeparatorChar + "test.frx");
    //!
                report.RegisterData(businessObjects, "OUNew"/*Новое имя для источника данных*/, 3);
    //(1*)           
                report.Design();
            }
    
    В пошаговой отладке в месте (1*) видно что у отчета 2 источника данных:
    report.Dictionary.DataSources[0] != null //старый источник
    report.Dictionary.DataSources[1] != null
    => Старые поля остаются, новые - добавляются.
  • отредактировано 20:31
    написал:
    раскоменчиваем код и переименовываем регистрируемый объект:

    Э нет, так не пойдет :)
    Откуда в данном случае FastReport узнает, что надо обновить старый источник данных? Привязка-то идет по имени, которое указано в RegisterData! Естественно, в этом случае будет два источника - старый, не привязанный к данным, и новый.
  • отредактировано 20:31
    Domoch написал: »
    А програмно это никак никак сделать нельзя?

    Подумаю, как лучше сделать.
  • отредактировано 20:31
    написал:
    Подумаю, как лучше сделать.
    Спасибо.
    написал:
    Откуда в данном случае FastReport узнает, что надо обновить старый источник данных?
    Он и не должен знать, хотелось бы чтоб новый источник данных регистрировался, а старый, для которого небыли переданны данные, удалялся (или хотя бы можно было определить, что данных для объекта нет и удалить его вручную).
    Привязка, как я понимаю, везде идет по именам/алиасам - у бэндов, полей, словаря данных... Просто, например, был в отчете бизнес-объект с вложенным полем "Имя", а потом это поле переименоавль в "Имя42". По хорошему, в этом случае необходимо отчистить словарь шаблона, по новой перерегистрировать данные шаблона отчета и активоровать их. Но если вызвать метод Clear словаря, то отчищается и привязка бэндов во всем отчете - ради 1го изменившегося поля заново заставлять пользователя перепривязывать данные по всему отчету крайне нежелательно. По этому хотелось бы иметь механизм отслеживания - есть ли в источнике данных (DataSourceBase, Column и т.д.) зарегистрированном в словаре данные или нет. Скажем, если данных нет, то удаляем этот источник/поле. Если к источнику есть привязанные в отчете бэнды - отвязываем их. С текстовыми объектами правда тут все-равно ничего не поделать, прейдется пользователю мучаться.

    Собственно конечная задача - сделать так, чтоб при изменениях в структуре бизнес-объекта для восстановки работоспособности отчета необходимо было внести минимум изменений в его шаблоне. Если какой-то источник данных (Бизнес-обект, поле бизнес-объекта, влошенный источник бизнес-объекта) исчез (был переименован или вообще удален), его не должно быть среди источников данных. Если появился новый источник данных - он должен автоматически попасть в дерево данных и все его поля и вложенные источники должны быть активны (т.е. дерево структуры нового источника должно быть полностью развернуто).
  • отредактировано 20:31
    написал:
    Он и не должен знать, хотелось бы чтоб новый источник данных регистрировался, а старый, для которого небыли переданны данные, удалялся (или хотя бы можно было определить, что данных для объекта нет и удалить его вручную).

    Такие моменты надо отслеживать самому. Пустой бизнес-объект можно определить так:
          for (int i = 0; i < report.Dictionary.DataSources.Count; i++)
          {
            DataSourceBase ds = report.Dictionary.DataSources[i];
            if (ds.Reference == null)
            {
              // тут бы надо пройтись по всем объектам отчета и очистить привязки к ds...
              ds.Dispose();
              i--;
            }
          }
    
    написал:
    Если появился новый источник данных - он должен автоматически попасть в дерево данных и все его поля и вложенные источники должны быть активны (т.е. дерево структуры нового источника должно быть полностью развернуто).

    Насчет "полностью развернуто" - это чревато. Дерево может быть не деревом, а графом. Сейчас степень начального разворота контролируется параметром maxNestingLevel при регистрации. Ну а пользователь может развернуть источник как угодно в окне "Выбрать данные...".
  • отредактировано 20:31
    AlexTZ написал: »
    Подумаю, как лучше сделать.

    В текущем билде сделано. Если регистрируется объект, у которого появилось новое поле типа IEnumerable, то его структура раскрывается до указанного maxNestingLevel.
  • отредактировано November 2009
    AlexTZ написал: »
    В текущем билде сделано. Если регистрируется объект, у которого появилось новое поле типа IEnumerable, то его структура раскрывается до указанного maxNestingLevel.
    Спасибо!
    ЗЫ "полностью развернуто" - т.е. с учетом maxNestingLevel.
  • отредактировано November 2009
    Здравствуйте, тут появилась небольшая хотелка: можно ли классу DataSourceCollection добавить метод FindByName, полный аналог FindByAlias, за исключением того, что сравнение идет по имени, а не по алиасу?
  • отредактировано 20:31
    Ок, добавлю.

Оставить комментарий

Многофункциональный текстовый редактор. Чтобы отредактировать стиль параграфа, нажмите TAB, чтобы перейти к меню абзаца. Там вы можете выбрать стиль. По умолчанию не выбран ни один стиль. Когда вы выберете текст, появится встроенное меню форматирования. Нажмите TAB, чтобы войти в него. Некоторые элементы, такие как многофункциональные вставки ссылок, картинок, индикаторов загрузки и сообщений об ошибок могут быть вставлены в редактор. Вы можете перемещаться по ним, используя стрелки внутри редактора и удалять с помощью клавиш delete или backspace.