2010-10-01 18 views
29

Desidero sapere qual è il modo più rapido per leggere e scrivere dati da e verso una cartella di lavoro Excel aperta a oggetti C#. Lo sfondo è che voglio sviluppare un'applicazione C# che viene utilizzata da Excel e utilizza i dati contenuti in Excel.Il modo più veloce per interfacciare dati Excel (Ccn) non salvati e oggetti C#

La business logic risiederà nell'applicazione C# ma i dati risiederanno in una cartella di lavoro di Excel. L'utente utilizzerà Excel e farà clic su un pulsante (o eseguirà qualcosa di simile) sulla cartella di lavoro di Excel per avviare l'applicazione C#. L'applicazione C# leggerà quindi i dati dalla cartella di lavoro di Excel, elaborerà i dati e quindi riscriverà i dati nella cartella di lavoro di Excel.
Ci possono essere numerosi blocchi di dati che devono essere letti e riscritti nella cartella di lavoro di Excel, ma di solito sono di dimensioni relativamente ridotte, ad esempio 10 righe e 20 colonne. Occasionalmente potrebbe essere necessario elaborare un ampio elenco di dati, dell'ordine di 50.000 righe e 40 colonne.

So che è relativamente facile dire usando VSTO ma voglio sapere qual è la soluzione più veloce (ma comunque robusta ed elegante) e avere un'idea della velocità. Non mi dispiace se la soluzione consiglia di utilizzare prodotti di terze parti o utilizza C++.

La soluzione ovvia è utilizzare VSTO o interoperabilità ma non so quale sia la prestazione rispetto a VBA che sto attualmente utilizzando per leggere i dati, o se ci sono altre soluzioni.

Questo è stato pubblicato su scambio di esperti dicendo che VSTO è stato drammaticamente più lento di VBA, ma quello era un paio di anni fa e non so se le prestazioni sono migliorate.

http://www.experts-exchange.com/Microsoft/Development/VSTO/Q_23635459.html

Grazie.

risposta

36

Se l'applicazione C# è un'applicazione autonoma, il processo di marshalling tra processi implicherà sempre il superamento di tutte le ottimizzazioni che è possibile eseguire passando da, ad esempio, C# a C++. Segui la tua lingua preferita in questa situazione, che suona come C#.

Se siete disposti a fare un componente aggiuntivo che viene eseguito all'interno di Excel, tuttavia, quindi le operazioni saranno eliminare le richieste cross-process e eseguire circa 50x più veloce.

Se si esegue Excel come componente aggiuntivo, VBA è tra le opzioni più veloci, ma implica ancora COM e quindi le chiamate C++ che utilizzano un componente aggiuntivo XLL sarebbero più veloci. Ma VBA è ancora abbastanza veloce in termini di chiamate al modello a oggetti di Excel. Per quanto riguarda la velocità di calcolo effettiva, tuttavia, VBA viene eseguito come codice pcode, non come codice completamente compilato, e quindi esegue circa 2-3 volte più lentamente del codice nativo. Sembra molto brutto, ma non è perché la stragrande maggioranza dei tempi di esecuzione eseguiti con un tipico componente aggiuntivo o applicazione Excel comporta chiamate al modello a oggetti di Excel, quindi VBA rispetto a un componente aggiuntivo COM completamente compilato, ad esempio nativamente compilato VB 6.0, sarebbe solo circa 5-15% più lento, che non è evidente.

VB 6.0 è un approccio COM compilato e viene eseguito 2-3 volte più veloce di VBA per le chiamate non correlate a Excel, ma VB 6.0 ha circa 12 anni e non verrà eseguito in modalità 64 bit, ad esempio installazione di Office 2010, che può essere installata per eseguire 32 bit o 64 bit. L'utilizzo di Excel a 64 bit al momento è minuscolo, ma crescerà in termini di utilizzo, quindi per questo motivo eviterò VB 6.0.

