如何允许用户在 C# 应用程序中定义财务公式

发布于 2024-09-01 14:51:39 字数 1022 浏览 7 评论 0原文

我需要允许我的用户能够定义根据数据计算值的公式。例如,

//Example 1
return GetMonetaryAmountFromDatabase("Amount due") * 1.2;
//Example 2
return GetMonetaryAmountFromDatabase("Amount due") * GetFactorFromDatabase("Discount");

我需要允许 / * + - 操作,还需要分配局部变量并执行 IF 语句,就像这样

var amountDue = GetMonetaryAmountFromDatabase("Amount due");
if (amountDue > 100000) return amountDue * 0.75;
if (amountDue > 50000) return amountDue * 0.9;
return amountDue;

该场景很复杂,因为我有以下结构..

  1. 客户(几百个)
  2. 配置(每个客户大约 10 个)
  3. 项目(每个客户配置大约 10,000 个)

因此我将执行 3 级循环。在每个“配置”级别,我将启动一个数据库事务并编译论坛,每个“项目”将使用相同的事务+编译的公式(每个配置大约有20个公式,每个项目将使用所有这些公式)。

这使事情变得更加复杂,因为我不能只使用编译器服务,因为这会导致内存使用量持续增长。我无法在每个“配置”循环级别使用新的 AppDomain,因为我需要传递的一些引用无法编组。

有什么建议吗?

- 更新 - 这就是我去的,谢谢! http://www.codeproject.com/Articles/53611/在 aC 应用程序中嵌入 IronPython

I need to allow my users to be able to define formulas which will calculate values based on data. For example

//Example 1
return GetMonetaryAmountFromDatabase("Amount due") * 1.2;
//Example 2
return GetMonetaryAmountFromDatabase("Amount due") * GetFactorFromDatabase("Discount");

I will need to allow / * + - operations, also to assign local variables and execute IF statements, like so

var amountDue = GetMonetaryAmountFromDatabase("Amount due");
if (amountDue > 100000) return amountDue * 0.75;
if (amountDue > 50000) return amountDue * 0.9;
return amountDue;

The scenario is complicated because I have the following structure..

  1. Customer (a few hundred)
  2. Configuration (about 10 per customer)
  3. Item (about 10,000 per customer configuration)

So I will perform a 3 level loop. At each "Configuration" level I will start a DB transaction and compile the forumlas, each "Item" will use the same transaction + compiled formulas (there are about 20 formulas per configuration, each item will use all of them).

This further complicates things because I can't just use the compiler services as it would result in continued memory usage growth. I can't use a new AppDomain per each "Configuration" loop level because some of the references I need to pass cannot be marshalled.

Any suggestions?

--Update--
This is what I went with, thanks!
http://www.codeproject.com/Articles/53611/Embedding-IronPython-in-a-C-Application

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

满天都是小星星 2024-09-08 14:51:39

Iron Python 允许您嵌入脚本引擎到您的应用程序中。还有许多其他解决方案。事实上,您可以通过谷歌搜索“C# 嵌入式脚本”之类的内容并找到一大堆选项。有些比其他更容易集成,有些比其他更容易编写脚本。

当然,总有VBA。但这实在是太丑陋了。

Iron Python Allows you to embed a scripting engine into your application. There are many other solutions. In fact, you can google something like "C# embedded scripting" and find a whole bunch of options. Some are easier than others to integrate, and some are easier than others to code up the scripts.

Of course, there is always VBA. But that's just downright ugly.

与酒说心事 2024-09-08 14:51:39

您可以在运行时创建一个简单的类,只需将逻辑写入字符串等,编译它,运行它并使其返回您需要的计算。本文向您展示如何从运行时访问编译器:http://www.codeproject。 com/KB/cs/codecompilation.aspx

You could create a simple class at runtime, just by writing your logic into a string or the like, compile it, run it and make it return the calculations you need. This article shows you how to access the compiler from runtime: http://www.codeproject.com/KB/cs/codecompilation.aspx

一刻暧昧 2024-09-08 14:51:39

几年前我也遇到过类似的问题。我有一个流量适中的网络应用程序,需要允许方程式,并且它需要与您的类似的功能,而且必须速度快。我经历了几个想法。

第一个解决方案涉及将计算列添加到我们的数据库中。我们的应用程序表将属性存储在列中(例如,有一列用于显示应付金额、另一个折扣等)。如果用户输入像 PropertyA * 2 这样的公式,代码将更改基础表以具有新的计算列。就添加和删除列而言,它很混乱。但它确实有一些优点:数据库(SQL Server)执行计算的速度非常快;数据库为我们处理了大量的错误检测;我可以假装计算值与非计算值相同,这意味着我不必修改任何使用非计算值的现有代码。

这种方法工作了一段时间,直到我们需要一个公式能够引用另一个公式,而 SQL Server 不允许这样做。所以我改用了脚本引擎。 IronPython 当时还不是很成熟,所以我选择了另一个引擎......我现在不记得是哪一个了。反正写起来很容易,就是有点慢。不是很多,每个查询可能只有几毫秒,但对于网络应用程序来说,时间确实是所有请求的总和。

