Calcul rapide de formules à partir d'Excel en C #

À quelle fréquence apprenez-vous des clients qu'ils enverront des données à Excel ou vous demanderont d'importer ou de télécharger dans un format compatible avec Excel? Je suis sûr que dans la plupart des domaines, Excel est l'un des outils les plus populaires, puissants et à la fois simples et pratiques. Mais le point le plus problématique est toujours l'intégration de ces données avec divers systèmes automatisés. Notre équipe a été invitée à envisager la possibilité d'effectuer des calculs de données à l'aide des paramètres d'un fichier Excel personnalisé.

Calcul rapide de formules à partir d'Excel en C #

Si vous devez choisir une bibliothèque productive pour travailler avec des fichiers Excel ou si vous cherchez une solution pour calculer des données financières complexes (et pas seulement) avec un outil pratique pour gérer et visualiser des formules hors de la boîte, bienvenue à cat.

En étudiant les exigences d'un nouveau projet dans l'entreprise, nous avons trouvé un point très intéressant: «La solution développée devrait être capable de calculer le coût du produit (à savoir le balcon) lors du changement de configuration et d'afficher instantanément le nouveau coût sur l'interface. Il est nécessaire de permettre de télécharger un fichier Excel avec toutes les fonctions de calcul et les listes de prix des composants. » Le client avait besoin de développer un portail pour concevoir la configuration des balcons en fonction des tailles, des formes, des types de vitrage et des matériaux utilisés, des types de fixations, ainsi que de nombreux autres paramètres connexes qui sont utilisés pour calculer le coût exact de production et les indicateurs de fiabilité.

Nous formalisons les exigences d'entrée afin qu'il soit plus facile de comprendre dans quel contexte il était nécessaire de résoudre le problème:

  • Calcul rapide des prix sur l'interface lors du changement des paramètres du balcon;
  • Calcul rapide des données de conception, y compris de nombreuses configurations différentes de balcons et offres individuelles, fournies par un fichier de calcul séparé;
  • À long terme - le développement de fonctionnalités à l'aide de diverses opérations consommatrices de ressources (tâches d'optimisation des paramètres, etc.)
  • Tout cela est sur un serveur distant avec sortie via API, car Toutes les formules sont la propriété intellectuelle du client et ne doivent pas être visibles par des tiers;
  • Flux de données d'entrée en pointe: l'utilisateur peut modifier fréquemment et rapidement les paramètres pour sélectionner la configuration du produit en fonction de ses besoins.

Cela semble très inhabituel et super tentant, commençons!

Solutions clé en main et préparation des données


Toute recherche pour résoudre des problèmes complexes commence par la navigation sur StackOverflow, GitHub et de nombreux forums à la recherche de solutions prêtes à l'emploi.

Plusieurs solutions prêtes à l'emploi ont été sélectionnées qui prenaient en charge la lecture des données à partir de fichiers Excel, ainsi que la possibilité d'effectuer des calculs basés sur des formules spécifiées dans la bibliothèque. Parmi ces bibliothèques, il existe à la fois des solutions totalement gratuites et des développements commerciaux. 


L'étape suivante consiste à écrire des tests de charge et à mesurer le temps d'exécution de chaque bibliothèque. Pour ce faire, préparez les données de test. Nous créons un nouveau fichier Excel, définissons 10 cellules pour les paramètres d'entrée et une formule ( rappelez-vous ce moment ) pour obtenir un résultat qui utilise tous les paramètres d'entrée. Dans la formule, nous essayons d'utiliser toutes les fonctions mathématiques possibles de diverses complexité de calcul et de les combiner de manière délicate.

Parce que c'est aussi de l'argent (le coût du produit), il est important de regarder l'exactitude des résultats. Dans ce cas, nous prendrons les valeurs résultantes obtenues à l'aide d' Excel Interop comme référenceparce que Les données ainsi obtenues sont calculées via le noyau Excel et correspondent aux valeurs que les clients voient lors du développement de formules et du calcul manuel des coûts. 

Comme runtime de référence, nous utiliserons du code natif écrit manuellement en C # pur pour afficher la même formule écrite dans Excel.

Formule d'essai initiale traduite:

public double Execute(double[] p)
{
    return Math.Pow(p[0] * p[8] / p[4] * Math.Sin(p[5]) * Math.Cos(p[2]) +
                          Math.Abs(p[1] - (p[2] + p[3] + p[4] + p[5] + p[6] + p[7] + p[8]))
                          * Math.Sqrt(p[0] * p[0] + p[1] * p[1]) / 2.0 * Math.PI, p[9]);
}