C#, se in esecuzione come un componente aggiuntivo di Excel eseguirà chiamate al modello di oggetti di Excel alla velocità di VBA ed eseguirà chiamate non di Excel 2-3 volte più veloci di VBA, se in esecuzione non sottoposte a scansione. L'approccio consigliato da Microsoft, tuttavia, è quello di eseguire completamente shimmed, ad esempio, facendo uso del COM Shim Wizard. Con il suo spessorato, Excel è protetto dal tuo codice (se è difettoso) e il tuo codice è completamente protetto da altri componenti aggiuntivi di terze parti che potrebbero potenzialmente causare problemi. Il lato negativo di questo, tuttavia, è che una soluzione spaziata viene eseguita in un AppDomain separato, che richiede il marshaling cross-AppDomain che incorre in una penalità di velocità di esecuzione di circa 40x, il che è molto evidente in molti contesti.

I componenti aggiuntivi che utilizzano Visual Studio Tools per Office (VSTO) vengono caricati automaticamente all'interno di uno shim ed eseguiti all'interno di un AppDomain separato. Non si può evitare questo se si utilizza VSTO. Pertanto, le chiamate al modello a oggetti di Excel comportano anche una riduzione della velocità di esecuzione di circa 40 volte. VSTO è un sistema meraviglioso per la creazione di componenti aggiuntivi di Excel molto ricchi, ma la velocità di esecuzione è la sua debolezza per applicazioni come la tua.

ExcelDna è un progetto open source gratuito che consente di utilizzare il codice C#, che viene quindi convertito per l'utente in un componente aggiuntivo XLL che utilizza codice C++. Cioè, ExcelDna analizza il tuo codice C# e crea il codice C++ richiesto per te. Non l'ho usato da solo, ma ho familiarità con il processo ed è molto impressionante. ExcelDna ottiene ottime recensioni da coloro che lo utilizzano. [Modifica: notare la seguente correzione come da commenti del Governo di seguito: "Ciao Mike - Voglio aggiungere una piccola correzione per chiarire l'implementazione di Excel-Dna: tutta la colla gestita da Excel funziona in runtime dal tuo assieme gestito usando la riflessione - non vi è alcun passo di pre-compilazione o generazione di codice C++ Inoltre, anche se Excel-Dna utilizza .NET, non è necessario alcun interuper COM quando si parla con Excel - come .xll l'interfaccia nativa può essere utilizzata direttamente da. NET (anche se puoi usare COM anche se vuoi). Ciò rende possibili UDF e macro ad alte prestazioni. " - Govert]

Si potrebbe anche voler guardare Add-in Express. Non è gratuito, ma ti permetterebbe di scrivere in C# e sebbene riduca la tua soluzione in un AppDomain separato, credo che la sua velocità di esecuzione sia eccezionale. Se capisco correttamente la sua velocità di esecuzione, allora non sono sicuro di come Add-in Express lo faccia, ma potrebbe avvantaggiarsi di qualcosa chiamato FastPath AppDomain marshalling. Non citare me su nessuna di queste cose, tuttavia, dato che non ho molta familiarità con Add-in Express. Dovresti controllare e fare le tue ricerche. [Modifica: Leggendo la risposta di Charles Williams, sembra che Add-in Express abiliti l'accesso all'API COM e C.E Govert afferma che Excel DNA abilita anche l'accesso alle API C sia COM che Fastrer. Quindi probabilmente vorresti verificarli entrambi e confrontarli con ExcelDna.]

Il mio consiglio sarebbe quello di cercare Add-in Express ed ExcelDna. Entrambi gli approcci ti consentono di codificare utilizzando C#, che ti sembra più familiare.

L'altro problema principale è come si effettuano le chiamate. Ad esempio, Excel è molto veloce quando gestisce un'intera gamma di dati passati e indietro come una matrice. Questo è molto più efficiente del looping delle celle individualmente. Ad esempio, il seguente codice fa uso del metodo di accesso Excel.Range.set_Value per assegnare una matrice 10 x 10 di valori in un intervallo 10 x 10 di celle in un solo colpo:

void AssignArrayToRange() 
{ 
    // Create the array. 
    object[,] myArray = new object[10, 10]; 

    // Initialize the array. 
    for (int i = 0; i < myArray.GetLength(0); i++) 
    { 
     for (int j = 0; j < myArray.GetLength(1); j++) 
     { 
      myArray[i, j] = i + j; 
     } 
    } 

    // Create a Range of the correct size: 
    int rows = myArray.GetLength(0); 
    int columns = myArray.GetLength(1); 
    Excel.Range range = myWorksheet.get_Range("A1", Type.Missing); 
    range = range.get_Resize(rows, columns); 

    // Assign the Array to the Range in one shot: 
    range.set_Value(Type.Missing, myArray); 
} 