就在那时,我决定为公式编写自己的解析器。也就是说,我有一个 PlusToken 类来添加两个值,一个 ItemToken 类对应于 GetValue("Discount") 等。当用户输入新公式时,验证器会解析该公式,确保其有效(例如,他们是否引用了不存在的列?),并将其存储为半编译形式,以便以后轻松解析。当用户请求计算值时,解析器会读取公式,对其进行解析,从数据库中找出需要哪些数据,并计算最终答案。前期需要做大量的工作,但效果很好,而且速度非常快。这是我学到的:

  1. 如果用户输入一个导致公式循环的公式,并且您尝试计算该公式的值,您将耗尽堆栈空间。如果您在网络应用程序上运行它,整个网络服务器将停止工作,直到您重置它。因此,在验证阶段检测周期非常重要。
  2. 如果您有多个公式,请将所有数据库调用聚合到一个位置,然后立即请求所有数据。快得多。
  3. 用户将在公式中输入古怪的东西。提供有用的错误消息的解析器将在以后避免很多麻烦。

I faced a similar problem a few years ago. I had a web app with moderate traffic that needed to allow equations, and it needed similar features to yours, and it had to be fast. I went through several ideas.

The first solution involved adding calculated columns to our database. Our tables for the app store the properties in columns (e.g., there's a column for Amount Due, another Discount, etc.). If the user typed in a formula like PropertyA * 2, the code would alter the underlying table to have a new calculated column. It's messy as far as adding and removing columns. It does have a few advantages though: the database (SQL Server) was really fast at doing the calculations; the database handled a lot of error detection for us; and I could pretend that the calculated values were the same as the non-calculated values, which meant that I didn't have to modify any existing code that worked with the non-calculated values.

That worked for a while until we needed the ability for a formula to reference another formula, and SQL Server doesn't allow that. So I switched to a scripting engine. IronPython wasn't very mature back then, so I chose another engine... I can't remember which one right now. Anyway, it was easy to write, but it was a little slow. Not a lot, maybe a few milliseconds per query, but for a web app the time really added up over all the requests.

That was when I decided to write my own parser for the formulas. That is, I have a PlusToken class to add two values, an ItemToken class that corresponds to GetValue("Discount"), etc. When the user enters a new formula, a validator parses the formula, makes sure it's valid (things like, did they reference a column that doesn't exist?), and stores it in a semi-compiled form that's easy to parse later. When the user requests a calculated value, a parser reads the formula, parses it, figures out what data is needed from the database, and computes the final answer. It took a fair amount of work up front, but it works well and it's really fast. Here's what I learned:

  1. If the user enters a formula that leads to a cycle in the formulas, and you try to compute the value of the formula, you'll run out of stack space. If you're running this on a web app, the entire web server will stop working until you reset it. So it's important to detect cycles at the validation stage.
  2. If you have more than a couple formulas, aggregate all the database calls in one place, then request all the data at once. Much faster.
  3. Users will enter wacky stuff into formulas. A parser that provides useful error messages will save a lot of headaches later on.
分开我的手 2024-09-08 14:51:39

如果自定义脚本没有变得比上面显示的更复杂,我会同意 Sylvestre 的观点:创建您自己的解析器,制作一棵树并自己执行逻辑。您可以生成 .Net 表达式树 或仅浏览语法自己创建树并在自己的代码中进行操作(下面的 Antlr 将帮助您生成此类代码)。

然后你就可以完全控制你的引用,你总是在 C# 中,所以你不需要担心内存管理(比你通常做的更多)等。 antlr.org/" rel="nofollow noreferrer">Antlr 是在 C# 中执行此操作的最佳工具。您可以从网站上获取小语言的示例,例如您的场景。

但是...如果这确实只是一个开始,最后您需要适当的脚本语言的几乎全部功能,那么您将需要将脚本语言嵌入到您的系统中。根据您的数字,您将遇到性能、内存管理以及可能的参考问题,正如您所指出的。有多种方法,但我无法真正为您的场景提供一种建议:我从未以如此规模做过。

If the custom scripts don't get more complex than the ones that you show above, I would agree with Sylvestre: Create your own parser, make a tree and do the logic yourself. You can generate a .Net expression tree or just go through the Syntax tree yourself and make the operations within your own code (Antlr below will help you generate such code).

Then you are in complete control of your references, you are always within C#, so you don't need to worry about memory management (any more than you would normally do) etc. IMO Antlr is the best tool for doing this in C# . You get examples from the site for little languages, like your scenario.

But... if this is really just a beginning and at the end you need almost full power of a proper scripting language, you would need to go into embedding a scripting language to your system. With your numbers, you will have a problem with performance, memory management and probably references as you noted. There are several approaches, but I cannot really give one recommendation for your scenario: I've never done it in such a scale.

断桥再见 2024-09-08 14:51:39

您可以构建两个基类 UnaryOperator (if, square, root...) 和 BinaryOperator (+ - / *) 并根据表达式构建一棵树。然后评估每个项目的树。

You could build two base classes UnaryOperator (if, square, root...) and BinaryOperator (+ - / *) and build a tree from the expression. Then evaluate the tree for each item.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文