Nous formons un flux de données d'entrée aléatoires pour N itérations (dans ce cas, nous utilisons 10 000 vecteurs).

Nous commençons les calculs pour chaque vecteur de paramètres d'entrée sur l'ensemble du flux, obtenons les résultats et mesurons le temps d'initialisation de la bibliothèque et le calcul général.

Pour comparer la précision des résultats, nous utilisons deux indicateurs - l'écart type et le pourcentage de valeurs correspondantes avec un certain degré de précision epsilon. Si vous générez au hasard une liste de valeurs d'entrée avec un point flottant après le point décimal, vous devez déterminer la précision des paramètres d'entrée - cela vous permettra d'obtenir les résultats corrects. Sinon, les nombres aléatoires peuvent avoir une grande différence d'ordres de grandeur - cela peut grandement affecter la précision des résultats et affecter l'estimation de l'erreur du résultat.

Parce que Initialement, nous supposons qu'il est nécessaire de fonctionner avec des valeurs constantes du coût des matériaux, ainsi qu'avec certaines constantes de différents domaines de connaissance, nous pouvons accepter que tous les paramètres d'entrée auront des valeurs précises à 3 décimales près. Idéalement, vous devez spécifier une plage de valeurs valide pour chacun des paramètres et les utiliser uniquement pour générer des valeurs aléatoires, mais puisque la formule de test a été compilée au hasard sans aucune justification mathématique et physique, il n'est donc pas possible de calculer une telle plage de valeurs pour les 10 paramètres dans un délai raisonnable. Par conséquent, dans les calculs, il est parfois possible d'obtenir une erreur de calcul. Nous excluons ces vecteurs de données d'entrée déjà au moment du calcul pour les indicateurs de précision,mais nous considérerons ces erreurs comme une caractéristique distincte.

Architecture de test 


Pour chaque bibliothèque distincte, sa propre classe a été créée qui implémente une interface ITestExecutor qui comprend 3 méthodes - SetUp, Execute et TearDown.

public interface ITestExecutor
{
    //      
    void SetUp();
    //   ,           
    double Execute(double[] p);
    //    ,      
    void TearDown();
}

Méthodes SetUpet TearDownutilisées une seule fois dans le processus de test de la bibliothèque et ne sont pas prises en compte lors de la mesure des calculs de temps sur l'ensemble des données d'entrée.

En conséquence, l'algorithme de test se résumait à ce qui suit:

  • Préparation des données (nous formons un flux de vecteurs d'entrée d'une précision donnée);
  • Allocation de mémoire pour les données résultantes (un tableau de résultats pour chaque bibliothèque, un tableau avec le nombre d'erreurs); 
  • Initialisation des bibliothèques;
  • Obtention des résultats de calcul pour chacune des bibliothèques avec sauvegarde du résultat dans un tableau pré-préparé et enregistrement du temps d'exécution;
  • Achèvement des travaux avec le code des bibliothèques;
  • Analyse des données:

Une représentation graphique de cet algorithme est présentée ci-dessous.

Organigramme des tests de performance et de précision des bibliothèques de support Excel


Résultats de la première itération


IndiceOriginaire deEPPlus 4
et EPPlus 5
NPOIFlècheInteropération Excel
Temps d'initialisation (ms)02572666321653
mer temps pour 1 passage (ms)0,00020,40860,68476,978238.8423
Moy. déviation0,0003940,0003950,0002370,000631n / a
Précision99,99%99,92%99,97%99,84%n / a
Erreurs0,0%1,94%1,94%1,52%1,94%

Pourquoi les résultats de EPPlus 5 et EPPlus 4 sont-ils combinés?
EPPlus . , , . , . EPPlus 5 , . , EPPlus, .

La première itération des tests a montré un bon résultat, dans lequel les leaders parmi les bibliothèques sont immédiatement devenus visibles. Comme le montrent les données ci-dessus, le leader absolu dans cette situation est la bibliothèque EPPlus.

Les résultats ne sont pas impressionnants par rapport au code natif, mais vous pouvez vivre.

Cela aurait pu être arrêté, mais après avoir discuté avec les clients, les premiers doutes se sont glissés: les résultats étaient trop bons.


Fonctionnalités de travail avec la bibliothèque Spire
Spire , InvalidCastException. , Excel- , , , . try...catch. , .

Rake de la première itération des tests


Ci-dessus, je vous ai demandé d'attirer votre attention sur un point qui a joué un rôle important dans l'obtention de résultats. Nous soupçonnions que les formules utilisées dans le vrai fichier Excel du client seraient loin d'être primitives, avec de nombreuses dépendances à l'intérieur, mais nous ne soupçonnions pas que cela pourrait grandement affecter les indicateurs. Néanmoins, au début, lors de la compilation des données de test, je ne l'avais pas prévu, et les données du client (au moins les informations selon lesquelles au moins 120 paramètres d'entrée sont utilisés dans le fichier final) ont laissé entendre que nous devons penser et ajouter des formules avec des dépendances entre les cellules .