Si può fare uso simile del metodo Accessor di Excel.Range.get_Value per leggere un array di valori da un intervallo in un unico passaggio. Fare questo e quindi eseguire il looping dei valori all'interno dell'array è molto più veloce del looping attraverso i valori all'interno delle celle dell'intervallo singolarmente.

+0

Per quanto riguarda la velocità: questa è anche la mia esperienza. VSTO può essere molto veloce/se/lo usi nel modo giusto, cioè scrivendo su interi intervalli contemporaneamente. –

+1

Ciao Francesco, sì, VSTO opera da un AppDomain separato da Excel per la protezione di Excel e il suo. Ma le chiamate cross-AppDomain sono circa 40 volte più lente di una chiamata standard, il che è molto evidente. Ad esempio, una routine che richiederebbe in precedenza 0,1 secondi impiegherebbe ora 4,0 secondi completi per essere eseguita. Quindi sono assolutamente d'accordo, in questo scenario bisogna essere ancora più attenti che mai a spostare tutti i dati in un colpo solo, per ridurre al minimo le chiamate al modello a oggetti di Excel. –

+1

Ciao Mike - Voglio aggiungere una piccola correzione per chiarire l'implementazione di Excel-Dna: tutta la colla gestita da Excel funziona in fase di runtime dall'assembly gestito utilizzando il reflection - non c'è alcun passo di pre-compilazione o generazione di codice C++. Inoltre, anche se Excel-Dna utilizza .NET, non è necessario alcun intervento di interoperabilità COM quando si parla con Excel: come .xll l'interfaccia nativa può essere utilizzata direttamente da .NET (sebbene sia possibile utilizzare anche COM se lo si desidera). Ciò rende possibili UDF e macro ad alte prestazioni. – Govert

0

Ho usato codice VBA (macro) per raccogliere & compattare i dati e ottenere questi dati in una chiamata a C# e viceversa. Questo sarà probabilmente l'approccio più performante.

Utilizzando C#, sarà sempre necessario utilizzare un po 'di marshalling. Usando VSTO o COM Interop, il livello di comunicazione di underlaying (overhead di marshalling) è lo stesso.

In VBA (Visual Basic per applicazione) si lavora direttamente sugli oggetti in Excel. Quindi l'accesso a questi dati sarà sempre più veloce.

Ma .... Una volta ottenuti i dati in C#, la manipolazione di questi dati può essere molto più veloce.

Se si utilizza VB6 o C++, si passa anche a un'interfaccia COM e si eseguirà anche il marshalling di processi incrociati.

Quindi, si sta cercando un metodo per ridurre al minimo le chiamate incrociate e il marshalling.

+0

Grazie, che ha un senso. Il problema è che l'applicazione C# sarà richiesta per caricare dati live mentre è in esecuzione dal foglio di lavoro Excel aperto e non so quali dati saranno necessari al momento dell'avvio del processo. Pertanto VSTO o COM sembrano le uniche opzioni, ma dal tuo post sembra che l'esecuzione sarà molto più lenta di VBA. –

+0

La comunicazione sarà più lenta, ma una volta in C# la gestione dei dati sarà probabilmente molto più veloce. – GvS

3

L'interfaccia più veloce per i dati di Excel è l'API C. Ci sono un certo numero di prodotti là fuori che collegano .NET ad Excel usando questa interfaccia.

2 prodotti Mi piace farlo questo Excel DNA (che è gratuito e open source) e Addin Express (che è un prodotto commerciale e ha sia l'interfaccia C che l'interfaccia COM disponibili).

+0

Grazie. Ho trovato un altro riferimento a questo qui: http://www.wilmott.com/messageview.cfm?catid=10&threadid=70379 tuttavia non sono sicuro che tu possa leggere i dati da un file excel live (non salvato) da aC# appliction usando Addin Express? Lo sai? Ho fatto la domanda sul loro sito. Grazie. –

+0

