05.09.2011, 12:46 | #1 |
Участник
|
Взаимодействие с Excel через .NET (семейство классов SysExcel)
Во вложении - проект с семейством классов SysExcel, переписанным на работу с Excel через .NET (Microsoft.Office.Interop.Excel). Модификация возникла как решение проблемы взаимодействия ядра AX 2009 с офисными приложениями через COM, что подробно описано в темах Ошибка времени выполнения в ComExcelDocument_RU.findRange() и Ошибка чтения файлов XLS под Windows 7. Через .NET экспорты/импорты стали работать намного стабильнее и субъективно чуть быстрее.
Чтобы проект скомпилировался, дополнительно нужны модификации, выложенные в темах Вспомогательные классы проверки условий и утверждений Класс для преобразования значений между различными значимыми типами Также для работы модификации, разумеется, нужны установленные сборки, в которых реализованы классы и enum'ы из пространства имен Microsoft.Office.Interop.Excel. Проверялось всё на Ms Office 2010 и ядре AX 2009 SP1 RU6/RU7. Внимание! Работу с Excel вашего или чужого кода через .NET нужно тщательно тестировать, к примеру, у меня не заработало объединение ячеек, используемое в \Classes\SysDataExcelCOM\addLookup, которое на ура отрабатывает через COM. Включается работа сеймейства SysExcel через .NET в методе SysExcel::mustInteropViaNET(). Я лично не решился пока на тотальное включение взаимодействия с Excel через .NET и сделал метод, который анализирует стек вызовов и включает работу через .NET в зависимости от того, видит ли он там "разрешенные" классы или нет. За счет этого для включения/выключения работы через .NET не пришлось как-то специально править код, работающий с классами SysExcel, хотя другого рода правки потребовались: пришлось вычистить те места, где работа шла напрямую с COM-объектами, обертками для которых служит семейство SysExcel, в результате пришлось добавить несколько новых классов-оберток. Для удобства импорта из Excel в SysExcelRange был добавлен метод valueTyped(), возвращающий значение ячейки, приведенное к требуемому базовому аксаптовскому типу. Вся логика преобразования типов реализована в отдельном классе. Также в SysExcelApplication был добавлен метод findRange(), позволяющий найти SysExcelRange из произвольного листа, - по аналогии с одноименным методом из ComExcelDocument_RU. По непонятным для меня причинам всё семейство SysExcel объявлено как выполняющееся сугубо на клиенте. В моем случае это ограничение снято, чтобы можно было формировать файлы Excel в пакетных заданиях, выполняющихся на сервере, однако, в выложенном проекте эти изменения не отражены. |
|
|
За это сообщение автора поблагодарили: mazzy (5), eugene egorov (2), raz (15), ZVV (5), naPmu3aH (1), Logger (18), lev (10), ziva (2), AvrDen (1), Bega (10), perestoronin (1), MikeR (10), IvanS (1), Stainless (1), S.Kuskov (15), suicest (1), Kabardian (4), Dark Light (2), Melkiades (1). |
05.09.2011, 13:51 | #2 |
Участник
|
Кстати, а кто нибудь решил проблему с некорректной работой объединения ячеек через .Net ?
(Метод Union из Excel в \Classes\SysDataExcelCOM\ ) |
|
05.09.2011, 15:47 | #3 |
Участник
|
Как раз очень интересует серверный режим работы, какие были подводные камни при переводе? (кроме изменения режима выполнения SysExcel на CalledFrom и простановке необходимых Permissions)
|
|
05.09.2011, 18:01 | #4 |
Участник
|
Подводный камень только один: решить, когда создавать SysExcelApplication нужно все-таки именно на клиенте, поскольку один и тот же серверный код может работать как в пакете, так и интерактивно, и в последнем случае, очевидно, Excel должен запускаться на клиентской машине. Я это решил так:
X++: public static client server SysExcelApplication_NET construct(ClassRunMode _contructOnTier = ClassRunMode::Client) { SysExcelApplication_NET ret; ; switch (_contructOnTier) { case ClassRunMode::Called : case ClassRunMode::ClientOrServer : ret = new SysExcelApplication_NET(); break; case ClassRunMode::Client : ret = classFactory::makeObjectOnClient( classnum(SysExcelApplication_NET) ); break; case ClassRunMode::Server : ret = classFactory::makeObjectOnServer( classnum(SysExcelApplication_NET) ); break; default : throw error( Error::unsupportedEnumValue( _contructOnTier ) ); } return ret; } public static client server SysExcelApplication construct() { SysExcelApplication ret; COM excel; real excelVersion; ; if (SysExcel::mustInteropViaNET()) { return SysExcelApplication_NET::construct( clientKind() == ClientType::Server ? ClassRunMode::Server : ClassRunMode::Client ); } // ... |
|
08.10.2012, 09:41 | #5 |
Участник
|
Цитата:
Сообщение от gl00mie
Подводный камень только один: решить, когда создавать SysExcelApplication нужно все-таки именно на клиенте, поскольку один и тот же серверный код может работать как в пакете, так и интерактивно, и в последнем случае, очевидно, Excel должен запускаться на клиентской машине. Я это решил так:
X++: public static client server SysExcelApplication_NET construct(ClassRunMode _contructOnTier = ClassRunMode::Client) { SysExcelApplication_NET ret; ; switch (_contructOnTier) { case ClassRunMode::Called : case ClassRunMode::ClientOrServer : ret = new SysExcelApplication_NET(); break; case ClassRunMode::Client : ret = classFactory::makeObjectOnClient( classnum(SysExcelApplication_NET) ); break; case ClassRunMode::Server : ret = classFactory::makeObjectOnServer( classnum(SysExcelApplication_NET) ); break; default : throw error( Error::unsupportedEnumValue( _contructOnTier ) ); } return ret; } public static client server SysExcelApplication construct() { SysExcelApplication ret; COM excel; real excelVersion; ; if (SysExcel::mustInteropViaNET()) { return SysExcelApplication_NET::construct( clientKind() == ClientType::Server ? ClassRunMode::Server : ClassRunMode::Client ); } // ... Права на шаблон у учетки АОСа FullControl. |
|
08.10.2012, 11:17 | #6 |
Участник
|
Нашел решение в интернете.
Решение: для Windows 2008 Server x64 создать папку C:\Windows\SysWOW64\config\systemprofile\Desktop для Windows 2008 Server x86 создать папку C:\Windows\System32\config\ systemprofile\Desktop Я даже не знаю, что сказать на этот счет, у меня просто нет слов. Microsoft *** Как я рад, что нашел это решение на 5-й странице поиска, с ужасом думаю что мог ее пролистать |
|
|
За это сообщение автора поблагодарили: Logger (3), Denicce (2), propeller (1). |
08.10.2012, 20:38 | #7 |
Участник
|
Можно было ограничиться поиском по форуму: Сохранение документа Excel
|
|
22.10.2012, 08:37 | #8 |
Участник
|
Не работает Microsoft.Office.Interop.Excel._Application.Union()
Цитата:
Внимание! Работу с Excel вашего или чужого кода через .NET нужно тщательно тестировать, к примеру, у меня не заработало объединение ячеек, используемое в \Classes\SysDataExcelCOM\addLookup, которое на ура отрабатывает через COM
|
|
06.03.2013, 11:36 | #9 |
Участник
|
Почему-то при работе через .NET на клиенте, процесс Excel остается в памяти, даже когда пользователь закрывает Excel. Этот процесс исчезает только с выходом из DAX. Так что если пользователь весь день запускает отчеты, то у него может закончится память, да и некрасиво это.
Сравнивал с аналогичным вызовом через COM, там процесс сразу исчезает после закрытия Excel. Есть идеи, как это побороть? Вот код, который воспроизводит проблему (специально вытащил вызовы .NET из классов gl00mie для наглядности): X++: Microsoft.Office.Interop.Excel.ApplicationClass application_net = new Microsoft.Office.Interop.Excel.ApplicationClass(); Microsoft.Office.Interop.Excel.Workbooks workbooks_net; ; workbooks_net = application_net.get_Workbooks(); workbooks_net.Add(""); application_net.set_Visible(true); |
|
06.03.2013, 11:55 | #10 |
Участник
|
Может попробовать принудительно запустить .Net сборку мусора ?
Обычно помогает. |
|
06.03.2013, 11:57 | #11 |
Участник
|
|
|
06.03.2013, 12:31 | #12 |
Участник
|
|
|
|
За это сообщение автора поблагодарили: Bega (5). |
06.03.2013, 12:36 | #13 |
Участник
|
Действительно, вот такой вариант работает.
X++: Microsoft.Office.Interop.Excel.ApplicationClass application_net = new Microsoft.Office.Interop.Excel.ApplicationClass(); Microsoft.Office.Interop.Excel.Workbooks workbooks_net; ; workbooks_net = application_net.get_Workbooks(); workbooks_net.Add(""); application_net.set_Visible(true); workbooks_net = null; application_net = null; System.GC::Collect(); |
|
|
За это сообщение автора поблагодарили: Ace of Database (2). |
06.03.2013, 13:08 | #14 |
Участник
|
Bega
Попробуйте X++: application_net.finalize |
|
06.03.2013, 14:16 | #15 |
Участник
|
|
|
06.03.2013, 15:21 | #16 |
Участник
|
Bega
В данном случае не вчитался и подумал о ком объекте. А раз тут дотНет, то есть еще вот такой момент, в C# после Collect вызывал метод WaitForPendingFinalizers. |
|
06.03.2013, 15:23 | #17 |
Участник
|
|
|
07.03.2013, 16:30 | #18 |
Участник
|
Есть какой-то способ вставки в range массива, как это было возможно с COM?
Вот пример для COM, он работает: X++: ComExcelDocument_RU comExcel; ArrayExtend_RU arrayStr = new ArrayExtend_RU(Types::String, 10); ; comExcel = new ComExcelDocument_RU(); comExcel.newFile("", false); arrayStr = new ArrayExtend_RU(Types::String, 2); arrayStr.value(1, "AAA"); arrayStr.value(2, "БББ"); comExcel.insertValue("A1:B1", arrayStr); comExcel.visible(true); X++: SysExcelApplication_NET sysExcelApplication; SysExcelWorksheet_NET sysExcelWorksheet; SysExcelRange_NET range; ArrayExtend_RU arrayStr = new ArrayExtend_RU(Types::String, 10); ; sysExcelApplication = SysExcelApplication_NET::construct(); sysExcelApplication.workbooks().add(""); sysExcelWorksheet = sysExcelApplication.worksheets().itemFromNum(1); arrayStr = new ArrayExtend_RU(Types::String, 2); arrayStr.value(1, "AAA"); arrayStr.value(2, "БББ"); range = sysExcelWorksheet.range("A1:B1"); range.value2(arrayStr); sysExcelApplication.visible(true); |
|
07.03.2013, 16:46 | #19 |
Участник
|
Нашел способ через System.Array:
X++: SysExcelApplication_NET sysExcelApplication; SysExcelWorksheet_NET sysExcelWorksheet; SysExcelRange_NET range; System.Array array; System.Type type; ; sysExcelApplication = SysExcelApplication_NET::construct(); sysExcelApplication.workbooks().add(""); sysExcelWorksheet = sysExcelApplication.worksheets().itemFromNum(1); type = System.Type::GetType('System.String'); array = System.Array::CreateInstance(type, 2); array.SetValue(CLRInterop::getObjectForAnyType("AAA"), 0); array.SetValue(CLRInterop::getObjectForAnyType("БББ"), 1); range = sysExcelWorksheet.range("A1:B1"); range.value2(array); sysExcelApplication.visible(true); |
|
|
За это сообщение автора поблагодарили: BOAL (2), Pustik (2), Logger (5), IvanS (1). |
09.10.2013, 07:31 | #20 |
Участник
|
При открытии шаблона через workBooks_net у меня возникает сообщение, что файл был восстановлен. С чем это может быть связано?
Выяснил, что такое поведение связано с шаблонами 2007 офиса. С 2003 нормально работает Последний раз редактировалось IvanS; 09.10.2013 в 08:24. |
|
Теги |
.net, ax2009, excel, законченный пример, полезное |
|
|