Nous commençons à préparer de nouvelles données pour la prochaine itération de tests. Arrêtons-nous sur 10 paramètres d'entrée et ajoutons 4 nouvelles formules supplémentaires en fonction uniquement des paramètres et 1 formule d'agrégation, qui est basée sur ces quatre cellules avec des formules et s'appuiera également sur les valeurs des données d'entrée.

La nouvelle formule qui sera utilisée pour les tests ultérieurs:

public double Execute(double[] p)
{
    var price1 = Math.Pow(p[0] * p[8] / p[4] * Math.Sin(p[5]) * Math.Cos(p[2]) +
                          Math.Abs(p[1] - (p[2] + p[3] + p[4] + p[5] + p[6] + p[7] + p[8]))
                          * Math.Sqrt(p[0] * p[0] + p[1] * p[1]) / 2.0 * Math.PI, p[9]);

    var price2 = p[4] * p[5] * p[2] / Math.Max(1, Math.Abs(p[7]));

    var price3 = Math.Abs(p[7] - p[3]) * p[2];

    var price4 = Math.Sqrt(Math.Abs(p[1] * p[2] + p[3] * p[4] + p[5] * p[6]) + 1.0);

    var price5 = p[0] * Math.Cos(p[1]) + p[2] * Math.Sin(p[1]);

    var sum = p[0] + p[1] + p[2] + p[3] + p[4] + p[5] + p[6] + p[7] + p[8] + p[9];

    var price6 = sum / Math.Max(1, Math.Abs((p[0] + p[1] + p[2] + p[3]) / 4.0))
                 + sum / Math.Max(1, Math.Abs((p[4] + p[5] + p[6] + p[7] + p[8] + p[9]) / 6.0));

    var pricingAverage = (price1 + price2 + price3 + price4 + price5 + price6) / 6.0;

    return pricingAverage / Math.Max(1, Math.Abs(price1 + price2 + price3 + price4 + price5 + price6));
}

Comme vous pouvez le voir, la formule résultante s'est avérée être beaucoup plus compliquée, ce qui a naturellement affecté les résultats - pas pour le mieux. Un tableau avec les résultats est présenté ci-dessous:

IndiceOriginaire deEPPlus 4 EPPlus 5NPOIFlècheInteropération Excel
Temps d'initialisation (ms)02413687221640
mer temps pour 1 passage (ms)0,00040,9174 (+ 124%)1,8996 (+ 177%)7,7647 (+ 11%)50,7194 (+ 30%)
Moy. déviation0,0358840.0000000.0000000.000000n / a
Précision98,79%100,00%100,00%100,00%n / a
Erreurs0,0%0,3%0,3%0,28%0,3%


Remarque: Parce que Excel Interop est trop volumineux, a dû être exclu du graphique.

Comme le montrent les résultats, la situation est devenue totalement impropre à une utilisation en production. Un peu triste, faites le plein de café et plongez la tête plus profondément dans l'étude - directement dans la génération de code. 


Génération de code


Si vous n'avez soudainement jamais fait face à une tâche similaire, nous effectuerons une brève excursion. 

La génération de code est un moyen de résoudre un problème en générant dynamiquement du code source basé sur des données d'entrée avec une compilation et une exécution ultérieures. Il y a à la fois une génération de code statique qui se produit pendant la construction du projet (je peux citer T4MVC comme exemple, qui crée un nouveau code basé sur des modèles et des métadonnées qui peuvent être utilisés lors de l'écriture du code d'application principal), et du code dynamique qui s'exécute pendant l'exécution. 