Addin Express può sicuramente leggere o scrivere su una cartella di lavoro di Excel non salvata a volontà, senza alcuna domanda. Il problema più grande è la velocità con cui viene eseguito, ma io * credo * che sia sia una soluzione sproporzionata che corre incredibilmente veloce, considerando che è stata rimpicciolita. Potrei sbagliarmi su questo, quindi devi fare le tue ricerche e test di tempo. Vedi la mia risposta completa su questo, ma penso che i migliori contendenti per voi sarebbero ExcelDna o Addin Express. –

+0

Wow, grazie per la risposta completa Mike! Lo apprezzo molto. Indagherò entrambi i contendenti e analizzerò le altre informazioni che mi hai fornito. –

4

Oltre ai commenti di Mike Rosenblum sull'uso degli array, vorrei aggiungere che ho utilizzato proprio l'approccio (array VSTO +) e quando l'ho misurato, la velocità di lettura effettiva era in pochi millisecondi. Basta ricordare di disabilitare la gestione degli eventi e l'aggiornamento delle schermate prima della lettura/scrittura, e ricordarsi di riattivare una volta completata l'operazione.

Utilizzando C#, è possibile creare array basati su 1 esattamente come fa lo stesso VBA di Excel. Questo è piuttosto utile, specialmente perché anche in VSTO, quando si estrae l'array da un oggetto Excel.Range, l'array è basato su 1, quindi mantenere gli array orientati a Excel 1-based aiuta a evitare di dover sempre verificare se il la matrice è basata su uno o su base zero. (Se la posizione della colonna nell'array ha significato per te, avere a che fare con array basati su 0 e 1 può essere un vero dolore).

Generalmente la lettura del Excel.Range in un array sarebbe simile a questa:

var myArray = (object[,])range.Value2; 


La mia variante di array-scrittura di Mike Rosenblum utilizza una matrice 1-based come questo:

int[] lowerBounds = new int[]{ 1, 1 }; 
int[] lengths = new int[] { rowCount, columnCount }; 
var myArray = 
    (object[,])Array.CreateInstance(typeof(object), lengths, lowerBounds); 

var dataRange = GetRangeFromMySources(); 

// this example is a bit too atomic; you probably want to disable 
// screen updates and events a bit higher up in the call stack... 
dataRange.Application.ScreenUpdating = false; 
dataRange.Application.EnableEvents = false; 

dataRange = dataRange.get_Resize(rowCount, columnCount); 
dataRange.set_Value(Excel.XlRangeValueDataType.xlRangeValueDefault, myArray); 

dataRange.Application.ScreenUpdating = true; 
dataRange.Application.EnableEvents = true; 
+0

Grazie mille code4life, è molto utile! Quando hai detto che la differenza nella velocità di lettura era minima, in millisecondi, intendi rispetto a VBA (perché Mike nota che le chiamate nei domini delle app sono circa 40 volte più lente, quindi VSTO sarebbe molto più lento di VBA quando interagisce con Excel)? Dal modo in cui sto ancora cercando di ottenere una risposta da Add-In Express, ti farò sapere quando riceverò una risposta. Grazie. –

+0

@jw_pr, penso che Mike sia abbastanza corretto in quello che dice. Penso che sia necessario ridurre le chiamate VSTO al minimo. Detto questo, la vera manipolazione del contenuto dell'array è molto più veloce sul lato VSTO, penso (ma questo è aneddotico). – code4life

+0

Indovino che è nell'intervallo di decine di millisecondi quando si attraversa l'AppDomain utilizzando VSTO.Ma se si dispone di una griglia 10x10 di celle, 10 ms per elaborare ciascuna cella arriva a circa 1 secondo completo per l'intera griglia di 100 celle. Questo è troppo lento e puoi vedere questo progresso a occhio. Dovresti testarlo anche su una serie di chiamate: impostazione dei valori, modifica della formattazione, ecc. –

3

Prima di tutto, la soluzione non può essere un UDF di Excel (funzione definita dall'utente). Nei nostri manuali, forniamo la seguente definizione: "Le UDF di Excel vengono utilizzate per creare funzioni personalizzate in Excel affinché l'utente possa utilizzarle nelle formule." Non mi dispiacerebbe se suggerisci una definizione migliore :)

Quella definizione mostra che una UDF non può aggiungere un pulsante all'interfaccia utente (so che gli XLL possono modificare l'interfaccia utente di CommandBar) o intercettare scorciatoie da tastiera oltre che eventi di Excel .

Cioè, ExcelDNA è fuori portata perché è stato progettato per lo sviluppo di componenti aggiuntivi XLL. Lo stesso vale per le funzionalità con targeting per Excel di Add-in Express poiché consente lo sviluppo di componenti aggiuntivi XLL e componenti aggiuntivi di automazione di Excel.

Poiché è necessario gestire eventi di Excel, la soluzione può essere un'applicazione autonoma, ma esistono ovvi limiti di tale approccio. L'unico vero modo è creare un componente aggiuntivo COM; consente di gestire eventi di Excel e aggiungere elementi personalizzati all'interfaccia utente di Excel.Avete tre possibilità:

  • VSTO
  • Add-in Express (COM add-in funzionalità)
  • Shared aggiuntivo (vedere la voce corrispondente nella finestra di dialogo Nuovo progetto in VS)

Se si parla di sviluppo di un componente aggiuntivo COM di Excel, i 3 strumenti sopra riportati offrono funzionalità diverse: visual designer, shimming, ecc. Ma non credo che differiscano nella velocità di accesso al modello a oggetti di Excel. Dire, non so (e non riesco a immaginare) perché ottenere un oggetto COM dall'AppDomain predefinito dovrebbe differire dall'ottenere lo stesso oggetto COM da un altro AppDomain. A proposito, è possibile verificare se lo shimming influenzi la velocità di operazione creando un componente aggiuntivo condiviso e quindi utilizzando il COM Shim Wizard per lo shim.

Velocità II. Come ti ho scritto ieri: "Il modo migliore per accelerare la lettura e la scrittura in un intervallo di celle è creare una variabile del tipo Excel.Range che si riferisce a quell'intervallo e quindi leggere/scrivere una matrice da/alla proprietà Value della variabile. " Ma contrariamente a quanto dice Francesco, non lo attribuisco a VSTO; questa è una funzionalità del modello a oggetti di Excel.

Velocità III. Le UDF di Excel più veloci sono scritte in C++ nativo, non in alcun linguaggio .NET. Non ho confrontato la velocità di un componente aggiuntivo XLL prodotto da ExcelDNA e Add-in Express; Non penso che troverai differenze sostanziali qui.

Per riassumere. Sono convinto che si stia procedendo in modo errato: i componenti aggiuntivi COM basati su Add-in Express, VSTO o Shared Add-in dovrebbero leggere e scrivere celle Excel alla stessa velocità. Sarò felice (sinceramente) se qualcuno smentisce questa affermazione.

Ora su altre domande. VSTO non consente lo sviluppo di un componente aggiuntivo COM che supporta Office 2000-2010. Richiede tre diverse codebase e almeno due versioni di Visual Studio per completamente supporto Office 2003-2010; è necessario avere i nervi saldi e una buona dose di fortuna per implementare un componente aggiuntivo basato su VSTO per Excel 2003. Con Add-in Express, si crea un componente aggiuntivo COM per tutte le versioni di Office con una singola base di codice; Add-in Express fornisce un progetto di installazione, pronto per installare il componente aggiuntivo in Excel 2000-2010 (a 32 e 64 bit); Anche l'implementazione ClickOnce è a bordo.

VSTO batte l'add-in Express in un'area: consente la creazione di cosiddetti add-in a livello di documento. Immagina una cartella di lavoro o un modello con qualche codice .NET dietro di esso; Non sarei sorpreso, tuttavia, se lo spiegamento di queste cose fosse un incubo.

Su eventi di Excel. Tutti gli eventi di Excel sono elencati in MSDN, per esempio, vedere Excel 2007 events

Saluti dalla Bielorussia (GMT + 2),

Andrei Smolin Add-in Express Team Leader

+0

Grazie mille Andrei per aver postato la tua risposta qui. Questo chiarisce il problema per me. Hai ragione che non voglio usare una UDF e ora capisco le differenze tra VSTO e Add-In Express. Capisco anche che la scelta non sarà determinata dalla velocità. È chiaro che la soluzione dovrà leggere intervalli anziché celle e l'algoritmo dovrà ottimizzare (minimizzare) l'interazione con Excel. Grazie ancora a te ea tutti coloro che hanno contribuito a questa risposta! Justin –

+0

Ciao Andrei, post eccellente, ma alcuni chiarimenti da seguire ... –

+3