Notre tâche est de former une nouvelle fonction basée sur les données source (formules d'Excel), qui reçoit le résultat basé sur le vecteur des valeurs d'entrée.

Pour ce faire, vous devez:

  • Lisez la formule du fichier;
  • Collectez toutes les dépendances;
  • C#;
  • ;
  • ;
  • .

Toutes les bibliothèques présentées conviennent à la lecture de formules, cependant, la bibliothèque EPPlus s'est avérée être l'interface la plus pratique pour une telle fonctionnalité . Après avoir fouillé un peu dans le code source de cette bibliothèque, j'ai découvert des classes publiques pour former une liste de jetons et sa transformation ultérieure en arbre d'expression. Bingo, je pensais! Un arbre d'expression prêt à l'emploi de la boîte est idéal, il suffit de le parcourir et de former notre code C #. 

Mais un gros problème m'attendait lorsque j'ai commencé à étudier les nœuds de l'arbre d'expression résultant. Certains nœuds, notamment l'appel aux fonctions Excel, ont encapsulé des informations sur la fonction utilisée et ses paramètres d'entrée et n'ont fourni aucun accès ouvert à ces données. Par conséquent, le travail avec l'arborescence d'expression terminée a dû être reporté.

Nous descendons d'un niveau plus bas et essayons de travailler avec la liste des jetons. Tout est assez simple ici: nous avons des jetons de type et de valeur. Parce que on nous donne une fonction et nous devons former une fonction, alors nous pouvons à la place convertir les jetons d'arbre en l'équivalent en C #. L'essentiel de cette approche est d'organiser des fonctions compatibles. La plupart des fonctions mathématiques étaient déjà compatibles - telles que le calcul du cosinus, du sinus, l'obtention de la racine et l'augmentation d'une puissance. Mais les fonctions d'agrégation - telles que la valeur maximale, le minimum, le montant - devaient être finalisées. La principale différence est que dans Excel, ces fonctions fonctionnent avec une plage de valeurs. Pour simplifier le prototype, nous allons créer des fonctions qui prennent une liste de paramètres en entrée, en étendant auparavant la plage de valeurs dans une liste linéaire.De cette façon, nous obtenons la conversion correcte et compatible de la syntaxe Excel à la syntaxe C #. 

Vous trouverez ci-dessous le code principal pour convertir une liste de jetons d'une formule Excel en un code C # valide.

private string TransformToSharpCode(string formula, ParsingContext parsingContext)
{
    // Initialize basic compile components, e.g. lexer
    var lexer = new Lexer(parsingContext.Configuration.FunctionRepository, parsingContext.NameValueProvider);

    // List of dependency variables that can be filled during formula transformation
    var variables = new Dictionary<string, string>();
    using (var scope = parsingContext.Scopes.NewScope(RangeAddress.Empty))
    {
        // Take resulting code line
        var compiledResultCode = TransformToSharpCode(formula, parsingContext, scope, lexer, variables);

        var output = new StringBuilder();

        // Define dependency variables in reverse order.
        foreach (var variableDefinition in variables.Reverse())
        {
            output.AppendLine($"var {variableDefinition.Key} = {variableDefinition.Value};");
        }

        // Take the result
        output.AppendLine($"return {compiledResultCode};");

        return output.ToString();
    }
}

private string TransformToSharpCode(ICollection<Token> tokens, ParsingContext parsingContext, ParsingScope scope, ILexer lexer, Dictionary<string, string> variables)
{
    var output = new StringBuilder();

    foreach (Token token in tokens)
    {
        switch (token.TokenType)
        {
            case TokenType.Function:
                output.Append(BuildFunctionName(token.Value));
                break;
            case TokenType.OpeningParenthesis:
                output.Append("(");
                break;
            case TokenType.ClosingParenthesis:
                output.Append(")");
                break;
            case TokenType.Comma:
                output.Append(", ");
                break;
            case TokenType.ExcelAddress:
                var address = token.Value;
                output.Append(TransformAddressToSharpCode(address, parsingContext, scope, lexer, variables));
                break;
            case TokenType.Decimal:
            case TokenType.Integer:
            case TokenType.Boolean:
                output.Append(token.Value);
                break;
            case TokenType.Operator:
                output.Append(token.Value);
                break;
        }
    }

    return output.ToString();
}

La nuance suivante dans la conversion était l'utilisation de constantes Excel - ce sont des fonctions, donc en C #, elles doivent également être enveloppées dans une fonction. 

Reste à résoudre une seule question: la conversion des références de cellule en paramètre. Dans le cas où le jeton contient des informations sur la cellule, nous déterminons d'abord ce qui est stocké dans cette cellule. S'il s'agit d'une formule, développez-la récursivement. Si la constante est remplacée par un lien analogique C #, de la forme p[row, column], où il ppeut s'agir d'un tableau à deux dimensions ou d'une classe d'accès indexée pour un mappage de données correct. Avec une gamme de cellules, nous faisons de même, il suffit de pré-étendre la gamme en cellules individuelles et de les traiter séparément. Ainsi, nous couvrons les principales fonctionnalités lors de la traduction d'une fonction Excel. 


Voici le code pour convertir un lien vers une cellule de feuille de calcul Excel en un code C # valide:

private string TransformAddressToSharpCode(string excelAddress, ParsingContext parsingContext, ParsingScope scope, ILexer lexer, Dictionary<string, string> variables)
{
    // Try to parse excel range of addresses
    // Currently, reference to another worksheet in address string is not supported

    var rangeParts = excelAddress.Split(':');
    if (rangeParts.Length == 1)
    {
        // Unpack single excel address
        return UnpackExcelAddress(excelAddress, parsingContext, scope, lexer, variables);
    }

    // Parse excel range address
    ParseAddressToIndexes(rangeParts[0], out int startRowIndex, out int startColumnIndex);
    ParseAddressToIndexes(rangeParts[1], out int endRowIndex, out int endColumnIndex);

    var rowDelta = endRowIndex - startRowIndex;
    var columnDelta = endColumnIndex - startColumnIndex;

    var allAccessors = new List<string>(Math.Abs(rowDelta * columnDelta));

    // TODO This part of code doesn't support reverse-ordered range address
    for (var rowIndex = startRowIndex; rowIndex <= endRowIndex; rowIndex++)
    {
        for (var columnIndex = startColumnIndex; columnIndex <= endColumnIndex; columnIndex++)
        {
            // Unpack single excel address
            allAccessors.Add(UnpackExcelAddress(rowIndex, columnIndex, parsingContext, scope, lexer, variables));
        }
    }

    return string.Join(", ", allAccessors);
}

private string UnpackExcelAddress(string excelAddress, ParsingContext parsingContext, ParsingScope scope, ILexer lexer, Dictionary<string, string> variables)
{
    ParseAddressToIndexes(excelAddress, out int rowIndex, out int columnIndex);
    return UnpackExcelAddress(rowIndex, columnIndex, parsingContext, scope, lexer, variables);
}

private string UnpackExcelAddress(int rowIndex, int columnIndex, ParsingContext parsingContext, ParsingScope scope, ILexer lexer, Dictionary<string, string> variables)
{
    var formula = parsingContext.ExcelDataProvider.GetRangeFormula(_worksheet.Name, rowIndex, columnIndex);
    if (string.IsNullOrWhiteSpace(formula))
    {
        // When excel address doesn't contain information about any excel formula, we should just use external input data parameter provider.
        return $"p[{rowIndex},{columnIndex}]";
    }

    // When formula is provided, try to identify that variable is not defined yet
    // TODO Worksheet name is not included in variable name, potentially that can cause conflicts
    // Extracting and reusing calculations via local variables improves performance for 0.0045ms
    var cellVariableId = $"C{rowIndex}R{columnIndex}";
    if (variables.ContainsKey(cellVariableId))
    {
        return cellVariableId;
    }

    // When variable does not exist, transform new formula and register that to variable scope
    variables.Add(cellVariableId, TransformToSharpCode(formula, parsingContext, scope, lexer, variables));

    return cellVariableId;
}

Il ne reste plus qu'à encapsuler le code converti résultant dans une fonction statique, lier l'assembly aux fonctions de compatibilité et compiler l'assembly dynamique. Chargez-le en mémoire, obtenez un lien vers notre fonction - et vous pouvez l'utiliser. 

Nous écrivons une classe wrapper pour tester et exécuter des tests avec mesure du temps. 

public void SetUp()
{
    // Initialize excel package by EPPlus library
    _package = new ExcelPackage(new FileInfo(_fileName));
    _workbook = _package.Workbook;
    _worksheet = _workbook.Worksheets[1];

    _inputRange = new ExcelRange[10];
    for (int rowIndex = 0; rowIndex < 10; rowIndex++)
    {
        _inputRange[rowIndex] = _worksheet.Cells[rowIndex + 1, 2];
    }

    // Access to result cell and extract formula string
    _resultRange = _worksheet.Cells[11, 2];

    var formula = _resultRange.Formula;

    // Initialize parsing context and setup data provider
    var parsingContext = ParsingContext.Create();
    parsingContext.ExcelDataProvider = new EpplusExcelDataProvider(_package);

    // Transform Excel formula to CSharp code
    var sourceCode = TransformToSharpCode(formula, parsingContext);

    // Compile CSharp code to IL dynamic assembly via helper wrappers
    _code = CodeGenerator.CreateCode<double>(
        sourceCode,
        new string[]
        {
            // List of used namespaces
            "System", // Required for Math functions
            "ExcelCalculations.PerformanceTests" // Required for Excel function wrappers stored at ExcelCompiledFunctions static class
        },
        new string[]
        {
            // Add reference to current compiled assembly, that is required to use Excel function wrappers located at ExcelCompiledFunctions static class
            "....\\bin\\Debug\\ExcelCalculations.PerformanceTests.exe"
        },
        // Notify that this source code should use parameter;
        // Use abstract p parameter - interface for values accessing.
        new CodeParameter("p", typeof(IExcelValueProvider))
    );
}

En conséquence, nous avons un prototype de cette solution et la marquons comme EPPlusCompiled, Mark-I . Après avoir exécuté les tests, nous obtenons le résultat tant attendu. L'accélération est presque 300 fois. Déjà pas mal, mais le code résultant est encore 16 fois plus lent que le code natif. Cela pourrait-il être mieux?

Oui, vous pouvez! Essayons d'améliorer le résultat car nous remplacerons tous les liens vers des cellules supplémentaires par des formules avec des variables. Notre test utilise plusieurs utilisations de cellules dépendantes dans la formule, donc dans la première version du traducteur, nous avons reçu plusieurs calculs des mêmes données. Par conséquent, il a été décidé d'utiliser des variables intermédiaires dans les calculs. Après avoir développé le code en utilisant la génération de variables dépendantes, nous avons obtenu une augmentation des performances de 2 fois de plus. Cette amélioration s'appelleEPPlusCompiled, Mark-II . Le tableau de comparaison est présenté ci-dessous:

Bibliothèquemer temps (ms)Coef. ralentissements
Originaire de0,000041
EPPlusCompiled, Mark-II0,0038
EPPlusCompiled, Mark-I0,0061seize
EPPlus1,20893023

Dans ces conditions et dans les délais impartis pour la tâche, nous avons obtenu un résultat qui nous rapproche des performances du code natif avec un léger décalage - 8 fois, par rapport à la version d'origine - un décalage de plusieurs ordres de grandeur, 3028 fois. Mais est-il possible d'améliorer le résultat et de se rapprocher le plus possible du code natif, si vous supprimez les délais et combien cela sera-t-il approprié?

Ma réponse est oui, mais, malheureusement, je n'ai plus eu le temps de mettre en œuvre ces techniques. Je consacrerai peut-être un article séparé à ce sujet. Pour le moment, je ne peux parler que des principales idées et options, en les écrivant sous la forme de courts résumés qui ont été vérifiés par transformation inverse. Par conversion inverse, j'entends la dégradation du code natif écrit à la main dans le sens du code généré. Cette approche vous permet de vérifier certaines thèses assez rapidement et ne nécessite pas de changements importants dans le code. Cela vous permet également de répondre à la question de savoir comment les performances du code natif se détérioreront dans certaines conditions, ce qui signifie que si le code généré est automatiquement amélioré dans la direction opposée, nous pouvons obtenir une amélioration des performances avec un coefficient similaire.

Résumés


  1. , ;
  2. inline ;
  3. - Sum, Max, Min ;
  4. Sum inline ;
  5. ( ) .


NativeEPPlus Compiled, Mark-IIEPPlus 4EPPlus 5NPOISpireExcel Interop
()02392413687221640
. 1 ()0,00040,0030,91741,89967,764750,7194
Moy. déviation0,0358840,00,00,00,0n / a
Précision98,79%100,0%100,0%100,0%100,0%n / a
Erreurs0,0%0,0%0,3%0,3%0,28%0,3%

Calcul rapide de formules à partir d'Excel en C #

Pour résumer les étapes, nous avons un mécanisme pour convertir les formules directement à partir d'un document Excel personnalisé en code de travail sur le serveur. Cela vous permet d'utiliser l'incroyable flexibilité de l'intégration d'Excel avec n'importe quelle solution d'entreprise sans perdre la puissante interface utilisateur avec laquelle un grand nombre d'utilisateurs sont habitués à travailler. Était-il possible de développer une interface aussi pratique avec le même ensemble d'outils pour analyser et visualiser les données que dans Excel pour une période de développement aussi courte?

Et quelles ont été les intégrations les plus inhabituelles et intéressantes avec les documents Excel que vous avez dû mettre en œuvre?

All Articles