"Cioè, ExcelDNA è fuori portata perché è stato progettato per lo sviluppo di componenti aggiuntivi XLL." <- Come mostra Govert nel post precedente, Excel-Dna può sicuramente gestire i comandi di menu, non solo le UDF. –

38

mi prendo questo come un sfida, e scommetto che il modo più veloce per mescolare i tuoi dati tra Excel e C# è di usare Excel-Dna - http://exceldna.codeplex.com. (Disclaimer: sviluppo Excel-Dna, ma è sempre vero ...)

Poiché utilizza l'interfaccia nativa .xll, salta tutti i sovraccarichi di integrazione COM che avresti con VSTO o un'altra aggiunta basata su COM. -in approccio. Con Excel-Dna puoi creare una macro collegata a un menu o un pulsante a nastro che legge un intervallo, lo elabora e lo scrive in un intervallo in Excel. Tutti utilizzano l'interfaccia nativa di Excel da C# - non un oggetto COM in vista.

Ho eseguito una piccola funzione di test che prende la selezione corrente in una matrice, piazza ogni numero nell'array e scrive il risultato in Foglio 2 a partire dalla cella A1. È sufficiente aggiungere il runtime Excel-Dna (gratuito) che è possibile scaricare da http://exceldna.codeplex.com.

Ho letto in C#, processo e riscrivo in Excel un intervallo di milioni di celle in meno di un secondo. È abbastanza veloce per te?

La mia funzione è simile al seguente:

using ExcelDna.Integration; 
public static class RangeTools { 

[ExcelCommand(MenuName="Range Tools", MenuText="Square Selection")] 
public static void SquareRange() 
{ 
    object[,] result; 

    // Get a reference to the current selection 
    ExcelReference selection = (ExcelReference)XlCall.Excel(XlCall.xlfSelection); 
    // Get the value of the selection 
    object selectionContent = selection.GetValue(); 
    if (selectionContent is object[,]) 
    { 
     object[,] values = (object[,])selectionContent; 
     int rows = values.GetLength(0); 
     int cols = values.GetLength(1); 
     result = new object[rows,cols]; 

     // Process the values 
     for (int i = 0; i < rows; i++) 
     { 
      for (int j = 0; j < cols; j++) 
      { 
       if (values[i,j] is double) 
       { 
        double val = (double)values[i,j]; 
        result[i,j] = val * val; 
       } 
       else 
       { 
        result[i,j] = values[i,j]; 
       } 
      } 
     } 
    } 
    else if (selectionContent is double) 
    { 
     double value = (double)selectionContent; 
     result = new object[,] {{value * value}}; 
    } 
    else 
    { 
     result = new object[,] {{"Selection was not a range or a number, but " + selectionContent.ToString()}}; 
    } 

    // Now create the target reference that will refer to Sheet 2, getting a reference that contains the SheetId first 
    ExcelReference sheet2 = (ExcelReference)XlCall.Excel(XlCall.xlSheetId, "Sheet2"); // Throws exception if no Sheet2 exists 
    // ... then creating the reference with the right size as new ExcelReference(RowFirst, RowLast, ColFirst, ColLast, SheetId) 
    int resultRows = result.GetLength(0); 
    int resultCols = result.GetLength(1); 
    ExcelReference target = new ExcelReference(0, resultRows-1, 0, resultCols-1, sheet2.SheetId); 
    // Finally setting the result into the target range. 
    target.SetValue(result); 
} 
} 
+0

Grazie mille Govert! Questo è molto interessante e deve essere l'approccio più veloce! Per interessi, ci sono limitazioni a questo approccio rispetto agli altri approcci che abbiamo discusso a cui puoi pensare? Grazie ancora per la tua risposta molto illuminante. –

+0

È geniale. – code4life

+0

Suppongo che l'utilizzo dell'API nativa ci si abitui se si dispone solo dell'esperienza VBA o VSTO, sebbene Excel-Dna lo renda molto più semplice di quanto lo sarebbe in C/C++. Come con qualsiasi piano basato su .NET, è necessario considerare l'offuscamento se si desidera proteggere la fonte. Tuttavia, la distribuzione è un piacere: puoi impacchettare tutto (comprese le altre librerie C#) in un unico file .xll che gli utenti possono aprire. – Govert

Problemi correlati